From e1bd7990a3467cae8d49049685df493872e829b8 Mon Sep 17 00:00:00 2001 From: Gaspar Capello Date: Tue, 20 Aug 2024 14:46:43 -0300 Subject: [PATCH] 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). --- src/app/cmd/set_pixel_format.cpp | 3 ++- src/app/cmd/set_pixel_format.h | 2 +- src/app/commands/cmd_change_pixel_format.cpp | 7 ++++--- src/app/commands/cmd_new_file.cpp | 5 +++++ src/app/doc_exporter.cpp | 11 ++++++++--- src/app/file/ase_format.cpp | 7 +++++++ src/app/file/file.cpp | 5 +++++ src/app/file/file_op_config.cpp | 3 ++- src/app/file/file_op_config.h | 9 +++++++-- src/app/pref/preferences.cpp | 8 -------- src/doc/sprite.cpp | 15 +-------------- 11 files changed, 42 insertions(+), 33 deletions(-) diff --git a/src/app/cmd/set_pixel_format.cpp b/src/app/cmd/set_pixel_format.cpp index f9233d59e..5c4110613 100644 --- a/src/app/cmd/set_pixel_format.cpp +++ b/src/app/cmd/set_pixel_format.cpp @@ -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(); } diff --git a/src/app/cmd/set_pixel_format.h b/src/app/cmd/set_pixel_format.h index d02cc4db4..fcb239aaf 100644 --- a/src/app/cmd/set_pixel_format.h +++ b/src/app/cmd/set_pixel_format.h @@ -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; diff --git a/src/app/commands/cmd_change_pixel_format.cpp b/src/app/commands/cmd_change_pixel_format.cpp index 541f1ce15..69cf5754e 100644 --- a/src/app/commands/cmd_change_pixel_format.cpp +++ b/src/app/commands/cmd_change_pixel_format.cpp @@ -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 diff --git a/src/app/commands/cmd_new_file.cpp b/src/app/commands/cmd_new_file.cpp index c357aa364..2dc1c6133 100644 --- a/src/app/commands/cmd_new_file.cpp +++ b/src/app/commands/cmd_new_file.cpp @@ -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(new Doc(sprite.get())); diff --git a/src/app/doc_exporter.cpp b/src/app/doc_exporter.cpp index d19579a50..dc072b6ff 100644 --- a/src/app/doc_exporter.cpp +++ b/src/app/doc_exporter.cpp @@ -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); } diff --git a/src/app/file/ase_format.cpp b/src/app/file/ase_format.cpp index ad19b5cdc..9d3e6776c 100644 --- a/src/app/file/ase_format.cpp +++ b/src/app/file/ase_format.cpp @@ -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 && diff --git a/src/app/file/file.cpp b/src/app/file/file.cpp index 72e23fa8b..ecad34f7a 100644 --- a/src/app/file/file.cpp +++ b/src/app/file/file.cpp @@ -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; diff --git a/src/app/file/file_op_config.cpp b/src/app/file/file_op_config.cpp index 8ca7a1232..1891b5a40 100644 --- a/src/app/file/file_op_config.cpp +++ b/src/app/file/file_op_config.cpp @@ -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(); } diff --git a/src/app/file/file_op_config.h b/src/app/file/file_op_config.h index 6bb9e3c66..74f074b10 100644 --- a/src/app/file/file_op_config.h +++ b/src/app/file/file_op_config.h @@ -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 diff --git a/src/app/pref/preferences.cpp b/src/app/pref/preferences.cpp index 53d9c9a46..30bb10446 100644 --- a/src/app/pref/preferences.cpp +++ b/src/app/pref/preferences.cpp @@ -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. diff --git a/src/doc/sprite.cpp b/src/doc/sprite.cpp index c555a8510..97dff921c 100644 --- a/src/doc/sprite.cpp +++ b/src/doc/sprite.cpp @@ -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();