Add different best fit criteria to compare colors for RgbMaps (fix #2787, #4416)

This commit is contained in:
Gaspar Capello 2024-04-26 08:58:36 -03:00 committed by David Capello
parent e12233a19d
commit 3cc1c63274
24 changed files with 476 additions and 228 deletions

View File

@ -321,6 +321,7 @@
<option id="to_gray" type="ToGrayAlgorithm" default="ToGrayAlgorithm::DEFAULT" /> <option id="to_gray" type="ToGrayAlgorithm" default="ToGrayAlgorithm::DEFAULT" />
<option id="advanced" type="bool" default="false" /> <option id="advanced" type="bool" default="false" />
<option id="rgbmap_algorithm" type="doc::RgbMapAlgorithm" default="doc::RgbMapAlgorithm::DEFAULT" /> <option id="rgbmap_algorithm" type="doc::RgbMapAlgorithm" default="doc::RgbMapAlgorithm::DEFAULT" />
<option id="fit_criteria" type="doc::FitCriteria" default="doc::FitCriteria::DEFAULT" />
</section> </section>
<section id="eyedropper" text="Editor"> <section id="eyedropper" text="Editor">
<option id="channel" type="EyedropperChannel" default="EyedropperChannel::COLOR_ALPHA" /> <option id="channel" type="EyedropperChannel" default="EyedropperChannel::COLOR_ALPHA" />

View File

@ -1255,6 +1255,14 @@ default = Default (Octree)
rgb5a3 = Table RGB 5 bits + Alpha 3 bits rgb5a3 = Table RGB 5 bits + Alpha 3 bits
octree = Octree 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] [open_file]
title = Open title = Open
loading = Loading file loading = Loading file

View File

@ -1,5 +1,5 @@
<!-- Aseprite --> <!-- Aseprite -->
<!-- Copyright (C) 2019-2020 Igara Studio S.A. --> <!-- Copyright (C) 2019-2024 Igara Studio S.A. -->
<!-- Copyright (C) 2017 David Capello --> <!-- Copyright (C) 2017 David Capello -->
<gui> <gui>
<window id="color_mode" text="@.title"> <window id="color_mode" text="@.title">
@ -23,10 +23,12 @@
<check text="@.flatten" id="flatten" /> <check text="@.flatten" id="flatten" />
<check id="advanced_check" text="@general.advanced_options" cell_hspan="2" /> <check id="advanced_check" text="@general.advanced_options" cell_hspan="2" />
<hbox id="advanced" cell_hspan="2"> <vbox id="advanced" cell_hspan="2">
<label text="@rgbmap_algorithm_selector.label" /> <label text="@rgbmap_algorithm_selector.label" cell_hspan="1" />
<hbox id="rgbmap_algorithm_placeholder" /> <hbox id="rgbmap_algorithm_placeholder" cell_hspan="1" />
</hbox> <label text="@best_fit_criteria_selector.label" cell_hspan="1" />
<hbox id="best_fit_criteria_placeholder" cell_hspan="1" />
</vbox>
<separator horizontal="true" /> <separator horizontal="true" />
<hbox> <hbox>

View File

@ -587,10 +587,12 @@
<slider id="nonactive_layers_opacity" min="0" max="255" width="128" /> <slider id="nonactive_layers_opacity" min="0" max="255" width="128" />
</hbox> </hbox>
<separator text="@.color_quantization" horizontal="true" /> <separator text="@.color_quantization" horizontal="true" />
<hbox> <grid columns="2">
<label text="@rgbmap_algorithm_selector.label" /> <label text="@rgbmap_algorithm_selector.label" />
<hbox id="rgbmap_algorithm_placeholder" /> <hbox id="rgbmap_algorithm_placeholder" />
</hbox> <label text="@best_fit_criteria_selector.label" />
<hbox id="best_fit_criteria_placeholder" />
</grid>
<separator text="@.performance" horizontal="true" /> <separator text="@.performance" horizontal="true" />
<hbox> <hbox>
<check id="shaders_for_color_selectors" <check id="shaders_for_color_selectors"

View File

