Add color fit criteria for Color Mode conversion (fix #2787, #4416)

Added other color comparison criterias (fit criteria) during
color mode conversion RGBA to Indexed or Grayscale to Indexed.
The 'fit criteria' will help us to recolor an RGB image with
a limited color palette taking into account different color
perception criteria (color spaces: RGB, linearized RGB,
CIE XYZ, CIE LAB).
This commit is contained in:
Gaspar Capello 2024-08-20 14:46:43 -03:00 committed by David Capello
parent 3cc1c63274
commit e1bd7990a3
11 changed files with 42 additions and 33 deletions

View File

@ -130,7 +130,8 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
false, // TODO is background? it depends of the layer where this tileset is used
mapAlgorithm,
toGray,
&superDel);
&superDel,
fitCriteria);
}
superDel.nextImage();
}

View File

@ -39,7 +39,7 @@ namespace cmd {
const doc::RgbMapAlgorithm mapAlgorithm,
doc::rgba_to_graya_func toGray,
render::TaskDelegate* delegate,
const doc::FitCriteria fitCriteria = doc::FitCriteria::DEFAULT);
const doc::FitCriteria fitCriteria);
protected:
void onExecute() override;

View File

@ -668,9 +668,10 @@ void ChangePixelFormatCommand::onExecute(Context* context)
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;
if (m_format == IMAGE_INDEXED) {
m_rgbmap = Preferences::instance().quantization.rgbmapAlgorithm();
m_fitCriteria = Preferences::instance().quantization.fitCriteria();
}
}
// No conversion needed

View File

@ -263,6 +263,11 @@ void NewFileCommand::onExecute(Context* ctx)
else
layer->setName(fmt::format("{} {}", Strings::commands_NewLayer_Layer(), 1));
}
if (sprite->pixelFormat() == IMAGE_INDEXED) {
sprite->rgbMap(0, Sprite::RgbMapFor(!layer->isBackground()),
Preferences::instance().quantization.rgbmapAlgorithm(),
Preferences::instance().quantization.fitCriteria());
}
// Show the sprite to the user
std::unique_ptr<Doc> doc(new Doc(sprite.get()));

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -1289,13 +1289,18 @@ void DocExporter::renderTexture(Context* ctx,
// Make the sprite compatible with the texture so the render()
// works correctly.
if (sample.sprite()->pixelFormat() != textureImage->pixelFormat()) {
RgbMapAlgorithm rgbmapAlgo =
Preferences::instance().quantization.rgbmapAlgorithm();
FitCriteria fc =
Preferences::instance().quantization.fitCriteria();
cmd::SetPixelFormat(
sample.sprite(),
textureImage->pixelFormat(),
render::Dithering(),
Sprite::DefaultRgbMapAlgorithm(), // TODO add rgbmap algorithm preference
rgbmapAlgo,
nullptr, // toGray is not needed because the texture is Indexed or RGB
nullptr) // TODO add a delegate to show progress
nullptr, // TODO add a delegate to show progress
fc)
.execute(ctx);
}

View File

@ -290,6 +290,13 @@ bool AseFormat::onLoad(FileOp* fop)
return false;
Sprite* sprite = delegate.sprite();
// Assign RgbMap
if (sprite->pixelFormat() == IMAGE_INDEXED)
sprite->rgbMap(0, Sprite::RgbMapFor(sprite->isOpaque()),
fop->config().rgbMapAlgorithm,
fop->config().fitCriteria);
fop->createDocument(sprite);
if (sprite->colorSpace() != nullptr &&

View File

@ -1356,6 +1356,11 @@ ImageRef FileOp::sequenceImageToLoad(
// Add the layer
sprite->root()->addLayer(layer);
// Assign RgbMap
if (sprite->pixelFormat() == IMAGE_INDEXED)
sprite->rgbMap(0, Sprite::RgbMapFor(sprite->isOpaque()),
m_config.rgbMapAlgorithm,
m_config.fitCriteria);
// Done
createDocument(sprite);
m_seq.layer = layer;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2019-2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -25,6 +25,7 @@ void FileOpConfig::fillFromPreferences()
defaultSliceColor = pref.slices.defaultColor();
workingCS = get_working_rgb_space_from_preferences();
rgbMapAlgorithm = pref.quantization.rgbmapAlgorithm();
fitCriteria = pref.quantization.fitCriteria();
cacheCompressedTilesets = pref.tileset.cacheCompressedTilesets();
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2019-2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -33,9 +33,14 @@ namespace app {
app::Color defaultSliceColor = app::Color::fromRgb(0, 0, 255);
// Algorithm used to create a palette from RGB files.
// Algorithm used to fit any color into the available palette colors in
// Indexed Color Mode.
doc::RgbMapAlgorithm rgbMapAlgorithm = doc::RgbMapAlgorithm::DEFAULT;
// Fit criteria used to compare colors during the conversion to
// Indexed Color Mode.
doc::FitCriteria fitCriteria = doc::FitCriteria::DEFAULT;
// Cache compressed tilesets. When we load a tileset from a
// .aseprite file, the compressed data will be stored on memory to
// make the save operation faster (as we can re-use the already

View File

@ -67,14 +67,6 @@ Preferences::Preferences()
load();
// Create a connection with the default RgbMapAlgorithm preferences
// to change the default algorithm in the "doc" layer.
quantization.rgbmapAlgorithm.AfterChange.connect(
[](const doc::RgbMapAlgorithm& newValue){
doc::Sprite::SetDefaultRgbMapAlgorithm(newValue);
});
doc::Sprite::SetDefaultRgbMapAlgorithm(quantization.rgbmapAlgorithm());
// Create a connection with the default document preferences grid
// bounds to sync the default grid bounds for new sprites in the
// "doc" layer.

View File

@ -36,7 +36,6 @@
namespace doc {
static RgbMapAlgorithm g_rgbMapAlgorithm = RgbMapAlgorithm::DEFAULT;
static gfx::Rect g_defaultGridBounds(0, 0, 16, 16);
// static
@ -56,18 +55,6 @@ void Sprite::SetDefaultGridBounds(const gfx::Rect& defGridBounds)
g_defaultGridBounds.h = 1;
}
// static
RgbMapAlgorithm Sprite::DefaultRgbMapAlgorithm()
{
return g_rgbMapAlgorithm;
}
// static
void Sprite::SetDefaultRgbMapAlgorithm(const RgbMapAlgorithm mapAlgo)
{
g_rgbMapAlgorithm = mapAlgo;
}
//////////////////////////////////////////////////////////////////////
// Constructors/Destructor
@ -441,7 +428,7 @@ RgbMap* Sprite::rgbMap(const frame_t frame,
const RgbMapFor forLayer) const
{
FitCriteria fc = FitCriteria::DEFAULT;
RgbMapAlgorithm algo = g_rgbMapAlgorithm;
RgbMapAlgorithm algo = RgbMapAlgorithm::DEFAULT;
if (m_rgbMap) {
fc = m_rgbMap->fitCriteria();
algo = m_rgbMap->rgbmapAlgorithm();