This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
KevinW1998's profile picture

Bitmap (.BMP) API

Started by KevinW1998, 24 January 2014 - 02:28 PM
KevinW1998 #1
Posted 24 January 2014 - 03:28 PM
So with the permission of mads I expanded his Bitmap Loader to a whole API. With this API you can make a Bitmap-Api-Object and load from or save to a bitmap file or paint file.

Every Bitmap file MUST BE:
* 24-Bit
* Uncompressed

Code:
Spoiler

local localFunctions = {}
localFunctions.colorRGB = {
{r = 240, g = 240, b = 240, code = colors.white},
{r = 235, g = 236, b = 68, code = colors.orange},
{r = 195, g = 84, b = 205, code = colors.magenta},
{r = 102, g = 137, b = 211, code = colors.lightBlue},
{r = 222, g = 222, b = 108, code = colors.yellow},
{r = 65, g = 205, b = 52, code = colors.lime},
{r = 216, g = 129, b = 152, code = colors.pink},
{r = 67, g = 67, b = 67, code = colors.gray},
{r = 153, g = 153, b = 153, code = colors.lightGray},
{r = 40, g = 118, b = 151, code = colors.cyan},
{r = 123, g = 47, b = 190, code = colors.purple},
{r = 37, g = 49, b = 146, code = colors.blue},
{r = 81, g = 48, b = 26, code = colors.brown},
{r = 59, g = 81, b = 26, code = colors.green},
{r = 179, g = 49, b = 44, code = colors.red},
{r = 0, g = 0, b = 0, code = colors.black}
}
localFunctions.PaintPx = {
{code = colors.white,pCode = "0"},
{code = colors.orange,pCode = "1"},
{code = colors.magenta,pCode = "2"},
{code = colors.lightBlue,pCode = "3"},
{code = colors.yellow,pCode = "4"},
{code = colors.lime,pCode = "5"},
{code = colors.pink,pCode = "6"},
{code = colors.gray,pCode = "7"},
{code = colors.lightGray,pCode = "8"},
{code = colors.cyan,pCode = "9"},
{code = colors.purple,pCode = "a"},
{code = colors.blue,pCode = "b"},
{code = colors.brown,pCode = "c"},
{code = colors.green,pCode = "d"},
{code = colors.red,pCode = "e"},
{code = colors.black,pCode = "f"}
}
function localFunctions:readBMP(file)
local f = assert(fs.open(file, "rb"))
local bytecode = ""
local byte = f:read()
while byte ~= nil do
  bytecode = bytecode .. string.char(byte)
  byte = f:read()
end
f:close()
return bytecode
end
function localFunctions:readWORD(str, offset)
local loByte = str:byte(offset)
local hiByte = str:byte(offset + 1)
return hiByte * 256 + loByte
end
function localFunctions:readDWORD(str, offset)
local loWord = localFunctions:readWORD(str, offset)
local hiWord = localFunctions:readWORD(str, offset + 2)
return hiWord * 65536 + loWord
end
function localFunctions:toWORD(num)
local loByte = 0
local hiByte = 0
if num / 256 < 1 then
  loByte = num
else
  hiByte = math.modf(num/256)
  loByte = num - hiByte * 256
end
return string.char(loByte)..string.char(hiByte)
end
function localFunctions:toDWORD(num)
local loByte = 0
local hiByte = 0
local loByte2 = 0
local hiByte2 = 0
local r = 0
local r2 = 0
if num / 16777216 >= 1 then --4 bytes
  hiByte2 = math.modf(num/16777216)
  r2 = num - hiByte2 * 16777216
  loByte2 = math.modf(r2/65536)
  r = r2 - loByte2 * 65536
  hiByte = math.modf(r/256)
  loByte = r - hiByte * 256
elseif num / 65536 >= 1 then --3 bytes
  loByte2 = math.modf(num/65536)
  r = num - loByte2 * 65536
  hiByte = math.modf(r/256)
  loByte = r - hiByte * 256
elseif num / 256 >= 1 then --2 bytes
  hiByte = math.modf(num/256)
  loByte = num - hiByte * 256
else
  loByte = num
