diff --git a/src/app/file/gif_format.cpp b/src/app/file/gif_format.cpp index 185c64bf1..0a1af26e9 100644 --- a/src/app/file/gif_format.cpp +++ b/src/app/file/gif_format.cpp @@ -1050,7 +1050,47 @@ public: m_fop->newBlend(), RgbMapAlgorithm::OCTREE, // TODO configurable? false); // Do not add the transparent color yet + m_transparentIndex = 0; + m_globalColormapPalette = newPalette; + m_globalColormap = createColorMap(&m_globalColormapPalette); + } + } + // The following "if" block is intended to address cases where + // the Color Mode of the animation is RGB and can be represented by + // an absolute palette because it contains fewer than 256 colors + // throughout the entire animation. In this way the memory space + // used to generate the GIF is much more efficient, since local + // palettes don't need to be inserted in each frame. It also + // fixes a display issue with GIF files (generated by + // Aseprite 1.3.13) on Discord when the GIF parameters were: + // - disposal = DO_NOT_DISPOSE = 1 + // - Local palettes exist. + if (m_spec.colorMode() == ColorMode::RGB || m_spec.colorMode() == ColorMode::GRAYSCALE) { + Palette newPalette(0, 512); + render::create_palette_from_sprite(m_sprite, + 0, + totalFrames() - 1, + false, + &newPalette, + nullptr, + m_fop->newBlend(), + RgbMapAlgorithm::OCTREE, + false); // No effect on OctreeMap. + // Case: palette with (256 colors + mask color) == 257 but + // the mask color isn't used in the sprite. + if (newPalette.size() == 257 && !m_sprite->isColorUsed(0)) { + // Forcing GIF with background + m_transparentIndex = -1; + m_hasBackground = true; + // Discard the mask color (palette entry = 0) + for (int i = 0; i < 256; i++) + newPalette.setEntry(i, newPalette.getEntry(i + 1)); + newPalette.resize(256); + m_globalColormapPalette = newPalette; + m_globalColormap = createColorMap(&m_globalColormapPalette); + } + else if (newPalette.size() <= 256) { m_transparentIndex = 0; m_globalColormapPalette = newPalette; m_globalColormap = createColorMap(&m_globalColormapPalette); @@ -1388,15 +1428,18 @@ private: if (!m_preservePaletteOrder) { const LockImageBits srcBits(m_deltaImage.get()); + const LockImageBits preBits(m_previousImage, frameBounds); LockImageBits dstBits(frameImage.get()); auto srcIt = srcBits.begin(); auto dstIt = dstBits.begin(); + auto preIt = preBits.begin(); for (int y = 0; y < frameBounds.h; ++y) { - for (int x = 0; x < frameBounds.w; ++x, ++srcIt, ++dstIt) { + for (int x = 0; x < frameBounds.w; ++x, ++srcIt, ++dstIt, ++preIt) { ASSERT(srcIt != srcBits.end()); ASSERT(dstIt != dstBits.end()); + ASSERT(preIt != preBits.end()); color_t color = *srcIt; int i; @@ -1410,9 +1453,19 @@ private: if (i < 0) i = octree.mapColor(color | rgba_a_mask); // alpha=255 } + // If the alpha in a pixel from m_deltaImage is < 128, the + // pixel is assumed to be 0. Then it should draw the pixel + // according defined m_transparentIndex or disposal method else { if (m_transparentIndex >= 0) i = m_transparentIndex; + else if (disposal == DisposalMethod::DO_NOT_DISPOSE) { + i = framePalette.findExactMatch(rgba_getr(*preIt), + rgba_getg(*preIt), + rgba_getb(*preIt), + 255, + -1); + } else i = m_bgIndex; } diff --git a/src/doc/primitives.cpp b/src/doc/primitives.cpp index be10329fe..c8df70f8c 100644 --- a/src/doc/primitives.cpp +++ b/src/doc/primitives.cpp @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (c) 2018-2023 Igara Studio S.A. +// Copyright (c) 2018-2025 Igara Studio S.A. // Copyright (c) 2001-2016 David Capello // // This file is released under the terms of the MIT license. @@ -352,6 +352,18 @@ bool is_plain_image_templ(const Image* img, const color_t color) return true; } +template +bool is_color_used_templ(const Image* img, const doc::color_t color) +{ + const LockImageBits bits(img); + auto it = bits.begin(), end = bits.end(); + for (; it != end; ++it) { + if (*it == color) + return true; + } + return false; +} + template int count_diff_between_images_templ(const Image* i1, const Image* i2) { @@ -464,6 +476,16 @@ bool is_plain_image(const Image* img, color_t c) return false; } +bool is_color_used(const Image* img, color_t c) +{ + ASSERT(img->pixelFormat() == IMAGE_RGB || img->pixelFormat() == IMAGE_GRAYSCALE); + switch (img->pixelFormat()) { + case IMAGE_RGB: return is_color_used_templ(img, c); + case IMAGE_GRAYSCALE: return is_color_used_templ(img, c); + } + return false; +} + bool is_empty_image(const Image* img) { color_t c = 0; // alpha = 0 diff --git a/src/doc/primitives.h b/src/doc/primitives.h index f8348d704..f12323547 100644 --- a/src/doc/primitives.h +++ b/src/doc/primitives.h @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (c) 2018-2023 Igara Studio S.A. +// Copyright (c) 2018-2025 Igara Studio S.A. // Copyright (c) 2001-2016 David Capello // // This file is released under the terms of the MIT license. @@ -65,6 +65,7 @@ void fill_ellipse(Image* image, color_t color); bool is_plain_image(const Image* img, color_t c); +bool is_color_used(const Image* img, color_t c); bool is_empty_image(const Image* img); int count_diff_between_images(const Image* i1, const Image* i2); diff --git a/src/doc/sprite.cpp b/src/doc/sprite.cpp index a698eb572..dea6f3574 100644 --- a/src/doc/sprite.cpp +++ b/src/doc/sprite.cpp @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (C) 2018-2024 Igara Studio S.A. +// Copyright (C) 2018-2025 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This file is released under the terms of the MIT license. @@ -223,6 +223,18 @@ bool Sprite::isOpaque() const return (bg && bg->isVisible()); } +bool Sprite::isColorUsed(const doc::color_t c) const +{ + ASSERT(pixelFormat() == IMAGE_RGB || pixelFormat() == IMAGE_GRAYSCALE); + for (Cel* cel : cels()) { + if (cel && cel->image()) { + if (is_color_used(cel->image(), c)) + return true; + } + } + return false; +} + bool Sprite::needAlpha() const { switch (pixelFormat()) { diff --git a/src/doc/sprite.h b/src/doc/sprite.h index f4b6852f5..c2022cc68 100644 --- a/src/doc/sprite.h +++ b/src/doc/sprite.h @@ -110,6 +110,9 @@ public: // Returns true if the sprite has a background layer and it's visible bool isOpaque() const; + // Returns true if the sprite is using a pixel with color c + bool isColorUsed(const doc::color_t c) const; + // Returns true if the rendered images will contain alpha values less // than 255. Only RGBA and Grayscale images without background needs // alpha channel in the render.