Skip to content

Commit

Permalink
added an option for pure-luajit and library of each file format, rath…
Browse files Browse the repository at this point in the history
…er than depending on umbrella libraries (luaimg, sdl_image, etc)
  • Loading branch information
thenumbernine committed Jun 16, 2015
1 parent 7017b85 commit 32a5a07
Show file tree
Hide file tree
Showing 9 changed files with 508 additions and 2 deletions.
8 changes: 8 additions & 0 deletions image.lua
@@ -1,3 +1,11 @@
-- png, jpeg, tiff, limited bmp and tga, ppm, fits
-- requires a separate shared object to be built
return require 'image.luaimg.image'

-- png, jpeg
-- sdl has more read support but only bmp write support =(
--return require 'image.sdl_image.image'

-- bmp, png, tiff
-- pure luajit ffi
return require 'image.luajit.image'
5 changes: 3 additions & 2 deletions luaimg/image.lua
Expand Up @@ -22,8 +22,8 @@ Image.loaders = {

function Image:init(w,h,ch)
if type(w) == 'string' then
local ext = w:match('.*%.(.-)$')
local loader = self.loaders[ext]
local ext = w:match'.*%.(.-)$'
local loader = ext and self.loaders[ext:lower()]
if not loader then
error("I don't know how to load a file with ext "..tostring(ext))
else
Expand Down Expand Up @@ -59,3 +59,4 @@ function Image:save(...)
end
return Image
176 changes: 176 additions & 0 deletions luajit/bmp.lua
@@ -0,0 +1,176 @@
--[[
NOTICE the BMP save/load operates as BGR
I'm also saving images upside-down ... but I'm working with flipped buffers so it's okay?
--]]
local ffi = require 'ffi'
local gc = require 'gcmem.gcmem'
require 'ffi.c.stdio'
ffi.cdef[[
//wtypes.h
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef long LONG;
//wingdi.h
#pragma pack(1)
struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
};
typedef struct tagBITMAPFILEHEADER BITMAPFILEHEADER;
struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
};
typedef struct tagBITMAPINFOHEADER BITMAPINFOHEADER;
//I don't trust MS semantics ... if you get any weird memory behavior, switch to using GCC's __attribute__ ((packed))
#pragma pack(0)
]]

local exports = {}

exports.load = function(filename)
local file = ffi.C.fopen(filename, 'rb')
if not file then error("failed to open file "..filename.." for reading") end

local fileHeader = gc.new('BITMAPFILEHEADER', 1)
ffi.C.fread(fileHeader, ffi.sizeof(fileHeader[0]), 1, file)