end
return string.char(loByte)..string.char(hiByte)..string.char(loByte2)..string.char(hiByte2)
end
function localFunctions:get4BitColor(r, g, B)/>/>/>/>/>
for k, v in ipairs(localFunctions.colorRGB) do
  --print(tostring(v.r).." "..tostring(v.g).." "..tostring(v.B)/>/>/>/>/>.." "..tostring(v.code)
  local lookVel = 10
  if r > v.r - lookVel and r < v.r + lookVel and g > v.g - lookVel and g < v.g + lookVel and b > v.b - lookVel and b < v.b + lookVel then
   return v.code
  end
end
return colors.white
end
function localFunctions:getRGB(color)
for k, v in ipairs(localFunctions.colorRGB) do
  if v.code == color then
   return v.r,v.g,v.b
  end
end
return 221,221,221
end

function localFunctions:isBMP(binary)
if localFunctions:readWORD(binary, 1) ~= 0x4D42 then
  return "Not a readable BMP File: Invalid Header"
elseif localFunctions:readWORD(binary, 29) ~= 24 then
  return "Not a readable BMP File: 24-bit BMP ONLY"
elseif localFunctions:readWORD(binary, 31) ~= 0 then
  return "Not a readable BMP File: File must be uncompressed"
else
  return -1
end
end
function localFunctions:readHeader(binary)
properities = {}
res = localFunctions:isBMP(binary)
if res ~= -1 then
  error(res,3)
end
properities.bytesize = localFunctions:readDWORD(binary, 3)
properities.width = localFunctions:readDWORD(binary, 19)
properities.height = localFunctions:readDWORD(binary, 23)
properities.bitCount = localFunctions:readDWORD(binary, 29)
return properities
end
function localFunctions:readBodyToTable(binary)
local offBits = localFunctions:readWORD(binary, 11)
local prop = localFunctions:readHeader(binary)
pixelTable = {}
rem = math.modf((prop.width)/4)
repToDWORD = (prop.width)-rem*4
for y = prop.height - 1, 0, -1 do
  offset = offBits +(repToDWORD*y) + (prop.width * prop.bitCount / 8) * y + 1
  for x = 1, prop.width do
   if type(pixelTable[x]) ~= "table" then
	pixelTable[x] = {}
   end
   local b = binary:byte(offset)
   local g = binary:byte(offset + 1)
   local r = binary:byte(offset + 2)
   offset = offset + 3
   --deb(offset.." "..b.." "..g.." "..r)
   cl = localFunctions:get4BitColor(r,g,B)/>/>/>/>/>
   pixelTable[x][prop.height - y] = cl
  end
end
return pixelTable
end
function localFunctions:emptyBMP(width,height)
local px = {}
for y = 1,height do
  for x = 1,width do
   if type(px[x]) ~= "table" then
	px[x] = {}
   end
   px[x][y] = colors.white
  end
end
return px
end
function localFunctions:toPaintPixel(px)
for i,v in ipairs(localFunctions.PaintPx) do
  if v.code == px then
   return v.pCode
  end
end
return "0"
end
function localFunctions:toColorPixel(ppx)
for i,v in ipairs(localFunctions.PaintPx) do
  if v.pCode == ppx then
   return v.code
  end
end
return colors.white
end
function localFunctions:generateBMPString(obj)
--File Header
local magicNum = localFunctions:toWORD(0x4D42)
local size = "" --unknown yet
local reserverd = localFunctions:toDWORD(0)
local OffBits = localFunctions:toDWORD(54)
--File Settings
local headerSize = localFunctions:toDWORD(40)
local width = localFunctions:toDWORD(obj.width)
local height = localFunctions:toDWORD(obj.height)
local planes = localFunctions:toWORD(1)
local bitCount = localFunctions:toWORD(24)
local compression = localFunctions:toDWORD(0)
local sizeImg = "" --unknown yet
local PelsPerMX = localFunctions:toDWORD(0)
local PelsPerMY = localFunctions:toDWORD(0)
local ClrUsed = localFunctions:toDWORD(0)
local ClrUsedImp = localFunctions:toDWORD(0)
--File Body
local rgbStr = ""
local rem = 0
local repToDWORD = 0
local remStr = ""
for y = obj.height, 1, -1 do
  for x = 1, obj.width do
   local r,g,b = localFunctions:getRGB(obj.pixels[x][y])
   rgbStr = rgbStr..string.char(B)/>/>/>/>/>..string.char(g)..string.char(r)
  end
  rem = math.modf((obj.width)/4)
  repToDWORD = (obj.width)-rem*4
  if repToDWORD ~= 0 then
   for i = 1,repToDWORD do
	rgbStr = rgbStr..string.char(0)
   end
  end
end
sizeImg = localFunctions:toDWORD(#rgbStr)
size = localFunctions:toDWORD(#rgbStr + 54)
local fHeader = magicNum..size..reserverd..OffBits
local fSettings = headerSize..width..height..planes..bitCount..compression..sizeImg..PelsPerMX..PelsPerMY..ClrUsed..ClrUsedImp
return fHeader..fSettings..rgbStr
end

BMP = {}
BMP.__index = BMP
function BMP:new(width, height)
local bmpObj = {}
setmetatable(bmpObj, BMP)
bmpObj.width = width
bmpObj.height = height
bmpObj.pixels = localFunctions:emptyBMP(width, height)
return bmpObj
end
function BMP:fromFile(filename)
local f = localFunctions:readBMP(filename)
local prop = localFunctions:readHeader(f)
local subObj = BMP:new(prop.width,prop.height)
subObj.pixels = localFunctions:readBodyToTable(f)
return subObj
end
function BMP:fromURL(URL)
if http then
  local hStream = http.get(URL)
  local tmpF = fs.open("_tmp_","w")
  tmpF.write(hStream.readAll())
  hStream.close()
  tmpF.close()
  local httpBMP = BMP:fromFile("_tmp_")
  fs.delete("_tmp_")
  return httpBMP
else
  error("HTTP api not active!",2)
end
end
function BMP:fromPaintFile(filename)
local pF = fs.open(filename, "r")
local rl = pF:readLine()
local height = 0
local width = 0
local pixels = {}
while(rl ~= nil)do
  height = height + 1
  if width == 0 then
   width = #rl
  end
  for sPos = 1,width do
   ssPos = string.sub(rl,sPos,sPos)
   if type(pixels[sPos]) ~= "table" then
	pixels[sPos] = {}
   end
   pixels[sPos][height] = localFunctions:toColorPixel(ssPos)
  end
  rl = pF:readLine()
end
local subObj = BMP:new(width, height)
subObj.pixels = pixels
pF:close()
return subObj
end
function BMP:toPaintFile(filename)
local pF = fs.open(filename, "w")
for y = 1, self.height do
  for x = 1, self.width do
   pF.write(localFunctions:toPaintPixel(self.pixels[x][y]))
  end
  pF.write("\n")
end
pF:close()
end
function BMP:toFile(filename)
local strHeader = localFunctions:generateBMPString(self)
local hFile = fs.open(filename,"wb")
for i = 1,#strHeader+1 do
  hFile.write(strHeader:byte(i))
end
hFile:close()
end
function BMP:setPixel(x,y,color)
self.pixels[x][y] = color
end
function BMP:getPixel(x,y)
return self.pixels[x][y]
end


Documentry:
SpoilerFirst you need to load the api! I suggest to use "BMPDll", what I used, but you can use every name you want ;)/>
So in my case: os.loadAPI("BMPDll")

h = BMPDll.BMP:new(width, height)
Creates a new Bitmap-Api-Object .

h = BMPDll.BMP:fromFile(filename)
Creates a new Bitmap-Api-Object from an exsisting Bitmap-File.

h = BMPDll.BMP:fromURL(URL)
Creates a new Bitmap-Api-Object from an Bitmap-File located on a webserver.

h = BMPDll.BMP:fromPaintFile(filename)
Creates a new Bitmap-Api-Object from an exsisting Computercraft-Paint-File.

h:toFile(filename)
Saves the Bitmap-Api-Object to a Bitmap-File.

h:toPaintFile(filename)
Saves the Bitmap-Api-Object to a Computercraft-Paint-File.

h:setPixel(x,y,color)
Set the pixel x,y to a Computercraft-Color (e.g. colors.green).

h:getPixel(x,y)
Get the color of a pixel x,y.

Simple Example:
Spoiler

h = BMPDll.BMP:new(10,10)
for i=1,10 do
  h:setPixel(i,i,colors.red)
end
h:toFile("sample.bmp")

pastebin get 2DavwMVN BMPDll

I hope you enjoy this API guys! I am happy with every feedback I get!

EDIT: Dammit…. wanted to post it in APIs and Utilities
Edited on 24 January 2014 - 02:51 PM
Csstform #2
Posted 24 January 2014 - 03:39 PM
I would suggest a pastebin link. Looks interesting!
KevinW1998 #3
Posted 24 January 2014 - 03:48 PM
Yea, I could do that
oeed #4
Posted 24 January 2014 - 06:35 PM
Does this actually use the real BMP format, or is it just named after it?
KevinW1998 #5
Posted 25 January 2014 - 01:20 AM
Of course real Bitmap Files. You can try it out ;)/>
oeed #6
Posted 25 January 2014 - 02:24 AM
Of course real Bitmap Files. You can try it out ;)/>
Awesome! You'd be surprised at the amount of image readers etc which just use a format that is inspired by the real thing though.
KevinW1998 #7
Posted 25 January 2014 - 03:35 AM
Awesome! You'd be surprised at the amount of image readers etc which just use a format that is inspired by the real thing though.

