Remove fillOrphansNodes(), now new colors are added to the octree color arragement

This gives more accuracy in the color picking criteria on new images
pasted into an INDEXED sprite.

Also, added findMaskColor to fix the behavior reported in #3207

Both issues are related when RGBMAP is created. The 'mask color' and
'mask index' must be defined correctly to include/exclude during the
table/octree map color search.

To do: Converting sprite to INDEXED should add 'mask color' to the
palette when color count < 256 and transparent color isn't in the
palette.
This commit is contained in:
Gaspar Capello 2022-07-05 09:48:35 -03:00 committed by David Capello
parent 54443ad20d
commit 2785a9fef7
9 changed files with 83 additions and 123 deletions

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A. // Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -22,6 +22,7 @@
#include "doc/document.h" #include "doc/document.h"
#include "doc/layer.h" #include "doc/layer.h"
#include "doc/palette.h" #include "doc/palette.h"
#include "doc/rgbmap.h"
#include "doc/sprite.h" #include "doc/sprite.h"
#include "doc/tilesets.h" #include "doc/tilesets.h"
#include "render/quantization.h" #include "render/quantization.h"
@ -177,6 +178,12 @@ void SetPixelFormat::setFormat(PixelFormat format)
Sprite* sprite = this->sprite(); Sprite* sprite = this->sprite();
sprite->setPixelFormat(format); sprite->setPixelFormat(format);
if (format == IMAGE_INDEXED) {
int maskIndex = sprite->palette(0)->findMaskColor();
sprite->setTransparentColor(maskIndex == -1 ? 0 : maskIndex);
}
else
sprite->setTransparentColor(0);
sprite->incrementVersion(); sprite->incrementVersion();
// Regenerate extras // Regenerate extras
@ -201,14 +208,25 @@ void SetPixelFormat::convertImage(doc::Sprite* sprite,
ASSERT(oldImage); ASSERT(oldImage);
ASSERT(oldImage->pixelFormat() != IMAGE_TILEMAP); ASSERT(oldImage->pixelFormat() != IMAGE_TILEMAP);
// Making the RGBMap for Image->INDEXDED conversion.
// TODO: this is needed only when newImage
RgbMap* rgbmap;
int newMaskIndex = (isBackground ? -1 : 0);
if (m_newFormat == IMAGE_INDEXED) {
rgbmap = sprite->rgbMap(frame, sprite->rgbMapForSprite(), mapAlgorithm);
if (m_oldFormat == IMAGE_INDEXED)
newMaskIndex = sprite->transparentColor();
else
newMaskIndex = rgbmap->maskIndex();
}
ImageRef newImage( ImageRef newImage(
render::convert_pixel_format render::convert_pixel_format
(oldImage.get(), nullptr, m_newFormat, (oldImage.get(), nullptr, m_newFormat,
dithering, dithering,
sprite->rgbMap(frame, sprite->rgbMapForSprite(), mapAlgorithm), rgbmap,
sprite->palette(frame), sprite->palette(frame),
isBackground, isBackground,
oldImage->maskColor(), newMaskIndex,
toGray, toGray,
delegate)); delegate));

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (c) 2020-2021 Igara Studio S.A. // Copyright (c) 2020-2022 Igara Studio S.A.
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information. // Read LICENSE.txt for more information.
@ -36,64 +36,19 @@ void OctreeNode::addColor(color_t c, int level, OctreeNode* parent,
(*m_children)[index].addColor(c, level + 1, this, paletteIndex, levelDeep); (*m_children)[index].addColor(c, level + 1, this, paletteIndex, levelDeep);
} }
void OctreeNode::fillOrphansNodes(const Palette* palette, int OctreeNode::mapColor(int r, int g, int b, int a, int mask_index, const Palette* palette, int level) const
const color_t upstreamBranchColor,
const int level)
{ {
for (int i=0; i<16; i++) { // New behavior: if mapColor do not have an exact rgba match, it must calculate which
OctreeNode& child = (*m_children)[i]; // color of the current palette is the bestfit and memorize the index in a octree leaf.
if (level >= 8) {
if (child.hasChildren()) { if (m_paletteIndex == -1)
child.fillOrphansNodes( m_paletteIndex = palette->findBestfit(r, g, b, a, mask_index);
palette, return m_paletteIndex;
upstreamBranchColor + hextetToBranchColor(i, level),
level + 1);
}
else if (!(child.isLeaf())) {
// Here the node IS NOT a Leaf and HAS NOT children
// So, we must assign palette index to the current node
// to fill the "map holes" (i.e "death tree branches")
// BUT, if the level is low (a few bits to identify a color)
// 0, 1, 2, or 3, we need to create branchs/Leaves until
// the desired minimum color MSB bits.
if (level < MIN_LEVEL_OCTREE_DEEP) {
child.fillMostSignificantNodes(level);
i--;
continue;
}
int currentBranchColorAdd = hextetToBranchColor(i, level);
color_t branchColorMed = upstreamBranchColor |
currentBranchColorAdd |
((level == 7) ? 0 : (0x01010101 << (6 - level))); // mid color adition
int indexMed = palette->findBestfit2(rgba_getr(branchColorMed),
rgba_getg(branchColorMed),
rgba_getb(branchColorMed),
rgba_geta(branchColorMed));
child.paletteIndex(indexMed);
}
} }
} int index = getHextet(r, g, b, a, level);
if (!m_children)
void OctreeNode::fillMostSignificantNodes(int level)
{
if (level < MIN_LEVEL_OCTREE_DEEP) {
m_children.reset(new std::array<OctreeNode, 16>()); m_children.reset(new std::array<OctreeNode, 16>());
level++; return (*m_children)[index].mapColor(r, g, b, a, mask_index, palette, level + 1);
for (int i=0; i<16; i++) {
OctreeNode& child = (*m_children)[i];
child.fillMostSignificantNodes(level);
}
}
}
int OctreeNode::mapColor(int r, int g, int b, int a, int level) const
{
OctreeNode& child = (*m_children)[getHextet(rgba(r, g, b, a), level)];
if (child.hasChildren())
return child.mapColor(r, g, b, a, level+1);
else
return child.m_paletteIndex;
} }
void OctreeNode::collectLeafNodes(OctreeNodes& leavesVector, int& paletteIndex) void OctreeNode::collectLeafNodes(OctreeNodes& leavesVector, int& paletteIndex)
@ -143,6 +98,14 @@ int OctreeNode::getHextet(color_t c, int level)
((c & (0x80000000 >> level)) ? 8 : 0); ((c & (0x80000000 >> level)) ? 8 : 0);
} }
int OctreeNode::getHextet(int r, int g, int b, int a, int level)
{
return ((r & (0x80 >> level)) ? 1 : 0) |
((g & (0x80 >> level)) ? 2 : 0) |
((b & (0x80 >> level)) ? 4 : 0) |
((a & (0x80 >> level)) ? 8 : 0);
}
// static // static
color_t OctreeNode::hextetToBranchColor(int hextet, int level) color_t OctreeNode::hextetToBranchColor(int hextet, int level)
{ {
@ -261,11 +224,6 @@ bool OctreeMap::makePalette(Palette* palette,
return true; return true;
} }
void OctreeMap::fillOrphansNodes(const Palette* palette)
{
m_root.fillOrphansNodes(palette, 0, 0);
}
void OctreeMap::feedWithImage(const Image* image, void OctreeMap::feedWithImage(const Image* image,
const bool withAlpha, const bool withAlpha,
const color_t maskColor, const color_t maskColor,
@ -310,13 +268,12 @@ void OctreeMap::feedWithImage(const Image* image,
int OctreeMap::mapColor(color_t rgba) const int OctreeMap::mapColor(color_t rgba) const
{ {
if (m_root.hasChildren()) return m_root.mapColor(rgba_getr(rgba),
return m_root.mapColor(rgba_getr(rgba), rgba_getg(rgba),
rgba_getg(rgba), rgba_getb(rgba),
rgba_getb(rgba), rgba_geta(rgba),
rgba_geta(rgba), 0); m_maskIndex,
else m_palette, 0);
return -1;
} }
void OctreeMap::regenerateMap(const Palette* palette, const int maskIndex) void OctreeMap::regenerateMap(const Palette* palette, const int maskIndex)
@ -354,7 +311,6 @@ void OctreeMap::regenerateMap(const Palette* palette, const int maskIndex)
} }
m_root.addColor(palette->entry(i), 0, &m_root, i, 8); m_root.addColor(palette->entry(i), 0, &m_root, i, 8);
} }
m_root.fillOrphansNodes(palette, 0, 0);
m_palette = palette; m_palette = palette;
m_modifications = palette->getModifications(); m_modifications = palette->getModifications();

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (c) 2020-2021 Igara Studio S.A. // Copyright (c) 2020-2022 Igara Studio S.A.
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information. // Read LICENSE.txt for more information.
@ -93,13 +93,7 @@ public:
void addColor(color_t c, int level, OctreeNode* parent, void addColor(color_t c, int level, OctreeNode* parent,
int paletteIndex = 0, int levelDeep = 7); int paletteIndex = 0, int levelDeep = 7);
void fillOrphansNodes(const Palette* palette, int mapColor(int r, int g, int b, int a, int mask_index, const Palette* palette, int level) const;
const color_t upstreamBranchColor,
const int level);
void fillMostSignificantNodes(int level);
int mapColor(int r, int g, int b, int a, int level) const;
void collectLeafNodes(OctreeNodes& leavesVector, int& paletteIndex); void collectLeafNodes(OctreeNodes& leavesVector, int& paletteIndex);
@ -114,11 +108,12 @@ private:
void paletteIndex(int index) { m_paletteIndex = index; } void paletteIndex(int index) { m_paletteIndex = index; }
static int getHextet(color_t c, int level); static int getHextet(color_t c, int level);
static int getHextet(int r, int g, int b, int a, int level);
static color_t hextetToBranchColor(int hextet, int level); static color_t hextetToBranchColor(int hextet, int level);
LeafColor m_leafColor; LeafColor m_leafColor;
int m_paletteIndex = 0; mutable int m_paletteIndex = -1;
std::unique_ptr<std::array<OctreeNode, 16>> m_children; mutable std::unique_ptr<std::array<OctreeNode, 16>> m_children;
OctreeNode* m_parent = nullptr; OctreeNode* m_parent = nullptr;
}; };
@ -142,12 +137,20 @@ public:
// RgbMap impl // RgbMap impl
void regenerateMap(const Palette* palette, const int maskIndex) override; void regenerateMap(const Palette* palette, const int maskIndex) override;
int mapColor(color_t rgba) const override; int mapColor(color_t rgba) const override;
int maskIndex() const override { return m_maskIndex; }
int mapColor(const int r, const int g,
const int b, const int a) const
{
ASSERT(r >= 0 && r < 256);
ASSERT(g >= 0 && g < 256);
ASSERT(b >= 0 && b < 256);
ASSERT(a >= 0 && a < 256);
return mapColor(rgba(r, g, b, a));
}
int moodifications() const { return m_modifications; }; int moodifications() const { return m_modifications; };
private: private:
void fillOrphansNodes(const Palette* palette);
OctreeNode m_root; OctreeNode m_root;
OctreeNodes m_leavesVector; OctreeNodes m_leavesVector;
const Palette* m_palette = nullptr; const Palette* m_palette = nullptr;