@ -579,6 +579,7 @@ target_sources(app-lib PRIVATE
ui/alpha_slider.cpp ui/alpha_slider.cpp
ui/app_menuitem.cpp ui/app_menuitem.cpp
ui/backup_indicator.cpp ui/backup_indicator.cpp
ui/best_fit_criteria_selector.cpp
ui/browser_view.cpp ui/browser_view.cpp
ui/brush_popup.cpp ui/brush_popup.cpp
ui/button_set.cpp ui/button_set.cpp

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2023 Igara Studio S.A. // Copyright (C) 2019-2024 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
@ -73,7 +73,8 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
const render::Dithering& dithering, const render::Dithering& dithering,
const doc::RgbMapAlgorithm mapAlgorithm, const doc::RgbMapAlgorithm mapAlgorithm,
doc::rgba_to_graya_func toGray, doc::rgba_to_graya_func toGray,
render::TaskDelegate* delegate) render::TaskDelegate* delegate,
const FitCriteria fitCriteria)
: WithSprite(sprite) : WithSprite(sprite)
, m_oldFormat(sprite->pixelFormat()) , m_oldFormat(sprite->pixelFormat())
, m_newFormat(newFormat) , m_newFormat(newFormat)
@ -108,7 +109,8 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
cel->layer()->isBackground(), cel->layer()->isBackground(),
mapAlgorithm, mapAlgorithm,
toGray, toGray,
&superDel); &superDel,
fitCriteria);
superDel.nextImage(); superDel.nextImage();
} }
@ -208,7 +210,8 @@ void SetPixelFormat::convertImage(doc::Sprite* sprite,
const bool isBackground, const bool isBackground,
const doc::RgbMapAlgorithm mapAlgorithm, const doc::RgbMapAlgorithm mapAlgorithm,
doc::rgba_to_graya_func toGray, doc::rgba_to_graya_func toGray,
render::TaskDelegate* delegate) render::TaskDelegate* delegate,
const doc::FitCriteria fitCriteria)
{ {
ASSERT(oldImage); ASSERT(oldImage);
ASSERT(oldImage->pixelFormat() != IMAGE_TILEMAP); ASSERT(oldImage->pixelFormat() != IMAGE_TILEMAP);
@ -218,7 +221,10 @@ void SetPixelFormat::convertImage(doc::Sprite* sprite,
RgbMap* rgbmap; RgbMap* rgbmap;
int newMaskIndex = (isBackground ? -1 : 0); int newMaskIndex = (isBackground ? -1 : 0);
if (m_newFormat == IMAGE_INDEXED) { 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) if (m_oldFormat == IMAGE_INDEXED)
newMaskIndex = sprite->transparentColor(); newMaskIndex = sprite->transparentColor();
else else

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -12,6 +12,7 @@
#include "app/cmd/with_sprite.h" #include "app/cmd/with_sprite.h"
#include "app/cmd_sequence.h" #include "app/cmd_sequence.h"
#include "doc/color.h" #include "doc/color.h"
#include "doc/fit_criteria.h"
#include "doc/frame.h" #include "doc/frame.h"
#include "doc/image_ref.h" #include "doc/image_ref.h"
#include "doc/pixel_format.h" #include "doc/pixel_format.h"
@ -37,7 +38,8 @@ namespace cmd {
const render::Dithering& dithering, const render::Dithering& dithering,
const doc::RgbMapAlgorithm mapAlgorithm, const doc::RgbMapAlgorithm mapAlgorithm,
doc::rgba_to_graya_func toGray, doc::rgba_to_graya_func toGray,
render::TaskDelegate* delegate); render::TaskDelegate* delegate,
const doc::FitCriteria fitCriteria = doc::FitCriteria::DEFAULT);
protected: protected:
void onExecute() override; void onExecute() override;
@ -56,7 +58,8 @@ namespace cmd {
const bool isBackground, const bool isBackground,
const doc::RgbMapAlgorithm mapAlgorithm, const doc::RgbMapAlgorithm mapAlgorithm,
doc::rgba_to_graya_func toGray, 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_oldFormat;
doc::PixelFormat m_newFormat; doc::PixelFormat m_newFormat;

View File

@ -23,6 +23,7 @@
#include "app/modules/palettes.h" #include "app/modules/palettes.h"
#include "app/sprite_job.h" #include "app/sprite_job.h"
#include "app/transaction.h" #include "app/transaction.h"
#include "app/ui/best_fit_criteria_selector.h"
#include "app/ui/dithering_selector.h" #include "app/ui/dithering_selector.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "app/ui/editor/editor_render.h" #include "app/ui/editor/editor_render.h"
@ -69,7 +70,6 @@ public:
const doc::frame_t frame, const doc::frame_t frame,
const doc::PixelFormat pixelFormat, const doc::PixelFormat pixelFormat,
const render::Dithering& dithering, const render::Dithering& dithering,
const doc::RgbMapAlgorithm rgbMapAlgorithm,
const gen::ToGrayAlgorithm toGray, const gen::ToGrayAlgorithm toGray,
const gfx::Point& pos, const gfx::Point& pos,
const bool newBlend) const bool newBlend)
@ -83,13 +83,11 @@ public:
sprite, frame, sprite, frame,
pixelFormat, pixelFormat,
dithering, dithering,
rgbMapAlgorithm,
toGray, toGray,
newBlend]() { // Copy the matrix newBlend]() { // Copy the matrix
run(sprite, frame, run(sprite, frame,
pixelFormat, pixelFormat,
dithering, dithering,
rgbMapAlgorithm,
toGray, toGray,
newBlend); newBlend);
}) })
@ -114,7 +112,6 @@ private:
const doc::frame_t frame, const doc::frame_t frame,
const doc::PixelFormat pixelFormat, const doc::PixelFormat pixelFormat,
const render::Dithering& dithering, const render::Dithering& dithering,
const doc::RgbMapAlgorithm rgbMapAlgorithm,
const gen::ToGrayAlgorithm toGray, const gen::ToGrayAlgorithm toGray,
const bool newBlend) { const bool newBlend) {
doc::ImageRef tmp( doc::ImageRef tmp(
@ -136,9 +133,7 @@ private:
m_image.get(), m_image.get(),
pixelFormat, pixelFormat,
dithering, dithering,
sprite->rgbMap(frame, sprite->rgbMap(frame),
sprite->rgbMapForSprite(),
rgbMapAlgorithm),
sprite->palette(frame), sprite->palette(frame),
(sprite->backgroundLayer() != nullptr), (sprite->backgroundLayer() != nullptr),
0, 0,
@ -193,6 +188,7 @@ public:
, m_selectedItem(nullptr) , m_selectedItem(nullptr)
, m_ditheringSelector(nullptr) , m_ditheringSelector(nullptr)
, m_mapAlgorithmSelector(nullptr) , m_mapAlgorithmSelector(nullptr)
, m_bestFitCriteriaSelector(nullptr)
, m_imageJustCreated(true) , m_imageJustCreated(true)
{ {
const auto& pref = Preferences::instance(); const auto& pref = Preferences::instance();
@ -219,6 +215,9 @@ public:
m_mapAlgorithmSelector = new RgbMapAlgorithmSelector; m_mapAlgorithmSelector = new RgbMapAlgorithmSelector;
m_mapAlgorithmSelector->setExpansive(true); m_mapAlgorithmSelector->setExpansive(true);
m_bestFitCriteriaSelector = new BestFitCriteriaSelector;
m_bestFitCriteriaSelector->setExpansive(true);
// Select default dithering method // Select default dithering method
{ {
int index = m_ditheringSelector->findItemIndex( int index = m_ditheringSelector->findItemIndex(
@ -230,8 +229,12 @@ public:
// Select default RgbMap algorithm // Select default RgbMap algorithm
m_mapAlgorithmSelector->algorithm(pref.quantization.rgbmapAlgorithm()); m_mapAlgorithmSelector->algorithm(pref.quantization.rgbmapAlgorithm());
// Select default best fit criteria
m_bestFitCriteriaSelector->criteria(pref.quantization.fitCriteria());
ditheringPlaceholder()->addChild(m_ditheringSelector); ditheringPlaceholder()->addChild(m_ditheringSelector);
rgbmapAlgorithmPlaceholder()->addChild(m_mapAlgorithmSelector); rgbmapAlgorithmPlaceholder()->addChild(m_mapAlgorithmSelector);
bestFitCriteriaPlaceholder()->addChild(m_bestFitCriteriaSelector);
const bool adv = pref.quantization.advanced(); const bool adv = pref.quantization.advanced();
advancedCheck()->setSelected(adv); advancedCheck()->setSelected(adv);
@ -240,6 +243,7 @@ public:
// Signals // Signals
m_ditheringSelector->Change.connect([this]{ onIndexParamChange(); }); m_ditheringSelector->Change.connect([this]{ onIndexParamChange(); });
m_mapAlgorithmSelector->Change.connect([this]{ onIndexParamChange(); }); m_mapAlgorithmSelector->Change.connect([this]{ onIndexParamChange(); });
m_bestFitCriteriaSelector->Change.connect([this]{ onIndexParamChange(); });
factor()->Change.connect([this]{ onIndexParamChange(); }); factor()->Change.connect([this]{ onIndexParamChange(); });
advancedCheck()->Click.connect( advancedCheck()->Click.connect(
@ -301,6 +305,13 @@ public:
return doc::RgbMapAlgorithm::DEFAULT; return doc::RgbMapAlgorithm::DEFAULT;
} }
doc::FitCriteria fitCriteria() const {
if (m_bestFitCriteriaSelector)
return m_bestFitCriteriaSelector->criteria();
else
return doc::FitCriteria::DEFAULT;
}
gen::ToGrayAlgorithm toGray() const { gen::ToGrayAlgorithm toGray() const {
static_assert( static_assert(
int(gen::ToGrayAlgorithm::LUMA) == 0 && int(gen::ToGrayAlgorithm::LUMA) == 0 &&
@ -331,7 +342,7 @@ public:
} }
} }
if (m_mapAlgorithmSelector) if (m_mapAlgorithmSelector || m_bestFitCriteriaSelector)
pref.quantization.advanced(advancedCheck()->isSelected()); pref.quantization.advanced(advancedCheck()->isSelected());
} }
@ -396,6 +407,12 @@ private:
visibleBounds.origin(), visibleBounds.origin(),
doc::BlendMode::SRC); doc::BlendMode::SRC);
m_editor->sprite()->rgbMap(
0,
m_editor->sprite()->rgbMapForSprite(),
rgbMapAlgorithm(),
fitCriteria());
m_editor->invalidate(); m_editor->invalidate();
progress()->setValue(0); progress()->setValue(0);
progress()->setVisible(false); progress()->setVisible(false);
@ -408,7 +425,6 @@ private:
m_editor->frame(), m_editor->frame(),
dstPixelFormat, dstPixelFormat,
dithering(), dithering(),
rgbMapAlgorithm(),
toGray(), toGray(),
visibleBounds.origin(), visibleBounds.origin(),
Preferences::instance().experimental.newBlend())); Preferences::instance().experimental.newBlend()));
@ -463,6 +479,7 @@ private:
ConversionItem* m_selectedItem; ConversionItem* m_selectedItem;
DitheringSelector* m_ditheringSelector; DitheringSelector* m_ditheringSelector;
RgbMapAlgorithmSelector* m_mapAlgorithmSelector; RgbMapAlgorithmSelector* m_mapAlgorithmSelector;
BestFitCriteriaSelector* m_bestFitCriteriaSelector;
bool m_imageJustCreated; bool m_imageJustCreated;
}; };
@ -485,6 +502,7 @@ private:
doc::PixelFormat m_format; doc::PixelFormat m_format;
render::Dithering m_dithering; render::Dithering m_dithering;
doc::RgbMapAlgorithm m_rgbmap; doc::RgbMapAlgorithm m_rgbmap;
doc::FitCriteria m_fitCriteria = FitCriteria::DEFAULT;
gen::ToGrayAlgorithm m_toGray; gen::ToGrayAlgorithm m_toGray;
}; };
@ -624,7 +642,10 @@ void ChangePixelFormatCommand::onExecute(Context* context)
{ {
bool flatten = false; bool flatten = false;
if (context->isUIAvailable() && m_showDlg) { if (!context->isUIAvailable()) {
// do nothing
}
else if (m_showDlg) {
ColorModeWindow window(Editor::activeEditor()); ColorModeWindow window(Editor::activeEditor());
window.remapWindow(); window.remapWindow();
@ -640,11 +661,17 @@ void ChangePixelFormatCommand::onExecute(Context* context)
m_format = window.pixelFormat(); m_format = window.pixelFormat();
m_dithering = window.dithering(); m_dithering = window.dithering();
m_rgbmap = window.rgbMapAlgorithm(); m_rgbmap = window.rgbMapAlgorithm();
m_fitCriteria = window.fitCriteria();
m_toGray = window.toGray(); m_toGray = window.toGray();
flatten = window.flattenEnabled(); flatten = window.flattenEnabled();
window.saveOptions(); 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 // No conversion needed
Doc* doc = context->activeDocument(); Doc* doc = context->activeDocument();
@ -679,7 +706,8 @@ void ChangePixelFormatCommand::onExecute(Context* context)
m_dithering, m_dithering,
m_rgbmap, m_rgbmap,
get_gray_func(m_toGray), get_gray_func(m_toGray),
&job)); // SpriteJob is a render::TaskDelegate &job,
m_fitCriteria)); // SpriteJob is a render::TaskDelegate
}); });
job.waitJob(); job.waitJob();
} }

