1// Aseprite
2// Copyright (C) 2018-2022 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/color_spaces.h"
13#include "app/console.h"
14#include "app/context.h"
15#include "app/doc.h"
16#include "app/file/file.h"
17#include "app/file/file_format.h"
18#include "app/file/format_options.h"
19#include "app/file/gif_format.h"
20#include "app/file/gif_options.h"
21#include "app/modules/gui.h"
22#include "app/pref/preferences.h"
23#include "app/util/autocrop.h"
24#include "base/file_handle.h"
25#include "base/fs.h"
26#include "doc/doc.h"
27#include "doc/octree_map.h"
28#include "gfx/clip.h"
29#include "render/dithering.h"
30#include "render/ordered_dither.h"
31#include "render/quantization.h"
32#include "render/render.h"
33#include "ui/button.h"
34
35#include "gif_options.xml.h"
36
37#include <algorithm>
38
39#include <gif_lib.h>
40
41#ifdef _WIN32
42 #include <io.h>
43 #define posix_lseek _lseek
44#else
45 #include <unistd.h>
46 #define posix_lseek lseek
47#endif
48
49#if GIFLIB_MAJOR < 5
50#define GifMakeMapObject MakeMapObject
51#define GifFreeMapObject FreeMapObject
52#define GifBitSize BitSize
53#endif
54
55#define GIF_TRACE(...)
56
57// GifBitSize can return 9 (it's a bug in giflib)
58#define GifBitSizeLimited(v) (std::min(GifBitSize(v), 8))
59
60namespace app {
61
62using namespace base;
63
64enum class DisposalMethod {
65 NONE,
66 DO_NOT_DISPOSE,
67 RESTORE_BGCOLOR,
68 RESTORE_PREVIOUS,
69};
70
71class GifFormat : public FileFormat {
72
73 const char* onGetName() const override {
74 return "gif";
75 }
76
77 void onGetExtensions(base::paths& exts) const override {
78 exts.push_back("gif");
79 }
80
81 dio::FileFormat onGetDioFormat() const override {
82 return dio::FileFormat::GIF_ANIMATION;
83 }
84
85 int onGetFlags() const override {
86 return
87 FILE_SUPPORT_LOAD |
88 FILE_SUPPORT_SAVE |
89 FILE_SUPPORT_RGB |
90 FILE_SUPPORT_RGBA |
91 FILE_SUPPORT_GRAY |
92 FILE_SUPPORT_GRAYA |
93 FILE_SUPPORT_INDEXED |
94 FILE_SUPPORT_FRAMES |
95 FILE_SUPPORT_PALETTES |
96 FILE_SUPPORT_GET_FORMAT_OPTIONS |
97 FILE_ENCODE_ABSTRACT_IMAGE;
98 }
99
100 bool onLoad(FileOp* fop) override;
101#ifdef ENABLE_SAVE
102 bool onSave(FileOp* fop) override;
103#endif
104 FormatOptionsPtr onAskUserForFormatOptions(FileOp* fop) override;
105};
106
107FileFormat* CreateGifFormat()
108{
109 return new GifFormat;
110}
111
112static int interlaced_offset[] = { 0, 4, 2, 1 };
113static int interlaced_jumps[] = { 8, 8, 4, 2 };
114
115// TODO this should be part of a GifEncoder instance
116// True if the GifEncoder should save the animation for Twitter:
117// * Frames duration >= 2, and
118// * Last frame 1/4 of its duration
119static bool fix_last_frame_duration = false;
120
121GifEncoderDurationFix::GifEncoderDurationFix(bool state)
122{
123 fix_last_frame_duration = state;
124}
125
126GifEncoderDurationFix::~GifEncoderDurationFix()
127{
128 fix_last_frame_duration = false;
129}
130
131struct GifFilePtr {
132public:
133#if GIFLIB_MAJOR >= 5
134 typedef int (*CloseFunc)(GifFileType*, int*);
135#else
136 typedef int (*CloseFunc)(GifFileType*);
137#endif
138
139 GifFilePtr(GifFileType* ptr, CloseFunc closeFunc) :
140 m_ptr(ptr), m_closeFunc(closeFunc) {
141 }
142
143 ~GifFilePtr() {
144#if GIFLIB_MAJOR >= 5
145 int errCode;
146 m_closeFunc(m_ptr, &errCode);
147#else
148 m_closeFunc(m_ptr);
149#endif
150 }
151
152 operator GifFileType*() {
153 return m_ptr;
154 }
155
156 GifFileType* operator->() {
157 return m_ptr;
158 }
159
160private:
161 GifFileType* m_ptr;
162 CloseFunc m_closeFunc;
163};
164
165static void process_disposal_method(const Image* previous,
166 Image* current,
167 const DisposalMethod disposal,
168 const gfx::Rect& frameBounds,
169 const color_t clearColor)
170 {
171 switch (disposal) {
172
173 case DisposalMethod::NONE:
174 case DisposalMethod::DO_NOT_DISPOSE:
175 // Do nothing
176 break;
177
178 case DisposalMethod::RESTORE_BGCOLOR:
179 fill_rect(current,
180 frameBounds.x,
181 frameBounds.y,
182 frameBounds.x+frameBounds.w-1,
183 frameBounds.y+frameBounds.h-1,
184 clearColor);
185 break;
186
187 case DisposalMethod::RESTORE_PREVIOUS:
188 current->copy(previous, gfx::Clip(frameBounds));
189 break;
190 }
191}
192
193static inline doc::color_t colormap2rgba(ColorMapObject* colormap, int i) {
194 return doc::rgba(
195 colormap->Colors[i].Red,
196 colormap->Colors[i].Green,
197 colormap->Colors[i].Blue, 255);
198}
199
200// Decodes a GIF file trying to keep the image in Indexed format. If
201// it's not possible to handle it as Indexed (e.g. it contains more
202// than 256 colors), the file will be automatically converted to RGB.
203//
204// This is a complex process because GIF files are made to be composed
205// over RGB output. Each frame is composed over the previous frame,
206// and combinations of local colormaps can output any number of
207// colors, not just 256. So previous RGB colors must be kept and
208// merged with new colormaps.
209class GifDecoder {
210public:
211 GifDecoder(FileOp* fop, GifFileType* gifFile, int fd, size_t filesize)
212 : m_fop(fop)
213 , m_gifFile(gifFile)
214 , m_fd(fd)
215 , m_filesize(filesize)
216 , m_sprite(nullptr)
217 , m_spriteBounds(0, 0, m_gifFile->SWidth, m_gifFile->SHeight)
218 , m_frameNum(0)
219 , m_opaque(false)
220 , m_disposalMethod(DisposalMethod::NONE)
221 , m_bgIndex(m_gifFile->SBackGroundColor >= 0 ? m_gifFile->SBackGroundColor: 0)
222 , m_localTransparentIndex(-1)
223 , m_frameDelay(1)
224 , m_remap(256)
225 , m_hasLocalColormaps(false)
226 , m_firstLocalColormap(nullptr) {
227 GIF_TRACE("GIF: background index=%d\n", (int)m_gifFile->SBackGroundColor);
228 GIF_TRACE("GIF: global colormap=%d, ncolors=%d\n",
229 (m_gifFile->SColorMap ? 1: 0),
230 (m_gifFile->SColorMap ? m_gifFile->SColorMap->ColorCount: 0));
231 }
232
233 ~GifDecoder() {
234 if (m_firstLocalColormap)
235 GifFreeMapObject(m_firstLocalColormap);
236 }
237
238 Sprite* releaseSprite() {
239 return m_sprite.release();
240 }
241
242 bool decode() {
243 GifRecordType recType;
244
245 // Read record by record
246 while ((recType = readRecordType()) != TERMINATE_RECORD_TYPE) {
247 readRecord(recType);
248
249 // Just one frame?
250 if (m_fop->isOneFrame() && m_frameNum > 0)
251 break;
252
253 if (m_fop->isStop())
254 break;
255
256 if (m_filesize > 0) {
257 int pos = posix_lseek(m_fd, 0, SEEK_CUR);
258 m_fop->setProgress(double(pos) / double(m_filesize));
259 }
260 }
261
262 if (m_sprite) {
263 // Add entries to include the transparent color
264 if (m_bgIndex >= m_sprite->palette(0)->size())
265 m_sprite->palette(0)->resize(m_bgIndex+1);
266
267 switch (m_sprite->pixelFormat()) {
268
269 case IMAGE_INDEXED: {
270 // Use the original global color map
271 ColorMapObject* global = m_gifFile->SColorMap;
272 if (!global)
273 global = m_firstLocalColormap;
274 if (global &&
275 global->ColorCount >= m_sprite->palette(0)->size() &&
276 !m_hasLocalColormaps) {
277 remapToGlobalColormap(global);
278 }
279 break;
280 }
281
282 case IMAGE_RGB:
283 // Avoid huge color palettes
284 if (m_sprite->palette(0)->size() > 256) {
285 reduceToAnOptimizedPalette();
286 }
287 break;
288 }
289
290 if (m_layer && m_opaque)
291 m_layer->configureAsBackground();
292
293 // sRGB is the default color space for GIF files
294 m_sprite->setColorSpace(gfx::ColorSpace::MakeSRGB());
295
296 return true;
297 }
298 else
299 return false;
300 }
301
302private:
303
304 GifRecordType readRecordType() {
305 GifRecordType type;
306 if (DGifGetRecordType(m_gifFile, &type) == GIF_ERROR)
307 throw Exception("Invalid GIF record in file.\n");
308
309 return type;
310 }
311
312 void readRecord(GifRecordType recordType) {
313 switch (recordType) {
314
315 case IMAGE_DESC_RECORD_TYPE:
316 readImageDescRecord();
317 break;
318
319 case EXTENSION_RECORD_TYPE:
320 readExtensionRecord();
321 break;
322 }
323 }
324
325 void readImageDescRecord() {
326 if (DGifGetImageDesc(m_gifFile) == GIF_ERROR)
327 throw Exception("Invalid GIF image descriptor.\n");
328
329 // These are the bounds of the image to read.
330 gfx::Rect frameBounds(
331 m_gifFile->Image.Left,
332 m_gifFile->Image.Top,
333 m_gifFile->Image.Width,
334 m_gifFile->Image.Height);
335
336#if 0 // Generally GIF files should contain frame bounds inside the
337 // canvas bounds (in other case the GIF will contain pixels that
338 // are not visible). In case that some app creates an invalid
339 // GIF files with bounds outside the canvas, we should support
340 // to load the GIF file anyway (which is what is done by other
341 // apps).
342 if (!m_spriteBounds.contains(frameBounds))
343 throw Exception("Image %d is out of sprite bounds.\n", (int)m_frameNum);
344#endif
345
346 // Create sprite if this is the first frame
347 if (!m_sprite)
348 createSprite();
349
350 // Add a frame if it's necessary
351 if (m_sprite->lastFrame() < m_frameNum)
352 m_sprite->addFrame(m_frameNum);
353
354 // Create a temporary image loading the frame pixels from the GIF file
355 std::unique_ptr<Image> frameImage;
356 // We don't know if a GIF file could contain empty bounds (width
357 // or height=0), but we check this just in case.
358 if (!frameBounds.isEmpty())
359 frameImage.reset(readFrameIndexedImage(frameBounds));
360
361 GIF_TRACE("GIF: Frame[%d] transparentIndex=%d localMap=%d\n",
362 (int)m_frameNum, m_localTransparentIndex,
363 m_gifFile->Image.ColorMap ? m_gifFile->Image.ColorMap->ColorCount: 0);
364
365 if (m_frameNum == 0) {
366 if (m_localTransparentIndex >= 0)
367 m_opaque = false;
368 else
369 m_opaque = true;
370 }
371
372 // Merge this frame colors with the current palette
373 if (frameImage)
374 updatePalette(frameImage.get());
375
376 // Convert the sprite to RGB if we have more than 256 colors
377 if ((m_sprite->pixelFormat() == IMAGE_INDEXED) &&
378 (m_sprite->palette(m_frameNum)->size() > 256)) {
379 GIF_TRACE("GIF: Converting to RGB because we have %d colors\n",
380 m_sprite->palette(m_frameNum)->size());
381
382 convertIndexedSpriteToRgb();
383 }
384
385 // Composite frame with previous frame
386 if (frameImage) {
387 if (m_sprite->pixelFormat() == IMAGE_INDEXED) {
388 compositeIndexedImageToIndexed(frameBounds, frameImage.get());
389 }
390 else {
391 compositeIndexedImageToRgb(frameBounds, frameImage.get());
392 }
393 }
394
395 // Create cel
396 createCel();
397
398 // Dispose/clear frame content
399 process_disposal_method(m_previousImage.get(),
400 m_currentImage.get(),
401 m_disposalMethod,
402 frameBounds,
403 m_bgIndex);
404
405 // Copy the current image into previous image
406 copy_image(m_previousImage.get(), m_currentImage.get());
407
408 // Set frame delay (1/100th seconds to milliseconds)
409 if (m_frameDelay >= 0)
410 m_sprite->setFrameDuration(m_frameNum, m_frameDelay*10);
411
412 // Reset extension variables
413 m_disposalMethod = DisposalMethod::NONE;
414 m_localTransparentIndex = -1;
415 m_frameDelay = 1;
416
417 // Next frame
418 ++m_frameNum;
419 }
420
421 Image* readFrameIndexedImage(const gfx::Rect& frameBounds) {
422 std::unique_ptr<Image> frameImage(
423 Image::create(IMAGE_INDEXED, frameBounds.w, frameBounds.h));
424
425 IndexedTraits::address_t addr;
426
427 if (m_gifFile->Image.Interlace) {
428 // Need to perform 4 passes on the image
429 for (int i=0; i<4; ++i)
430 for (int y = interlaced_offset[i]; y < frameBounds.h; y += interlaced_jumps[i]) {
431 addr = frameImage->getPixelAddress(0, y);
432 if (DGifGetLine(m_gifFile, addr, frameBounds.w) == GIF_ERROR)
433 throw Exception("Invalid interlaced image data.");
434 }
435 }
436 else {
437 for (int y = 0; y < frameBounds.h; ++y) {
438 addr = frameImage->getPixelAddress(0, y);
439 if (DGifGetLine(m_gifFile, addr, frameBounds.w) == GIF_ERROR)
440 throw Exception("Invalid image data (%d).\n"
441#if GIFLIB_MAJOR >= 5
442 , m_gifFile->Error
443#else
444 , GifLastError()
445#endif
446 );
447 }
448 }
449
450 return frameImage.release();
451 }
452
453 ColorMapObject* getFrameColormap() {
454 ColorMapObject* global = m_gifFile->SColorMap;
455 ColorMapObject* colormap = m_gifFile->Image.ColorMap;
456
457 if (!colormap) {
458 // Doesn't have local map, use the global one
459 colormap = global;
460 }
461 else if (!m_hasLocalColormaps) {
462 if (!global) {
463 if (!m_firstLocalColormap) {
464 m_firstLocalColormap = GifMakeMapObject(256, nullptr);
465 for (int i=0; i<colormap->ColorCount; ++i) {
466 m_firstLocalColormap->Colors[i].Red = colormap->Colors[i].Red;
467 m_firstLocalColormap->Colors[i].Green = colormap->Colors[i].Green;
468 m_firstLocalColormap->Colors[i].Blue = colormap->Colors[i].Blue;
469 }
470 }
471 global = m_firstLocalColormap;
472 }
473
474 if (global->ColorCount != colormap->ColorCount)
475 m_hasLocalColormaps = true;
476 else {
477 for (int i=0; i<colormap->ColorCount; ++i) {
478 if (global->Colors[i].Red != colormap->Colors[i].Red ||
479 global->Colors[i].Green != colormap->Colors[i].Green ||
480 global->Colors[i].Blue != colormap->Colors[i].Blue) {
481 m_hasLocalColormaps = true;
482 break;
483 }
484 }
485 }
486 }
487
488 if (!colormap)
489 throw Exception("There is no color map.");
490
491 return colormap;
492 }
493
494 // Adds colors used in the GIF frame so we can draw it over
495 // m_currentImage. If the frame contains a local colormap, we try to
496 // find them in the current sprite palette (using
497 // Palette::findExactMatch()) so we don't add duplicated entries.
498 // To do so we use a Remap (m_remap variable) which matches the
499 // original GIF frame colors with the current sprite colors.
500 void updatePalette(const Image* frameImage) {
501 ColorMapObject* colormap = getFrameColormap();
502 const int ncolors = colormap->ColorCount;
503 bool isLocalColormap = (m_gifFile->Image.ColorMap ? true: false);
504
505 GIF_TRACE("GIF: Local colormap=%d, ncolors=%d\n", isLocalColormap, ncolors);
506
507 // We'll calculate the list of used colormap indexes in this
508 // frameImage.
509 PalettePicks usedEntries(ncolors);
510 if (isLocalColormap) {
511 // With this we avoid discarding the transparent index when a
512 // frame indicates that it uses a specific index as transparent
513 // but the image is completely opaque anyway.
514 if (!m_opaque && m_frameNum == 0 && m_localTransparentIndex >= 0 &&
515 m_localTransparentIndex < ncolors) {
516 usedEntries[m_localTransparentIndex] = true;
517 }
518
519 for (const auto& i : LockImageBits<IndexedTraits>(frameImage)) {
520 if (i >= 0 && i < ncolors && i != m_localTransparentIndex)
521 usedEntries[i] = true;
522 }
523 }
524 // Mark all entries as used if the colormap is global.
525 else {
526 usedEntries.all();
527 }
528
529 // Number of colors (indexes) used in the frame image.
530 int usedNColors = usedEntries.picks();
531
532 // Check if we need an extra color equal to the bg color in a
533 // transparent frameImage.
534 bool needsExtraBgColor = false;
535 bool needCheckLocalTransparent = m_bgIndex != m_localTransparentIndex ||
536 (ncolors > m_localTransparentIndex
537 && m_localTransparentIndex >= 0
538 && usedEntries[m_localTransparentIndex]);
539
540 if (m_sprite->pixelFormat() == IMAGE_INDEXED &&
541 !m_opaque &&
542 needCheckLocalTransparent) {
543 for (const auto& i : LockImageBits<IndexedTraits>(frameImage)) {
544 if (i == m_bgIndex) {
545 needsExtraBgColor = true;
546 break;
547 }
548 }
549 }
550
551 std::unique_ptr<Palette> palette;
552 if (m_frameNum == 0)
553 palette.reset(new Palette(m_frameNum, usedNColors + (needsExtraBgColor ? 1: 0)));
554 else {
555 palette.reset(new Palette(*m_sprite->palette(m_frameNum-1)));
556 palette->setFrame(m_frameNum);
557 }
558 resetRemap(std::max(ncolors, palette->size()));
559
560 // Number of colors in the colormap that are part of the current
561 // sprite palette.
562 int found = 0;
563 if (m_frameNum > 0) {
564 ColorMapObject* globalCMap = m_gifFile->SColorMap;
565 ColorMapObject* localCMap = m_gifFile->Image.ColorMap;
566 if (globalCMap && !m_hasLocalColormaps)
567 found = usedEntries.size();
568 else {
569 for (int i=0; i<ncolors; ++i) {
570 if (!usedEntries[i])
571 continue;
572
573 if (localCMap && i < localCMap->ColorCount &&
574 rgba(localCMap->Colors[i].Red,
575 localCMap->Colors[i].Green,
576 localCMap->Colors[i].Blue, 255) == palette->getEntry(i)) {
577 ++found;
578 continue;
579 }
580
581 int j = palette->findExactMatch(colormap->Colors[i].Red,
582 colormap->Colors[i].Green,
583 colormap->Colors[i].Blue, 255,
584 (m_opaque ? -1: m_bgIndex));
585 if (j >= 0) {
586 m_remap.map(i, j);
587 ++found;
588 }
589 }
590 }
591 }
592
593 // All needed colors in the colormap are present in the current
594 // palette.
595 if (found == usedNColors)
596 return;
597
598 // In other case, we need to add the missing colors...
599
600 // First index that acts like a base for new colors in palette.
601 int base = (m_frameNum == 0 ? 0: palette->size());
602
603 // Number of colors in the image that aren't in the palette.
604 int missing = (usedNColors - found);
605
606 GIF_TRACE("GIF: Bg index=%d,\n"
607 " Local transparent index=%d,\n"
608 " Need extra index to show bg color=%d,\n "
609 " Found colors in palette=%d,\n"
610 " Used colors in local pixels=%d,\n"
611 " Base for new colors in palette=%d,\n"
612 " Colors in the image missing in the palette=%d,\n"
613 " New palette size=%d\n",
614 m_bgIndex, m_localTransparentIndex, needsExtraBgColor,
615 found, usedNColors, base, missing,
616 base + missing + (needsExtraBgColor ? 1: 0));
617
618 Palette oldPalette(*palette);
619 palette->resize(base + missing + (needsExtraBgColor ? 1: 0));
620 resetRemap(std::max(ncolors, palette->size()));
621
622 for (int i=0; i<ncolors; ++i) {
623 if (!usedEntries[i])
624 continue;
625
626 int j = -1;
627
628 if (m_frameNum > 0) {
629 j = oldPalette.findExactMatch(
630 colormap->Colors[i].Red,
631 colormap->Colors[i].Green,
632 colormap->Colors[i].Blue, 255,
633 (m_opaque ? -1: m_bgIndex));
634 }
635
636 if (j < 0) {
637 j = base++;
638 palette->setEntry(j, colormap2rgba(colormap, i));
639 }
640 m_remap.map(i, j);
641 }
642
643 if (needsExtraBgColor) {
644 int i = m_bgIndex;
645 int j = base++;
646 palette->setEntry(j, colormap2rgba(colormap, i));
647 // m_firstLocalColorMap, is used only if we have no global color map in the gif source,
648 // and we want to preserve original color indexes, as much we can.
649 // If the palette size is > 256, m_firstLocalColormal is no more useful, because
650 // the sprite pixel format will be converted in RGBA image, and the colors will
651 // be picked from the sprite palette, instead of m_firstLocalColorMap.
652 if (m_firstLocalColormap && m_firstLocalColormap->ColorCount > j) {
653 // We need add this extra color to m_firstLocalColormap, because
654 // it might has not been considered in the first getFrameColormap execution.
655 // (this happen when: in the first execution of getFrameColormap function
656 // an extra color was not needed)
657 m_firstLocalColormap->Colors[j].Red = rgba_getr(palette->getEntry(j));
658 m_firstLocalColormap->Colors[j].Green = rgba_getg(palette->getEntry(j));
659 m_firstLocalColormap->Colors[j].Blue = rgba_getb(palette->getEntry(j));
660 }
661 m_remap.map(i, j);
662 }
663
664 ASSERT(base == palette->size());
665 m_sprite->setPalette(palette.get(), false);
666 }
667
668 void compositeIndexedImageToIndexed(const gfx::Rect& frameBounds,
669 const Image* frameImage) {
670 gfx::Clip clip(frameBounds.x, frameBounds.y, 0, 0,
671 frameBounds.w, frameBounds.h);
672 if (!clip.clip(m_currentImage->width(),
673 m_currentImage->height(),
674 frameImage->width(),
675 frameImage->height()))
676 return;
677
678 const LockImageBits<IndexedTraits> srcBits(frameImage, clip.srcBounds());
679 LockImageBits<IndexedTraits> dstBits(m_currentImage.get(), clip.dstBounds());
680
681 auto srcIt = srcBits.begin(), srcEnd = srcBits.end();
682 auto dstIt = dstBits.begin(), dstEnd = dstBits.end();
683
684 // Compose the frame image with the previous frame
685 for (; srcIt != srcEnd && dstIt != dstEnd; ++srcIt, ++dstIt) {
686 color_t i = *srcIt;
687 if (int(i) == m_localTransparentIndex)
688 continue;
689
690 i = m_remap[i];
691 *dstIt = i;
692 }
693
694 ASSERT(srcIt == srcEnd);
695 ASSERT(dstIt == dstEnd);
696 }
697
698 void compositeIndexedImageToRgb(const gfx::Rect& frameBounds,
699 const Image* frameImage) {
700 gfx::Clip clip(frameBounds.x, frameBounds.y, 0, 0,
701 frameBounds.w, frameBounds.h);
702 if (!clip.clip(m_currentImage->width(),
703 m_currentImage->height(),
704 frameImage->width(),
705 frameImage->height()))
706 return;
707
708 const LockImageBits<IndexedTraits> srcBits(frameImage, clip.srcBounds());
709 LockImageBits<RgbTraits> dstBits(m_currentImage.get(), clip.dstBounds());
710
711 auto srcIt = srcBits.begin(), srcEnd = srcBits.end();
712 auto dstIt = dstBits.begin(), dstEnd = dstBits.end();
713
714 ColorMapObject* colormap = getFrameColormap();
715
716 // Compose the frame image with the previous frame
717 for (; srcIt != srcEnd && dstIt != dstEnd; ++srcIt, ++dstIt) {
718 color_t i = *srcIt;
719 if (int(i) == m_localTransparentIndex)
720 continue;
721
722 i = rgba(
723 colormap->Colors[i].Red,
724 colormap->Colors[i].Green,
725 colormap->Colors[i].Blue, 255);
726
727 *dstIt = i;
728 }
729
730 ASSERT(srcIt == srcEnd);
731 ASSERT(dstIt == dstEnd);
732 }
733
734 void createCel() {
735 Cel* cel = new Cel(m_frameNum, ImageRef(0));
736 try {
737 ImageRef celImage(Image::createCopy(m_currentImage.get()));
738 try {
739 cel->data()->setImage(celImage, m_layer);
740 }
741 catch (...) {
742 throw;
743 }
744 m_layer->addCel(cel);
745 }
746 catch (...) {
747 delete cel;
748 throw;
749 }
750 }
751
752 void readExtensionRecord() {
753 int extCode;
754 GifByteType* extension;
755 if (DGifGetExtension(m_gifFile, &extCode, &extension) == GIF_ERROR)
756 throw Exception("Invalid GIF extension record.\n");
757
758 if (extCode == GRAPHICS_EXT_FUNC_CODE) {
759 if (extension[0] >= 4) {
760 m_disposalMethod = (DisposalMethod)((extension[1] >> 2) & 7);
761 m_localTransparentIndex = (extension[1] & 1) ? extension[4]: -1;
762 m_frameDelay = (extension[3] << 8) | extension[2];
763
764 GIF_TRACE("GIF: Disposal method: %d\n Transparent index: %d\n Frame delay: %d\n",
765 m_disposalMethod, m_localTransparentIndex, m_frameDelay);
766 }
767 }
768
769 while (extension) {
770 if (DGifGetExtensionNext(m_gifFile, &extension) == GIF_ERROR)
771 throw Exception("Invalid GIF extension record.\n");
772 }
773 }
774
775 void createSprite() {
776 ColorMapObject* colormap = nullptr;
777 if (m_gifFile->SColorMap) {
778 colormap = m_gifFile->SColorMap;
779 }
780 else if (m_gifFile->Image.ColorMap) {
781 colormap = m_gifFile->Image.ColorMap;
782 }
783 int ncolors = (colormap ? colormap->ColorCount: 1);
784 int w = m_spriteBounds.w;
785 int h = m_spriteBounds.h;
786
787 m_sprite.reset(new Sprite(ImageSpec(ColorMode::INDEXED, w, h), ncolors));
788 m_sprite->setTransparentColor(m_bgIndex);
789
790 m_currentImage.reset(Image::create(IMAGE_INDEXED, w, h));
791 m_previousImage.reset(Image::create(IMAGE_INDEXED, w, h));
792 m_currentImage->setMaskColor(m_bgIndex);
793 m_previousImage->setMaskColor(m_bgIndex);
794 clear_image(m_currentImage.get(), m_bgIndex);
795 clear_image(m_previousImage.get(), m_bgIndex);
796
797 m_layer = new LayerImage(m_sprite.get());
798 m_sprite->root()->addLayer(m_layer);
799 }
800
801 void resetRemap(int ncolors) {
802 m_remap = Remap(ncolors);
803 for (int i=0; i<ncolors; ++i)
804 m_remap.map(i, i);
805 }
806
807 // Converts the whole sprite read so far because it contains more
808 // than 256 colors at the same time.
809 void convertIndexedSpriteToRgb() {
810 for (Cel* cel : m_sprite->uniqueCels()) {
811 Image* oldImage = cel->image();
812 ImageRef newImage(
813 render::convert_pixel_format
814 (oldImage, nullptr, IMAGE_RGB,
815 render::Dithering(),
816 nullptr, // rgbmap isn't needed, because isn't used in
817 // INDEXED->RGB conversions
818 m_sprite->palette(cel->frame()),
819 m_opaque,
820 m_bgIndex,
821 nullptr));
822
823 m_sprite->replaceImage(oldImage->id(), newImage);
824 }
825
826 m_currentImage.reset(
827 render::convert_pixel_format
828 (m_currentImage.get(), NULL, IMAGE_RGB,
829 render::Dithering(),
830 nullptr,
831 m_sprite->palette(m_frameNum),
832 m_opaque,
833 m_bgIndex));
834
835 m_previousImage.reset(
836 render::convert_pixel_format
837 (m_previousImage.get(), NULL, IMAGE_RGB,
838 render::Dithering(),
839 nullptr,
840 m_sprite->palette(std::max(0, m_frameNum-1)),
841 m_opaque,
842 m_bgIndex));
843
844 m_sprite->setPixelFormat(IMAGE_RGB);
845 }
846
847 void remapToGlobalColormap(ColorMapObject* colormap) {
848 Palette* oldPalette = m_sprite->palette(0);
849 Palette newPalette(0, colormap->ColorCount);
850
851 for (int i=0; i<colormap->ColorCount; ++i) {
852 newPalette.setEntry(i, colormap2rgba(colormap, i));;
853 }
854
855 Remap remap = create_remap_to_change_palette(
856 oldPalette, &newPalette, m_bgIndex,
857 m_opaque); // We cannot remap the transparent color if the
858 // sprite isn't opaque, because we
859 // cannot write the header again
860
861 for (Cel* cel : m_sprite->uniqueCels())
862 doc::remap_image(cel->image(), remap);
863
864 m_sprite->setPalette(&newPalette, false);
865 }
866
867 void reduceToAnOptimizedPalette() {
868 OctreeMap octree;
869 const Palette* palette = m_sprite->palette(0);
870
871 // Feed the octree with palette colors
872 for (int i=0; i<palette->size(); ++i)
873 octree.addColor(palette->getEntry(i));
874
875 Palette newPalette(0, 256);
876 octree.makePalette(&newPalette, 256, 8);
877 m_sprite->setPalette(&newPalette, false);
878 }
879
880 FileOp* m_fop;
881 GifFileType* m_gifFile;
882 int m_fd;
883 size_t m_filesize;
884 std::unique_ptr<Sprite> m_sprite;
885 gfx::Rect m_spriteBounds;
886 LayerImage* m_layer;
887 int m_frameNum;
888 bool m_opaque;
889 DisposalMethod m_disposalMethod;
890 int m_bgIndex;
891 int m_localTransparentIndex;
892 int m_frameDelay;
893 ImageRef m_currentImage;
894 ImageRef m_previousImage;
895 Remap m_remap;
896 bool m_hasLocalColormaps; // Indicates that this fila contains local colormaps
897
898 // This is a copy of the first local color map. It's used to see if
899 // all local colormaps are the same, so we can use it as a global
900 // colormap.
901 ColorMapObject* m_firstLocalColormap;
902};
903
904bool GifFormat::onLoad(FileOp* fop)
905{
906 // The filesize is used only to report some progress when we decode
907 // the GIF file.
908 size_t filesize = base::file_size(fop->filename());
909
910#if GIFLIB_MAJOR >= 5
911 int errCode = 0;
912#endif
913 int fd = open_file_descriptor_with_exception(fop->filename(), "rb");
914 GifFilePtr gif_file(DGifOpenFileHandle(fd
915#if GIFLIB_MAJOR >= 5
916 , &errCode
917#endif
918 ), &DGifCloseFile);
919
920 if (!gif_file) {
921 fop->setError("Error loading GIF header.\n");
922 return false;
923 }
924
925 GifDecoder decoder(fop, gif_file, fd, filesize);
926 if (decoder.decode()) {
927 fop->createDocument(decoder.releaseSprite());
928 return true;
929 }
930 else
931 return false;
932}
933
934#ifdef ENABLE_SAVE
935
936// Our stragegy to encode GIF files depends of the sprite color mode:
937//
938// 1) If the sprite is indexed, we have two paths:
939// * For opaque an opaque sprite we can save it as it is (with the
940// same indexes/pixels and same color palette). This brings us
941// the best possible to compress the GIF file (using the best
942// disposal method to update only the differences between each
943// frame).
944// * For transparent sprites we offer to the user the option to
945// preserve the original palette or not
946// (m_preservePaletteOrders). If the palette must be preserve,
947// some level of compression will be sacrificed.
948//
949// 2) For RGB sprites the palette is created on each frame depending
950// on the updated rectangle between frames, i.e. each to new frame
951// incorporates a minimal rectangular region with changes from the
952// previous frame, we can calculate the palette required for this
953// rectangle and use it as a local colormap for the frame (if each
954// frame uses previous color in the palette there is no need to
955// introduce a new palette).
956//
957// Note: In the following algorithm you will find the "pixel clearing"
958// term, this happens when we need to clear an opaque color with the
959// gif transparent bg color. This is the worst possible case, because
960// on transparent gif files, the only way to get the transparent color
961// (bg color) is using the RESTORE_BGCOLOR disposal method (so we lost
962// the chance to use DO_NOT_DISPOSE in these cases).
963//
964class GifEncoder {
965public:
966 typedef int gifframe_t;
967
968 GifEncoder(FileOp* fop, GifFileType* gifFile)
969 : m_fop(fop)
970 , m_gifFile(gifFile)
971 , m_sprite(fop->document()->sprite())
972 , m_img(fop->abstractImage())
973 , m_spec(m_img->spec())
974 , m_spriteBounds(m_spec.bounds())
975 , m_hasBackground(m_img->isOpaque())
976 , m_bitsPerPixel(1)
977 , m_globalColormap(nullptr)
978 , m_globalColormapPalette(*m_sprite->palette(0))
979 , m_preservePaletteOrder(false) {
980
981 const auto gifOptions = std::static_pointer_cast<GifOptions>(fop->formatOptions());
982
983 LOG("GIF: Saving with options: interlaced=%d loop=%d\n",
984 gifOptions->interlaced(), gifOptions->loop());
985
986 m_interlaced = gifOptions->interlaced();
987 m_loop = (gifOptions->loop() ? 0: -1);
988 m_lastFrameBounds = m_spriteBounds;
989 m_lastDisposal = DisposalMethod::NONE;
990
991 if (m_spec.colorMode() == ColorMode::INDEXED) {
992 for (Palette* palette : m_sprite->getPalettes()) {
993 int bpp = GifBitSizeLimited(palette->size());
994 m_bitsPerPixel = std::max(m_bitsPerPixel, bpp);
995 }
996 }
997 else {
998 m_bitsPerPixel = 8;
999 }
1000
1001 if (m_spec.colorMode() == ColorMode::INDEXED &&
1002 m_img->palettes().size() == 1) {
1003 // If some layer has opacity < 255 or a different blend mode, we
1004 // need to create color palettes.
1005 bool quantizeColormaps = false;
1006 for (const Layer* layer : m_sprite->allVisibleLayers()) {
1007 if (layer->isVisible() && layer->isImage()) {
1008 const LayerImage* imageLayer = static_cast<const LayerImage*>(layer);
1009 if (imageLayer->opacity() < 255 ||
1010 imageLayer->blendMode() != BlendMode::NORMAL) {
1011 quantizeColormaps = true;
1012 break;
1013 }
1014 }
1015 }
1016
1017 if (!quantizeColormaps) {
1018 m_globalColormap = createColorMap(&m_globalColormapPalette);
1019 m_bgIndex = m_spec.maskColor();
1020 // For indexed and opaque sprite, we can preserve the exact
1021 // palette order without lossing compression rate.
1022 if (m_hasBackground)
1023 m_preservePaletteOrder = true;
1024 // Only for transparent indexed images the user can choose to
1025 // preserve or not the palette order.
1026 else
1027 m_preservePaletteOrder = gifOptions->preservePaletteOrder();
1028 }
1029 else
1030 m_bgIndex = 0;
1031 }
1032 else {
1033 m_bgIndex = 0;
1034 }
1035
1036 // This is the transparent index to use as "local transparent"
1037 // index for each gif frame. In case that we use a global colormap
1038 // (and we don't need to preserve the original palette), we can
1039 // try to find a place for a global transparent index.
1040 m_transparentIndex = (m_hasBackground ? -1: m_bgIndex);
1041 if (m_globalColormap) {
1042 // The variable m_globalColormap is != nullptr only on indexed images
1043 ASSERT(m_spec.colorMode() == ColorMode::INDEXED);
1044
1045 const Palette* pal = m_sprite->palette(0);
1046 bool maskColorFounded = false;
1047 for (int i=0; i<pal->size(); i++) {
1048 if (doc::rgba_geta(pal->getEntry(i)) == 0) {
1049 maskColorFounded = true;
1050 m_transparentIndex = i;
1051 break;
1052 }
1053 }
1054
1055#if 0
1056 // If the palette contains room for one extra color for the
1057 // mask, we can use that index.
1058 if (!maskColorFounded && pal->size() < 256) {
1059 maskColorFounded = true;
1060
1061 Palette newPalette(*pal);
1062 newPalette.addEntry(0);
1063 ASSERT(newPalette.size() <= 256);
1064
1065 m_transparentIndex = newPalette.size() - 1;
1066 m_globalColormapPalette = newPalette;
1067 m_globalColormap = createColorMap(&m_globalColormapPalette);
1068 }
1069 else
1070#endif
1071 if (// If all colors are opaque/used in the sprite
1072 !maskColorFounded &&
1073 // We aren't obligated to preserve the original palette
1074 !m_preservePaletteOrder &&
1075 // And the sprite is transparent
1076 !m_hasBackground) {
1077 // We create a new palette with 255 colors + one extra entry
1078 // for the transparent color
1079 Palette newPalette(0, 256);
1080 render::create_palette_from_sprite(
1081 m_sprite,
1082 0,
1083 totalFrames()-1,
1084 false,
1085 &newPalette,
1086 nullptr,
1087 m_fop->newBlend(),
1088 RgbMapAlgorithm::OCTREE, // TODO configurable?
1089 false); // Do not add the transparent color yet
1090
1091 m_transparentIndex = 0;
1092 m_globalColormapPalette = newPalette;
1093 m_globalColormap = createColorMap(&m_globalColormapPalette);
1094 }
1095 }
1096
1097 // Create the 3 temporary images (previous/current/next) to
1098 // compare pixels between them.
1099 for (int i=0; i<3; ++i)
1100 m_images[i].reset(Image::create((m_preservePaletteOrder)? IMAGE_INDEXED : IMAGE_RGB,
1101 m_spriteBounds.w,
1102 m_spriteBounds.h));
1103 }
1104
1105 ~GifEncoder() {
1106 if (m_globalColormap)
1107 GifFreeMapObject(m_globalColormap);
1108 }
1109
1110 bool encode() {
1111 writeHeader();
1112 if (m_loop >= 0)
1113 writeLoopExtension();
1114
1115 // Previous and next images are used to decide the best disposal
1116 // method (e.g. if it's more convenient to restore the background
1117 // color or to restore the previous frame to reach the next one).
1118 m_previousImage = m_images[0].get();
1119 m_currentImage = m_images[1].get();
1120 m_nextImage = m_images[2].get();
1121
1122 auto frame_beg = m_fop->roi().selectedFrames().begin();
1123#if _DEBUG
1124 auto frame_end = m_fop->roi().selectedFrames().end();
1125#endif
1126 auto frame_it = frame_beg;
1127
1128 // In this code "gifFrame" will be the GIF frame, and "frame" will
1129 // be the doc::Sprite frame.
1130 gifframe_t nframes = totalFrames();
1131 for (gifframe_t gifFrame=0; gifFrame<nframes; ++gifFrame) {
1132 ASSERT(frame_it != frame_end);
1133 frame_t frame = *frame_it;
1134 ++frame_it;
1135
1136 if (gifFrame == 0)
1137 renderFrame(frame, m_nextImage);
1138 else
1139 std::swap(m_previousImage, m_currentImage);
1140
1141 // Render next frame
1142 std::swap(m_currentImage, m_nextImage);
1143 if (gifFrame+1 < nframes)
1144 renderFrame(*frame_it, m_nextImage);
1145
1146 gfx::Rect frameBounds = m_spriteBounds;
1147 DisposalMethod disposal = DisposalMethod::DO_NOT_DISPOSE;
1148
1149 // Creation of the deltaImage (difference image result respect
1150 // to current VS previous frame image). At the same time we
1151 // must scan the next image, to check if some pixel turns to
1152 // transparent (0), if the case, we need to force disposal
1153 // method of the current image to RESTORE_BG. Further, at the
1154 // same time, we must check if we can go without color zero (0).
1155
1156 calculateDeltaImageFrameBoundsDisposal(gifFrame, frameBounds, disposal);
1157
1158 writeImage(gifFrame, frame, frameBounds, disposal,
1159 // Only the last frame in the animation needs the fix
1160 (fix_last_frame_duration && gifFrame == nframes-1));
1161
1162 m_fop->setProgress(double(gifFrame+1) / double(nframes));
1163 }
1164 return true;
1165 }
1166
1167private:
1168
1169 void calculateDeltaImageFrameBoundsDisposal(gifframe_t gifFrame,
1170 gfx::Rect& frameBounds,
1171 DisposalMethod& disposal) {
1172 if (gifFrame == 0) {
1173 m_deltaImage.reset(Image::createCopy(m_currentImage));
1174 frameBounds = m_spriteBounds;
1175
1176 // The first frame (frame 0) is good to force to disposal = DO_NOT_DISPOSE,
1177 // but when the next frame (frame 1) has a "pixel clearing",
1178 // we must change disposal to RESTORE_BGCOLOR.
1179
1180 // "Pixel clearing" detection:
1181 if (!m_hasBackground && !m_preservePaletteOrder) {
1182 const LockImageBits<RgbTraits> bits2(m_currentImage);
1183 const LockImageBits<RgbTraits> bits3(m_nextImage);
1184 typename LockImageBits<RgbTraits>::const_iterator it2, it3, end2, end3;
1185 for (it2 = bits2.begin(), end2 = bits2.end(),
1186 it3 = bits3.begin(), end3 = bits3.end();
1187 it2 != end2 && it3 != end3; ++it2, ++it3) {
1188 if (rgba_geta(*it2) != 0 && rgba_geta(*it3) == 0) {
1189 disposal = DisposalMethod::RESTORE_BGCOLOR;
1190 break;
1191 }
1192 }
1193 }
1194 else if (m_preservePaletteOrder)
1195 disposal = DisposalMethod::RESTORE_BGCOLOR;
1196 }
1197 else {
1198 int x1 = 0;
1199 int y1 = 0;
1200 int x2 = 0;
1201 int y2 = 0;
1202
1203 if (!m_preservePaletteOrder) {
1204 // When m_lastDisposal was RESTORE_BGBOLOR it implies
1205 // we will have to cover with colors the entire previous frameBounds plus
1206 // the current frameBounds due to color changes, so we must start with
1207 // a frameBounds equal to the previous frame iteration (saved in m_lastFrameBounds).
1208 // Then we must cover all the resultant frameBounds with full color
1209 // in m_currentImage, the output image will be saved in deltaImage.
1210 if (m_lastDisposal == DisposalMethod::RESTORE_BGCOLOR) {
1211 x1 = m_lastFrameBounds.x;
1212 y1 = m_lastFrameBounds.y;
1213 x2 = m_lastFrameBounds.x + m_lastFrameBounds.w - 1;
1214 y2 = m_lastFrameBounds.y + m_lastFrameBounds.h - 1;
1215 }
1216 else {
1217 x1 = m_spriteBounds.w - 1;
1218 y1 = m_spriteBounds.h - 1;
1219 }
1220
1221 int i = 0;
1222 int x, y;
1223 const LockImageBits<RgbTraits> bits1(m_previousImage);
1224 LockImageBits<RgbTraits> bits2(m_currentImage);
1225 const LockImageBits<RgbTraits> bits3(m_nextImage);
1226 m_deltaImage.reset(Image::create(PixelFormat::IMAGE_RGB, m_spriteBounds.w, m_spriteBounds.h));
1227 clear_image(m_deltaImage.get(), 0);
1228 LockImageBits<RgbTraits> deltaBits(m_deltaImage.get());
1229 typename LockImageBits<RgbTraits>::iterator deltaIt;
1230 typename LockImageBits<RgbTraits>::iterator it2, end2;
1231 typename LockImageBits<RgbTraits>::const_iterator it1, it3, end1, deltaEnd;
1232
1233 bool previousImageMatchsCurrent = true;
1234 for (it1 = bits1.begin(), end1 = bits1.end(),
1235 it2 = bits2.begin(), end2 = bits2.end(),
1236 it3 = bits3.begin(),
1237 deltaIt = deltaBits.begin();
1238 it1 != end1 && it2 != end2; ++it1, ++it2, ++it3, ++deltaIt, ++i) {
1239 x = i % m_spriteBounds.w;
1240 y = i / m_spriteBounds.w;
1241 // While we are checking color differences,
1242 // we enlarge the frameBounds where the color differences take place
1243 if ((rgba_geta(*it2) != 0 && *it1 != *it2) || rgba_geta(*it3) == 0) {
1244 previousImageMatchsCurrent = false;
1245 *it2 = (rgba_geta(*it2) ? *it2 : 0);
1246 *deltaIt = *it2;
1247 if (x < x1) x1 = x;
1248 if (x > x2) x2 = x;
1249 if (y < y1) y1 = y;
1250 if (y > y2) y2 = y;
1251 }
1252
1253 // We need to change disposal mode DO_NOT_DISPOSE to RESTORE_BGCOLOR only
1254 // if we found a "pixel clearing" in the next Image. RESTORE_BGCOLOR is
1255 // our way to clear pixels.
1256 if (rgba_geta(*it2) != 0 && rgba_geta(*it3) == 0) {
1257 disposal = DisposalMethod::RESTORE_BGCOLOR;
1258 }
1259 }
1260 if (previousImageMatchsCurrent)
1261 frameBounds = gfx::Rect(m_lastFrameBounds);
1262 else
1263 frameBounds = gfx::Rect(x1, y1, x2-x1+1, y2-y1+1);
1264 }
1265 else
1266 disposal = DisposalMethod::RESTORE_BGCOLOR;
1267
1268 // We need to conditionate the deltaImage to the next step: 'writeImage()'
1269 // To do it, we need to crop deltaImage in frameBounds.
1270 // If disposal method changed to RESTORE_BGCOLOR deltaImage we need to reproduce ALL the colors of m_currentImage
1271 // contained in frameBounds (so, we will overwrite delta image with a cropped current image).
1272 // In the other hand, if disposal is still DO_NOT_DISPOSAL, delta image will be a cropped image
1273 // from itself in frameBounds.
1274 if (disposal == DisposalMethod::RESTORE_BGCOLOR || m_lastDisposal == DisposalMethod::RESTORE_BGCOLOR) {
1275 m_deltaImage.reset(crop_image(m_currentImage, frameBounds, 0));
1276 }
1277 else {
1278 m_deltaImage.reset(crop_image(m_deltaImage.get(), frameBounds, 0));
1279 disposal = DisposalMethod::DO_NOT_DISPOSE;
1280 }
1281 m_lastFrameBounds = frameBounds;
1282 }
1283
1284 // TODO We could join both frames in a longer one (with more duration)
1285 if (frameBounds.isEmpty())
1286 frameBounds = gfx::Rect(0, 0, 1, 1);
1287
1288 m_lastDisposal = disposal;
1289 }
1290
1291 doc::frame_t totalFrames() const {
1292 return m_fop->roi().frames();
1293 }
1294
1295 void writeHeader() {
1296 if (EGifPutScreenDesc(m_gifFile,
1297 m_spriteBounds.w,
1298 m_spriteBounds.h,
1299 m_bitsPerPixel,
1300 m_bgIndex, m_globalColormap) == GIF_ERROR)
1301 throw Exception("Error writing GIF header.\n");
1302 }
1303
1304 void writeLoopExtension() {
1305#if GIFLIB_MAJOR >= 5
1306 if (EGifPutExtensionLeader(m_gifFile, APPLICATION_EXT_FUNC_CODE) == GIF_ERROR)
1307 throw Exception("Error writing GIF graphics extension record (header section).");
1308
1309 unsigned char extension_bytes[11];
1310 memcpy(extension_bytes, "NETSCAPE2.0", 11);
1311 if (EGifPutExtensionBlock(m_gifFile, 11, extension_bytes) == GIF_ERROR)
1312 throw Exception("Error writing GIF graphics extension record (first block).");
1313
1314 extension_bytes[0] = 1;
1315 extension_bytes[1] = (m_loop & 0xff);
1316 extension_bytes[2] = (m_loop >> 8) & 0xff;
1317 if (EGifPutExtensionBlock(m_gifFile, 3, extension_bytes) == GIF_ERROR)
1318 throw Exception("Error writing GIF graphics extension record (second block).");
1319
1320 if (EGifPutExtensionTrailer(m_gifFile) == GIF_ERROR)
1321 throw Exception("Error writing GIF graphics extension record (trailer section).");
1322
1323#else
1324 unsigned char extension_bytes[11];
1325
1326 memcpy(extension_bytes, "NETSCAPE2.0", 11);
1327 if (EGifPutExtensionFirst(m_gifFile, APPLICATION_EXT_FUNC_CODE, 11, extension_bytes) == GIF_ERROR)
1328 throw Exception("Error writing GIF graphics extension record.\n");
1329
1330 extension_bytes[0] = 1;
1331 extension_bytes[1] = (m_loop & 0xff);
1332 extension_bytes[2] = (m_loop >> 8) & 0xff;
1333 if (EGifPutExtensionNext(m_gifFile, APPLICATION_EXT_FUNC_CODE, 3, extension_bytes) == GIF_ERROR)
1334 throw Exception("Error writing GIF graphics extension record.\n");
1335
1336 if (EGifPutExtensionLast(m_gifFile, APPLICATION_EXT_FUNC_CODE, 0, NULL) == GIF_ERROR)
1337 throw Exception("Error writing GIF graphics extension record.\n");
1338#endif
1339 }
1340
1341 // Writes graphics extension record (to save the duration of the
1342 // frame and maybe the transparency index).
1343 void writeExtension(const gifframe_t gifFrame,
1344 const frame_t frame,
1345 const int transparentIndex,
1346 const DisposalMethod disposalMethod,
1347 const bool fixDuration) {
1348 unsigned char extension_bytes[5];
1349 int frameDelay = m_img->frameDuration(frame) / 10;
1350
1351 // Fix duration for Twitter. It looks like the last frame must be
1352 // 1/4 of its duration for some strange reason in the Twitter
1353 // conversion from GIF to video.
1354 if (fixDuration)
1355 frameDelay = std::max(2, frameDelay/4);
1356 if (fix_last_frame_duration)
1357 frameDelay = std::max(2, frameDelay);
1358
1359 extension_bytes[0] = (((int(disposalMethod) & 7) << 2) |
1360 (transparentIndex >= 0 ? 1: 0));
1361 extension_bytes[1] = (frameDelay & 0xff);
1362 extension_bytes[2] = (frameDelay >> 8) & 0xff;
1363 extension_bytes[3] = (transparentIndex >= 0 ? transparentIndex: 0);
1364
1365 if (EGifPutExtension(m_gifFile, GRAPHICS_EXT_FUNC_CODE, 4, extension_bytes) == GIF_ERROR)
1366 throw Exception("Error writing GIF graphics extension record for frame %d.\n", gifFrame);
1367 }
1368
1369 static gfx::Rect calculateFrameBounds(Image* a, Image* b) {
1370 gfx::Rect frameBounds;
1371 int x1, y1, x2, y2;
1372
1373 if (get_shrink_rect2(&x1, &y1, &x2, &y2, a, b)) {
1374 frameBounds.x = x1;
1375 frameBounds.y = y1;
1376 frameBounds.w = x2 - x1 + 1;
1377 frameBounds.h = y2 - y1 + 1;
1378 }
1379
1380 return frameBounds;
1381 }
1382
1383
1384 void writeImage(const gifframe_t gifFrame,
1385 const frame_t frame,
1386 const gfx::Rect& frameBounds,
1387 const DisposalMethod disposal,
1388 const bool fixDuration) {
1389 Palette framePalette;
1390 if (m_globalColormap)
1391 framePalette = m_globalColormapPalette;
1392 else
1393 framePalette = calculatePalette();
1394
1395 OctreeMap octree;
1396 octree.regenerateMap(&framePalette, m_transparentIndex);
1397 ImageRef frameImage(Image::create(IMAGE_INDEXED,
1398 frameBounds.w,
1399 frameBounds.h,
1400 m_frameImageBuf));
1401
1402 // Every frame might use a small portion of the global palette,
1403 // to optimize the gif file size, we will analize which colors
1404 // will be used in each processed frame.
1405 PalettePicks usedColors(framePalette.size());
1406
1407 int localTransparent = m_transparentIndex;
1408 ColorMapObject* colormap = m_globalColormap;
1409 Remap remap(256);
1410
1411 if (!m_preservePaletteOrder) {
1412 const LockImageBits<RgbTraits> srcBits(m_deltaImage.get());
1413 LockImageBits<IndexedTraits> dstBits(frameImage.get());
1414
1415 auto srcIt = srcBits.begin();
1416 auto dstIt = dstBits.begin();
1417
1418 for (int y=0; y<frameBounds.h; ++y) {
1419 for (int x=0; x<frameBounds.w; ++x, ++srcIt, ++dstIt) {
1420 ASSERT(srcIt != srcBits.end());
1421 ASSERT(dstIt != dstBits.end());
1422
1423 color_t color = *srcIt;
1424 int i;
1425
1426 if (rgba_geta(color) >= 128) {
1427 i = framePalette.findExactMatch(
1428 rgba_getr(color),
1429 rgba_getg(color),
1430 rgba_getb(color),
1431 255,
1432 m_transparentIndex);
1433 if (i < 0)
1434 i = octree.mapColor(color | rgba_a_mask); // alpha=255
1435 }
1436 else {
1437 if (m_transparentIndex >= 0)
1438 i = m_transparentIndex;
1439 else
1440 i = m_bgIndex;
1441 }
1442
1443 ASSERT(i >= 0);
1444
1445 // This can happen when transparent color is outside the
1446 // palette range (TODO something that shouldn't be possible
1447 // from the program).
1448 if (i >= usedColors.size())
1449 usedColors.resize(i+1);
1450 usedColors[i] = true;
1451
1452 *dstIt = i;
1453 }
1454 }
1455
1456 int usedNColors = usedColors.picks();
1457
1458 for (int i=0; i<remap.size(); ++i)
1459 remap.map(i, i);
1460
1461 if (!colormap) {
1462 Palette reducedPalette(0, usedNColors);
1463
1464 for (int i=0, j=0; i<framePalette.size(); ++i) {
1465 if (usedColors[i]) {
1466 reducedPalette.setEntry(j, framePalette.getEntry(i));
1467 remap.map(i, j);
1468 ++j;
1469 }
1470 }
1471
1472 colormap = createColorMap(&reducedPalette);
1473 if (localTransparent >= 0)
1474 localTransparent = remap[localTransparent];
1475 }
1476
1477 if (localTransparent >= 0 && m_transparentIndex != localTransparent)
1478 remap.map(m_transparentIndex, localTransparent);
1479 }
1480 else {
1481 frameImage.reset(Image::createCopy(m_deltaImage.get()));
1482 for (int i=0; i<colormap->ColorCount; ++i)
1483 remap.map(i, i);
1484 }
1485
1486 // Write extension record.
1487 writeExtension(gifFrame, frame, localTransparent,
1488 disposal, fixDuration);
1489
1490 // Write the image record.
1491 if (EGifPutImageDesc(m_gifFile,
1492 frameBounds.x, frameBounds.y,
1493 frameBounds.w, frameBounds.h,
1494 m_interlaced ? 1: 0,
1495 (colormap != m_globalColormap ? colormap: nullptr)) == GIF_ERROR) {
1496 throw Exception("Error writing GIF frame %d.\n", gifFrame);
1497 }
1498
1499 std::vector<uint8_t> scanline(frameBounds.w);
1500
1501 // Write the image data (pixels).
1502 if (m_interlaced) {
1503 // Need to perform 4 passes on the images.
1504 for (int i=0; i<4; ++i)
1505 for (int y=interlaced_offset[i]; y<frameBounds.h; y+=interlaced_jumps[i]) {
1506 IndexedTraits::address_t addr =
1507 (IndexedTraits::address_t)frameImage->getPixelAddress(0, y);
1508
1509 for (int i=0; i<frameBounds.w; ++i, ++addr)
1510 scanline[i] = remap[*addr];
1511
1512 if (EGifPutLine(m_gifFile, &scanline[0], frameBounds.w) == GIF_ERROR)
1513 throw Exception("Error writing GIF image scanlines for frame %d.\n", gifFrame);
1514 }
1515 }
1516 else {
1517 // Write all image scanlines (not interlaced in this case).
1518 for (int y=0; y<frameBounds.h; ++y) {
1519 IndexedTraits::address_t addr =
1520 (IndexedTraits::address_t)frameImage->getPixelAddress(0, y);
1521
1522 for (int i=0; i<frameBounds.w; ++i, ++addr)
1523 scanline[i] = remap[*addr];
1524
1525 if (EGifPutLine(m_gifFile, &scanline[0], frameBounds.w) == GIF_ERROR)
1526 throw Exception("Error writing GIF image scanlines for frame %d.\n", gifFrame);
1527 }
1528 }
1529
1530 if (colormap && colormap != m_globalColormap)
1531 GifFreeMapObject(colormap);
1532 }
1533
1534 Palette calculatePalette() {
1535 OctreeMap octree;
1536 const LockImageBits<RgbTraits> imageBits(m_deltaImage.get());
1537 auto it = imageBits.begin(), end = imageBits.end();
1538 bool maskColorFounded = false;
1539 for (; it != end; ++it) {
1540 color_t c = *it;
1541 if (rgba_geta(c) == 0) {
1542 maskColorFounded = true;
1543 continue;
1544 }
1545 octree.addColor(c);
1546 }
1547 Palette palette;
1548 if (maskColorFounded) {
1549 // If there is a mask color, the OctreeMap::makePalette adds it
1550 // by default at entry == 0.
1551 octree.makePalette(&palette, 256, 8);
1552 m_transparentIndex = 0;
1553 return palette;
1554 }
1555 else {
1556 // If there isn't mask color we need to remove the 0 entry
1557 // added in OctreeMap::makePalette.
1558 octree.makePalette(&palette, 257, 8);
1559 Palette paletteWithoutMask(0, palette.size() - 1);
1560 for (int i=0; i < paletteWithoutMask.size(); i++)
1561 paletteWithoutMask.setEntry(i, palette.entry(i+1));
1562 m_transparentIndex = -1;
1563 return paletteWithoutMask;
1564 }
1565 }
1566
1567 void renderFrame(frame_t frame, Image* dst) {
1568 if (m_preservePaletteOrder)
1569 clear_image(dst, m_bgIndex);
1570 else
1571 clear_image(dst, 0);
1572 m_img->renderFrame(frame, dst);
1573 }
1574
1575private:
1576
1577 ColorMapObject* createColorMap(const Palette* palette) {
1578 int n = 1 << GifBitSizeLimited(palette->size());
1579 ColorMapObject* colormap = GifMakeMapObject(n, nullptr);
1580
1581 // Color space conversions
1582 ConvertCS convert = convert_from_custom_to_srgb(m_img->osColorSpace());
1583
1584 for (int i=0; i<n; ++i) {
1585 color_t color;
1586 if (i < palette->size())
1587 color = palette->getEntry(i);
1588 else
1589 color = rgba(0, 0, 0, 255);
1590
1591 color = convert(color);
1592
1593 colormap->Colors[i].Red = rgba_getr(color);
1594 colormap->Colors[i].Green = rgba_getg(color);
1595 colormap->Colors[i].Blue = rgba_getb(color);
1596 }
1597
1598 return colormap;
1599 }
1600
1601 FileOp* m_fop;
1602 GifFileType* m_gifFile;
1603 const Sprite* m_sprite;
1604 const FileAbstractImage* m_img;
1605 const ImageSpec m_spec;
1606 gfx::Rect m_spriteBounds;
1607 bool m_hasBackground;
1608 int m_bgIndex;
1609 int m_transparentIndex;
1610 int m_bitsPerPixel;
1611 // Global palette to use on all frames, or nullptr in case that we
1612 // have to quantize the palette on each frame.
1613 ColorMapObject* m_globalColormap;
1614 Palette m_globalColormapPalette;
1615 bool m_interlaced;
1616 int m_loop;
1617 bool m_preservePaletteOrder;
1618 gfx::Rect m_lastFrameBounds;
1619 DisposalMethod m_lastDisposal;
1620 ImageBufferPtr m_frameImageBuf;
1621 ImageRef m_images[3];
1622 Image* m_previousImage;
1623 Image* m_currentImage;
1624 Image* m_nextImage;
1625 std::unique_ptr<Image> m_deltaImage;
1626};
1627
1628bool GifFormat::onSave(FileOp* fop)
1629{
1630#if GIFLIB_MAJOR >= 5
1631 int errCode = 0;
1632#endif
1633 int fd = base::open_file_descriptor_with_exception(fop->filename(), "wb");
1634 GifFilePtr gif_file(EGifOpenFileHandle(fd
1635#if GIFLIB_MAJOR >= 5
1636 , &errCode
1637#endif
1638 ), &EGifCloseFile);
1639
1640 if (!gif_file)
1641 throw Exception("Error creating GIF file.\n");
1642
1643 GifEncoder encoder(fop, gif_file);
1644 bool result = encoder.encode();
1645 if (result)
1646 base::sync_file_descriptor(fd);
1647 return result;
1648}
1649
1650#endif // ENABLE_SAVE
1651
1652FormatOptionsPtr GifFormat::onAskUserForFormatOptions(FileOp* fop)
1653{
1654 auto opts = fop->formatOptionsOfDocument<GifOptions>();
1655#ifdef ENABLE_UI
1656 if (fop->context() && fop->context()->isUIAvailable()) {
1657 try {
1658 auto& pref = Preferences::instance();
1659
1660 if (pref.isSet(pref.gif.interlaced))
1661 opts->setInterlaced(pref.gif.interlaced());
1662 if (pref.isSet(pref.gif.loop))
1663 opts->setLoop(pref.gif.loop());
1664 if (pref.isSet(pref.gif.preservePaletteOrder))
1665 opts->setPreservePaletteOrder(pref.gif.preservePaletteOrder());
1666
1667 if (pref.gif.showAlert()) {
1668 app::gen::GifOptions win;
1669 win.interlaced()->setSelected(opts->interlaced());
1670 win.loop()->setSelected(opts->loop());
1671 win.preservePaletteOrder()->setSelected(opts->preservePaletteOrder());
1672
1673 if (fop->document()->sprite()->pixelFormat() == PixelFormat::IMAGE_INDEXED &&
1674 !fop->document()->sprite()->isOpaque())
1675 win.preservePaletteOrder()->setEnabled(true);
1676 else {
1677 win.preservePaletteOrder()->setEnabled(false);
1678 if (fop->document()->sprite()->pixelFormat() == PixelFormat::IMAGE_INDEXED && fop->document()->sprite()->isOpaque())
1679 win.preservePaletteOrder()->setSelected(true);
1680 else
1681 win.preservePaletteOrder()->setSelected(false);
1682 }
1683
1684 win.openWindowInForeground();
1685
1686 if (win.closer() == win.ok()) {
1687 pref.gif.interlaced(win.interlaced()->isSelected());
1688 pref.gif.loop(win.loop()->isSelected());
1689 pref.gif.preservePaletteOrder(win.preservePaletteOrder()->isSelected());
1690 pref.gif.showAlert(!win.dontShow()->isSelected());
1691
1692 opts->setInterlaced(pref.gif.interlaced());
1693 opts->setLoop(pref.gif.loop());
1694 opts->setPreservePaletteOrder(pref.gif.preservePaletteOrder());
1695 }
1696 else {
1697 opts.reset();
1698 }
1699 }
1700 }
1701 catch (std::exception& e) {
1702 Console::showException(e);
1703 return std::shared_ptr<GifOptions>(nullptr);
1704 }
1705 }
1706#endif // ENABLE_UI
1707 return opts;
1708}
1709
1710} // namespace app
1711