View File

@ -483,37 +483,14 @@ int Palette::findBestfit(int r, int g, int b, int a, int mask_index) const
return bestfit; return bestfit;
} }
int Palette::findBestfit2(int r, int g, int b, int a) const int Palette::findMaskColor() const
{ {
ASSERT(r >= 0 && r <= 255);
ASSERT(g >= 0 && g <= 255);
ASSERT(b >= 0 && b <= 255);
ASSERT(a >= 0 && a <= 255);
int bestfit = 0;
int lowest = std::numeric_limits<int>::max();
int size = m_colors.size(); int size = m_colors.size();
for (int i = 0; i < size; ++i) {
for (int i=0; i<size; ++i) { if (m_colors[i] == 0)
color_t rgb = m_colors[i]; return i;
int rDiff = r - rgba_getr(rgb);
int gDiff = g - rgba_getg(rgb);
int bDiff = b - rgba_getb(rgb);
int aDiff = a - rgba_geta(rgb);
// TODO We should have two different ways to calculate the
// distance between colors, like "Perceptual" and "Linear", or a
// way to configure these coefficients.
int diff = rDiff * rDiff * 900 +
gDiff * gDiff * 3481 +
bDiff * bDiff * 121 +
aDiff * aDiff * 900; // there is no scientific reason to choose this value.
if (diff < lowest) {
lowest = diff;
bestfit = i;
}
} }
return bestfit; return -1;
} }
void Palette::applyRemap(const Remap& remap) void Palette::applyRemap(const Remap& remap)

