mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 04:19:12 +00:00
Change octree quantization is the new palette creation method on gif format coding/decoding
This commit is contained in:
parent
06a387fd45
commit
bec5a14e22
@ -24,6 +24,7 @@
|
||||
#include "base/file_handle.h"
|
||||
#include "base/fs.h"
|
||||
#include "doc/doc.h"
|
||||
#include "doc/octree_map.h"
|
||||
#include "gfx/clip.h"
|
||||
#include "render/dithering.h"
|
||||
#include "render/ordered_dither.h"
|
||||
@ -798,7 +799,8 @@ private:
|
||||
render::convert_pixel_format
|
||||
(oldImage, nullptr, IMAGE_RGB,
|
||||
render::Dithering(),
|
||||
nullptr,
|
||||
nullptr, // rgbmap isn't needed, because isn't used in
|
||||
// INDEXED->RGB conversions
|
||||
m_sprite->palette(cel->frame()),
|
||||
m_opaque,
|
||||
m_bgIndex,
|
||||
@ -849,16 +851,15 @@ private:
|
||||
}
|
||||
|
||||
void reduceToAnOptimizedPalette() {
|
||||
render::PaletteOptimizer optimizer;
|
||||
OctreeMap octree;
|
||||
const Palette* palette = m_sprite->palette(0);
|
||||
|
||||
// Feed the palette optimizer with pixels inside frameBounds
|
||||
for (int i=0; i<palette->size(); ++i) {
|
||||
optimizer.feedWithRgbaColor(palette->getEntry(i));
|
||||
}
|
||||
// Feed the octree with palette colors
|
||||
for (int i=0; i<palette->size(); ++i)
|
||||
octree.addColor(palette->getEntry(i));
|
||||
|
||||
Palette newPalette(0, 256);
|
||||
optimizer.calculate(&newPalette, m_bgIndex);
|
||||
octree.makePalette(&newPalette, 256, 8);
|
||||
m_sprite->setPalette(&newPalette, false);
|
||||
}
|
||||
|
||||
@ -1060,7 +1061,7 @@ public:
|
||||
!m_hasBackground) {
|
||||
// We create a new palette with 255 colors + one extra entry
|
||||
// for the transparent color
|
||||
Palette newPalette(0, 255);
|
||||
Palette newPalette(0, 256);
|
||||
render::create_palette_from_sprite(
|
||||
m_sprite,
|
||||
0,
|
||||
@ -1069,15 +1070,10 @@ public:
|
||||
&newPalette,
|
||||
nullptr,
|
||||
m_fop->newBlend(),
|
||||
RgbMapAlgorithm::DEFAULT, // TODO configurable?
|
||||
RgbMapAlgorithm::OCTREE, // TODO configurable?
|
||||
false); // Do not add the transparent color yet
|
||||
|
||||
// We will use the last palette entry (e.g. index=255) as the
|
||||
// transparent index
|
||||
newPalette.addEntry(0);
|
||||
ASSERT(newPalette.size() <= 256);
|
||||
|
||||
m_transparentIndex = newPalette.size() - 1;
|
||||
m_transparentIndex = 0;
|
||||
m_globalColormapPalette = newPalette;
|
||||
m_globalColormap = createColorMap(&m_globalColormapPalette);
|
||||
}
|
||||
@ -1377,11 +1373,10 @@ private:
|
||||
if (m_globalColormap)
|
||||
framePalette = m_globalColormapPalette;
|
||||
else
|
||||
framePalette = calculatePalette(frameBounds, disposal);
|
||||
|
||||
RgbMapRGB5A3 rgbmap; // TODO RgbMapRGB5A3 configurable?
|
||||
rgbmap.regenerateMap(&framePalette, m_transparentIndex);
|
||||
framePalette = calculatePalette();
|
||||
|
||||
OctreeMap octree;
|
||||
octree.regenerateMap(&framePalette, m_transparentIndex);
|
||||
ImageRef frameImage(Image::create(IMAGE_INDEXED,
|
||||
frameBounds.w,
|
||||
frameBounds.h,
|
||||
@ -1419,7 +1414,7 @@ private:
|
||||
255,
|
||||
m_transparentIndex);
|
||||
if (i < 0)
|
||||
i = rgbmap.mapColor(color | rgba_a_mask); // alpha=255
|
||||
i = octree.mapColor(color | rgba_a_mask); // alpha=255
|
||||
}
|
||||
else {
|
||||
if (m_transparentIndex >= 0)
|
||||
@ -1519,174 +1514,38 @@ private:
|
||||
GifFreeMapObject(colormap);
|
||||
}
|
||||
|
||||
Palette calculatePalette(const gfx::Rect& frameBounds,
|
||||
const DisposalMethod disposal) {
|
||||
// First, we must check the palette color count in m_deltaImage (our best shot
|
||||
// to find the smaller palette color count)
|
||||
Palette pal(createOptimizedPalette(m_deltaImage.get(), m_deltaImage->bounds(), 256));
|
||||
if (pal.size() == 256) {
|
||||
// Here the palette has 256 colors, there is no place to include
|
||||
// the 0 color (createOptimizedPalette() doesn't create an entry
|
||||
// for it).
|
||||
//
|
||||
// We have two paths:
|
||||
// 1- Giving a try to palette generation on m_currentImage in frameBouns limits.
|
||||
// 2- If the previous step is not possible (color count > 256), we will to start
|
||||
// to approximate colors from m_deltaImage with some criterion. Final target:
|
||||
// to approximate the palette to 255 colors + clear color (0)).
|
||||
|
||||
// 1- Giving a try to palette generation on m_currentImage in frameBouns limits.
|
||||
// if disposal == RESTORE_BGCOLOR m_deltaImage already is a cropped copy of m_currentImage.
|
||||
Palette auxPalette;
|
||||
if (disposal == DisposalMethod::DO_NOT_DISPOSE)
|
||||
auxPalette = createOptimizedPalette(m_currentImage, frameBounds, 257);
|
||||
else
|
||||
auxPalette = pal;
|
||||
|
||||
if (auxPalette.size() <= 256) {
|
||||
// We are fine with color count in m_currentImage contained in
|
||||
// frameBounds (we got 256 or less colors):
|
||||
m_transparentIndex = -1;
|
||||
pal = auxPalette;
|
||||
if (disposal == DisposalMethod::DO_NOT_DISPOSE) {
|
||||
ASSERT(frameBounds.w >= 1);
|
||||
m_deltaImage.reset(crop_image(m_currentImage, frameBounds, 0));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 2- If the previous step fails, we will to start to approximate colors from m_deltaImage
|
||||
// with some criterion:
|
||||
|
||||
// Final target: to approximate the palette to 255 colors + clear color (0)).
|
||||
// CRITERION:
|
||||
// Find a palette of 220 or less colors (in high precision) into the square border
|
||||
// contained in m_deltaImage, then into the center square quantize the remaining colors
|
||||
// to complete a palette of 255 colors, finally add the transparent color (0).
|
||||
//
|
||||
// m_currentImage__ __ m_deltaImage (same rectangle size as `frameBounds` variable)
|
||||
// | |
|
||||
// --------------*----|-----------
|
||||
// | | |
|
||||
// | --------------*- |
|
||||
// | | | |
|
||||
// | | ________ | |
|
||||
// | | | | *--------------- square border (we will collect
|
||||
// | | | | | | high precision colors from this area, less than 220)
|
||||
// | | | | | |
|
||||
// | | | *--------------------- center rectangle (we will to quantize
|
||||
// | | | | | | colors contained in this area)
|
||||
// | | |________| | |
|
||||
// | | | |
|
||||
// | |________________| |
|
||||
// | |
|
||||
// |_______________________________|
|
||||
//
|
||||
|
||||
const gfx::Size deltaSize = m_deltaImage->size();
|
||||
int thicknessTop = deltaSize.h / 4;
|
||||
int thicknessLeft = deltaSize.w / 4;
|
||||
int repeatCounter = 0;
|
||||
while (repeatCounter < 10 && thicknessTop > 0 && thicknessLeft > 0) {
|
||||
|
||||
// ----------------
|
||||
// |________________|
|
||||
// | | | |
|
||||
// | | | |
|
||||
// | |________| |
|
||||
// |________________|
|
||||
render::PaletteOptimizer optimizer;
|
||||
gfx::Rect auxRect(0, 0, deltaSize.w, thicknessTop);
|
||||
optimizer.feedWithImage(m_deltaImage.get(), auxRect, false);
|
||||
|
||||
// ----------------
|
||||
// | ________ |
|
||||
// | | | |
|
||||
// | | | |
|
||||
// |___|________|___|
|
||||
// |________________|
|
||||
auxRect = gfx::Rect(0, deltaSize.h - thicknessTop - 1, deltaSize.w, thicknessTop);
|
||||
optimizer.feedWithImage(m_deltaImage.get(), auxRect, false);
|
||||
|
||||
// ----------------
|
||||
// |____________ |
|
||||
// | | | |
|
||||
// | | | |
|
||||
// |___|________| |
|
||||
// |________________|
|
||||
auxRect = gfx::Rect(0, thicknessTop, thicknessLeft, deltaSize.h - 2 * thicknessTop);
|
||||
optimizer.feedWithImage(m_deltaImage.get(), auxRect, false);
|
||||
|
||||
// ----------------
|
||||
// | _____________|
|
||||
// | | | |
|
||||
// | | | |
|
||||
// | |________|___|
|
||||
// |________________|
|
||||
auxRect = gfx::Rect(deltaSize.w - thicknessLeft - 1, thicknessTop, thicknessLeft, deltaSize.h - 2 * thicknessTop);
|
||||
optimizer.feedWithImage(m_deltaImage.get(), auxRect, false);
|
||||
|
||||
int maxBorderColorCount = 220;
|
||||
if (optimizer.isHighPrecision() && (optimizer.highPrecisionSize() < maxBorderColorCount)) {
|
||||
pal.resize(optimizer.highPrecisionSize());
|
||||
optimizer.calculate(&pal, -1);
|
||||
break;
|
||||
}
|
||||
else if (thicknessTop <= 1 || thicknessLeft <= 1) {
|
||||
pal.resize(0);
|
||||
thicknessTop = 0;
|
||||
thicknessLeft = 0;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
thicknessTop -= thicknessTop / 2;
|
||||
thicknessLeft -= thicknessLeft / 2;
|
||||
}
|
||||
|
||||
repeatCounter++;
|
||||
}
|
||||
// Quantize the colors contained into center rectangle and add these in `pal`:
|
||||
if (pal.size() < 255) {
|
||||
gfx::Rect centerRect(thicknessLeft,
|
||||
thicknessTop,
|
||||
deltaSize.w - 2 * thicknessLeft,
|
||||
deltaSize.h - 2 * thicknessTop);
|
||||
Palette centerPalette(0, 255 - pal.size());
|
||||
centerPalette = createOptimizedPalette(m_deltaImage.get(),
|
||||
centerRect, 255 - pal.size());
|
||||
for (int i=0; i < centerPalette.size(); i++)
|
||||
pal.addEntry(centerPalette.getEntry(i));
|
||||
}
|
||||
// Finally add transparent color:
|
||||
ASSERT(pal.size() <= 255);
|
||||
pal.addEntry(0);
|
||||
m_transparentIndex = pal.size() - 1;
|
||||
Palette calculatePalette()
|
||||
{
|
||||
OctreeMap octree;
|
||||
const LockImageBits<RgbTraits> imageBits(m_deltaImage.get());
|
||||
auto it = imageBits.begin(), end = imageBits.end();
|
||||
bool maskColorFounded = false;
|
||||
for (; it != end; ++it) {
|
||||
color_t i = *it;
|
||||
if (i == 0 || m_transparentIndex == i) {
|
||||
maskColorFounded = true;
|
||||
continue;
|
||||
}
|
||||
octree.addColor(i);
|
||||
}
|
||||
// We are fine, we got 255 or less, there is room for the transparent color
|
||||
else if (pal.size() <= 255) {
|
||||
pal.addEntry(0);
|
||||
m_transparentIndex = pal.size() - 1;
|
||||
Palette palette;
|
||||
if (maskColorFounded) {
|
||||
// If there is a mask color, the OctreeMap::makePalette adds it
|
||||
// by default at entry == 0.
|
||||
octree.makePalette(&palette, 256, 8);
|
||||
m_transparentIndex = 0;
|
||||
return palette;
|
||||
}
|
||||
return pal;
|
||||
}
|
||||
|
||||
static Palette createOptimizedPalette(const Image* image,
|
||||
const gfx::Rect& bounds,
|
||||
const int ncolors) {
|
||||
render::PaletteOptimizer optimizer;
|
||||
|
||||
// Feed the palette optimizer with pixels inside the given bounds
|
||||
for (const auto& color : LockImageBits<RgbTraits>(image, bounds)) {
|
||||
if (rgba_geta(color) >= 128) // Note: the mask color won't be part of the final palette
|
||||
optimizer.feedWithRgbaColor(
|
||||
rgba(rgba_getr(color),
|
||||
rgba_getg(color),
|
||||
rgba_getb(color), 255));
|
||||
else {
|
||||
// If there isn't mask color we need to remove the 0 entry
|
||||
// added in OctreeMap::makePalette.
|
||||
octree.makePalette(&palette, 257, 8);
|
||||
Palette paletteWithoutMask(0, palette.size() - 1);
|
||||
for (int i=0; i < paletteWithoutMask.size(); i++)
|
||||
paletteWithoutMask.setEntry(i, palette.entry(i+1));
|
||||
m_transparentIndex = -1;
|
||||
return paletteWithoutMask;
|
||||
}
|
||||
|
||||
Palette palette(0, ncolors);
|
||||
optimizer.calculate(&palette, -1);
|
||||
return palette;
|
||||
}
|
||||
|
||||
void renderFrame(frame_t frame, Image* dst) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user