diff --git a/data/pref.xml b/data/pref.xml
index b4e7c81e1..6e7f29e21 100644
--- a/data/pref.xml
+++ b/data/pref.xml
@@ -321,6 +321,7 @@
+
diff --git a/data/strings/en.ini b/data/strings/en.ini
index d9ece8cf2..320f08290 100644
--- a/data/strings/en.ini
+++ b/data/strings/en.ini
@@ -1255,6 +1255,14 @@ default = Default (Octree)
rgb5a3 = Table RGB 5 bits + Alpha 3 bits
octree = Octree
+[best_fit_criteria_selector]
+label = Color Best Fit Criteria:
+default = Default (Euclidean)
+rgb = RGB
+linearized_rgb = Linearized RGB
+cie_xyz = CIEXYZ
+cie_lab = CIELAB
+
[open_file]
title = Open
loading = Loading file
diff --git a/data/widgets/color_mode.xml b/data/widgets/color_mode.xml
index e02ce7fab..83d074e68 100644
--- a/data/widgets/color_mode.xml
+++ b/data/widgets/color_mode.xml
@@ -1,5 +1,5 @@
-
+
@@ -23,10 +23,12 @@
-
-
-
-
+
+
+
+
+
+
diff --git a/data/widgets/options.xml b/data/widgets/options.xml
index fbd339960..9d04d39ab 100644
--- a/data/widgets/options.xml
+++ b/data/widgets/options.xml
@@ -587,10 +587,12 @@
-
+
-
+
+
+
pixelFormat())
, m_newFormat(newFormat)
@@ -108,7 +109,8 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
cel->layer()->isBackground(),
mapAlgorithm,
toGray,
- &superDel);
+ &superDel,
+ fitCriteria);
superDel.nextImage();
}
@@ -208,7 +210,8 @@ void SetPixelFormat::convertImage(doc::Sprite* sprite,
const bool isBackground,
const doc::RgbMapAlgorithm mapAlgorithm,
doc::rgba_to_graya_func toGray,
- render::TaskDelegate* delegate)
+ render::TaskDelegate* delegate,
+ const doc::FitCriteria fitCriteria)
{
ASSERT(oldImage);
ASSERT(oldImage->pixelFormat() != IMAGE_TILEMAP);
@@ -218,7 +221,10 @@ void SetPixelFormat::convertImage(doc::Sprite* sprite,
RgbMap* rgbmap;
int newMaskIndex = (isBackground ? -1 : 0);
if (m_newFormat == IMAGE_INDEXED) {
- rgbmap = sprite->rgbMap(frame, sprite->rgbMapForSprite(), mapAlgorithm);
+ rgbmap = sprite->rgbMap(frame,
+ sprite->rgbMapForSprite(),
+ mapAlgorithm,
+ fitCriteria);
if (m_oldFormat == IMAGE_INDEXED)
newMaskIndex = sprite->transparentColor();
else
diff --git a/src/app/cmd/set_pixel_format.h b/src/app/cmd/set_pixel_format.h
index db817a033..d02cc4db4 100644
--- a/src/app/cmd/set_pixel_format.h
+++ b/src/app/cmd/set_pixel_format.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2019-2020 Igara Studio S.A.
+// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@@ -12,6 +12,7 @@
#include "app/cmd/with_sprite.h"
#include "app/cmd_sequence.h"
#include "doc/color.h"
+#include "doc/fit_criteria.h"
#include "doc/frame.h"
#include "doc/image_ref.h"
#include "doc/pixel_format.h"
@@ -37,7 +38,8 @@ namespace cmd {
const render::Dithering& dithering,
const doc::RgbMapAlgorithm mapAlgorithm,
doc::rgba_to_graya_func toGray,
- render::TaskDelegate* delegate);
+ render::TaskDelegate* delegate,
+ const doc::FitCriteria fitCriteria = doc::FitCriteria::DEFAULT);
protected:
void onExecute() override;
@@ -56,7 +58,8 @@ namespace cmd {
const bool isBackground,
const doc::RgbMapAlgorithm mapAlgorithm,
doc::rgba_to_graya_func toGray,
- render::TaskDelegate* delegate);
+ render::TaskDelegate* delegate,
+ const doc::FitCriteria fitCriteria = doc::FitCriteria::DEFAULT);
doc::PixelFormat m_oldFormat;
doc::PixelFormat m_newFormat;
diff --git a/src/app/commands/cmd_change_pixel_format.cpp b/src/app/commands/cmd_change_pixel_format.cpp
index 341f6c4b4..541f1ce15 100644
--- a/src/app/commands/cmd_change_pixel_format.cpp
+++ b/src/app/commands/cmd_change_pixel_format.cpp
@@ -23,6 +23,7 @@
#include "app/modules/palettes.h"
#include "app/sprite_job.h"
#include "app/transaction.h"
+#include "app/ui/best_fit_criteria_selector.h"
#include "app/ui/dithering_selector.h"
#include "app/ui/editor/editor.h"
#include "app/ui/editor/editor_render.h"
@@ -69,7 +70,6 @@ public:
const doc::frame_t frame,
const doc::PixelFormat pixelFormat,
const render::Dithering& dithering,
- const doc::RgbMapAlgorithm rgbMapAlgorithm,
const gen::ToGrayAlgorithm toGray,
const gfx::Point& pos,
const bool newBlend)
@@ -83,13 +83,11 @@ public:
sprite, frame,
pixelFormat,
dithering,
- rgbMapAlgorithm,
toGray,
newBlend]() { // Copy the matrix
run(sprite, frame,
pixelFormat,
dithering,
- rgbMapAlgorithm,
toGray,
newBlend);
})
@@ -114,7 +112,6 @@ private:
const doc::frame_t frame,
const doc::PixelFormat pixelFormat,
const render::Dithering& dithering,
- const doc::RgbMapAlgorithm rgbMapAlgorithm,
const gen::ToGrayAlgorithm toGray,
const bool newBlend) {
doc::ImageRef tmp(
@@ -136,9 +133,7 @@ private:
m_image.get(),
pixelFormat,
dithering,
- sprite->rgbMap(frame,
- sprite->rgbMapForSprite(),
- rgbMapAlgorithm),
+ sprite->rgbMap(frame),
sprite->palette(frame),
(sprite->backgroundLayer() != nullptr),
0,
@@ -193,6 +188,7 @@ public:
, m_selectedItem(nullptr)
, m_ditheringSelector(nullptr)
, m_mapAlgorithmSelector(nullptr)
+ , m_bestFitCriteriaSelector(nullptr)
, m_imageJustCreated(true)
{
const auto& pref = Preferences::instance();
@@ -219,6 +215,9 @@ public:
m_mapAlgorithmSelector = new RgbMapAlgorithmSelector;
m_mapAlgorithmSelector->setExpansive(true);
+ m_bestFitCriteriaSelector = new BestFitCriteriaSelector;
+ m_bestFitCriteriaSelector->setExpansive(true);
+
// Select default dithering method
{
int index = m_ditheringSelector->findItemIndex(
@@ -230,8 +229,12 @@ public:
// Select default RgbMap algorithm
m_mapAlgorithmSelector->algorithm(pref.quantization.rgbmapAlgorithm());
+ // Select default best fit criteria
+ m_bestFitCriteriaSelector->criteria(pref.quantization.fitCriteria());
+
ditheringPlaceholder()->addChild(m_ditheringSelector);
rgbmapAlgorithmPlaceholder()->addChild(m_mapAlgorithmSelector);
+ bestFitCriteriaPlaceholder()->addChild(m_bestFitCriteriaSelector);
const bool adv = pref.quantization.advanced();
advancedCheck()->setSelected(adv);
@@ -240,6 +243,7 @@ public:
// Signals
m_ditheringSelector->Change.connect([this]{ onIndexParamChange(); });
m_mapAlgorithmSelector->Change.connect([this]{ onIndexParamChange(); });
+ m_bestFitCriteriaSelector->Change.connect([this]{ onIndexParamChange(); });
factor()->Change.connect([this]{ onIndexParamChange(); });
advancedCheck()->Click.connect(
@@ -301,6 +305,13 @@ public:
return doc::RgbMapAlgorithm::DEFAULT;
}
+ doc::FitCriteria fitCriteria() const {
+ if (m_bestFitCriteriaSelector)
+ return m_bestFitCriteriaSelector->criteria();
+ else
+ return doc::FitCriteria::DEFAULT;
+ }
+
gen::ToGrayAlgorithm toGray() const {
static_assert(
int(gen::ToGrayAlgorithm::LUMA) == 0 &&
@@ -331,7 +342,7 @@ public:
}
}
- if (m_mapAlgorithmSelector)
+ if (m_mapAlgorithmSelector || m_bestFitCriteriaSelector)
pref.quantization.advanced(advancedCheck()->isSelected());
}
@@ -396,6 +407,12 @@ private:
visibleBounds.origin(),
doc::BlendMode::SRC);
+ m_editor->sprite()->rgbMap(
+ 0,
+ m_editor->sprite()->rgbMapForSprite(),
+ rgbMapAlgorithm(),
+ fitCriteria());
+
m_editor->invalidate();
progress()->setValue(0);
progress()->setVisible(false);
@@ -408,7 +425,6 @@ private:
m_editor->frame(),
dstPixelFormat,
dithering(),
- rgbMapAlgorithm(),
toGray(),
visibleBounds.origin(),
Preferences::instance().experimental.newBlend()));
@@ -463,6 +479,7 @@ private:
ConversionItem* m_selectedItem;
DitheringSelector* m_ditheringSelector;
RgbMapAlgorithmSelector* m_mapAlgorithmSelector;
+ BestFitCriteriaSelector* m_bestFitCriteriaSelector;
bool m_imageJustCreated;
};
@@ -485,6 +502,7 @@ private:
doc::PixelFormat m_format;
render::Dithering m_dithering;
doc::RgbMapAlgorithm m_rgbmap;
+ doc::FitCriteria m_fitCriteria = FitCriteria::DEFAULT;
gen::ToGrayAlgorithm m_toGray;
};
@@ -624,7 +642,10 @@ void ChangePixelFormatCommand::onExecute(Context* context)
{
bool flatten = false;
- if (context->isUIAvailable() && m_showDlg) {
+ if (!context->isUIAvailable()) {
+ // do nothing
+ }
+ else if (m_showDlg) {
ColorModeWindow window(Editor::activeEditor());
window.remapWindow();
@@ -640,11 +661,17 @@ void ChangePixelFormatCommand::onExecute(Context* context)
m_format = window.pixelFormat();
m_dithering = window.dithering();
m_rgbmap = window.rgbMapAlgorithm();
+ m_fitCriteria = window.fitCriteria();
m_toGray = window.toGray();
flatten = window.flattenEnabled();
window.saveOptions();
}
+ else {
+ // TO DO: in a first approach a simple conversion to indexed color mode
+ // it's just via the old fit criteria (Euclidean color distance).
+ m_fitCriteria = FitCriteria::DEFAULT;
+ }
// No conversion needed
Doc* doc = context->activeDocument();
@@ -679,7 +706,8 @@ void ChangePixelFormatCommand::onExecute(Context* context)
m_dithering,
m_rgbmap,
get_gray_func(m_toGray),
- &job)); // SpriteJob is a render::TaskDelegate
+ &job,
+ m_fitCriteria)); // SpriteJob is a render::TaskDelegate
});
job.waitJob();
}
diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp
index 90d07121e..4814ae5dd 100644
--- a/src/app/commands/cmd_options.cpp
+++ b/src/app/commands/cmd_options.cpp
@@ -26,6 +26,7 @@
#include "app/recent_files.h"
#include "app/resource_finder.h"
#include "app/tx.h"
+#include "app/ui/best_fit_criteria_selector.h"
#include "app/ui/color_button.h"
#include "app/ui/main_window.h"
#include "app/ui/pref_widget.h"
@@ -478,6 +479,10 @@ public:
m_rgbmapAlgorithmSelector.setExpansive(true);
m_rgbmapAlgorithmSelector.algorithm(m_pref.quantization.rgbmapAlgorithm());
+ bestFitCriteriaPlaceholder()->addChild(&m_bestFitCriteriaSelector);
+ m_bestFitCriteriaSelector.setExpansive(true);
+ m_bestFitCriteriaSelector.criteria(m_pref.quantization.fitCriteria());
+
if (m_pref.editor.showScrollbars())
showScrollbars()->setSelected(true);
@@ -828,6 +833,7 @@ public:
m_pref.experimental.flashLayer(flashLayer()->isSelected());
m_pref.experimental.nonactiveLayersOpacity(nonactiveLayersOpacity()->getValue());
m_pref.quantization.rgbmapAlgorithm(m_rgbmapAlgorithmSelector.algorithm());
+ m_pref.quantization.fitCriteria(m_bestFitCriteriaSelector.criteria());
#ifdef LAF_WINDOWS
{
@@ -1842,6 +1848,7 @@ private:
std::vector m_colorSpaces;
std::string m_templateTextForDisplayCS;
RgbMapAlgorithmSelector m_rgbmapAlgorithmSelector;
+ BestFitCriteriaSelector m_bestFitCriteriaSelector;
ButtonSet* m_themeVars = nullptr;
SamplingSelector* m_samplingSelector = nullptr;
};
diff --git a/src/app/script/values.cpp b/src/app/script/values.cpp
index 70c91d7e5..4151c106d 100644
--- a/src/app/script/values.cpp
+++ b/src/app/script/values.cpp
@@ -342,6 +342,7 @@ FOR_ENUM(app::tools::RotationAlgorithm)
FOR_ENUM(doc::AniDir)
FOR_ENUM(doc::BrushPattern)
FOR_ENUM(doc::ColorMode)
+FOR_ENUM(doc::FitCriteria)
FOR_ENUM(doc::RgbMapAlgorithm)
FOR_ENUM(filters::HueSaturationFilter::Mode)
FOR_ENUM(filters::TiledMode)
diff --git a/src/app/ui/best_fit_criteria_selector.cpp b/src/app/ui/best_fit_criteria_selector.cpp
new file mode 100644
index 000000000..a6823d417
--- /dev/null
+++ b/src/app/ui/best_fit_criteria_selector.cpp
@@ -0,0 +1,46 @@
+// Aseprite
+// Copyright (C) 2024 Igara Studio S.A.
+//
+// This program is distributed under the terms of
+// the End-User License Agreement for Aseprite.
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "app/ui/best_fit_criteria_selector.h"
+
+#include "app/i18n/strings.h"
+
+namespace app {
+
+BestFitCriteriaSelector::BestFitCriteriaSelector()
+{
+ // addItem() must match the FitCriteria enum
+ static_assert(int(doc::FitCriteria::DEFAULT) == 0 &&
+ int(doc::FitCriteria::RGB) == 1 &&
+ int(doc::FitCriteria::linearizedRGB) == 2 &&
+ int(doc::FitCriteria::CIEXYZ) == 3 &&
+ int(doc::FitCriteria::CIELAB) == 4,
+ "Unexpected doc::FitCriteria values");
+
+ addItem(Strings::best_fit_criteria_selector_default());
+ addItem(Strings::best_fit_criteria_selector_rgb());
+ addItem(Strings::best_fit_criteria_selector_linearized_rgb());
+ addItem(Strings::best_fit_criteria_selector_cie_xyz());
+ addItem(Strings::best_fit_criteria_selector_cie_lab());
+
+ criteria(doc::FitCriteria::DEFAULT);
+}
+
+doc::FitCriteria BestFitCriteriaSelector::criteria()
+{
+ return (doc::FitCriteria)getSelectedItemIndex();
+}
+
+void BestFitCriteriaSelector::criteria(const doc::FitCriteria criteria)
+{
+ setSelectedItemIndex((int)criteria);
+}
+
+} // namespace app
diff --git a/src/app/ui/best_fit_criteria_selector.h b/src/app/ui/best_fit_criteria_selector.h
new file mode 100644
index 000000000..712c28c83
--- /dev/null
+++ b/src/app/ui/best_fit_criteria_selector.h
@@ -0,0 +1,27 @@
+// Aseprite
+// Copyright (C) 2024 Igara Studio S.A.
+//
+// This program is distributed under the terms of
+// the End-User License Agreement for Aseprite.
+
+#ifndef APP_UI_BEST_FIT_CRITERIA_SELECTOR_H_INCLUDED
+#define APP_UI_BEST_FIT_CRITERIA_SELECTOR_H_INCLUDED
+#pragma once
+
+#include "doc/fit_criteria.h"
+#include "doc/palette.h"
+#include "ui/combobox.h"
+
+namespace app {
+
+ class BestFitCriteriaSelector : public ui::ComboBox {
+ public:
+ BestFitCriteriaSelector();
+
+ doc::FitCriteria criteria();
+ void criteria(doc::FitCriteria criteria);
+ };
+
+} // namespace app
+
+#endif
diff --git a/src/doc/CMakeLists.txt b/src/doc/CMakeLists.txt
index 621dfc7db..308fe7c77 100644
--- a/src/doc/CMakeLists.txt
+++ b/src/doc/CMakeLists.txt
@@ -61,6 +61,7 @@ add_library(doc-lib
primitives.cpp
remap.cpp
render_plan.cpp
+ rgbmap_base.cpp
rgbmap_rgb5a3.cpp
selected_frames.cpp
selected_layers.cpp
diff --git a/src/doc/fit_criteria.h b/src/doc/fit_criteria.h
new file mode 100644
index 000000000..7704b14d3
--- /dev/null
+++ b/src/doc/fit_criteria.h
@@ -0,0 +1,23 @@
+// Aseprite Document Library
+// Copyright (C) 2024 Igara Studio S.A.
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifndef DOC_FIT_CRITERIA_H_INCLUDED
+#define DOC_FIT_CRITERIA_H_INCLUDED
+#pragma once
+
+namespace doc {
+
+ enum class FitCriteria {
+ DEFAULT = 0,
+ RGB = 1,
+ linearizedRGB = 2,
+ CIEXYZ = 3,
+ CIELAB = 4
+ };
+
+} // namespace doc
+
+#endif
diff --git a/src/doc/octree_map.cpp b/src/doc/octree_map.cpp
index b4304bb2f..d76949f83 100644
--- a/src/doc/octree_map.cpp
+++ b/src/doc/octree_map.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (c) 2020-2023 Igara Studio S.A.
+// Copyright (c) 2020-2024 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -35,19 +35,21 @@ void OctreeNode::addColor(color_t c, int level, OctreeNode* parent,
(*m_children)[index].addColor(c, level + 1, this, paletteIndex, levelDeep);
}
-int OctreeNode::mapColor(int r, int g, int b, int a, int mask_index, const Palette* palette, int level) const
+int OctreeNode::mapColor(int r, int g, int b, int a, int mask_index,
+ const Palette* palette, int level,
+ const OctreeMap* octree) const
{
// New behavior: if mapColor do not have an exact rgba match, it must calculate which
// color of the current palette is the bestfit and memorize the index in a octree leaf.
if (level >= 8) {
if (m_paletteIndex == -1)
- m_paletteIndex = palette->findBestfit(r, g, b, a, mask_index);
+ m_paletteIndex = octree->findBestfit(r, g, b, a, mask_index);
return m_paletteIndex;
}
int index = getHextet(r, g, b, a, level);
if (!m_children)
m_children.reset(new std::array());
- return (*m_children)[index].mapColor(r, g, b, a, mask_index, palette, level + 1);
+ return (*m_children)[index].mapColor(r, g, b, a, mask_index, palette, level + 1, octree);
}
void OctreeNode::collectLeafNodes(OctreeNodes& leavesVector, int& paletteIndex)
@@ -268,10 +270,13 @@ int OctreeMap::mapColor(color_t rgba) const
rgba_getb(rgba),
rgba_geta(rgba),
m_maskIndex,
- m_palette, 0);
+ m_palette, 0,
+ this);
}
-void OctreeMap::regenerateMap(const Palette* palette, const int maskIndex)
+void OctreeMap::regenerateMap(const Palette* palette,
+ const int maskIndex,
+ const FitCriteria fitCriteria)
{
ASSERT(palette);
if (!palette)
@@ -280,9 +285,12 @@ void OctreeMap::regenerateMap(const Palette* palette, const int maskIndex)
// Skip useless regenerations
if (m_palette == palette &&
m_modifications == palette->getModifications() &&
- m_maskIndex == maskIndex)
+ m_maskIndex == maskIndex &&
+ m_fitCriteria == fitCriteria)
return;
+ m_palette = palette;
+ m_fitCriteria = fitCriteria;
m_root = OctreeNode();
m_leavesVector.clear();
m_maskIndex = maskIndex;
@@ -293,10 +301,10 @@ void OctreeMap::regenerateMap(const Palette* palette, const int maskIndex)
}
else {
m_maskColor = palette->getEntry(maskIndex);
- maskColorBestFitIndex = palette->findBestfit(rgba_getr(m_maskColor),
- rgba_getg(m_maskColor),
- rgba_getb(m_maskColor),
- rgba_geta(m_maskColor), maskIndex);
+ maskColorBestFitIndex = findBestfit(rgba_getr(m_maskColor),
+ rgba_getg(m_maskColor),
+ rgba_getb(m_maskColor),
+ rgba_geta(m_maskColor), maskIndex);
}
for (int i=0; isize(); i++) {
@@ -307,7 +315,6 @@ void OctreeMap::regenerateMap(const Palette* palette, const int maskIndex)
m_root.addColor(palette->entry(i), 0, &m_root, i, 8);
}
- m_palette = palette;
m_modifications = palette->getModifications();
}
diff --git a/src/doc/octree_map.h b/src/doc/octree_map.h
index a4a9a0244..b50f3c6cd 100644
--- a/src/doc/octree_map.h
+++ b/src/doc/octree_map.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (c) 2020-2022 Igara Studio S.A.
+// Copyright (c) 2020-2024 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -11,7 +11,7 @@
#include "doc/color.h"
#include "doc/image_impl.h"
#include "doc/palette.h"
-#include "doc/rgbmap.h"
+#include "doc/rgbmap_base.h"
#include
#include
@@ -26,6 +26,8 @@
namespace doc {
class OctreeNode;
+class OctreeMap;
+
using OctreeNodes = std::vector;
class OctreeNode {
@@ -93,7 +95,9 @@ public:
void addColor(color_t c, int level, OctreeNode* parent,
int paletteIndex = 0, int levelDeep = 7);
- int mapColor(int r, int g, int b, int a, int mask_index, const Palette* palette, int level) const;
+ int mapColor(int r, int g, int b, int a, int mask_index,
+ const Palette* palette, int level,
+ const OctreeMap* octree) const;
void collectLeafNodes(OctreeNodes& leavesVector, int& paletteIndex);
@@ -117,7 +121,7 @@ private:
OctreeNode* m_parent = nullptr;
};
-class OctreeMap : public RgbMap {
+class OctreeMap : public RgbMapBase {
public:
void addColor(color_t color, int levelDeep = 7) {
m_root.addColor(color, 0, &m_root, 0, levelDeep);
@@ -135,27 +139,23 @@ public:
const int levelDeep = 7);
// RgbMap impl
- void regenerateMap(const Palette* palette, const int maskIndex) 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));
+ void regenerateMap(const Palette* palette,
+ const int maskIndex,
+ const FitCriteria fitCriteria) override;
+ void regenerateMap(const Palette* palette,
+ const int maskIndex) override {
+ regenerateMap(palette, maskIndex, m_fitCriteria);
}
- int moodifications() const { return m_modifications; };
+ int mapColor(color_t rgba) const override;
+
+ RgbMapAlgorithm rgbmapAlgorithm() const override {
+ return RgbMapAlgorithm::OCTREE;
+ }
private:
OctreeNode m_root;
OctreeNodes m_leavesVector;
- const Palette* m_palette = nullptr;
- int m_modifications = 0;
- int m_maskIndex = 0;
color_t m_maskColor = 0;
};
diff --git a/src/doc/palette.cpp b/src/doc/palette.cpp
index 681c3e432..753d91adb 100644
--- a/src/doc/palette.cpp
+++ b/src/doc/palette.cpp
@@ -1,5 +1,5 @@
// Aseprite Document Library
-// Copyright (c) 2020-2023 Igara Studio S.A.
+// Copyright (c) 2020-2024 Igara Studio S.A.
// Copyright (c) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
@@ -26,14 +26,6 @@ namespace doc {
using namespace gfx;
-enum class FitCriteria {
- OLD,
- RGB,
- linearizedRGB,
- CIEXYZ,
- CIELAB
-};
-
Palette::Palette()
: Palette(0, 256)
{
@@ -346,154 +338,46 @@ void Palette::initBestfit()
}
}
-// Auxiliary function for rgbToOtherSpace()
-static double f(double t)
-{
- if (t > 0.00885645171)
- return std::pow(t, 0.3333333333333333);
- else
- return (t / 0.12841855 + 0.137931034);
-}
-
-// Auxiliary function for findBestfit()
-static void rgbToOtherSpace(double& r, double& g, double& b, FitCriteria fc)
-{
- if (fc == FitCriteria::RGB)
- return;
- double Rl, Gl, Bl;
- // Linearization:
- r = r / 255.0;
- g = g / 255.0;
- b = b / 255.0;
- if (r <= 0.04045)
- Rl = r / 12.92;
- else
- Rl = std::pow((r + 0.055) / 1.055, 2.4);
- if (g <= 0.04045)
- Gl = g / 12.92;
- else
- Gl = std::pow((g + 0.055) / 1.055, 2.4);
- if (b <= 0.04045)
- Bl = b / 12.92;
- else
- Bl = std::pow((b + 0.055) / 1.055, 2.4);
- if (fc == FitCriteria::linearizedRGB) {
- r = Rl;
- g = Gl;
- b = Bl;
- return;
- }
- // Conversion lineal RGB to CIE XYZ
- r = 41.24564*Rl + 35.75761 * Gl + 18.04375 * Bl;
- g = 21.26729*Rl + 71.51522 * Gl + 7.2175 * Bl;
- b = 1.93339*Rl + 11.91920 * Gl + 95.03041 * Bl;
- switch (fc) {
-
- case FitCriteria::CIEXYZ:
- return;
-
- case FitCriteria::CIELAB: {
- // Converting CIEXYZ to CIELAB:
- // For Standard Illuminant D65:
- // const double xn = 95.0489;
- // const double yn = 100.0;
- // const double zn = 108.884;
- double xxn = r / 95.0489;
- double yyn = g / 100.0;
- double zzn = b / 108.884;
- double fyyn = f(yyn);
-
- double Lstar = 116.0 * fyyn - 16.0;
- double aStar = 500.0 * (f(xxn) - fyyn);
- double bStar = 200.0 * (fyyn - f(zzn));
-
- r = Lstar;
- g = aStar;
- b = bStar;
- return;
- }
- }
-}
-
int Palette::findBestfit(int r, int g, int b, int a, int mask_index) const
{
ASSERT(r >= 0 && r <= 255);
ASSERT(g >= 0 && g <= 255);
ASSERT(b >= 0 && b <= 255);
ASSERT(a >= 0 && a <= 255);
+ ASSERT(!col_diff.empty());
- FitCriteria fc = FitCriteria::OLD;
-
- if (fc == FitCriteria::OLD) {
- ASSERT(!col_diff.empty());
-
- r >>= 3;
- g >>= 3;
- b >>= 3;
- a >>= 3;
-
- // Mask index is like alpha = 0, so we can use it as transparent color.
- if (a == 0 && mask_index >= 0)
- return mask_index;
-
- int bestfit = 0;
- int lowest = std::numeric_limits::max();
- int size = std::min(256, int(m_colors.size()));
-
- for (int i=0; i>3) - g) & 127];
- if (coldiff < lowest) {
- coldiff += col_diff_r[(((rgba_getr(rgb)>>3) - r) & 127)];
- if (coldiff < lowest) {
- coldiff += col_diff_b[(((rgba_getb(rgb)>>3) - b) & 127)];
- if (coldiff < lowest) {
- coldiff += col_diff_a[(((rgba_geta(rgb)>>3) - a) & 127)];
- if (coldiff < lowest && i != mask_index) {
- if (coldiff == 0)
- return i;
-
- bestfit = i;
- lowest = coldiff;
- }
- }
- }
- }
- }
-
- return bestfit;
- }
+ r >>= 3;
+ g >>= 3;
+ b >>= 3;
+ a >>= 3;
+ // Mask index is like alpha = 0, so we can use it as transparent color.
if (a == 0 && mask_index >= 0)
return mask_index;
int bestfit = 0;
- double lowest = std::numeric_limits::max();
- int size = m_colors.size();
- // Linearice:
- double x = double(r);
- double y = double(g);
- double z = double(b);
-
- rgbToOtherSpace(x, y, z, fc);
+ int lowest = std::numeric_limits::max();
+ int size = std::min(256, int(m_colors.size()));
for (int i=0; iXYZ and r,g,b is assumed CIE XYZ
- rgbToOtherSpace(Xpal, Ypal, Zpal, fc);
- double xDiff = x - Xpal;
- double yDiff = y - Ypal;
- double zDiff = z - Zpal;
- double aDiff = double(a - rgba_geta(rgb)) / 128.0;
- double diff = xDiff * xDiff + yDiff * yDiff + zDiff * zDiff + aDiff * aDiff;
- if (diff < lowest) {
- lowest = diff;
- bestfit = i;
+ int coldiff = col_diff_g[((rgba_getg(rgb)>>3) - g) & 127];
+ if (coldiff < lowest) {
+ coldiff += col_diff_r[(((rgba_getr(rgb)>>3) - r) & 127)];
+ if (coldiff < lowest) {
+ coldiff += col_diff_b[(((rgba_getb(rgb)>>3) - b) & 127)];
+ if (coldiff < lowest) {
+ coldiff += col_diff_a[(((rgba_geta(rgb)>>3) - a) & 127)];
+ if (coldiff < lowest && i != mask_index) {
+ if (coldiff == 0)
+ return i;
+
+ bestfit = i;
+ lowest = coldiff;
+ }
+ }
+ }
}
}
return bestfit;
diff --git a/src/doc/rgbmap.h b/src/doc/rgbmap.h
index ac1bf9fd0..faec5a38c 100644
--- a/src/doc/rgbmap.h
+++ b/src/doc/rgbmap.h
@@ -1,5 +1,5 @@
// Aseprite Document Library
-// Copyright (c) 2020-2022 Igara Studio S.A.
+// Copyright (c) 2020-2024 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -10,6 +10,8 @@
#include "base/debug.h"
#include "doc/color.h"
+#include "doc/fit_criteria.h"
+#include "doc/rgbmap_algorithm.h"
namespace doc {
@@ -20,13 +22,26 @@ namespace doc {
public:
virtual ~RgbMap() { }
- virtual void regenerateMap(const Palette* palette, const int maskIndex) = 0;
+ virtual void regenerateMap(const Palette* palette,
+ const int maskIndex,
+ const FitCriteria fitCriteria) = 0;
+
+ virtual void regenerateMap(const Palette* palette,
+ const int maskIndex) = 0;
// 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 maskIndex() const = 0;
+ virtual RgbMapAlgorithm rgbmapAlgorithm() const = 0;
+
+ virtual int modifications() const = 0;
+
+ // Color Best Fit Criteria used to generate the rgbmap
+ virtual FitCriteria fitCriteria() const = 0;
+ virtual void fitCriteria(const FitCriteria fitCriteria) = 0;
+
int mapColor(const int r,
const int g,
const int b,
diff --git a/src/doc/rgbmap_base.cpp b/src/doc/rgbmap_base.cpp
new file mode 100644
index 000000000..53d97256d
--- /dev/null
+++ b/src/doc/rgbmap_base.cpp
@@ -0,0 +1,131 @@
+// Aseprite Document Library
+// Copyright (c) 2024 Igara Studio S.A.
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "doc/rgbmap_base.h"
+
+#include
+
+namespace doc {
+
+// Auxiliary function for rgbToOtherSpace()
+double f(double t)
+{
+ if (t > 0.00885645171)
+ return std::pow(t, 0.3333333333333333);
+ else
+ return (t / 0.12841855 + 0.137931034);
+}
+
+// Auxiliary function for findBestfit()
+void RgbMapBase::rgbToOtherSpace(double& r, double& g, double& b) const
+{
+ if (m_fitCriteria == FitCriteria::RGB)
+ return;
+ double Rl, Gl, Bl;
+ // Linearization:
+ r = r / 255.0;
+ g = g / 255.0;
+ b = b / 255.0;
+ if (r <= 0.04045)
+ Rl = r / 12.92;
+ else
+ Rl = std::pow((r + 0.055) / 1.055, 2.4);
+ if (g <= 0.04045)
+ Gl = g / 12.92;
+ else
+ Gl = std::pow((g + 0.055) / 1.055, 2.4);
+ if (b <= 0.04045)
+ Bl = b / 12.92;
+ else
+ Bl = std::pow((b + 0.055) / 1.055, 2.4);
+ if (m_fitCriteria == FitCriteria::linearizedRGB) {
+ r = Rl;
+ g = Gl;
+ b = Bl;
+ return;
+ }
+ // Conversion lineal RGB to CIE XYZ
+ r = 41.24564*Rl + 35.75761 * Gl + 18.04375 * Bl;
+ g = 21.26729*Rl + 71.51522 * Gl + 7.2175 * Bl;
+ b = 1.93339*Rl + 11.91920 * Gl + 95.03041 * Bl;
+ switch (m_fitCriteria) {
+
+ case FitCriteria::CIEXYZ:
+ return;
+
+ case FitCriteria::CIELAB: {
+ // Converting CIEXYZ to CIELAB:
+ // For Standard Illuminant D65:
+ // const double xn = 95.0489;
+ // const double yn = 100.0;
+ // const double zn = 108.884;
+ double xxn = r / 95.0489;
+ double yyn = g / 100.0;
+ double zzn = b / 108.884;
+ double fyyn = f(yyn);
+
+ double Lstar = 116.0 * fyyn - 16.0;
+ double aStar = 500.0 * (f(xxn) - fyyn);
+ double bStar = 200.0 * (fyyn - f(zzn));
+
+ r = Lstar;
+ g = aStar;
+ b = bStar;
+ return;
+ }
+ }
+}
+
+int RgbMapBase::findBestfit(int r, int g, int b, int a,
+ int mask_index) const
+{
+ ASSERT(r >= 0 && r <= 255);
+ ASSERT(g >= 0 && g <= 255);
+ ASSERT(b >= 0 && b <= 255);
+ ASSERT(a >= 0 && a <= 255);
+
+ if (m_fitCriteria == FitCriteria::DEFAULT)
+ return m_palette->findBestfit(r, g, b, a, mask_index);
+
+ if (a == 0 && mask_index >= 0)
+ return mask_index;
+
+ int bestfit = 0;
+ double lowest = std::numeric_limits::max();
+ const int size = m_palette->size();
+ // Linearice:
+ double x = double(r);
+ double y = double(g);
+ double z = double(b);
+
+ rgbToOtherSpace(x, y, z);
+
+ for (int i=0; igetEntry(i);
+ double Xpal = double(rgba_getr(rgb));
+ double Ypal = double(rgba_getg(rgb));
+ double Zpal = double(rgba_getb(rgb));
+ // Palette color conversion RGB-->XYZ and r,g,b is assumed CIE XYZ
+ rgbToOtherSpace(Xpal, Ypal, Zpal);
+ const double xDiff = x - Xpal;
+ const double yDiff = y - Ypal;
+ const double zDiff = z - Zpal;
+ const double aDiff = double(a - rgba_geta(rgb)) / 128.0;
+
+ double diff = xDiff * xDiff + yDiff * yDiff + zDiff * zDiff + aDiff * aDiff;
+ if (diff < lowest) {
+ lowest = diff;
+ bestfit = i;
+ }
+ }
+ return bestfit;
+}
+
+} // namespace doc
diff --git a/src/doc/rgbmap_base.h b/src/doc/rgbmap_base.h
new file mode 100644
index 000000000..3004adc7d
--- /dev/null
+++ b/src/doc/rgbmap_base.h
@@ -0,0 +1,42 @@
+// Aseprite Document Library
+// Copyright (c) 2024 Igara Studio S.A.
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifndef DOC_RGBMAP_BASE_H_INCLUDED
+#define DOC_RGBMAP_BASE_H_INCLUDED
+#pragma once
+
+#include "doc/fit_criteria.h"
+#include "doc/palette.h"
+#include "doc/rgbmap.h"
+
+namespace doc {
+
+class RgbMapBase : public RgbMap {
+public:
+ int findBestfit(int r, int g, int b, int a,
+ int mask_index) const;
+
+ // RgbMap impl
+ int modifications() const override { return m_modifications; }
+ int maskIndex() const override { return m_maskIndex; }
+ FitCriteria fitCriteria() const override { return m_fitCriteria; }
+ void fitCriteria(const FitCriteria fitCriteria) override {
+ m_fitCriteria = fitCriteria;
+ }
+
+private:
+ void rgbToOtherSpace(double& r, double& g, double& b) const;
+
+protected:
+ FitCriteria m_fitCriteria;
+ const Palette* m_palette = nullptr;
+ int m_modifications = 0;
+ int m_maskIndex = 0;
+};
+
+} // namespace doc
+
+#endif
diff --git a/src/doc/rgbmap_rgb5a3.cpp b/src/doc/rgbmap_rgb5a3.cpp
index 5f0ec2653..89482f1b5 100644
--- a/src/doc/rgbmap_rgb5a3.cpp
+++ b/src/doc/rgbmap_rgb5a3.cpp
@@ -1,5 +1,5 @@
// Aseprite Document Library
-// Copyright (c) 2020 Igara Studio S.A.
+// Copyright (c) 2020-2024 Igara Studio S.A.
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
@@ -22,23 +22,21 @@ namespace doc {
#define ASIZE 8
#define MAPSIZE (RSIZE*GSIZE*BSIZE*ASIZE)
-RgbMapRGB5A3::RgbMapRGB5A3()
- : m_map(MAPSIZE)
- , m_palette(nullptr)
- , m_modifications(0)
- , m_maskIndex(0)
-{
-}
+RgbMapRGB5A3::RgbMapRGB5A3() : m_map(MAPSIZE) {}
-void RgbMapRGB5A3::regenerateMap(const Palette* palette, int maskIndex)
+void RgbMapRGB5A3::regenerateMap(const Palette* palette,
+ const int maskIndex,
+ const FitCriteria fitCriteria)
{
// Skip useless regenerations
if (m_palette == palette &&
m_modifications == palette->getModifications() &&
- m_maskIndex == maskIndex)
+ m_maskIndex == maskIndex &&
+ m_fitCriteria == fitCriteria)
return;
m_palette = palette;
+ m_fitCriteria = fitCriteria;
m_modifications = palette->getModifications();
m_maskIndex = maskIndex;
@@ -50,7 +48,7 @@ void RgbMapRGB5A3::regenerateMap(const Palette* palette, int maskIndex)
int RgbMapRGB5A3::generateEntry(int i, int r, int g, int b, int a) const
{
return m_map[i] =
- m_palette->findBestfit(
+ findBestfit(
scale_5bits_to_8bits(r>>3),
scale_5bits_to_8bits(g>>3),
scale_5bits_to_8bits(b>>3),
diff --git a/src/doc/rgbmap_rgb5a3.h b/src/doc/rgbmap_rgb5a3.h
index e17ab4ab3..0ff72e8bd 100644
--- a/src/doc/rgbmap_rgb5a3.h
+++ b/src/doc/rgbmap_rgb5a3.h
@@ -13,7 +13,8 @@
#include "base/disable_copying.h"
#include "base/ints.h"
#include "doc/object.h"
-#include "doc/rgbmap.h"
+#include "doc/palette.h"
+#include "doc/rgbmap_base.h"
#include
@@ -22,7 +23,7 @@ namespace doc {
class Palette;
// It acts like a cache for Palette:findBestfit() calls.
- class RgbMapRGB5A3 : public RgbMap {
+ class RgbMapRGB5A3 : public RgbMapBase {
// Bit activated on m_map entries that aren't yet calculated.
const uint16_t INVALID = 256;
@@ -30,7 +31,14 @@ namespace doc {
RgbMapRGB5A3();
// RgbMap impl
- void regenerateMap(const Palette* palette, int maskIndex) override;
+ void regenerateMap(const Palette* palette,
+ const int maskIndex,
+ const FitCriteria fitCriteria) override;
+ void regenerateMap(const Palette* palette,
+ const int maskIndex) override {
+ regenerateMap(palette, maskIndex, m_fitCriteria);
+ }
+
int mapColor(const color_t rgba) const override {
const uint8_t r = rgba_getr(rgba);
const uint8_t g = rgba_getg(rgba);
@@ -42,15 +50,14 @@ namespace doc {
return (v & INVALID) ? generateEntry(i, r, g, b, a): v;
}
- int maskIndex() const override { return m_maskIndex; }
+ RgbMapAlgorithm rgbmapAlgorithm() const override {
+ return RgbMapAlgorithm::RGB5A3;
+ }
private:
int generateEntry(int i, int r, int g, int b, int a) const;
mutable std::vector m_map;
- const Palette* m_palette;
- int m_modifications;
- int m_maskIndex;
DISABLE_COPYING(RgbMapRGB5A3);
};
diff --git a/src/doc/sprite.cpp b/src/doc/sprite.cpp
index 81842e2ea..c555a8510 100644
--- a/src/doc/sprite.cpp
+++ b/src/doc/sprite.cpp
@@ -440,18 +440,24 @@ RgbMap* Sprite::rgbMap(const frame_t frame) const
RgbMap* Sprite::rgbMap(const frame_t frame,
const RgbMapFor forLayer) const
{
- return rgbMap(frame,
- forLayer,
- g_rgbMapAlgorithm);
+ FitCriteria fc = FitCriteria::DEFAULT;
+ RgbMapAlgorithm algo = g_rgbMapAlgorithm;
+ if (m_rgbMap) {
+ fc = m_rgbMap->fitCriteria();
+ algo = m_rgbMap->rgbmapAlgorithm();
+ }
+ return rgbMap(frame, forLayer, algo, fc);
}
RgbMap* Sprite::rgbMap(const frame_t frame,
const RgbMapFor forLayer,
- RgbMapAlgorithm mapAlgo) const
+ const RgbMapAlgorithm mapAlgo,
+ const FitCriteria fitCriteria) const
{
- if (!m_rgbMap || m_rgbMapAlgorithm != mapAlgo) {
- m_rgbMapAlgorithm = mapAlgo;
- switch (m_rgbMapAlgorithm) {
+ if (!m_rgbMap ||
+ m_rgbMap->rgbmapAlgorithm() != mapAlgo ||
+ m_rgbMap->fitCriteria() != fitCriteria) {
+ switch (mapAlgo) {
case RgbMapAlgorithm::RGB5A3: m_rgbMap.reset(new RgbMapRGB5A3); break;
case RgbMapAlgorithm::DEFAULT:
case RgbMapAlgorithm::OCTREE: m_rgbMap.reset(new OctreeMap); break;
@@ -460,6 +466,7 @@ RgbMap* Sprite::rgbMap(const frame_t frame,
ASSERT(false);
return nullptr;
}
+ m_rgbMap->fitCriteria(fitCriteria);
}
int maskIndex;
if (forLayer == RgbMapFor::OpaqueLayer)
@@ -469,7 +476,7 @@ RgbMap* Sprite::rgbMap(const frame_t frame,
if (maskIndex == -1)
maskIndex = 0;
}
- m_rgbMap->regenerateMap(palette(frame), maskIndex);
+ m_rgbMap->regenerateMap(palette(frame), maskIndex, fitCriteria);
return m_rgbMap.get();
}
diff --git a/src/doc/sprite.h b/src/doc/sprite.h
index 039cb833f..f0cf9731b 100644
--- a/src/doc/sprite.h
+++ b/src/doc/sprite.h
@@ -13,6 +13,7 @@
#include "doc/cel_data.h"
#include "doc/cel_list.h"
#include "doc/color.h"
+#include "doc/fit_criteria.h"
#include "doc/frame.h"
#include "doc/image_buffer.h"
#include "doc/image_ref.h"
@@ -167,7 +168,8 @@ namespace doc {
const RgbMapFor forLayer) const;
RgbMap* rgbMap(const frame_t frame,
const RgbMapFor forLayer,
- RgbMapAlgorithm mapAlgo) const;
+ const RgbMapAlgorithm mapAlgo,
+ const FitCriteria fitCriteria = FitCriteria::DEFAULT) const;
////////////////////////////////////////
// Frames
@@ -263,7 +265,6 @@ namespace doc {
gfx::Rect m_gridBounds; // grid settings
// Current rgb map
- mutable RgbMapAlgorithm m_rgbMapAlgorithm;
mutable std::unique_ptr m_rgbMap;
Tags m_tags;