View File

@ -101,7 +101,7 @@ namespace doc {
int findExactMatch(int r, int g, int b, int a, int mask_index) const; int findExactMatch(int r, int g, int b, int a, int mask_index) const;
bool findExactMatch(color_t color) const; bool findExactMatch(color_t color) const;
int findBestfit(int r, int g, int b, int a, int mask_index) const; int findBestfit(int r, int g, int b, int a, int mask_index) const;
int findBestfit2(int r, int g, int b, int a) const; int findMaskColor() const;
void applyRemap(const Remap& remap); void applyRemap(const Remap& remap);

View File

@ -1,5 +1,5 @@
// Aseprite Document Library // Aseprite Document Library
// Copyright (c) 2020 Igara Studio S.A. // Copyright (c) 2020-2022 Igara Studio S.A.
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information. // Read LICENSE.txt for more information.
@ -25,6 +25,8 @@ namespace doc {
// Should return the best index in a palette that matches the given RGBA values. // Should return the best index in a palette that matches the given RGBA values.
virtual int mapColor(const color_t rgba) const = 0; virtual int mapColor(const color_t rgba) const = 0;
virtual int maskIndex() const = 0;
int mapColor(const int r, int mapColor(const int r,
const int g, const int g,
const int b, const int b,

View File

@ -1,5 +1,5 @@
// Aseprite Document Library // Aseprite Document Library
// Copyright (c) 2020 Igara Studio S.A. // Copyright (c) 2020-2022 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello // Copyright (c) 2001-2016 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -41,7 +41,7 @@ namespace doc {
return (v & INVALID) ? generateEntry(i, r, g, b, a): v; return (v & INVALID) ? generateEntry(i, r, g, b, a): v;
} }
int maskIndex() const { return m_maskIndex; } int maskIndex() const override { return m_maskIndex; }
private: private:
int generateEntry(int i, int r, int g, int b, int a) const; int generateEntry(int i, int r, int g, int b, int a) const;

View File

@ -395,9 +395,6 @@ RgbMap* Sprite::rgbMap(const frame_t frame,
const RgbMapFor forLayer, const RgbMapFor forLayer,
RgbMapAlgorithm mapAlgo) const RgbMapAlgorithm mapAlgo) const
{ {
int maskIndex = (forLayer == RgbMapFor::OpaqueLayer ?
-1: transparentColor());
if (!m_rgbMap || m_rgbMapAlgorithm != mapAlgo) { if (!m_rgbMap || m_rgbMapAlgorithm != mapAlgo) {
m_rgbMapAlgorithm = mapAlgo; m_rgbMapAlgorithm = mapAlgo;
switch (m_rgbMapAlgorithm) { switch (m_rgbMapAlgorithm) {
@ -410,7 +407,14 @@ RgbMap* Sprite::rgbMap(const frame_t frame,
return nullptr; return nullptr;
} }
} }
int maskIndex;
if (forLayer == RgbMapFor::OpaqueLayer)
maskIndex = -1;
else {
maskIndex = palette(frame)->findMaskColor();
if (maskIndex == -1)
maskIndex = 0;
}
m_rgbMap->regenerateMap(palette(frame), maskIndex); m_rgbMap->regenerateMap(palette(frame), maskIndex);
return m_rgbMap.get(); return m_rgbMap.get();
} }

View File

@ -1,5 +1,5 @@
// Aseprite Render Library // Aseprite Render Library
// Copyright (c) 2019-2021 Igara Studio S.A. // Copyright (c) 2019-2022 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello // Copyright (c) 2001-2018 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -237,7 +237,7 @@ Image* convert_pixel_format(
a = rgba_geta(c); a = rgba_geta(c);
if (a == 0) if (a == 0)
*dst_it = new_mask_color; *dst_it = (new_mask_color == -1? 0 : new_mask_color);
else if (rgbmap) else if (rgbmap)
*dst_it = rgbmap->mapColor(c); *dst_it = rgbmap->mapColor(c);
else else
@ -296,7 +296,7 @@ Image* convert_pixel_format(
c = graya_getv(c); c = graya_getv(c);
if (a == 0) if (a == 0)
*dst_it = new_mask_color; *dst_it = (new_mask_color == -1? 0 : new_mask_color);
else if (rgbmap) else if (rgbmap)
*dst_it = rgbmap->mapColor(c, c, c, a); *dst_it = rgbmap->mapColor(c, c, c, a);
else else