--[[
print('file header:')
print('type', ('%x'):format(fileHeader[0].bfType))
print('size', fileHeader[0].bfSize)
print('reserved1', fileHeader[0].bfReserved1)
print('reserved2', fileHeader[0].bfReserved2)
print('offset', fileHeader[0].bfOffBits)
--]]
assert(fileHeader[0].bfType == 0x4d42, "image has bad signature")
-- assert that the reserved are zero?
local infoHeader = gc.new('BITMAPINFOHEADER', 1)
ffi.C.fread(infoHeader, ffi.sizeof(infoHeader[0]), 1, file)
--[[
print('info header:')
print('size', infoHeader[0].biSize)
print('width', infoHeader[0].biWidth)
print('height', infoHeader[0].biHeight)
print('planes', infoHeader[0].biPlanes)
print('bitcount', infoHeader[0].biBitCount)
print('compression', infoHeader[0].biCompression)
print('size of image', infoHeader[0].biSizeImage)
print('biXPelsPerMeter', infoHeader[0].biXPelsPerMeter)
print('biYPelsPerMeter', infoHeader[0].biYPelsPerMeter)
print('colors used', infoHeader[0].biClrUsed)
print('colors important', infoHeader[0].biClrImportant)
--]]
assert(infoHeader[0].biBitCount == 24, "only supports 24-bpp images")
assert(infoHeader[0].biCompression == 0, "only supports uncompressed images")
ffi.C.fseek(file, fileHeader[0].bfOffBits, ffi.C.SEEK_SET)
local width = infoHeader[0].biWidth
local height = infoHeader[0].biHeight
assert(height >= 0, "currently doesn't support flipped images")
local data = gc.new('char', width * height * 3)
local padding = (4-(3 * width))%4
for y=height-1,0,-1 do
-- read it out as BGR
ffi.C.fread(data + 3 * width * y, 3 * width, 1, file)
if padding ~= 0 then
ffi.C.fseek(file, padding, ffi.C.SEEK_SET)
end
end
ffi.C.fclose(file)
return {
data = data,
width = width,
height = height,
xdpi = infoHeader[0].biXPelsPerMeter,
ydpi = infoHeader[0].biYPelsPerMeter,
}
end
exports.save = function(args)
local filename = assert(args.filename, "expected filename")
local width = assert(args.width, "expected width")
local height = assert(args.height, "expected height")
local data = assert(args.data, "expected data")
local padding = (4-(3*width))%4
local rowsize = width * 3 + padding
local fileHeader = gc.new('BITMAPFILEHEADER', 1)
local infoHeader = gc.new('BITMAPINFOHEADER', 1)
local offset = ffi.sizeof(fileHeader[0]) + ffi.sizeof(infoHeader[0])
local file = ffi.C.fopen(filename, 'wb')
if not file then error("failed to open file "..filename.." for writing") end
fileHeader[0].bfType = 0x4d42
fileHeader[0].bfSize = rowsize * height + offset
fileHeader[0].bfReserved1 = 0
fileHeader[0].bfReserved2 = 0
fileHeader[0].bfOffBits = offset
ffi.C.fwrite(fileHeader, ffi.sizeof(fileHeader[0]), 1, file)
infoHeader[0].biSize = ffi.sizeof(infoHeader[0])
infoHeader[0].biWidth = width
infoHeader[0].biHeight = height
infoHeader[0].biPlanes = 1
infoHeader[0].biBitCount = 24
infoHeader[0].biCompression = 0
infoHeader[0].biSizeImage = 0 -- rowsize * height? the source has zero here
infoHeader[0].biXPelsPerMeter = args.xdpi or 300
infoHeader[0].biYPelsPerMeter = args.ydpi or 300
ffi.C.fwrite(infoHeader, ffi.sizeof(infoHeader[0]), 1, file)
local zero = gc.new('int', 1)
zero[0] = 0
local row = gc.new('unsigned char', 3 * width)
for y=height-1,0,-1 do
ffi.copy(row, data + 3 * width * y, 3 * width)
for x=0,width-1 do
row[0+3*x], row[2+3*x] = row[2+3*x], row[0+3*x]
end
ffi.C.fwrite(row, 3 * width, 1, file)
if padding ~= 0 then
ffi.C.fwrite(zero, padding, 1, file)
end
end
gc.free(zero)
gc.free(row)
gc.free(fileHeader)
gc.free(infoHeader)
ffi.C.fclose(file)
end
return exports
76 changes: 76 additions & 0 deletions luajit/image.lua
@@ -0,0 +1,76 @@
--[[
TODO all the loaders are currently designed to work for RGB,
whereas the other options (luaimg, sdl_image) are designed to work for RGBA
so this needs to be changed to work with RGBA too
--]]
local ffi = require 'ffi'
local class = require 'ext.class'
local Image = class()
Image.loaders = {
png = require 'image.luajit.png',
bmp = require 'image.luajit.bmp',
tif = require 'image.luajit.tiff',
tiff = require 'image.luajit.tiff',
}
function Image:init(w,h,ch)
ch = ch or 4
if type(w) == 'string' then
local ext = w:match'.*%.(.-)$'
local loader = ext and self.loaders[ext:lower()]
if not loader then
error("I don't know how to load a file with ext "..tostring(ext))
end
local result = loader.load(w)
self.buffer = result.data
self.width = result.width
self.height = result.height
self.channels = 3
else
self.buffer = ffi.new('unsigned char[?]', w*h*ch)
self.width = w
self.height = h
self.channels = ch
end
end
function Image:save(filename, ...)
assert(self.channels == 3, "expected only 3 channels")
local ext = filename:match'.*%.(.-)$'
local loader = ext and self.loaders[ext:lower()]
if not loader then
error("I don't know how to load a file with ext "..tostring(ext))
end
loader.save{
filename = filename,
width = self.width,
height = self.height,
data = self.buffer,
}
end
function Image:size()
return self.width, self.height, self.channels
end
function Image:__call(x,y,r,g,b,a)
local i = self.channels * (x + self.width * y)
local pixels = self.buffer
local _r = pixels[i+0] / 255
local _g = self.channels > 1 and pixels[i+1] / 255
local _b = self.channels > 2 and pixels[i+2] / 255
local _a = self.channels > 3 and pixels[i+3] / 255
if r ~= nil then pixels[i+0] = math.floor(r * 255) end
if self.channels > 1 and g ~= nil then pixels[i+1] = math.floor(g * 255) end
if self.channels > 2 and b ~= nil then pixels[i+2] = math.floor(b * 255) end
if self.channels > 3 and a ~= nil then pixels[i+3] = math.floor(a * 255) end
return _r, _g, _b, _a
end
function Image:data()
return self.buffer
end
return Image
113 changes: 113 additions & 0 deletions luajit/png.lua
@@ -0,0 +1,113 @@
local ffi = require 'ffi'
require 'ffi.c.string' --memcpy
local png = require 'ffi.png'
local exports = {}

