1 | // Aseprite FLIC Library |
2 | // Copyright (c) 2015 David Capello |
3 | // |
4 | // This file is released under the terms of the MIT license. |
5 | // Read LICENSE.txt for more information. |
6 | |
7 | #include "flic.h" |
8 | #include "flic_details.h" |
9 | |
10 | namespace flic { |
11 | |
12 | template<typename Iterator> |
13 | static int count_consecutive_values(Iterator begin, Iterator end) |
14 | { |
15 | Iterator prev = nullptr; |
16 | int same = 0; |
17 | for (Iterator it=begin; it!=end; prev=it, ++it) { |
18 | if (!prev || *prev == *it) |
19 | ++same; |
20 | else |
21 | break; |
22 | } |
23 | return same; |
24 | } |
25 | |
26 | template<typename Iterator> |
27 | static int count_max_consecutive_values(Iterator begin, Iterator end, Iterator* maxStart) |
28 | { |
29 | Iterator prev = nullptr; |
30 | Iterator curStart = nullptr; |
31 | *maxStart = nullptr; |
32 | int max = 0; |
33 | int same = 0; |
34 | for (Iterator it=begin; it!=end; prev=it, ++it) { |
35 | if (!prev || *prev == *it) { |
36 | if (!curStart) |
37 | curStart = it; |
38 | |
39 | ++same; |
40 | if (max < same) { |
41 | max = same; |
42 | *maxStart = curStart; |
43 | } |
44 | } |
45 | else { |
46 | same = 0; |
47 | curStart = nullptr; |
48 | } |
49 | } |
50 | return max; |
51 | } |
52 | |
53 | template<typename Iterator1, typename Iterator2> |
54 | static int count_max_consecutive_equal_values(Iterator1 begin1, Iterator1 end1, |
55 | Iterator2 begin2, Iterator2 end2, |
56 | Iterator2* maxStart) |
57 | { |
58 | Iterator1 it1 = begin1; |
59 | Iterator2 it2 = begin2; |
60 | Iterator2 curStart = nullptr; |
61 | *maxStart = nullptr; |
62 | int max = 0; |
63 | int same = 0; |
64 | for (; it1!=end1 && it2!=end2; ++it1, ++it2) { |
65 | if (*it1 == *it2) { |
66 | if (!curStart) |
67 | curStart = it2; |
68 | |
69 | ++same; |
70 | if (max < same) { |
71 | max = same; |
72 | *maxStart = curStart; |
73 | } |
74 | } |
75 | else { |
76 | same = 0; |
77 | curStart = nullptr; |
78 | } |
79 | } |
80 | return max; |
81 | } |
82 | |
83 | Encoder::Encoder(FileInterface* file) |
84 | : m_file(file) |
85 | , m_frameCount(0) |
86 | , m_offsetFrame1(0) |
87 | , m_offsetFrame2(0) |
88 | { |
89 | } |
90 | |
91 | Encoder::~Encoder() |
92 | { |
93 | // Fill header information |
94 | if (m_file->ok()) { |
95 | uint32_t size = m_file->tell(); |
96 | m_file->seek(0); |
97 | |
98 | write32(size); // Write file size |
99 | write16(FLC_MAGIC_NUMBER); // Always as FLC file |
100 | write16(m_frameCount); // Number of frames |
101 | |
102 | m_file->seek(80); |
103 | write32(m_offsetFrame1); |
104 | write32(m_offsetFrame2); |
105 | } |
106 | } |
107 | |
108 | void Encoder::(const Header& ) |
109 | { |
110 | write32(0); // File size, to be completed in ~Encoder() |
111 | write16(0); // File type |
112 | write16(0); // Number of frames |
113 | write16(m_width = header.width); |
114 | write16(m_height = header.height); |
115 | write16(8); |
116 | write16(0); // Flags |
117 | write32(header.speed); |
118 | m_file->seek(128); |
119 | } |
120 | |
121 | void Encoder::writeFrame(const Frame& frame) |
122 | { |
123 | uint32_t frameStartPos = m_file->tell(); |
124 | int nchunks = 0; |
125 | |
126 | switch (m_frameCount) { |
127 | case 0: m_offsetFrame1 = frameStartPos; break; |
128 | case 1: m_offsetFrame2 = frameStartPos; break; |
129 | } |
130 | |
131 | write32(0); // Frame size will be written at the end of this function |
132 | write16(0); // Magic number |
133 | write16(0); // Number of chunks |
134 | write32(0); // Padding |
135 | write32(0); |
136 | |
137 | if (m_frameCount == 0 || m_prevColormap != frame.colormap) { |
138 | writeColorChunk(frame); |
139 | ++nchunks; |
140 | } |
141 | |
142 | if (m_frameCount == 0) { |
143 | writeBrunChunk(frame); |
144 | ++nchunks; |
145 | |
146 | // Create the buffer to store previous frame pixels |
147 | m_prevFrameData.resize(m_height*frame.rowstride); |
148 | std::copy(frame.pixels, |
149 | frame.pixels+m_height*frame.rowstride, |
150 | m_prevFrameData.begin()); |
151 | } |
152 | else { |
153 | writeLcChunk(frame); |
154 | ++nchunks; |
155 | } |
156 | |
157 | size_t frameEndPos = m_file->tell(); |
158 | m_file->seek(frameStartPos); |
159 | write32(frameEndPos - frameStartPos); // Frame size |
160 | write16(FLI_FRAME_MAGIC_NUMBER); // Chunk type |
161 | write16(nchunks); // Number of chunks |
162 | |
163 | m_file->seek(frameEndPos); |
164 | ++m_frameCount; |
165 | } |
166 | |
167 | void Encoder::writeRingFrame(const Frame& frame) |
168 | { |
169 | writeFrame(frame); |
170 | --m_frameCount; |
171 | } |
172 | |
173 | void Encoder::writeColorChunk(const Frame& frame) |
174 | { |
175 | // Chunk header |
176 | size_t chunkBeginPos = m_file->tell(); |
177 | write32(0); // Chunk size (this will be re-written below) |
178 | write16(0); // Chunk type |
179 | write16(0); // Write number of packets in this chunk |
180 | |
181 | // Write packets |
182 | int npackets = 0; |
183 | int skip = 0; |
184 | for (int i=0; i<256; ) { |
185 | if (m_frameCount == 0 || |
186 | m_prevColormap[i] != frame.colormap[i]) { |
187 | int ncolors; |
188 | if (m_frameCount == 0) { |
189 | ncolors = 256; |
190 | } |
191 | else { |
192 | ncolors = 1; |
193 | for (int j=i+1; j<256; ++j) { |
194 | if (m_prevColormap[j] != frame.colormap[j]) |
195 | ++ncolors; |
196 | } |
197 | } |
198 | |
199 | assert(ncolors > 0); |
200 | |
201 | ++npackets; |
202 | m_file->write8(skip); // How many colors to skip from previous packet |
203 | m_file->write8(ncolors == 256 ? 0: ncolors); // 0 means 256 colors |
204 | |
205 | // Write colors |
206 | for (int j=i; j<ncolors; ++j) { |
207 | const Color a = frame.colormap[j]; |
208 | m_file->write8(a.r); |
209 | m_file->write8(a.g); |
210 | m_file->write8(a.b); |
211 | } |
212 | |
213 | i += ncolors; |
214 | skip = 0; |
215 | } |
216 | else { |
217 | ++skip; |
218 | ++i; |
219 | } |
220 | } |
221 | |
222 | assert(npackets > 0); |
223 | |
224 | // Update chunk size |
225 | size_t chunkEndPos = m_file->tell(); |
226 | m_file->seek(chunkBeginPos); |
227 | |
228 | if ((chunkEndPos - chunkBeginPos) & 1) // Avoid odd chunk size |
229 | ++chunkEndPos; |
230 | |
231 | write32(chunkEndPos - chunkBeginPos); // Chunk size |
232 | write16(FLI_COLOR_256_CHUNK); // Chunk type |
233 | write16(npackets); // Number of packets |
234 | m_file->seek(chunkEndPos); |
235 | |
236 | m_prevColormap = frame.colormap; |
237 | } |
238 | |
239 | void Encoder::writeBrunChunk(const Frame& frame) |
240 | { |
241 | // Chunk header |
242 | size_t chunkBeginPos = m_file->tell(); |
243 | write32(0); // Chunk size (this will be re-written below) |
244 | write16(FLI_BRUN_CHUNK); |
245 | |
246 | for (int y=0; y<m_height; ++y) |
247 | writeBrunLineChunk(frame, y); |
248 | |
249 | // Update chunk size |
250 | size_t chunkEndPos = m_file->tell(); |
251 | m_file->seek(chunkBeginPos); |
252 | |
253 | if ((chunkEndPos - chunkBeginPos) & 1) // Avoid odd chunk size |
254 | ++chunkEndPos; |
255 | |
256 | write32(chunkEndPos - chunkBeginPos); |
257 | m_file->seek(chunkEndPos); |
258 | } |
259 | |
260 | void Encoder::writeBrunLineChunk(const Frame& frame, int y) |
261 | { |
262 | size_t npacketsPos = m_file->tell(); |
263 | m_file->write8(0); // Number of packets, it will be re-written later |
264 | |
265 | // Number of packets |
266 | int npackets = 0; |
267 | |
268 | uint8_t* it = frame.pixels + y*frame.rowstride; |
269 | for (int x=0; x<m_width; ) { |
270 | int remain = (m_width-x); |
271 | uint8_t* maxSameStart = nullptr; |
272 | |
273 | int samePixels = count_consecutive_values(it, it+remain); |
274 | int maxSamePixels = count_max_consecutive_values(it, it+remain, &maxSameStart); |
275 | |
276 | // We can compress 127 equal pixels in one packet |
277 | if (samePixels > 127) |
278 | samePixels = 127; |
279 | |
280 | if (samePixels >= 4) { |
281 | // One packet to compress "samePixels" |
282 | ++npackets; |
283 | m_file->write8(samePixels); |
284 | m_file->write8(*it); |
285 | |
286 | it += samePixels; |
287 | x += samePixels; |
288 | } |
289 | else { |
290 | // We can include 128 pixels in one packet |
291 | if (remain > 128) |
292 | remain = 128; |
293 | |
294 | // Is it better to reduce this packet just to compress future same pixels? |
295 | if (maxSamePixels >= 4 && remain > (maxSameStart-it)) |
296 | remain = (maxSameStart-it); |
297 | |
298 | assert(remain > 0); |
299 | |
300 | ++npackets; |
301 | m_file->write8(-remain); |
302 | for (int i=0; i<remain; ++i, ++it) |
303 | m_file->write8(*it); |
304 | |
305 | x += remain; |
306 | } |
307 | } |
308 | |
309 | size_t restorePos = m_file->tell(); |
310 | m_file->seek(npacketsPos); |
311 | m_file->write8(npackets < 255 ? npackets: 255); |
312 | m_file->seek(restorePos); |
313 | } |
314 | |
315 | void Encoder::writeLcChunk(const Frame& frame) |
316 | { |
317 | int skipLines = 0; |
318 | for (int y=0; y<m_height; ++y) { |
319 | std::vector<uint8_t>::iterator prevIt = |
320 | m_prevFrameData.begin() + y*frame.rowstride; |
321 | uint8_t* it = frame.pixels + y*frame.rowstride; |
322 | |
323 | for (int x=0; x<m_width; ++x, ++it, ++prevIt) { |
324 | if (*prevIt != *it) |
325 | goto firstScanDone; |
326 | } |
327 | |
328 | ++skipLines; |
329 | } |
330 | |
331 | firstScanDone:; |
332 | |
333 | int skipEndLines = 0; |
334 | for (int y=m_height-1; y > skipLines; --y) { |
335 | std::vector<uint8_t>::iterator prevIt = |
336 | m_prevFrameData.begin() + y*frame.rowstride; |
337 | uint8_t* it = frame.pixels + y*frame.rowstride; |
338 | |
339 | for (int x=0; x<m_width; ++x, ++it, ++prevIt) { |
340 | if (*prevIt != *it) |
341 | goto secondScanDone; |
342 | } |
343 | |
344 | ++skipEndLines; |
345 | } |
346 | |
347 | secondScanDone:; |
348 | |
349 | int nlines = (m_height - skipEndLines - skipLines); |
350 | |
351 | // Chunk header |
352 | size_t chunkBeginPos = m_file->tell(); |
353 | write32(0); // Chunk size (this will be re-written below) |
354 | write16(FLI_LC_CHUNK); |
355 | write16(skipLines); // How many lines to skip |
356 | write16(nlines); |
357 | |
358 | for (int y=skipLines; y<skipLines+nlines; ++y) |
359 | writeLcLineChunk(frame, y); |
360 | |
361 | // Update the previous frame data |
362 | if (nlines > 0) |
363 | std::copy(frame.pixels+(skipLines*frame.rowstride), |
364 | frame.pixels+((skipLines+nlines)*frame.rowstride), |
365 | m_prevFrameData.begin()+(skipLines*frame.rowstride)); |
366 | |
367 | // Update chunk size |
368 | size_t chunkEndPos = m_file->tell(); |
369 | m_file->seek(chunkBeginPos); |
370 | |
371 | if ((chunkEndPos - chunkBeginPos) & 1) // Avoid odd chunk size |
372 | ++chunkEndPos; |
373 | |
374 | write32(chunkEndPos - chunkBeginPos); |
375 | m_file->seek(chunkEndPos); |
376 | } |
377 | |
378 | void Encoder::writeLcLineChunk(const Frame& frame, int y) |
379 | { |
380 | size_t npacketsPos = m_file->tell(); |
381 | m_file->write8(0); // Number of packets, it will be re-written later |
382 | |
383 | // Number of packets |
384 | int npackets = 0; |
385 | int skipPixels = 0; |
386 | |
387 | std::vector<uint8_t>::iterator prevIt = |
388 | m_prevFrameData.begin() + y*frame.rowstride; |
389 | uint8_t* it = frame.pixels + y*frame.rowstride; |
390 | |
391 | for (int x=0; x<m_width; ) { |
392 | if (*prevIt != *it) { |
393 | while (skipPixels > 255) { |
394 | // One empty packet to skip 255 pixels that are equal to the previous frame |
395 | ++npackets; |
396 | m_file->write8(255); |
397 | m_file->write8(0); |
398 | |
399 | skipPixels -= 255; |
400 | } |
401 | |
402 | // New packet |
403 | ++npackets; |
404 | m_file->write8(skipPixels); |
405 | |
406 | int remain = (m_width-x); |
407 | if (remain > 128) |
408 | remain = 128; |
409 | |
410 | // Calculate if there is a strip of equal pixels with the |
411 | // previous frame in the following pixels |
412 | uint8_t* maxUnchangedStart = nullptr; |
413 | int maxUnchangedPixels = |
414 | count_max_consecutive_equal_values(prevIt, prevIt+remain, |
415 | it, it+remain, |
416 | &maxUnchangedStart); |
417 | if (maxUnchangedPixels > 4 && remain > (maxUnchangedStart-it)) |
418 | remain = (maxUnchangedStart-it); |
419 | |
420 | // Check if we can create a compressed packet |
421 | uint8_t* maxSameStart = nullptr; |
422 | int samePixels = count_consecutive_values(it, it+remain); |
423 | int maxSamePixels = count_max_consecutive_values(it, it+remain, &maxSameStart); |
424 | |
425 | // We can compress 128 equal pixels in one packet |
426 | if (samePixels > 128) |
427 | samePixels = 128; |
428 | |
429 | if (samePixels >= 4) { |
430 | // One packet to compress "samePixels" |
431 | m_file->write8(-samePixels); |
432 | m_file->write8(*it); |
433 | |
434 | prevIt += samePixels; |
435 | it += samePixels; |
436 | x += samePixels; |
437 | } |
438 | else { |
439 | // We can include 127 pixels in one packet |
440 | if (remain > 127) |
441 | remain = 127; |
442 | |
443 | // Is it better to reduce this packet just to compress future same pixels? |
444 | if (maxSamePixels >= 4 && remain > (maxSameStart-it)) |
445 | remain = (maxSameStart-it); |
446 | |
447 | assert(remain > 0); |
448 | |
449 | m_file->write8(remain); |
450 | for (int i=0; i<remain; ++i, ++it) |
451 | m_file->write8(*it); |
452 | |
453 | prevIt += remain; |
454 | x += remain; |
455 | } |
456 | |
457 | skipPixels = 0; |
458 | } |
459 | else { |
460 | ++skipPixels; |
461 | ++prevIt; |
462 | ++it; |
463 | ++x; |
464 | } |
465 | } |
466 | |
467 | if (skipPixels != m_width) { |
468 | assert(npackets != 0); |
469 | |
470 | size_t restorePos = m_file->tell(); |
471 | m_file->seek(npacketsPos); |
472 | m_file->write8(npackets < 255 ? npackets: 255); |
473 | m_file->seek(restorePos); |
474 | } |
475 | else { |
476 | assert(npackets == 0); |
477 | } |
478 | } |
479 | |
480 | void Encoder::write16(uint16_t value) |
481 | { |
482 | // Little endian |
483 | m_file->write8(value & 0x00FF); |
484 | m_file->write8((value & 0xFF00) >> 8); |
485 | } |
486 | |
487 | void Encoder::write32(uint32_t value) |
488 | { |
489 | // Little endian |
490 | m_file->write8((int)value & 0x00FF); |
491 | m_file->write8((int)((value & 0x0000FF00L) >> 8)); |
492 | m_file->write8((int)((value & 0x00FF0000L) >> 16)); |
493 | m_file->write8((int)((value & 0xFF000000L) >> 24)); |
494 | } |
495 | |
496 | } // namespace flic |
497 | |