heh :D/>
Symmetryc #8
Posted 26 January 2014 - 12:14 AM
Hmm, the colors are slightly off (CC's colors changed since Mads made his), but other than that looks great :D/>
Edited on 25 January 2014 - 11:15 PM
KevinW1998 #9
Posted 26 January 2014 - 02:25 AM
Hmm, the colors are slightly off (CC's colors changed since Mads made his), but other than that looks great :D/>/>
I did change the colors. I made a screenshot of all the cc-colors and used the color picking tool from photoshop to get the RGB-value . Anyway… good to see that you enjoy my API ;)/>
Symmetryc #10
Posted 26 January 2014 - 08:13 AM
Hmmm, BIT sent me a hex listing of the colors a little while ago that he got from the the code of CC itself I believe and it's slightly different…
KevinW1998 #11
Posted 26 January 2014 - 09:14 AM
Hmmm, BIT sent me a hex listing of the colors a little while ago that he got from the the code of CC itself I believe and it's slightly different…
Well anyone can change the color-code in the first 16 lines of the source code, until I made a API for it. If you want you can send me the color-table and I would change it.
Symmetryc #12
Posted 26 January 2014 - 10:16 AM
http://pastebin.com/WP4WzQjy

Replying on mobile, hopefully it works…
theoriginalbit #13
Posted 26 January 2014 - 10:19 AM
http://pastebin.com/WP4WzQjy
Replying on mobile, hopefully it works…
looks about right…