local libpngVersion = "1.5.13"

exports.load = function(filename)
assert(filename, "expected filename")

local header = ffi.new('char[8]') -- 8 is the maximum size that can be checked

-- open file and test for it being a png */
local fp = ffi.C.fopen(filename, 'rb')
if not fp then
error(string.format("[read_png_file] File %s could not be opened for reading", filename))
end

ffi.C.fread(header, 1, 8, fp)
if png.png_sig_cmp(header, 0, 8) ~= 0 then
error(string.format("[read_png_file] File %s is not recognized as a PNG file", filename))
end

-- initialize stuff */
local png_ptr = png.png_create_read_struct(libpngVersion, nil, nil, nil)

if not png_ptr then
error("[read_png_file] png_create_read_struct failed")
end

local info_ptr = png.png_create_info_struct(png_ptr)
if not info_ptr then
error("[read_png_file] png_create_info_struct failed")
end

png.png_init_io(png_ptr, fp)
png.png_set_sig_bytes(png_ptr, 8)

png.png_read_png(png_ptr, info_ptr, png.PNG_TRANSFORM_IDENTITY, nil)

local width = png.png_get_image_width(png_ptr, info_ptr)
local height = png.png_get_image_height(png_ptr, info_ptr)
local colorType = png.png_get_color_type(png_ptr, info_ptr)
local bit_depth = png.png_get_bit_depth(png_ptr, info_ptr)
if colorType ~= png.PNG_COLOR_TYPE_RGB then
error("expected colorType PNG_COLOR_TYPE_RGB, got "..colorType)
end
assert(bit_depth == 8, "can only handle 8-bit images at the moment")

local number_of_passes = png.png_set_interlace_handling(png_ptr)
png.png_read_update_info(png_ptr, info_ptr)

-- read file */

assert(ffi.sizeof('png_byte') == 1)
local row_pointers = png.png_get_rows(png_ptr, info_ptr)

local data = ffi.new('unsigned char[?]', width * height * 3)
for y=0,height-1 do
ffi.C.memcpy(ffi.cast('char*', data) + 3*width*(height-1-y), row_pointers[y], 3 * width)
end

-- TODO free row_pointers?

ffi.C.fclose(fp)

return {data=data, width=width, height=height}
end

exports.save = function(args)
-- args:
local filename = assert(args.filename, "expected filename")
local width = assert(args.width, "expected width")
local height = assert(args.height, "expected height")
local data = assert(args.data, "expected data")

local fp = ffi.C.fopen(filename, 'wb')
if not fp then error("failed to open file "..filename.." for writing") end

-- initialize stuff */
local png_ptr = png.png_create_write_struct(libpngVersion, nil, nil, nil)

if not png_ptr then
error "[write_png_file] png_create_write_struct failed"
end

local info_ptr = png.png_create_info_struct(png_ptr)
if not info_ptr then
error("[write_png_file] png_create_info_struct failed")
end

png.png_init_io(png_ptr, fp)

png.png_set_IHDR(png_ptr, info_ptr, width, height,
8, png.PNG_COLOR_TYPE_RGB, png.PNG_INTERLACE_NONE,
png.PNG_COMPRESSION_TYPE_BASE, png.PNG_FILTER_TYPE_BASE)

png.png_write_info(png_ptr, info_ptr)

local rowptrs = ffi.new('unsigned char *[?]', height)
for y=0,height-1 do
rowptrs[y] = data + 3*width*(height-1-y)
end
png.png_write_image(png_ptr, rowptrs)

png.png_write_end(png_ptr, nil)

-- close file
ffi.C.fclose(fp)
end

return exports

0 comments on commit 32a5a07

Please sign in to comment.