-
Notifications
You must be signed in to change notification settings - Fork 0
/
image.t
371 lines (325 loc) · 10.9 KB
/
image.t
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
local m = require("mem")
local templatize = require("templatize")
local util = require("util")
local Vec = require("linalg").Vec
local C = terralib.includecstring [[
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
]]
local FI = os.getenv("FREEIMAGE_H_PATH") and terralib.includec(os.getenv("FREEIMAGE_H_PATH")) or
error("Environment variable 'FREEIMAGE_H_PATH' not defined.")
if os.getenv("FREEIMAGE_LIB_PATH") then
terralib.linklibrary(os.getenv("FREEIMAGE_LIB_PATH"))
else
error("Environment variable 'FREEIMAGE_LIB_PATH' not defined.")
end
FI.FreeImage_Initialise(0)
-- Tear down FreeImage only when it is safe to destroy this module
local struct FIMemSentinel {}
terra FIMemSentinel:__destruct()
FI.FreeImage_DeInitialise()
end
local __fiMemSentinel = terralib.new(FIMemSentinel)
m.gc(__fiMemSentinel)
local function makeEnum(names, startVal)
local enum = {}
for i,n in ipairs(names) do
enum[n] = startVal + (i-1)
end
return enum
end
-- FreeImage types
local Type = makeEnum({"UNKNOWN", "BITMAP", "UINT16", "INT16", "UINT32", "INT32", "FLOAT", "DOUBLE", "COMPLEX", "RGB16",
"RGBA16", "RGBF", "RGBAF"}, 0)
-- FreeImage formats
local Format = makeEnum({"UNKNOWN", "BMP", "ICO", "JPEG", "JNG", "KOALA", "LBM", "MNG", "PBM", "PBMRAW",
"PCD", "PCX", "PGM", "PGMRAW", "PNG", "PPM", "PPMRAW", "RAS", "TARGA", "TIFF", "WBMP", "PSD", "CUT", "XBM", "XPM",
"DDS", "GIF", "HDR", "FAXG3", "SGI", "EXR", "J2K", "JP2", "PFM", "PICT", "RAW"}, -1)
Format.IFF = Format.LBM
-- Code gen helpers
local function arrayElems(ptr, num)
local t = {}
for i=1,num do
local iminus1 = i-1
table.insert(t, `ptr[iminus1])
end
return t
end
local function wrap(exprs, unaryFn)
local t = {}
for _,e in ipairs(exprs) do table.insert(t, `[unaryFn(e)]) end
return t
end
local function typeAndBitsPerPixel(dataType, numChannels)
-- Bytes to bits
local function B2b(B)
return 8*B
end
assert(numChannels > 0 and numChannels <= 4)
-- 8-bit per channel image (standard bitmaps)
if dataType == uint8 then
return Type.BITMAP, B2b(terralib.sizeof(uint8)*numChannels)
-- Signed 16-bit per channel image (only supports single channel)
elseif dataType == int16 and numChannels == 1 then
return Type.INT16, B2b(terralib.sizeof(int16))
-- Unsigned 16-bit per channel image
elseif dataType == uint16 then
local s = terralib.sizeof(uint16)
-- Single-channel
if numChannels == 1 then
return Type.UINT16, B2b(s)
-- RGB
elseif numChannels == 3 then
return Type.RGB16, B2b(s*3)
-- RGBA
elseif numChannels == 4 then
return Type.RGBA16, B2b(s*4)
end
-- Signed 32-bit per channel image (only supports single channel)
elseif dataType == int32 and numChannels == 1 then
return Type.INT32, B2b(terralib.sizeof(int32))
-- Unsigned 32-bit per channel image (only supports single channel)
elseif dataType == uint32 and numChannels == 1 then
return Type.UINT32, B2b(terralib.sizeof(uint32))
-- Single precision floating point per chanel image
elseif dataType == float then
local s = terralib.sizeof(float)
-- Single-channel
if numChannels == 1 then
return Type.FLOAT, B2b(s)
-- RGB
elseif numChannels == 3 then
return Type.RGBF, B2b(s*3)
-- RGBA
elseif numChannels == 4 then
return Type.RGBAF, B2b(s*4)
end
-- Double-precision floating point image (only supports single channel)
elseif dataType == double then
return Type.DOUBLE, B2b(terralib.sizeof(double))
else
error(string.format("FreeImage does not support images with %u %s's per pixel", numChannels, tostring(dataType)))
end
end
local Image = templatize(function(dataType, numChannels)
local Color = Vec(dataType, numChannels)
local struct ImageT
{
data: &Color,
width: uint,
height: uint
}
ImageT.Color = Color
ImageT.metamethods.__typename = function(self)
return string.format("Image(%s, %d)", tostring(dataType), numChannels)
end
terra ImageT:getPixelPtr(x: uint, y: uint)
return self.data + y*self.width + x
end
util.inline(ImageT.methods.getPixelPtr)
terra ImageT:getPixelValue(x: uint, y: uint)
return m.copy(@self.getPixelPtr(x, y))
end
util.inline(ImageT.methods.getPixelValue)
ImageT.metamethods.__apply = macro(function(self, x, y)
return `@(self.data + y*self.width + x)
end)
terra ImageT:setPixel(x: uint, y: uint, color: &Color)
var pix = self:getPixelPtr(x, y)
[Color.entryExpList(pix)] = [Color.entryExpList(color)]
end
util.inline(ImageT.methods.setPixel)
terra ImageT:__construct() : {}
self.width = 0
self.height = 0
self.data = nil
end
terra ImageT:__construct(width: uint, height: uint) : {}
self:__construct()
self.width = width
self.height = height
if width*height > 0 then
self.data = [&Color](C.malloc(width*height*sizeof(Color)))
for y=0,self.height do
for x=0,self.width do
self:getPixelPtr(x, y):__construct()
end
end
end
end
terra ImageT:__destruct()
C.free(self.data)
end
terra ImageT:resize(width: uint, height: uint)
if self.width ~= width or self.height ~= height then
self:__destruct()
self:__construct(width, height)
end
end
terra ImageT:__copy(other: &ImageT)
self.width = other.width
self.height = other.height
self.data = [&Color](C.malloc(self.width*self.height*sizeof(Color)))
for y=0,self.height do
for x=0,self.width do
@self:getPixelPtr(x, y) = m.copy(@other:getPixelPtr(x, y))
end
end
end
terra ImageT:clear(color: Color)
for y=0,self.height do
for x=0,self.width do
@self:getPixelPtr(x, y) = color
end
end
end
-- Quantize/dequantize channel values
local makeQuantize = templatize(function(srcDataType, tgtDataType)
local function B2b(B)
return 8*B
end
return function(x)
if tgtDataType:isfloat() and srcDataType:isintegral() then
local tsize = terralib.sizeof(srcDataType)
local maxtval = (2 ^ B2b(tsize)) - 1
return `[tgtDataType](x/[tgtDataType](maxtval))
elseif tgtDataType:isintegral() and srcDataType:isfloat() then
local tsize = terralib.sizeof(tgtDataType)
local maxtval = (2 ^ B2b(tsize)) - 1
-- return `C.fmin(C.fmax([tgtDataType](x * maxtval), 0.0), maxtval)
return `[tgtDataType](C.fmin(C.fmax(x, 0.0), 1.0) * maxtval)
else
return `[tgtDataType](x)
end
end
end)
-- Load and return an image
local loadImage = templatize(function(fileDataType)
local b2B = macro(function(b)
return `b/8
end)
local quantize = makeQuantize(fileDataType, dataType)
return terra(fibitmap: &FI.FIBITMAP)
var bpp = FI.FreeImage_GetBPP(fibitmap)
var fileNumChannels = b2B(bpp) / sizeof(fileDataType)
-- FreeImage flips R and B for 24 and 32 bit images
var isBGR = [fileDataType == uint8] and (fileNumChannels == 3 or fileNumChannels == 4)
var numChannelsToCopy = fileNumChannels
if numChannels < numChannelsToCopy then numChannelsToCopy = numChannels end
var w = FI.FreeImage_GetWidth(fibitmap)
var h = FI.FreeImage_GetHeight(fibitmap)
var image = ImageT.stackAlloc(w, h)
for y=0,h do
var scanline = [&fileDataType](FI.FreeImage_GetScanLine(fibitmap, y))
for x=0,w do
var fibitmapPixelPtr = scanline + x*fileNumChannels
var imagePixelPtr = image:getPixelPtr(x, y)
for c=0,numChannelsToCopy do
imagePixelPtr(c) = [quantize(`fibitmapPixelPtr[c])]
end
-- If we have a 3 or 4 element uint8 image (read:
-- a 24 or 32 bit image), then FreeImage flips R and B
-- for little endian machines (all x86 machines)
-- We need to flip it back
if isBGR then
var tmp = imagePixelPtr(0)
imagePixelPtr(0) = imagePixelPtr(2)
imagePixelPtr(2) = tmp
end
end
end
return image
end
end)
ImageT.methods.load = terra(format: int, filename: rawstring)
var fibitmap = FI.FreeImage_Load(format, filename, 0)
if fibitmap == nil then
util.fatalError("Could not load image file '%s'\n", filename)
end
var fit = FI.FreeImage_GetImageType(fibitmap)
var bpp = FI.FreeImage_GetBPP(fibitmap)
var image : ImageT
if fit == Type.BITMAP then
image = [loadImage(uint8)](fibitmap)
elseif fit == Type.INT16 then
image = [loadImage(int16)](fibitmap)
elseif fit == Type.UINT16 or fit == Type.RGB16 or fit == Type.RGBA16 then
image = [loadImage(uint16)](fibitmap)
elseif fit == Type.INT32 then
image = [loadImage(int32)](fibitmap)
elseif fit == Type.UINT32 then
image = [loadImage(uint32)](fibitmap)
elseif fit == Type.FLOAT or fit == Type.RGBF or fit == Type.RGBAF then
image = [loadImage(float)](fibitmap)
elseif fit == Type.DOUBLE then
image = [loadImage(double)](fibitmap)
else
util.fatalError("Attempt to load unsupported image type.\n")
end
FI.FreeImage_Unload(fibitmap)
return image
end
-- Save an existing image
ImageT.save = templatize(function(fileDataType)
-- Default to internal dataType
fileDataType = fileDataType or dataType
local quantize = makeQuantize(dataType, fileDataType)
local fit, bpp = typeAndBitsPerPixel(fileDataType, numChannels)
-- FreeImage flips R and B for 24 and 32 bit images
local isBGR = fileDataType == uint8 and (numChannels == 3 or numChannels == 4)
return terra(image: &ImageT, format: int, filename: rawstring)
var fibitmap = FI.FreeImage_AllocateT(fit, image.width, image.height, bpp, 0, 0, 0)
if fibitmap == nil then
util.fatalError("Unable to allocate FreeImage bitmap to save image.\n")
end
-- C.printf("width: %u, height: %u, fit: %d, bpp: %d\n", image.width, image.height, fit, bpp)
for y=0,image.height do
var scanline = [&fileDataType](FI.FreeImage_GetScanLine(fibitmap, y))
for x=0,image.width do
var fibitmapPixelPtr = scanline + x*numChannels
var imagePixelPtr = image:getPixelPtr(x, y)
for c=0,numChannels do
fibitmapPixelPtr[c] = [quantize(`imagePixelPtr(c))]
end
-- If we have a 3 or 4 element uint8 image (read:
-- a 24 or 32 bit image), then FreeImage flips R and B
-- for little endian machines (all x86 machines)
-- We need to flip it back
[util.optionally(isBGR, function() return quote
var tmp = fibitmapPixelPtr[0]
fibitmapPixelPtr[0] = fibitmapPixelPtr[2]
fibitmapPixelPtr[2] = tmp
end end)]
end
end
if FI.FreeImage_Save(format, fibitmap, filename, 0) == 0 then
util.fatalError("Failed to save image named '%s'\n", filename)
end
FI.FreeImage_Unload(fibitmap)
end
end)
-- Convenience method for most common save case
terra ImageT:save(format: int, filename: rawstring)
[ImageT.save()](self, format, filename)
end
m.addConstructors(ImageT)
return ImageT
end)
-- -- TEST
-- local terra test()
-- var flowersInt = [Image(uint8, 3)].load(Format.JPEG, "flowers.jpeg")
-- [Image(uint8, 3).save()](&flowersInt, Format.PNG, "flowersInt.png")
-- m.destruct(flowersInt)
-- var flowersFloat = [Image(float, 3)].load(Format.JPEG, "flowers.jpeg")
-- [Image(float, 3).save(uint8)](&flowersFloat, Format.PNG, "flowersFloat.png")
-- m.destruct(flowersFloat)
-- end
-- test()
return
{
-- Type = Type,
Format = Format,
Image = Image,
__fiMemSentinel = __fiMemSentinel
}