View File

@ -26,6 +26,7 @@
#include "app/recent_files.h" #include "app/recent_files.h"
#include "app/resource_finder.h" #include "app/resource_finder.h"
#include "app/tx.h" #include "app/tx.h"
#include "app/ui/best_fit_criteria_selector.h"
#include "app/ui/color_button.h" #include "app/ui/color_button.h"
#include "app/ui/main_window.h" #include "app/ui/main_window.h"
#include "app/ui/pref_widget.h" #include "app/ui/pref_widget.h"
@ -478,6 +479,10 @@ public:
m_rgbmapAlgorithmSelector.setExpansive(true); m_rgbmapAlgorithmSelector.setExpansive(true);
m_rgbmapAlgorithmSelector.algorithm(m_pref.quantization.rgbmapAlgorithm()); 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()) if (m_pref.editor.showScrollbars())
showScrollbars()->setSelected(true); showScrollbars()->setSelected(true);
@ -828,6 +833,7 @@ public:
m_pref.experimental.flashLayer(flashLayer()->isSelected()); m_pref.experimental.flashLayer(flashLayer()->isSelected());
m_pref.experimental.nonactiveLayersOpacity(nonactiveLayersOpacity()->getValue()); m_pref.experimental.nonactiveLayersOpacity(nonactiveLayersOpacity()->getValue());
m_pref.quantization.rgbmapAlgorithm(m_rgbmapAlgorithmSelector.algorithm()); m_pref.quantization.rgbmapAlgorithm(m_rgbmapAlgorithmSelector.algorithm());
m_pref.quantization.fitCriteria(m_bestFitCriteriaSelector.criteria());
#ifdef LAF_WINDOWS #ifdef LAF_WINDOWS
{ {
@ -1842,6 +1848,7 @@ private:
std::vector<os::ColorSpaceRef> m_colorSpaces; std::vector<os::ColorSpaceRef> m_colorSpaces;
std::string m_templateTextForDisplayCS; std::string m_templateTextForDisplayCS;
RgbMapAlgorithmSelector m_rgbmapAlgorithmSelector; RgbMapAlgorithmSelector m_rgbmapAlgorithmSelector;
BestFitCriteriaSelector m_bestFitCriteriaSelector;
ButtonSet* m_themeVars = nullptr; ButtonSet* m_themeVars = nullptr;
SamplingSelector* m_samplingSelector = nullptr; SamplingSelector* m_samplingSelector = nullptr;
}; };

View File

@ -342,6 +342,7 @@ FOR_ENUM(app::tools::RotationAlgorithm)
FOR_ENUM(doc::AniDir) FOR_ENUM(doc::AniDir)
FOR_ENUM(doc::BrushPattern) FOR_ENUM(doc::BrushPattern)
FOR_ENUM(doc::ColorMode) FOR_ENUM(doc::ColorMode)
FOR_ENUM(doc::FitCriteria)
FOR_ENUM(doc::RgbMapAlgorithm) FOR_ENUM(doc::RgbMapAlgorithm)
FOR_ENUM(filters::HueSaturationFilter::Mode) FOR_ENUM(filters::HueSaturationFilter::Mode)
FOR_ENUM(filters::TiledMode) FOR_ENUM(filters::TiledMode)

View File

@ -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

View File

@ -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

View File

@ -61,6 +61,7 @@ add_library(doc-lib
primitives.cpp primitives.cpp
remap.cpp remap.cpp
render_plan.cpp render_plan.cpp
rgbmap_base.cpp
rgbmap_rgb5a3.cpp rgbmap_rgb5a3.cpp
selected_frames.cpp selected_frames.cpp
selected_layers.cpp selected_layers.cpp

23
src/doc/fit_criteria.h Normal file
View File

@ -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

View File

@ -1,5 +1,5 @@
// Aseprite // 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. // This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information. // 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); (*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 // 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. // color of the current palette is the bestfit and memorize the index in a octree leaf.
if (level >= 8) { if (level >= 8) {
if (m_paletteIndex == -1) 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; return m_paletteIndex;
} }
int index = getHextet(r, g, b, a, level); int index = getHextet(r, g, b, a, level);
if (!m_children) if (!m_children)
m_children.reset(new std::array<OctreeNode, 16>()); m_children.reset(new std::array<OctreeNode, 16>());
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) void OctreeNode::collectLeafNodes(OctreeNodes& leavesVector, int& paletteIndex)
@ -268,10 +270,13 @@ int OctreeMap::mapColor(color_t rgba) const
rgba_getb(rgba), rgba_getb(rgba),
rgba_geta(rgba), rgba_geta(rgba),
m_maskIndex, 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); ASSERT(palette);
if (!palette) if (!palette)
@ -280,9 +285,12 @@ void OctreeMap::regenerateMap(const Palette* palette, const int maskIndex)
// Skip useless regenerations // Skip useless regenerations
if (m_palette == palette && if (m_palette == palette &&
m_modifications == palette->getModifications() && m_modifications == palette->getModifications() &&
m_maskIndex == maskIndex) m_maskIndex == maskIndex &&
m_fitCriteria == fitCriteria)
return; return;
m_palette = palette;
m_fitCriteria = fitCriteria;
m_root = OctreeNode(); m_root = OctreeNode();
m_leavesVector.clear(); m_leavesVector.clear();
m_maskIndex = maskIndex; m_maskIndex = maskIndex;
@ -293,7 +301,7 @@ void OctreeMap::regenerateMap(const Palette* palette, const int maskIndex)
} }
else { else {
m_maskColor = palette->getEntry(maskIndex); m_maskColor = palette->getEntry(maskIndex);
maskColorBestFitIndex = palette->findBestfit(rgba_getr(m_maskColor), maskColorBestFitIndex = findBestfit(rgba_getr(m_maskColor),
rgba_getg(m_maskColor), rgba_getg(m_maskColor),
rgba_getb(m_maskColor), rgba_getb(m_maskColor),
rgba_geta(m_maskColor), maskIndex); rgba_geta(m_maskColor), maskIndex);
@ -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_root.addColor(palette->entry(i), 0, &m_root, i, 8);
} }
m_palette = palette;
m_modifications = palette->getModifications(); m_modifications = palette->getModifications();
} }

View File

@ -1,5 +1,5 @@
// Aseprite // 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. // This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information. // Read LICENSE.txt for more information.
@ -11,7 +11,7 @@
#include "doc/color.h" #include "doc/color.h"
#include "doc/image_impl.h" #include "doc/image_impl.h"
#include "doc/palette.h" #include "doc/palette.h"
#include "doc/rgbmap.h" #include "doc/rgbmap_base.h"
#include <array> #include <array>
#include <memory> #include <memory>
@ -26,6 +26,8 @@
namespace doc { namespace doc {
class OctreeNode; class OctreeNode;
class OctreeMap;
using OctreeNodes = std::vector<OctreeNode*>; using OctreeNodes = std::vector<OctreeNode*>;
class OctreeNode { class OctreeNode {
@ -93,7 +95,9 @@ 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);
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); void collectLeafNodes(OctreeNodes& leavesVector, int& paletteIndex);
@ -117,7 +121,7 @@ private:
OctreeNode* m_parent = nullptr; OctreeNode* m_parent = nullptr;
}; };
class OctreeMap : public RgbMap { class OctreeMap : public RgbMapBase {
public: public:
void addColor(color_t color, int levelDeep = 7) { void addColor(color_t color, int levelDeep = 7) {
m_root.addColor(color, 0, &m_root, 0, levelDeep); m_root.addColor(color, 0, &m_root, 0, levelDeep);
@ -135,27 +139,23 @@ public:
const int levelDeep = 7); const int levelDeep = 7);
// RgbMap impl // RgbMap impl
void regenerateMap(const Palette* palette, const int maskIndex) override; void regenerateMap(const Palette* palette,
int mapColor(color_t rgba) const override; const int maskIndex,
int maskIndex() const override { return m_maskIndex; } const FitCriteria fitCriteria) override;
int mapColor(const int r, const int g, void regenerateMap(const Palette* palette,
const int b, const int a) const const int maskIndex) override {
{ regenerateMap(palette, maskIndex, m_fitCriteria);
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 mapColor(color_t rgba) const override;
RgbMapAlgorithm rgbmapAlgorithm() const override {
return RgbMapAlgorithm::OCTREE;
}
private: private:
OctreeNode m_root; OctreeNode m_root;
OctreeNodes m_leavesVector; OctreeNodes m_leavesVector;
const Palette* m_palette = nullptr;
int m_modifications = 0;
int m_maskIndex = 0;
color_t m_maskColor = 0; color_t m_maskColor = 0;
}; };

View File

@ -1,5 +1,5 @@
// Aseprite Document Library // 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 // Copyright (c) 2001-2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -26,14 +26,6 @@ namespace doc {
using namespace gfx; using namespace gfx;
enum class FitCriteria {
OLD,
RGB,
linearizedRGB,
CIEXYZ,
CIELAB
};
Palette::Palette() Palette::Palette()
: Palette(0, 256) : Palette(0, 256)
{ {
@ -346,85 +338,12 @@ 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 int Palette::findBestfit(int r, int g, int b, int a, int mask_index) const
{ {
ASSERT(r >= 0 && r <= 255); ASSERT(r >= 0 && r <= 255);
ASSERT(g >= 0 && g <= 255); ASSERT(g >= 0 && g <= 255);
ASSERT(b >= 0 && b <= 255); ASSERT(b >= 0 && b <= 255);
ASSERT(a >= 0 && a <= 255); ASSERT(a >= 0 && a <= 255);
FitCriteria fc = FitCriteria::OLD;
if (fc == FitCriteria::OLD) {
ASSERT(!col_diff.empty()); ASSERT(!col_diff.empty());
r >>= 3; r >>= 3;
@ -461,41 +380,6 @@ int Palette::findBestfit(int r, int g, int b, int a, int mask_index) const
} }
} }
} }
return bestfit;
}
if (a == 0 && mask_index >= 0)
return mask_index;
int bestfit = 0;
double lowest = std::numeric_limits<double>::max();
int size = m_colors.size();
// Linearice:
double x = double(r);
double y = double(g);
double z = double(b);
rgbToOtherSpace(x, y, z, fc);
for (int i=0; i<size; ++i) {
color_t rgb = m_colors[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, 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;
}
}
return bestfit; return bestfit;
} }

View File

@ -1,5 +1,5 @@
// Aseprite Document Library // 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. // This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information. // Read LICENSE.txt for more information.
@ -10,6 +10,8 @@
#include "base/debug.h" #include "base/debug.h"
#include "doc/color.h" #include "doc/color.h"
#include "doc/fit_criteria.h"
#include "doc/rgbmap_algorithm.h"
namespace doc { namespace doc {
@ -20,13 +22,26 @@ namespace doc {
public: public:
virtual ~RgbMap() { } 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. // 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; 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, int mapColor(const int r,
const int g, const int g,
const int b, const int b,

131
src/doc/rgbmap_base.cpp Normal file
View File

@ -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 <cmath>
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<double>::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; i<size; ++i) {
color_t rgb = m_palette->getEntry(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

42
src/doc/rgbmap_base.h Normal file
View File

@ -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

View File

@ -1,5 +1,5 @@
// Aseprite Document Library // Aseprite Document Library
// Copyright (c) 2020 Igara Studio S.A. // Copyright (c) 2020-2024 Igara Studio S.A.
// Copyright (c) 2001-2015 David Capello // Copyright (c) 2001-2015 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -22,23 +22,21 @@ namespace doc {
#define ASIZE 8 #define ASIZE 8
#define MAPSIZE (RSIZE*GSIZE*BSIZE*ASIZE) #define MAPSIZE (RSIZE*GSIZE*BSIZE*ASIZE)
RgbMapRGB5A3::RgbMapRGB5A3() RgbMapRGB5A3::RgbMapRGB5A3() : m_map(MAPSIZE) {}
: m_map(MAPSIZE)
, m_palette(nullptr)
, m_modifications(0)
, m_maskIndex(0)
{
}
void RgbMapRGB5A3::regenerateMap(const Palette* palette, int maskIndex) void RgbMapRGB5A3::regenerateMap(const Palette* palette,
const int maskIndex,
const FitCriteria fitCriteria)
{ {
// Skip useless regenerations // Skip useless regenerations
if (m_palette == palette && if (m_palette == palette &&
m_modifications == palette->getModifications() && m_modifications == palette->getModifications() &&
m_maskIndex == maskIndex) m_maskIndex == maskIndex &&
m_fitCriteria == fitCriteria)
return; return;
m_palette = palette; m_palette = palette;
m_fitCriteria = fitCriteria;
m_modifications = palette->getModifications(); m_modifications = palette->getModifications();
m_maskIndex = maskIndex; 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 int RgbMapRGB5A3::generateEntry(int i, int r, int g, int b, int a) const
{ {
return m_map[i] = return m_map[i] =
m_palette->findBestfit( findBestfit(
scale_5bits_to_8bits(r>>3), scale_5bits_to_8bits(r>>3),
scale_5bits_to_8bits(g>>3), scale_5bits_to_8bits(g>>3),
scale_5bits_to_8bits(b>>3), scale_5bits_to_8bits(b>>3),

View File

@ -13,7 +13,8 @@
#include "base/disable_copying.h" #include "base/disable_copying.h"
#include "base/ints.h" #include "base/ints.h"
#include "doc/object.h" #include "doc/object.h"
#include "doc/rgbmap.h" #include "doc/palette.h"
#include "doc/rgbmap_base.h"
#include <vector> #include <vector>
@ -22,7 +23,7 @@ namespace doc {
class Palette; class Palette;
// It acts like a cache for Palette:findBestfit() calls. // 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. // Bit activated on m_map entries that aren't yet calculated.
const uint16_t INVALID = 256; const uint16_t INVALID = 256;
@ -30,7 +31,14 @@ namespace doc {
RgbMapRGB5A3(); RgbMapRGB5A3();
// RgbMap impl // 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 { int mapColor(const color_t rgba) const override {
const uint8_t r = rgba_getr(rgba); const uint8_t r = rgba_getr(rgba);
const uint8_t g = rgba_getg(rgba); const uint8_t g = rgba_getg(rgba);
@ -42,15 +50,14 @@ 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 override { return m_maskIndex; } RgbMapAlgorithm rgbmapAlgorithm() const override {
return RgbMapAlgorithm::RGB5A3;
}
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;
mutable std::vector<uint16_t> m_map; mutable std::vector<uint16_t> m_map;
const Palette* m_palette;
int m_modifications;
int m_maskIndex;
DISABLE_COPYING(RgbMapRGB5A3); DISABLE_COPYING(RgbMapRGB5A3);
}; };

View File

@ -440,18 +440,24 @@ RgbMap* Sprite::rgbMap(const frame_t frame) const
RgbMap* Sprite::rgbMap(const frame_t frame, RgbMap* Sprite::rgbMap(const frame_t frame,
const RgbMapFor forLayer) const const RgbMapFor forLayer) const
{ {
return rgbMap(frame, FitCriteria fc = FitCriteria::DEFAULT;
forLayer, RgbMapAlgorithm algo = g_rgbMapAlgorithm;
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, RgbMap* Sprite::rgbMap(const frame_t frame,
const RgbMapFor forLayer, const RgbMapFor forLayer,
RgbMapAlgorithm mapAlgo) const const RgbMapAlgorithm mapAlgo,
const FitCriteria fitCriteria) const
{ {
if (!m_rgbMap || m_rgbMapAlgorithm != mapAlgo) { if (!m_rgbMap ||
m_rgbMapAlgorithm = mapAlgo; m_rgbMap->rgbmapAlgorithm() != mapAlgo ||
switch (m_rgbMapAlgorithm) { m_rgbMap->fitCriteria() != fitCriteria) {
switch (mapAlgo) {
case RgbMapAlgorithm::RGB5A3: m_rgbMap.reset(new RgbMapRGB5A3); break; case RgbMapAlgorithm::RGB5A3: m_rgbMap.reset(new RgbMapRGB5A3); break;
case RgbMapAlgorithm::DEFAULT: case RgbMapAlgorithm::DEFAULT:
case RgbMapAlgorithm::OCTREE: m_rgbMap.reset(new OctreeMap); break; case RgbMapAlgorithm::OCTREE: m_rgbMap.reset(new OctreeMap); break;
@ -460,6 +466,7 @@ RgbMap* Sprite::rgbMap(const frame_t frame,
ASSERT(false); ASSERT(false);
return nullptr; return nullptr;
} }
m_rgbMap->fitCriteria(fitCriteria);
} }
int maskIndex; int maskIndex;
if (forLayer == RgbMapFor::OpaqueLayer) if (forLayer == RgbMapFor::OpaqueLayer)
@ -469,7 +476,7 @@ RgbMap* Sprite::rgbMap(const frame_t frame,
if (maskIndex == -1) if (maskIndex == -1)
maskIndex = 0; maskIndex = 0;
} }
m_rgbMap->regenerateMap(palette(frame), maskIndex); m_rgbMap->regenerateMap(palette(frame), maskIndex, fitCriteria);
return m_rgbMap.get(); return m_rgbMap.get();
} }

View File

@ -13,6 +13,7 @@
#include "doc/cel_data.h" #include "doc/cel_data.h"
#include "doc/cel_list.h" #include "doc/cel_list.h"
#include "doc/color.h" #include "doc/color.h"
#include "doc/fit_criteria.h"
#include "doc/frame.h" #include "doc/frame.h"
#include "doc/image_buffer.h" #include "doc/image_buffer.h"
#include "doc/image_ref.h" #include "doc/image_ref.h"
@ -167,7 +168,8 @@ namespace doc {
const RgbMapFor forLayer) const; const RgbMapFor forLayer) const;
RgbMap* rgbMap(const frame_t frame, RgbMap* rgbMap(const frame_t frame,
const RgbMapFor forLayer, const RgbMapFor forLayer,
RgbMapAlgorithm mapAlgo) const; const RgbMapAlgorithm mapAlgo,
const FitCriteria fitCriteria = FitCriteria::DEFAULT) const;
//////////////////////////////////////// ////////////////////////////////////////
// Frames // Frames
@ -263,7 +265,6 @@ namespace doc {
gfx::Rect m_gridBounds; // grid settings gfx::Rect m_gridBounds; // grid settings
// Current rgb map // Current rgb map
mutable RgbMapAlgorithm m_rgbMapAlgorithm;
mutable std::unique_ptr<RgbMap> m_rgbMap; mutable std::unique_ptr<RgbMap> m_rgbMap;
Tags m_tags; Tags m_tags;