-
Notifications
You must be signed in to change notification settings - Fork 1
/
png_encode.lua
338 lines (271 loc) · 9.51 KB
/
png_encode.lua
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
--- A PNG encoder.
--
-- Permission is hereby granted, free of charge, to any person obtaining
-- a copy of this software and associated documentation files (the
-- "Software"), to deal in the Software without restriction, including
-- without limitation the rights to use, copy, modify, merge, publish,
-- distribute, sublicense, and/or sell copies of the Software, and to
-- permit persons to whom the Software is furnished to do so, subject to
-- the following conditions:
--
-- The above copyright notice and this permission notice shall be
-- included in all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--
-- [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
--
-- Standard library imports --
local byte = string.byte
local ceil = math.ceil
local char = string.char
local concat = table.concat
local floor = math.floor
local gmatch = string.gmatch
local ipairs = ipairs
local min = math.min
local open = io.open
local unpack = unpack
-- Modules --
local operators = require("bitwise_ops.operators")
-- Imports --
local band = operators.band
local bnot = operators.bnot
local bxor = operators.bxor
local rshift = operators.rshift
-- Cached module references --
local _ToString_Interleaved_
local _ToString_RGBA_
-- Exports --
local M = {}
--[[
From http://www.chrfr.de/software/midp_png.html:
/*
* Minimal PNG encoder to create PNG streams (and MIDP images) from RGBA arrays.
*
* Copyright 2006-2009 Christian Fröschlin
*
* www.chrfr.de
*
*
* Changelog:
*
* 09/22/08: Fixed Adler checksum calculation and byte order
* for storing length of zlib deflate block. Thanks
* to Miloslav Ruzicka for noting this.
*
* 05/12/09: Split PNG and ZLIB functionality into separate classes.
* Added support for images > 64K by splitting the data into
* multiple uncompressed deflate blocks.
*
* Terms of Use:
*
* You may use the PNG encoder free of charge for any purpose you desire, as long
* as you do not claim credit for the original sources and agree not to hold me
* responsible for any damage arising out of its use.
*
* If you have a suitable location in GUI or documentation for giving credit,
* I'd appreciate a mention of
*
* PNG encoder (C) 2006-2009 by Christian Fröschlin, www.chrfr.de
*
* but that's not mandatory.
*
*/
--]]
-- Common save-to-file logic
local function SaveStr (name, str)
local file = open(name, "wb")
if file then
file:write(str)
file:close()
end
return file ~= nil
end
--- Saves color data as a PNG.
-- @string name Name of file to save.
-- @array colors Color values, stored as { _red1_, _green1_, _blue1_, _alpha1_, _red2_, ... }
-- @uint w Width of saved image. The height is computed automatically from this and #_colors_.
-- @ptable[opt] opts Save options. Fields:
--
-- * **from_01**: If true, color values are interpreted as being ∈ [0, 1], instead of
-- [0, 255] (the default).
-- * **yfunc**: Yield function, called periodically during the save (no arguments), e.g. to
-- yield within a coroutine. If absent, a no-op.
-- @treturn boolean Was the file written?
function M.Save_Interleaved (name, colors, w, opts)
return SaveStr(name, _ToString_Interleaved_(colors, w, opts))
end
--- Variant of @{Save_Interleaved}, with colors as separate channels.
-- @string name Name of file to save.
-- @array r Array of red values...
-- @array g ...green values...
-- @array b ...blue values...
-- @array a ...and alpha values.
-- @uint w Width of saved image. The height is computed automatically from this and the
-- minimum of #_r_, #_g_, #_b_, #_a_ (typically, these will all be the same).
-- @ptable[opt] opts As per @{Save_Interleaved}.
-- @treturn boolean Was the file written?
function M.Save_RGBA (name, r, g, b, a, w, opts)
return SaveStr(name, _ToString_RGBA_(r, g, b, a, w, opts))
end
-- Computes the Adler checksum
local function Adler (data)
local s1, s2 = 1, 0
for _, b in ipairs(data) do
local abs = b >= 0 and b or b + 256
s1 = (s1 + abs) % 65521
s2 = (s2 + s1) % 65521
end
return s2 * 2^16 + s1
end
-- Serializes a 32-bit number to bytes
local function U32 (num)
local low1, low2, low3 = num % 2^8, num % 2^16, num % 2^24
return char((num - low3) / 2^24, (low3 - low2) / 2^16, (low2 - low1) / 2^8, low1)
end
-- Writes up to 32K of an uncompressed block
local function WriteUncompressedBlock (stream, is_last, data, offset, len)
local nlen = band(bnot(len), 0xFFFF)
local lenFF, nlenFF = band(len, 0xFF), band(nlen, 0xFF)
stream[#stream + 1] = char(is_last and 1 or 0) -- Final flag, Compression type 0
stream[#stream + 1] = char(lenFF) -- Length LSB
stream[#stream + 1] = char(rshift(len - lenFF, 8)) -- Length MSB
stream[#stream + 1] = char(nlenFF) -- Length 1st complement LSB
stream[#stream + 1] = char(rshift(nlen - nlenFF, 8)) -- Length 1st complement MSB
for i = 1, len, 512 do -- Break up data into unpack-supported sizes
stream[#stream + 1] = char(unpack(data, offset + i, offset + min(i + 511, len))) -- Data
end
end
-- Maximum number of uncompressed bytes to write at once --
local BlockSize = 32000
-- Hard-coded first bytes in uncompressed block --
local Bytes = char(8, (31 - (8 * 256) % 31) % 31) -- CM = 8, CMINFO = 0; FCHECK (FDICT / FLEVEL = 0)
-- Built-in "zlib" for uncompressed blocks
local function UncompressedWrite (data, yfunc)
local subs, pos, n = { Bytes }, 0, #data
repeat
yfunc()
local left = n - pos
local is_last = left <= BlockSize
WriteUncompressedBlock(subs, is_last, data, pos, is_last and left or BlockSize)
pos = pos + BlockSize
until is_last
subs[#subs + 1] = U32(Adler(data))
return concat(subs, "")
end
-- LUT for CRC --
local CRC
-- Generates CRC table
local function CreateCRCTable ()
local t = {}
for i = 0, 255 do
local c = i
for _ = 1, 8 do
local bit = band(c, 0x1)
c = .5 * (c - bit)
if bit ~= 0 then
c = bxor(c, 0xEDB88320)
end
end
t[#t + 1] = c
end
return t
end
-- Update the CRC with new data
local function UpdateCRC (crc, bytes)
CRC = CRC or CreateCRCTable()
for b in gmatch(bytes, ".") do
crc = bxor(CRC[band(bxor(crc, byte(b)), 0xFF) + 1], rshift(crc, 8))
end
return crc
end
-- Serialize data as a PNG chunk
local function ToChunk (stream, id, bytes)
stream[#stream + 1] = U32(#bytes)
stream[#stream + 1] = id
stream[#stream + 1] = bytes
local crc = 0xFFFFFFFF
crc = UpdateCRC(crc, id)
crc = UpdateCRC(crc, bytes)
stream[#stream + 1] = U32(bnot(crc))
end
-- Default yield function: no-op
local function DefYieldFunc () end
-- Common string serialize behavior after canonicalizing data
local function Finish (data, extra, w, h, yfunc, opts)
local n = #data
-- Do any [0, 1] to [0, 255] conversion.
if opts then
for i = 1, opts.from_01 and n or 0 do
data[i] = min(floor(data[i] * 255), 255)
end
end
-- Pad the last row with 0, if necessary.
for _ = 1, extra do
data[n], data[n + 1], data[n + 2], data[n + 3], n = 0, 0, 0, 0, n + 4
end
-- Process the data, gather it into chunks, and emit the final byte stream.
local stream = { "\137\080\078\071\013\010\026\010" }
ToChunk(stream, "IHDR", U32(w) .. U32(h) .. char(8, 6, 0, 0, 0)) -- Bit depth, colortype (ARGB), compression, filter, interlace
ToChunk(stream, "IDAT", UncompressedWrite(data, yfunc))
ToChunk(stream, "IEND", "")
return concat(stream, "")
end
--- Variant of @{Save_Interleaved} that emits a raw byte stream, instead of saving to file.
-- @array colors As per @{Save_Interleaved}.
-- @uint w As per @{Save_Interleaved}.
-- @ptable[opt] opts As per @{Save_Interleaved}.
-- @treturn string Byte stream.
function M.ToString_Interleaved (colors, w, opts)
local ncolors = floor(#colors / 4)
local h, data = ceil(ncolors / w), {}
local si, di, extra = 1, 1, w * h - ncolors
local yfunc = (opts and opts.yfunc) or DefYieldFunc
-- Inject filters and do a standard write.
repeat
data[di], di = 0, di + 1 -- No filter
local count = min(w, ncolors)
for _ = 1, count * 4 do
data[di], si, di = colors[si], si + 1, di + 1
end
ncolors = ncolors - count
yfunc()
until ncolors == 0
return Finish(data, extra, w, h, yfunc, opts)
end
--- Variant of @{Save_RGBA} that emits a raw byte stream, instead of saving to file.
-- @array r Array of red values...
-- @array g ...green values...
-- @array b ...blue values...
-- @array a ...and alpha values.
-- @uint w As per @{Save_RGBA}.
-- @ptable[opt] opts As per @{Save_RGBA}.
-- @treturn string Byte stream.
function M.ToString_RGBA (r, g, b, a, w, opts)
local ncolors = min(#r, #g, #b, #a)
local h, data = ceil(ncolors / w), {}
local si, di, extra = 1, 1, w * h - ncolors
local yfunc = (opts and opts.yfunc) or DefYieldFunc
-- Interleave color streams, inject filters, and do a standard write.
repeat
data[di], di = 0, di + 1 -- No filter
for _ = 1, min(w, ncolors) do
data[di], data[di + 1], data[di + 2], data[di + 3] = r[si], g[si], b[si], a[si]
si, di, ncolors = si + 1, di + 4, ncolors - 1
end
yfunc()
until ncolors == 0
return Finish(data, extra, w, h, yfunc, opts)
end
-- Cache module members.
_ToString_Interleaved_ = M.ToString_Interleaved
_ToString_RGBA_ = M.ToString_RGBA
-- Export the module.
return M