Advertisement
Tag365

Bitmap API

Sep 29th, 2015
276
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 15.27 KB | None | 0 0
  1. -----------------------------------------
  2. -- Bitmap API built by Matthew Cenance --
  3. -----------------------------------------
  4.  
  5. -- The section of code below is from CrazedProgrammer's RGB API. --
  6.  
  7. -- Uses portion of the RGB API version 1.0 by CrazedProgrammer.
  8. -- Slightly modified to add additional functions for dithering.
  9. -- You can find info and documentation on these pages:
  10. -- http://cp.msdev.nl/computercraft/rgb-api/
  11. -- You may use this in your ComputerCraft programs and modify it without asking.
  12. -- However, you may not publish this API under your name without asking me.
  13. -- If you have any suggestions, bug reports or questions then please send an email to:
  14. local hex = {"F0F0F0", "F2B233", "E57FD8", "99B2F2", "DEDE6C", "7FCC19", "F2B2CC", "4C4C4C", "999999", "4C99B2", "B266E5", "3366CC", "7F664C", "57A64E", "CC4C4C", "191919"}
  15. local rgb = {}
  16. for i=1,16,1 do
  17.     rgb[i] = {tonumber(hex[i]:sub(1, 2), 16), tonumber(hex[i]:sub(3, 4), 16), tonumber(hex[i]:sub(5, 6), 16)}
  18. end
  19. local rgb2 = {}
  20. for i=1,16,1 do
  21.     rgb2[i] = {}
  22.     for j=1,16,1 do
  23.         rgb2[i][j] = {(rgb[i][1] * 34 + rgb[j][1] * 20) / 54, (rgb[i][2] * 34 + rgb[j][2] * 20) / 54, (rgb[i][3] * 34 + rgb[j][3] * 20) / 54}
  24.     end
  25. end
  26.  
  27. -- Returns a ComputerCraft color from the Color3.
  28. local fromRGB = function (r, g, b)
  29.     local precision = 1e100
  30.     local difference = 1e100
  31.     local color = 0
  32.     for i=1, 16, 1 do
  33.         difference = math.sqrt((math.max(rgb[i][1], r) - math.min(rgb[i][1], r)) ^ 2 + (math.max(rgb[i][2], g) - math.min(rgb[i][2], g)) ^ 2 + (math.max(rgb[i][3], b) - math.min(rgb[i][3], b)) ^ 2)
  34.         if difference < precision then
  35.             precision = difference
  36.             color = i - 1
  37.         end
  38.     end
  39.     return 2 ^ color
  40. end
  41.  
  42. -- Returns two ComputerCraft colors from the Color3 and the difference between the ComputerCraft colors and the actual colors.
  43. local fromRGBForDither = function (r, g, b)
  44.     local precision, precision2 = 1e100, 1e100
  45.     local difference = 1e100
  46.     local color, color2 = 0, 0
  47.     for i=1, 16, 1 do
  48.         difference = math.sqrt((math.max(rgb[i][1], r) - math.min(rgb[i][1], r)) ^ 2 + (math.max(rgb[i][2], g) - math.min(rgb[i][2], g)) ^ 2 + (math.max(rgb[i][3], b) - math.min(rgb[i][3], b)) ^ 2)
  49.         if difference < precision then
  50.             precision2, color2 = precision, color
  51.             precision = difference
  52.             color = i - 1
  53.         end
  54.     end
  55.     return 2 ^ color, 2 ^ color2, precision, precision2
  56. end
  57.  
  58. local dithers = {
  59.     " ",
  60.     ".",
  61.     "-",
  62.     "+",
  63.     "%",
  64.     "*",
  65.     "#",
  66.     "@",
  67. }
  68.  
  69. local ditherWeight = 100.25
  70.  
  71. -- Returns values which can be used to dither.
  72. local ditherRGB = function (r, g, b)
  73.     local bgcolor, textcolor, precision, precision2 = fromRGBForDither(r, g, b)
  74.     local charId = ((precision2 - precision)/ditherWeight)*#dithers
  75.     if charId >= #dithers + 1 then
  76.         charId = #dithers - math.ceil((charId > 0 and charId) or 1)
  77.         local char = dithers[charId]
  78.         return char, bgcolor, textcolor
  79.     end
  80.     charId = math.floor((charId > 0 and charId) or 1)
  81.     local char = dithers[(charId < #dithers and charId) or #dithers]
  82.     return char, textcolor, bgcolor
  83. end
  84.  
  85. -- End of the RGB API. The code below starts the Bitmap API. --
  86. -- Color API --
  87. local tHex = {
  88.     [ colors.white ] = "0",
  89.     [ colors.orange ] = "1",
  90.     [ colors.magenta ] = "2",
  91.     [ colors.lightBlue ] = "3",
  92.     [ colors.yellow ] = "4",
  93.     [ colors.lime ] = "5",
  94.     [ colors.pink ] = "6",
  95.     [ colors.gray ] = "7",
  96.     [ colors.lightGray ] = "8",
  97.     [ colors.cyan ] = "9",
  98.     [ colors.purple ] = "a",
  99.     [ colors.blue ] = "b",
  100.     [ colors.brown ] = "c",
  101.     [ colors.green ] = "d",
  102.     [ colors.red ] = "e",
  103.     [ colors.black ] = "f",
  104. }
  105.  
  106. local Color3 = {}
  107. Color3.__index = Color3
  108. Color3.__tostring = function(value)
  109.     return "Red: "..value.red.." Green:"..value.green.." Blue:"..value.blue
  110. end
  111.  
  112. -- Creates a new Color3.
  113. function Color3.new(red, green, blue)
  114.     if not blue then error("Expected red, green, blue", 2) end
  115.     local self = setmetatable({red = red or 0, green = green or 0, blue = blue or 0}, Color3)
  116.     return self
  117. end
  118.  
  119. -- Returns the Color3.
  120. function Color3:GetValue()
  121.     local maxValue = math.max(self.red, self.green, self.blue)
  122.     local divideBy = 255/maxValue
  123.     return Color3.new(self.red*divideBy, self.green*divideBy, self.blue*divideBy)
  124. end
  125.  
  126. -- Returns the hue of the color.
  127. function Color3:GetHue()
  128.     local red, green, blue = self.red, self.green, self.blue
  129.     return math.atan2(math.sqrt(3)*(green - (blue)), 2*red - green - blue)
  130. end
  131.  
  132. -- Returns a terminal color that is closest to the Color3 value that can be used to draw to the terminal.
  133. function Color3:ToComputerCraftColor()
  134.     return fromRGB(self.red, self.green, self.blue)
  135. end
  136.  
  137. -- Returns three values which can be used to dither.
  138. function Color3:ToComputerCraftDither()
  139.     return ditherRGB(self.red, self.green, self.blue)
  140. end
  141.  
  142. -- End of Color API --
  143. -- Returns the bytes in the value.
  144. function getValues(tab, startingKey, keys)
  145.     startingKey = startingKey + 1
  146.     local variables = {nil, nil, nil, nil, nil, nil, nil, nil}
  147.     for k=startingKey, startingKey + (keys - 1) do
  148.         variables[(k - startingKey) + 1] = tab[k]
  149.     end
  150.     return variables
  151. end
  152.  
  153. -- Returns a number from the byte table.
  154. function tableWithNumbersToString(tab)
  155.     local tab2 = {}
  156.     for k=1, #tab do
  157.         tab2[k] = string.char(tab[k] or 0)
  158.     end
  159.     return table.concat(tab2, "")
  160. end
  161.  
  162. -- Returns a string from the byte table.
  163. function tableWithNumbersToNumber(tab)
  164.     local number = 0
  165.     local deg = #tab
  166.     for key=1, #tab do
  167.         number = number + bit32.lshift(tab[key], (key-1)*8)
  168.     end
  169.     return number
  170. end
  171.  
  172.  
  173. function getNumber(...)
  174.     return tableWithNumbersToNumber(getValues(...))
  175. end
  176.  
  177. function getString(...)
  178.     return tableWithNumbersToString(getValues(...))
  179. end
  180.  
  181. -- Bitmap API --
  182. -- Compression Types.
  183. local BI_RGB = 0        -- Uncompressed Red Green Blue format.
  184. local BI_RLE8 = 1       -- RLE 8-bit/pixel.
  185. local BI_RLE4 = 2       -- RLE 4-bit/pixel.
  186. local BI_BITFIELDS = 3  -- Huffman 1D.
  187. local BI_CMYK = 11      -- Uncompressed CYMK format.
  188.  
  189. local Bitmap = {}
  190. -- Bitmap metatable.
  191. Bitmap.__index = Bitmap
  192. Bitmap.__tostring = function(self)
  193.     if self.bitmapInfo then
  194.         return "Bitmap object (Width: "..self.bitmapInfo.Width..", Height: "..self.bitmapInfo.Height..")"
  195.     else
  196.         return "Bitmap class"
  197.     end
  198. end
  199.  
  200. -- Creates a bitmap object with the selected width and height.
  201. function Bitmap.new( width, height, backgroundColor )
  202.     -- Create the bitmap object.
  203.     local self = setmetatable({}, Bitmap)
  204.    
  205.     -- Load the advanced Bitmap info.
  206.     local bitmapInfo = {
  207.         Identity = "BM",
  208.         Size = 54,
  209.         App = "LApi",
  210.         BitMapOffset = 54,
  211.         HeaderSize = 54,
  212.         Width = width,
  213.         Height = height,
  214.         ColorPlanes = 1,
  215.         BitsPerPixel = 24,
  216.         CompressionType = BI_RGB,
  217.         RawBitmapDataSize = 0,
  218.         HorizontalResolution = 2,
  219.         VerticalResolution = 2,
  220.         ColorsInColorPalette = 0,
  221.         ImportantColors = 0,
  222.     }
  223. end
  224.  
  225. -- Loads a bitmap from the Hard Disk.
  226. function Bitmap.load( _sPath )
  227.     -- Create the bitmap object.
  228.     local self = setmetatable({}, Bitmap)
  229.     local tBytes = {}
  230.    
  231.     -- Check that the file exists.
  232.     if fs.exists( _sPath ) then
  233.         local file = io.open( _sPath, "rb" )
  234.         local sLine = file:read()
  235.         while sLine do
  236.             tBytes[#tBytes + 1] = sLine
  237.             sLine = file:read()
  238.         end
  239.         file:close()
  240.     else
  241.         error("There is no file located at ".._sPath..".", 2)
  242.     end
  243.    
  244.     -- Load the advanced Bitmap info.
  245.     local bitmapInfo = {
  246.         Identity = getString(tBytes, 0, 2),
  247.         Size = getNumber(tBytes, 2, 4),
  248.         App = getString(tBytes, 6, 4),
  249.         BitMapOffset = getNumber(tBytes, 10, 4),
  250.         HeaderSize = getNumber(tBytes, 14, 4),
  251.         Width = getNumber(tBytes, 18, 4),
  252.         Height = getNumber(tBytes, 22, 4),
  253.         ColorPlanes = getNumber(tBytes, 26, 2),
  254.         BitsPerPixel = getNumber(tBytes, 28, 2),
  255.         CompressionType = getNumber(tBytes, 30, 4),
  256.         RawBitmapDataSize = getNumber(tBytes, 34, 4),
  257.         HorizontalResolution = getNumber(tBytes, 38, 4),
  258.         VerticalResolution = getNumber(tBytes, 42, 4),
  259.         ColorsInColorPalette = getNumber(tBytes, 46, 4),
  260.         ImportantColors = getNumber(tBytes, 50, 4),
  261.     }
  262.     self.Width = bitmapInfo.Width
  263.     self.Height = bitmapInfo.Height
  264.     self.bitmapInfo = bitmapInfo
  265.    
  266.     -- Do we know the identity of this file? If not then this is not a valid Bitmap file and cannot be loaded.
  267.     if bitmapInfo.Identity ~= "BM" then
  268.         error(_sPath.." is not a valid bitmap file.", 2)
  269.     end
  270.    
  271.     -- Attempt to load the Bitmap data from the file if we can.
  272.     local success, errorMessage = pcall(function()
  273.         if bitmapInfo.CompressionType == BI_RGB then
  274.             local BitsPerPixel = math.ceil(bitmapInfo.BitsPerPixel)
  275.             local BytesPerPixel = BitsPerPixel/8
  276.             local BitMapOffset = bitmapInfo.BitMapOffset
  277.             local BitmapWidth = bitmapInfo.Width
  278.             local BitmapHeight = bitmapInfo.Height
  279.             local RowSize = (math.floor(( BitsPerPixel * BitmapWidth  + 31) / 32) * 4)
  280.             local RowSizeBytes = math.ceil(RowSize/8)
  281.             local PixelArraySize = RowSize*math.ceil(BitmapHeight)
  282.             local PixelArraySizeBytes = RowSizeBytes*math.ceil(BitmapHeight)
  283.            
  284.             local offset = 0 -- The offset used to get a pixel's color.
  285.             local bit16 = 255/15
  286.             local byte1, byte2
  287.             bitmapInfo.Bitmap = {{},{},{},{}} -- The data of the bitmap.
  288.             -- Load the bitmap data into the table.
  289.             if BitsPerPixel == 16 then -- 16 bits per pixel.
  290.                 for x = 1, BitmapWidth do
  291.                     bitmapInfo.Bitmap[x] = {}
  292.                     for y = 1, BitmapHeight do
  293.                         offset = BitMapOffset + ( (x - 1)*BytesPerPixel ) + ( (BitmapHeight - (y - 1) - 1)*RowSize )
  294.                         byte1, byte2 = tBytes[offset+2], tBytes[offset+1]
  295.                         bitmapInfo.Bitmap[x][y] = Color3.new(math.floor((byte1%16)*bit16), math.floor(bit32.rshift(byte2, 4)*bit16), math.floor((byte2%16)*bit16))
  296.                     end
  297.                 end
  298.             else -- 24 bits per pixel.
  299.                 for x = 1, BitmapWidth do
  300.                     bitmapInfo.Bitmap[x] = {}
  301.                     for y = 1, BitmapHeight do
  302.                         offset = BitMapOffset + ( (x - 1)*BytesPerPixel ) + ( (BitmapHeight - (y - 1) - 1)*RowSize )
  303.                         bitmapInfo.Bitmap[x][y] = Color3.new(tBytes[offset+3], tBytes[offset+2], tBytes[offset+1])
  304.                     end
  305.                 end
  306.             end
  307.             return true
  308.         end
  309.         return "Compression type "..bitmapInfo.CompressionType.." is not able to be loaded by this API."
  310.     end)
  311.    
  312.     -- Did we succeed in loading the Bitmap data?
  313.     if success and errorMessage == true then
  314.         bitmapInfo.LoadedBitmap = true
  315.     else
  316.         bitmapInfo.LoadedBitmap = false
  317.         bitmapInfo.ErrorMessage = errorMessage
  318.         error(errorMessage)
  319.     end
  320.    
  321.     -- Return the new Bitmap object.
  322.     return self
  323. end
  324.  
  325. -- Saves the bitmap to the Hard Disk as a bitmap file.
  326. -- For bitmap objects created using the Bitmap.load function,
  327. -- the Bitmap array must be loaded successfully to work correctly.
  328. function Bitmap:Save(_sPath)
  329.     error("This function is not implemented.", 2)
  330. end
  331.  
  332. -- Converts the bitmap to an Advanced Computer Paint file.
  333. -- For bitmap objects created using the Bitmap.load function,
  334. -- the Bitmap array must be loaded successfully to work correctly.
  335. function Bitmap:SaveAsPaintFile(_sPath, scale)
  336.     if self.bitmapInfo.LoadedBitmap == true then
  337.         local file = fs.open(_sPath, "w")
  338.         if file then
  339.             local scale = scale or 1
  340.             local term = terminal or term
  341.             local Bitmap = self.bitmapInfo.Bitmap
  342.             for x=1, #Bitmap, math.max(1/scale, 1) do
  343.                 for y=1, #Bitmap[1], math.max(1/scale, 1) do
  344.                     local color = Bitmap[x][y]:ToComputerCraftColor()
  345.                     file.write(tHex[color])
  346.                 end
  347.                 file.write("\n")
  348.             end
  349.             file.close()
  350.         end
  351.     else
  352.         error("LoadedBitmap is not equal to true, meaning that the bitmap data was not loaded. This function can only be used if the bitmap data is available.", 2)
  353.     end
  354. end
  355.  
  356. -- Works with the Minecraft mod called ComputerCraft only.
  357. -- Draws the bitmap to the terminal.
  358. -- For bitmap objects created using the Bitmap.load function,
  359. -- the Bitmap array must be loaded successfully to work correctly.
  360. -- The dither argument determines whether the drawn picture will be dithered or not.
  361. function Bitmap:DrawToTerm(xPos, yPos, scale, dithered, terminal)
  362.     if not term then
  363.         error("This function is only supported on ComputerCraft.", 2)
  364.     end
  365.     if self.bitmapInfo.LoadedBitmap == true then
  366.         -- Draw the bitmap to the screen.
  367.         local scale = scale or 1
  368.         local term = terminal or term
  369.         local Bitmap = self.bitmapInfo.Bitmap
  370.         local x2, y2 = 0, 0
  371.         local text, textcolor, bgcolor
  372.         if dithered then
  373.             -- Dithered drawing.
  374.             for x=1, #Bitmap, (math.max(1/scale, 1)) do
  375.                 x2 = x2 + 1
  376.                 y2 = 0
  377.                 for y=1, #Bitmap[1], (math.max(1/scale, 1)) do
  378.                     y2 = y2 + 1
  379.                     text, textcolor, bgcolor = Bitmap[math.ceil(x)][math.ceil(y)]:ToComputerCraftDither()
  380.                     term.setCursorPos(xPos + math.floor((x2-1)), yPos + math.floor((y2-1)))
  381.                     term.setBackgroundColor(bgcolor or 1)
  382.                     term.setTextColor(textcolor or colors.black)
  383.                     term.write(text or " ")
  384.                 end
  385.             end
  386.         else
  387.             for x=1, #Bitmap, (math.max(1/scale, 1)) do
  388.                 x2 = x2 + 1
  389.                 y2 = 0
  390.                 for y=1, #Bitmap[1], (math.max(1/scale, 1)) do
  391.                     y2 = y2 + 1
  392.                     bgcolor = Bitmap[math.ceil(x)][math.ceil(y)]:ToComputerCraftColor()
  393.                     term.setCursorPos(xPos + math.floor((x2-1)), yPos + math.floor((y2-1)))
  394.                     term.setBackgroundColor(bgcolor or 1)
  395.                     term.write(" ")
  396.                 end
  397.             end
  398.         end
  399.     else
  400.         error("LoadedBitmap is not equal to true, meaning that the bitmap data was not loaded. This function can only be used if the bitmap data is available.", 2)
  401.     end
  402. end
  403.  
  404. -- Retrieves the pixel color at a specified location on the bitmap surface.
  405. -- For bitmap objects created using the Bitmap.load function,
  406. -- the Bitmap array must be loaded successfully to work correctly.
  407. function Bitmap:GetPixel(x, y)
  408.     if self.bitmapInfo.LoadedBitmap == true then
  409.         local Width, Height = bitmapInfo.Width, bitmapInfo.Height
  410.         local X = ((x < Width and x) or Width)
  411.         local Y = ((y < Height and y) or Height)
  412.         return self.bitmapInfo.Bitmap[(X > 1 and X) or 1][(Y > 1 and Y) or 1]
  413.     end
  414.     return false
  415. end
  416.  
  417. -- Sets the color for a specified pixel.
  418. -- For bitmap objects created using the Bitmap.load function,
  419. -- the Bitmap array must be loaded successfully to work correctly.
  420. function Bitmap:SetPixel(color, x, y)
  421.     if self.bitmapInfo.LoadedBitmap == true then
  422.         local Width, Height = bitmapInfo.Width, bitmapInfo.Height
  423.         local X = ((x < Width and x) or Width)
  424.         local Y = ((y < Height and y) or Height)
  425.         self.bitmapInfo.Bitmap[(X > 1 and X) or 1][(Y > 1 and Y) or 1] = color
  426.     end
  427.     return false
  428. end
  429.  
  430. _G.Color3 = Color3
  431. _G.Bitmap = Bitmap
  432.  
  433. -- Command Line Functionality for ComputerCraft. --
  434. local tArgs = {...}
  435. if #tArgs > 0 then
  436.     local myBitmap
  437.     if tArgs[1] == "convert" then
  438.         if tArgs[3] then
  439.             myBitmap = Bitmap.load(tArgs[2])
  440.             myBitmap:SaveAsPaintFile(tArgs[3])
  441.         else
  442.             print("Usage: bmpreader convert <path> <copy to>")
  443.         end
  444.     elseif tArgs[1] == "view" then
  445.         if tArgs[2] then
  446.             local width, height = term.getSize()
  447.             myBitmap = Bitmap.load(tArgs[2])
  448.             term.clear()
  449.             local scale = math.min(width/myBitmap.Width, height/myBitmap.Height, 1)
  450.             local x, y = width*.5 + 1 - (scale*width*.5), height*.5 + 1 - (scale*height*.5)
  451.             myBitmap:DrawToTerm(1, 1, scale, true)
  452.             os.pullEvent("key")
  453.             term.setCursorPos(1,1)
  454.             term.setTextColor(colors.white)
  455.             term.setBackgroundColor(colors.black)
  456.             term.clear()
  457.         else
  458.             print("Usage: bmpreader view <path>")
  459.         end
  460.     else
  461.         print("This is an API, but you can use it directly on the command line using these functions:")
  462.         print("Convert a bitmap image to the ComputerCraft paint format: bmpreader convert <path> <copy to>")
  463.         print("View a bitmap image: bmpreader view <path>")
  464.     end
  465. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement