Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added an option for pure-luajit and library of each file format, rath…
…er than depending on umbrella libraries (luaimg, sdl_image, etc)
- Loading branch information
1 parent
7017b85
commit 32a5a07
Showing
9 changed files
with
508 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
|
Oops, something went wrong.