diff --git a/data/pref.xml b/data/pref.xml
index b9c5e5e57..1f108865d 100644
--- a/data/pref.xml
+++ b/data/pref.xml
@@ -197,6 +197,7 @@
+
diff --git a/data/strings/en.ini b/data/strings/en.ini
index d03409ae1..c60179726 100644
--- a/data/strings/en.ini
+++ b/data/strings/en.ini
@@ -697,6 +697,7 @@ Check in case that you want to establish
the given option as the default option.
END
reset = Reset
+advanced_options = Advanced Options
[gif_options]
title = GIF Options
@@ -996,12 +997,16 @@ background = Background:
transparent = &Transparent
white = &White
black = &Black
-advanced_options = Advanced Options
pixel_ratio = Pixel Aspect Ratio:
square_pixels = Square Pixels (1:1)
double_wide = Double-wide Pixels (2:1)
double_high = Double-high Pixels (1:2)
+[rgbmap_algorithm_selector]
+label = RGB to palette index mapping:
+rgb5a3 = Table RGB 5 bits + Alpha 3 bits
+octree = Octree without Alpha
+
[open_sequence]
title = Notice
description = Do you want to load the following files as an animation?
@@ -1291,7 +1296,7 @@ bg_color = Background Color:
[palette_from_sprite]
title = Palette from Sprite
-new_palette = Create a new palette with a specific number of colors (or less):
+new_palette = Create new palette, color count limit:
replace_palette = Replace current palette
replace_range = Replace current range
alpha_channel = Create entries with alpha component
diff --git a/data/widgets/color_mode.xml b/data/widgets/color_mode.xml
index b20fdc50d..e02ce7fab 100644
--- a/data/widgets/color_mode.xml
+++ b/data/widgets/color_mode.xml
@@ -13,12 +13,21 @@
+
+
+
+
+
+
+
+
+
diff --git a/data/widgets/new_sprite.xml b/data/widgets/new_sprite.xml
index 4454f6949..2041ef933 100644
--- a/data/widgets/new_sprite.xml
+++ b/data/widgets/new_sprite.xml
@@ -1,5 +1,6 @@
-
+
+
@@ -26,7 +27,7 @@
-
+
diff --git a/data/widgets/options.xml b/data/widgets/options.xml
index 613d7d8af..c606d86d8 100644
--- a/data/widgets/options.xml
+++ b/data/widgets/options.xml
@@ -504,6 +504,10 @@
+
+
+
+
diff --git a/data/widgets/palette_from_sprite.xml b/data/widgets/palette_from_sprite.xml
index b6d882c5f..11a73a731 100644
--- a/data/widgets/palette_from_sprite.xml
+++ b/data/widgets/palette_from_sprite.xml
@@ -1,5 +1,6 @@
-
+
+
@@ -7,7 +8,15 @@
+
+
+
+
+
+
+
+
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index e1e3bc1ae..29bc6c962 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -367,6 +367,7 @@ if(ENABLE_UI)
ui/preview_editor.cpp
ui/recent_listbox.cpp
ui/resources_listbox.cpp
+ ui/rgbmap_algorithm_selector.cpp
ui/search_entry.cpp
ui/select_accelerator.cpp
ui/selection_mode_field.cpp
diff --git a/src/app/cmd/set_pixel_format.cpp b/src/app/cmd/set_pixel_format.cpp
index 4c45e33be..381987264 100644
--- a/src/app/cmd/set_pixel_format.cpp
+++ b/src/app/cmd/set_pixel_format.cpp
@@ -69,6 +69,7 @@ private:
SetPixelFormat::SetPixelFormat(Sprite* sprite,
const PixelFormat newFormat,
const render::Dithering& dithering,
+ const doc::RgbMapAlgorithm mapAlgorithm,
doc::rgba_to_graya_func toGray,
render::TaskDelegate* delegate)
: WithSprite(sprite)
@@ -79,6 +80,7 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
return;
SuperDelegate superDel(sprite->uniqueCels().size(), delegate);
+ const auto rgbMapFor = sprite->rgbMapForSprite();
for (Cel* cel : sprite->uniqueCels()) {
ImageRef old_image = cel->imageRef();
@@ -86,7 +88,7 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
render::convert_pixel_format
(old_image.get(), nullptr, newFormat,
dithering,
- sprite->rgbMap(cel->frame()),
+ sprite->rgbMap(cel->frame(), rgbMapFor, mapAlgorithm),
sprite->palette(cel->frame()),
cel->layer()->isBackground(),
old_image->maskColor(),
diff --git a/src/app/cmd/set_pixel_format.h b/src/app/cmd/set_pixel_format.h
index 480c4d5ab..93b71bf8c 100644
--- a/src/app/cmd/set_pixel_format.h
+++ b/src/app/cmd/set_pixel_format.h
@@ -13,6 +13,7 @@
#include "app/cmd_sequence.h"
#include "doc/color.h"
#include "doc/pixel_format.h"
+#include "doc/rgbmap_algorithm.h"
namespace doc {
class Sprite;
@@ -32,6 +33,7 @@ namespace cmd {
SetPixelFormat(doc::Sprite* sprite,
const doc::PixelFormat newFormat,
const render::Dithering& dithering,
+ const doc::RgbMapAlgorithm mapAlgorithm,
doc::rgba_to_graya_func toGray,
render::TaskDelegate* delegate);
diff --git a/src/app/commands/cmd_change_pixel_format.cpp b/src/app/commands/cmd_change_pixel_format.cpp
index 217ddd4b1..b7eefae82 100644
--- a/src/app/commands/cmd_change_pixel_format.cpp
+++ b/src/app/commands/cmd_change_pixel_format.cpp
@@ -26,6 +26,7 @@
#include "app/ui/dithering_selector.h"
#include "app/ui/editor/editor.h"
#include "app/ui/editor/editor_render.h"
+#include "app/ui/rgbmap_algorithm_selector.h"
#include "app/ui/skin/skin_theme.h"
#include "base/bind.h"
#include "base/thread.h"
@@ -89,6 +90,7 @@ 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)
@@ -102,11 +104,13 @@ public:
sprite, frame,
pixelFormat,
dithering,
+ rgbMapAlgorithm,
toGray,
newBlend]() { // Copy the matrix
run(sprite, frame,
pixelFormat,
dithering,
+ rgbMapAlgorithm,
toGray,
newBlend);
})
@@ -131,6 +135,7 @@ 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(
@@ -152,7 +157,9 @@ private:
m_image.get(),
pixelFormat,
dithering,
- sprite->rgbMap(frame),
+ sprite->rgbMap(frame,
+ sprite->rgbMapForSprite(),
+ rgbMapAlgorithm),
sprite->palette(frame),
(sprite->backgroundLayer() != nullptr),
0,
@@ -189,9 +196,11 @@ public:
, m_imageBuffer(new doc::ImageBuffer)
, m_selectedItem(nullptr)
, m_ditheringSelector(nullptr)
+ , m_mapAlgorithmSelector(nullptr)
, m_imageJustCreated(true)
{
- doc::PixelFormat from = m_editor->sprite()->pixelFormat();
+ const auto& pref = Preferences::instance();
+ const doc::PixelFormat from = m_editor->sprite()->pixelFormat();
// Add the color mode in the window title
switch (from) {
@@ -209,22 +218,48 @@ public:
m_ditheringSelector = new DitheringSelector(DitheringSelector::SelectBoth);
m_ditheringSelector->setExpansive(true);
+ m_mapAlgorithmSelector = new RgbMapAlgorithmSelector;
+ m_mapAlgorithmSelector->setExpansive(true);
+
// Select default dithering method
{
int index = m_ditheringSelector->findItemIndex(
- Preferences::instance().quantization.ditheringAlgorithm());
+ pref.quantization.ditheringAlgorithm());
if (index >= 0)
m_ditheringSelector->setSelectedItemIndex(index);
}
- m_ditheringSelector->Change.connect(
- base::Bind(&ColorModeWindow::onDithering, this));
- ditheringPlaceholder()->addChild(m_ditheringSelector);
+ // Select default RgbMap algorithm
+ m_mapAlgorithmSelector->algorithm(pref.experimental.rgbmapAlgorithm());
- factor()->Change.connect(base::Bind(&ColorModeWindow::onDithering, this));
+ ditheringPlaceholder()->addChild(m_ditheringSelector);
+ rgbmapAlgorithmPlaceholder()->addChild(m_mapAlgorithmSelector);
+
+ const bool adv = pref.quantization.advanced();
+ advancedCheck()->setSelected(adv);
+ advanced()->setVisible(adv);
+
+ // Signals
+ m_ditheringSelector->Change.connect(
+ base::Bind(&ColorModeWindow::onIndexParamChange, this));
+ m_mapAlgorithmSelector->Change.connect(
+ base::Bind(&ColorModeWindow::onIndexParamChange, this));
+ factor()->Change.connect(
+ base::Bind(&ColorModeWindow::onIndexParamChange, this));
+
+ advancedCheck()->Click.connect(
+ [this](ui::Event&){
+ advanced()->setVisible(advancedCheck()->isSelected());
+
+ const gfx::Rect origBounds = bounds();
+ setBounds(gfx::Rect(bounds().origin(), sizeHint()));
+ manager()->invalidateRect(origBounds);
+ });
}
else {
amount()->setVisible(false);
+ advancedCheck()->setVisible(false);
+ advanced()->setVisible(false);
}
if (from != IMAGE_GRAYSCALE) {
colorMode()->addChild(new ConversionItem(IMAGE_GRAYSCALE));
@@ -242,7 +277,7 @@ public:
progress()->setReadOnly(true);
// Default dithering factor
- factor()->setValue(Preferences::instance().quantization.ditheringFactor());
+ factor()->setValue(pref.quantization.ditheringFactor());
// Select first option
colorMode()->selectIndex(0);
@@ -267,6 +302,13 @@ public:
return d;
}
+ doc::RgbMapAlgorithm rgbMapAlgorithm() const {
+ if (m_mapAlgorithmSelector)
+ return m_mapAlgorithmSelector->algorithm();
+ else
+ return doc::RgbMapAlgorithm::DEFAULT;
+ }
+
gen::ToGrayAlgorithm toGray() const {
static_assert(
int(gen::ToGrayAlgorithm::LUMA) == 0 &&
@@ -280,20 +322,25 @@ public:
return flatten()->isSelected();
}
- // Save the dithering method used for the future
- void saveDitheringOptions() {
+ void saveOptions() {
+ auto& pref = Preferences::instance();
+
+ // Save the dithering method used for the future
if (m_ditheringSelector) {
if (auto item = m_ditheringSelector->getSelectedItem()) {
- Preferences::instance().quantization.ditheringAlgorithm(
+ pref.quantization.ditheringAlgorithm(
item->text());
if (m_ditheringSelector->ditheringAlgorithm() ==
render::DitheringAlgorithm::ErrorDiffusion) {
- Preferences::instance().quantization.ditheringFactor(
+ pref.quantization.ditheringFactor(
factor()->getValue());
}
}
}
+
+ if (m_mapAlgorithmSelector)
+ pref.quantization.advanced(advancedCheck()->isSelected());
}
private:
@@ -368,6 +415,7 @@ private:
m_editor->frame(),
dstPixelFormat,
dithering(),
+ rgbMapAlgorithm(),
toGray(),
visibleBounds.origin(),
Preferences::instance().experimental.newBlend()));
@@ -375,7 +423,7 @@ private:
m_timer.start();
}
- void onDithering() {
+ void onIndexParamChange() {
stop();
m_selectedItem = nullptr;
onChangeColorMode();
@@ -421,6 +469,7 @@ private:
std::unique_ptr m_bgThread;
ConversionItem* m_selectedItem;
DitheringSelector* m_ditheringSelector;
+ RgbMapAlgorithmSelector* m_mapAlgorithmSelector;
bool m_imageJustCreated;
};
@@ -441,6 +490,7 @@ private:
bool m_useUI;
doc::PixelFormat m_format;
render::Dithering m_dithering;
+ doc::RgbMapAlgorithm m_rgbmap;
gen::ToGrayAlgorithm m_toGray;
};
@@ -450,6 +500,7 @@ ChangePixelFormatCommand::ChangePixelFormatCommand()
m_useUI = true;
m_format = IMAGE_RGB;
m_dithering = render::Dithering();
+ m_rgbmap = doc::RgbMapAlgorithm::DEFAULT;
m_toGray = gen::ToGrayAlgorithm::DEFAULT;
}
@@ -497,6 +548,15 @@ void ChangePixelFormatCommand::onLoadParams(const Params& params)
m_dithering.matrix(render::BayerMatrix(8));
}
+ // TODO change this with NewParams as in ColorQuantizationParams
+ std::string rgbmap = params.get("rgbmap");
+ if (rgbmap == "octree")
+ m_rgbmap = doc::RgbMapAlgorithm::OCTREE;
+ else if (rgbmap == "rgb5a3")
+ m_rgbmap = doc::RgbMapAlgorithm::RGB5A3;
+ else
+ m_rgbmap = doc::RgbMapAlgorithm::DEFAULT;
+
std::string toGray = params.get("toGray");
if (toGray == "luma")
m_toGray = gen::ToGrayAlgorithm::LUMA;
@@ -570,10 +630,11 @@ void ChangePixelFormatCommand::onExecute(Context* context)
m_format = window.pixelFormat();
m_dithering = window.dithering();
+ m_rgbmap = window.rgbMapAlgorithm();
m_toGray = window.toGray();
flatten = window.flattenEnabled();
- window.saveDitheringOptions();
+ window.saveOptions();
}
#endif // ENABLE_UI
@@ -607,6 +668,7 @@ void ChangePixelFormatCommand::onExecute(Context* context)
new cmd::SetPixelFormat(
sprite, m_format,
m_dithering,
+ m_rgbmap,
get_gray_func(m_toGray),
&job)); // SpriteJob is a render::TaskDelegate
});
diff --git a/src/app/commands/cmd_color_quantization.cpp b/src/app/commands/cmd_color_quantization.cpp
index 5bf34b858..57dd8d42f 100644
--- a/src/app/commands/cmd_color_quantization.cpp
+++ b/src/app/commands/cmd_color_quantization.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2019 Igara Studio S.A.
+// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@@ -20,10 +20,12 @@
#include "app/sprite_job.h"
#include "app/transaction.h"
#include "app/ui/color_bar.h"
+#include "app/ui/rgbmap_algorithm_selector.h"
#include "app/ui_context.h"
#include "doc/palette.h"
#include "doc/sprite.h"
#include "render/quantization.h"
+#include "ui/manager.h"
#include "palette_from_sprite.xml.h"
@@ -36,8 +38,53 @@ struct ColorQuantizationParams : public NewParams {
Param withAlpha { this, true, "withAlpha" };
Param maxColors { this, 256, "maxColors" };
Param useRange { this, false, "useRange" };
+ Param algorithm { this, RgbMapAlgorithm::DEFAULT, "algorithm" };
};
+#if ENABLE_UI
+
+class PaletteFromSpriteWindow : public app::gen::PaletteFromSprite {
+public:
+ PaletteFromSpriteWindow() {
+ rgbmapAlgorithmPlaceholder()->addChild(&m_algoSelector);
+
+ advancedCheck()->Click.connect(
+ [this](ui::Event&){
+ advanced()->setVisible(advancedCheck()->isSelected());
+
+ const gfx::Rect origBounds = bounds();
+ setBounds(gfx::Rect(bounds().origin(), sizeHint()));
+ manager()->invalidateRect(origBounds);
+ });
+
+ m_algoSelector.Change.connect(
+ [this](){
+ switch (algorithm()) {
+ case RgbMapAlgorithm::RGB5A3:
+ alphaChannel()->setEnabled(true);
+ break;
+ case RgbMapAlgorithm::OCTREE:
+ alphaChannel()->setSelected(false);
+ alphaChannel()->setEnabled(false);
+ break;
+ }
+ });
+ }
+
+ doc::RgbMapAlgorithm algorithm() {
+ return m_algoSelector.algorithm();
+ }
+
+ void algorithm(const doc::RgbMapAlgorithm mapAlgo) {
+ m_algoSelector.algorithm(mapAlgo);
+ }
+
+private:
+ RgbMapAlgorithmSelector m_algoSelector;
+};
+
+#endif
+
class ColorQuantizationCommand : public CommandWithNewParams {
public:
ColorQuantizationCommand();
@@ -65,8 +112,10 @@ void ColorQuantizationCommand::onExecute(Context* ctx)
const bool ui = (params().ui() && ctx->isUIAvailable());
#endif
+ auto& pref = Preferences::instance();
bool withAlpha = params().withAlpha();
int maxColors = params().maxColors();
+ RgbMapAlgorithm algorithm = params().algorithm();
bool createPal;
Site site = ctx->activeSite();
@@ -74,16 +123,22 @@ void ColorQuantizationCommand::onExecute(Context* ctx)
#ifdef ENABLE_UI
if (ui) {
- app::gen::PaletteFromSprite window;
+ PaletteFromSpriteWindow window;
{
ContextReader reader(ctx);
const Palette* curPalette = site.sprite()->palette(site.frame());
+ if (!params().algorithm.isSet())
+ algorithm = pref.experimental.rgbmapAlgorithm();
if (!params().withAlpha.isSet())
- withAlpha = App::instance()->preferences().quantization.withAlpha();
+ withAlpha = pref.quantization.withAlpha();
+ const bool advanced = pref.quantization.advanced();
+ window.advancedCheck()->setSelected(advanced);
+ window.advanced()->setVisible(advanced);
+
+ window.algorithm(algorithm);
window.newPalette()->setSelected(true);
- window.alphaChannel()->setSelected(withAlpha);
window.ncolors()->setTextf("%d", maxColors);
if (entries.picks() > 1) {
@@ -107,7 +162,10 @@ void ColorQuantizationCommand::onExecute(Context* ctx)
maxColors = window.ncolors()->textInt();
withAlpha = window.alphaChannel()->isSelected();
- App::instance()->preferences().quantization.withAlpha(withAlpha);
+ algorithm = window.algorithm();
+
+ pref.quantization.withAlpha(withAlpha);
+ pref.quantization.advanced(window.advancedCheck()->isSelected());
if (window.newPalette()->isSelected()) {
createPal = true;
@@ -139,14 +197,15 @@ void ColorQuantizationCommand::onExecute(Context* ctx)
Palette tmpPalette(frame, entries.picks());
SpriteJob job(reader, "Color Quantization");
- const bool newBlend = Preferences::instance().experimental.newBlend();
+ const bool newBlend = pref.experimental.newBlend();
job.startJobWithCallback(
- [sprite, withAlpha, &tmpPalette, &job, newBlend]{
+ [sprite, withAlpha, &tmpPalette, &job, newBlend, algorithm]{
render::create_palette_from_sprite(
sprite, 0, sprite->lastFrame(),
withAlpha, &tmpPalette,
- &job,
- newBlend); // SpriteJob is a render::TaskDelegate
+ &job, // SpriteJob is a render::TaskDelegate
+ newBlend,
+ algorithm);
});
job.waitJob();
if (job.isCanceled())
diff --git a/src/app/commands/cmd_new_file.cpp b/src/app/commands/cmd_new_file.cpp
index 0089157da..b3f4d60bf 100644
--- a/src/app/commands/cmd_new_file.cpp
+++ b/src/app/commands/cmd_new_file.cpp
@@ -275,7 +275,8 @@ void NewFileCommand::onExecute(Context* ctx)
if (clipboardPalette.isBlack()) {
render::create_palette_from_sprite(
sprite.get(), 0, sprite->lastFrame(), true,
- &clipboardPalette, nullptr, true);
+ &clipboardPalette, nullptr, true,
+ Preferences::instance().experimental.rgbmapAlgorithm());
}
sprite->setPalette(&clipboardPalette, false);
}
diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp
index 35a46171f..66ac2b40b 100644
--- a/src/app/commands/cmd_options.cpp
+++ b/src/app/commands/cmd_options.cpp
@@ -28,6 +28,7 @@
#include "app/tx.h"
#include "app/ui/color_button.h"
#include "app/ui/pref_widget.h"
+#include "app/ui/rgbmap_algorithm_selector.h"
#include "app/ui/separator_in_view.h"
#include "app/ui/skin/skin_theme.h"
#include "base/bind.h"
@@ -400,6 +401,10 @@ public:
nonactiveLayersOpacity()->setValue(m_pref.experimental.nonactiveLayersOpacity());
+ rgbmapAlgorithmPlaceholder()->addChild(&m_rgbmapAlgorithmSelector);
+ m_rgbmapAlgorithmSelector.setExpansive(true);
+ m_rgbmapAlgorithmSelector.algorithm(m_pref.experimental.rgbmapAlgorithm());
+
if (m_pref.editor.showScrollbars())
showScrollbars()->setSelected(true);
@@ -695,6 +700,7 @@ public:
m_pref.experimental.useNativeFileDialog(nativeFileDialog()->isSelected());
m_pref.experimental.flashLayer(flashLayer()->isSelected());
m_pref.experimental.nonactiveLayersOpacity(nonactiveLayersOpacity()->getValue());
+ m_pref.experimental.rgbmapAlgorithm(m_rgbmapAlgorithmSelector.algorithm());
#ifdef _WIN32
{
@@ -1606,6 +1612,7 @@ private:
int m_restoreUIScaling;
std::vector m_colorSpaces;
std::string m_templateTextForDisplayCS;
+ RgbMapAlgorithmSelector m_rgbmapAlgorithmSelector;
};
class OptionsCommand : public Command {
diff --git a/src/app/commands/new_params.cpp b/src/app/commands/new_params.cpp
index 20fb006e7..113aa737d 100644
--- a/src/app/commands/new_params.cpp
+++ b/src/app/commands/new_params.cpp
@@ -19,6 +19,7 @@
#include "base/string.h"
#include "doc/algorithm/resize_image.h"
#include "doc/color_mode.h"
+#include "doc/rgbmap_algorithm.h"
#include "filters/color_curve.h"
#include "filters/hue_saturation_filter.h"
#include "filters/outline_filter.h"
@@ -192,6 +193,17 @@ void Param::fromString(const std::string& value)
setValue(tools::string_id_to_ink_type(value));
}
+template<>
+void Param::fromString(const std::string& value)
+{
+ if (base::utf8_icmp(value, "octree") == 0)
+ setValue(doc::RgbMapAlgorithm::OCTREE);
+ else if (base::utf8_icmp(value, "rgb5a3") == 0)
+ setValue(doc::RgbMapAlgorithm::RGB5A3);
+ else
+ setValue(doc::RgbMapAlgorithm::DEFAULT);
+}
+
//////////////////////////////////////////////////////////////////////
// Convert values from Lua
//////////////////////////////////////////////////////////////////////
@@ -326,6 +338,15 @@ void Param::fromLua(lua_State* L, int index)
script::get_value_from_lua(L, index);
}
+template<>
+void Param::fromLua(lua_State* L, int index)
+{
+ if (lua_type(L, index) == LUA_TSTRING)
+ fromString(lua_tostring(L, index));
+ else
+ setValue((doc::RgbMapAlgorithm)lua_tointeger(L, index));
+}
+
void CommandWithNewParamsBase::loadParamsFromLuaTable(lua_State* L, int index)
{
onResetValues();
diff --git a/src/app/doc_exporter.cpp b/src/app/doc_exporter.cpp
index c5348a60f..dd7374da6 100644
--- a/src/app/doc_exporter.cpp
+++ b/src/app/doc_exporter.cpp
@@ -1180,6 +1180,7 @@ void DocExporter::renderTexture(Context* ctx,
sample.sprite(),
textureImage->pixelFormat(),
render::Dithering(),
+ Sprite::DefaultRgbMapAlgorithm(), // TODO add rgbmap algorithm preference
nullptr, // toGray is not needed because the texture is Indexed or RGB
nullptr) // TODO add a delegate to show progress
.execute(ctx);
diff --git a/src/app/file/file.cpp b/src/app/file/file.cpp
index bb4444e22..a251a2ef2 100644
--- a/src/app/file/file.cpp
+++ b/src/app/file/file.cpp
@@ -905,7 +905,8 @@ void FileOp::postLoad()
std::shared_ptr palette(
render::create_palette_from_sprite(
sprite, frame_t(0), sprite->lastFrame(), true,
- nullptr, nullptr, m_config.newBlend));
+ nullptr, nullptr, m_config.newBlend,
+ m_config.rgbMapAlgorithm));
sprite->resetPalettes();
sprite->setPalette(palette.get(), false);
diff --git a/src/app/file/file_op_config.cpp b/src/app/file/file_op_config.cpp
index b64a6f15f..9c1da155b 100644
--- a/src/app/file/file_op_config.cpp
+++ b/src/app/file/file_op_config.cpp
@@ -22,6 +22,7 @@ void FileOpConfig::fillFromPreferences()
newBlend = Preferences::instance().experimental.newBlend();
defaultSliceColor = Preferences::instance().slices.defaultColor();
workingCS = get_working_rgb_space_from_preferences();
+ rgbMapAlgorithm = Preferences::instance().experimental.rgbmapAlgorithm();
}
} // namespace app
diff --git a/src/app/file/file_op_config.h b/src/app/file/file_op_config.h
index 2f31a69df..398e034cc 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 Igara Studio S.A.
+// Copyright (C) 2019-2020 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@@ -10,6 +10,7 @@
#include "app/color.h"
#include "app/pref/preferences.h"
+#include "doc/rgbmap_algorithm.h"
#include "gfx/color_space.h"
namespace app {
@@ -32,6 +33,9 @@ namespace app {
app::Color defaultSliceColor = app::Color::fromRgb(0, 0, 255);
+ // Algorithm used to create a palette from RGB files.
+ doc::RgbMapAlgorithm rgbMapAlgorithm = doc::RgbMapAlgorithm::DEFAULT;
+
void fillFromPreferences();
};
diff --git a/src/app/file/gif_format.cpp b/src/app/file/gif_format.cpp
index 2c1ef628a..4e2f7a26b 100644
--- a/src/app/file/gif_format.cpp
+++ b/src/app/file/gif_format.cpp
@@ -1172,7 +1172,7 @@ private:
const DisposalMethod disposal,
const bool fixDuration) {
std::unique_ptr framePaletteRef;
- std::unique_ptr rgbmapRef;
+ std::unique_ptr rgbmapRef;
Palette* framePalette = m_sprite->palette(frame);
RgbMap* rgbmap = m_sprite->rgbMap(frame);
@@ -1181,9 +1181,9 @@ private:
framePaletteRef.reset(createOptimizedPalette(frameBounds));
framePalette = framePaletteRef.get();
- rgbmapRef.reset(new RgbMap);
+ rgbmapRef.reset(new RgbMapRGB5A3);
+ rgbmapRef->regenerateMap(framePalette, m_transparentIndex);
rgbmap = rgbmapRef.get();
- rgbmap->regenerate(framePalette, m_transparentIndex);
}
// We will store the frameBounds pixels in frameImage, with the
@@ -1229,6 +1229,8 @@ private:
int i;
if (rgba_geta(color) >= 128) {
+ color |= rgba_a_mask; // Set alpha=255
+
i = framePalette->findExactMatch(
rgba_getr(color),
rgba_getg(color),
@@ -1236,10 +1238,7 @@ private:
255,
m_transparentIndex);
if (i < 0)
- i = rgbmap->mapColor(rgba_getr(color),
- rgba_getg(color),
- rgba_getb(color),
- 255);
+ i = rgbmap->mapColor(color);
}
else {
ASSERT(m_transparentIndex >= 0);
diff --git a/src/app/pref/preferences.cpp b/src/app/pref/preferences.cpp
index 48d2deee4..830e8838b 100644
--- a/src/app/pref/preferences.cpp
+++ b/src/app/pref/preferences.cpp
@@ -54,6 +54,14 @@ Preferences::Preferences()
load();
+ // Create a connection with the default RgbMapAlgorithm preferences
+ // to change the default algorithm in the "doc" layer.
+ experimental.rgbmapAlgorithm.AfterChange.connect(
+ [](const doc::RgbMapAlgorithm& newValue){
+ doc::Sprite::SetDefaultRgbMapAlgorithm(newValue);
+ });
+ doc::Sprite::SetDefaultRgbMapAlgorithm(experimental.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/app/pref/preferences.h b/src/app/pref/preferences.h
index ee426c16a..983b51e17 100644
--- a/src/app/pref/preferences.h
+++ b/src/app/pref/preferences.h
@@ -25,6 +25,7 @@
#include "doc/color_mode.h"
#include "doc/frame.h"
#include "doc/layer_list.h"
+#include "doc/rgbmap_algorithm.h"
#include "doc/sprite.h"
#include "filters/hue_saturation_filter.h"
#include "filters/tiled_mode.h"
diff --git a/src/app/script/values.cpp b/src/app/script/values.cpp
index 311356834..a90890102 100644
--- a/src/app/script/values.cpp
+++ b/src/app/script/values.cpp
@@ -184,6 +184,7 @@ FOR_ENUM(app::tools::RotationAlgorithm)
FOR_ENUM(doc::AniDir)
FOR_ENUM(doc::BrushPattern)
FOR_ENUM(doc::ColorMode)
+FOR_ENUM(doc::RgbMapAlgorithm)
FOR_ENUM(filters::HueSaturationFilter::Mode)
FOR_ENUM(filters::TiledMode)
FOR_ENUM(render::OnionskinPosition)
diff --git a/src/app/tools/ink_processing.h b/src/app/tools/ink_processing.h
index 541f7442b..75cd65a26 100644
--- a/src/app/tools/ink_processing.h
+++ b/src/app/tools/ink_processing.h
@@ -292,10 +292,7 @@ public:
c = m_palette->getEntry(c);
c = rgba_blender_normal(c, m_color, m_opacity);
- *m_dstAddress = m_rgbmap->mapColor(rgba_getr(c),
- rgba_getg(c),
- rgba_getb(c),
- rgba_geta(c));
+ *m_dstAddress = m_rgbmap->mapColor(c);
}
private:
@@ -365,10 +362,7 @@ public:
c = m_palette->getEntry(c);
c = rgba_blender_merge(c, m_color, m_opacity);
- *m_dstAddress = m_rgbmap->mapColor(rgba_getr(c),
- rgba_getg(c),
- rgba_getb(c),
- rgba_geta(c));
+ *m_dstAddress = m_rgbmap->mapColor(c);
}
private:
@@ -520,8 +514,7 @@ public:
doc::rgba(m_area.r, m_area.g, m_area.b, m_area.a),
m_opacity);
- *m_dstAddress = m_rgbmap->mapColor(
- rgba_getr(c), rgba_getg(c), rgba_getb(c), rgba_geta(c));
+ *m_dstAddress = m_rgbmap->mapColor(c);
}
else {
*m_dstAddress = *m_srcAddress;
@@ -633,8 +626,7 @@ public:
color_t c = rgba_blender_normal(
m_palette->getEntry(*m_srcAddress), m_color2, m_opacity);
- *m_dstAddress = m_rgbmap->mapColor(
- rgba_getr(c), rgba_getg(c), rgba_getb(c), rgba_geta(c));
+ *m_dstAddress = m_rgbmap->mapColor(c);
}
}
}
@@ -721,10 +713,7 @@ void JumbleInkProcessing::processPixel(int x, int y)
tc, m_opacity);
if (rgba_geta(c) >= 128)
- *m_dstAddress = m_rgbmap->mapColor(rgba_getr(c),
- rgba_getg(c),
- rgba_getb(c),
- rgba_geta(c));
+ *m_dstAddress = m_rgbmap->mapColor(c);
else
*m_dstAddress = 0;
}
@@ -1060,10 +1049,7 @@ void GradientInkProcessing::processPixel(int x, int y)
c0 = m_palette->getEntry(c0);
c = rgba_blender_normal(c0, c, m_opacity);
- *m_dstAddress = m_rgbmap->mapColor(rgba_getr(c),
- rgba_getg(c),
- rgba_getb(c),
- rgba_geta(c));
+ *m_dstAddress = m_rgbmap->mapColor(c);
++m_tmpAddress;
}
@@ -1108,10 +1094,7 @@ public:
void processPixel(int x, int y) {
color_t c = rgba_blender_neg_bw(m_palette->getEntry(*m_srcAddress), m_color, 255);
- *m_dstAddress = m_rgbmap->mapColor(rgba_getr(c),
- rgba_getg(c),
- rgba_getb(c),
- rgba_geta(c));
+ *m_dstAddress = m_rgbmap->mapColor(c);
}
private:
diff --git a/src/app/ui/rgbmap_algorithm_selector.cpp b/src/app/ui/rgbmap_algorithm_selector.cpp
new file mode 100644
index 000000000..cc128d9e2
--- /dev/null
+++ b/src/app/ui/rgbmap_algorithm_selector.cpp
@@ -0,0 +1,40 @@
+// Aseprite
+// Copyright (C) 2020 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/rgbmap_algorithm_selector.h"
+
+#include "app/i18n/strings.h"
+
+namespace app {
+
+RgbMapAlgorithmSelector::RgbMapAlgorithmSelector()
+{
+ // addItem() must match the RgbMapAlgorithm enum
+ static_assert(int(doc::RgbMapAlgorithm::RGB5A3) == 0 &&
+ int(doc::RgbMapAlgorithm::OCTREE) == 1,
+ "Unexpected doc::RgbMapAlgorithm values");
+
+ addItem(Strings::rgbmap_algorithm_selector_rgb5a3());
+ addItem(Strings::rgbmap_algorithm_selector_octree());
+
+ algorithm(doc::RgbMapAlgorithm::DEFAULT);
+}
+
+doc::RgbMapAlgorithm RgbMapAlgorithmSelector::algorithm()
+{
+ return (doc::RgbMapAlgorithm)getSelectedItemIndex();
+}
+
+void RgbMapAlgorithmSelector::algorithm(const doc::RgbMapAlgorithm mapAlgo)
+{
+ setSelectedItemIndex((int)mapAlgo);
+}
+
+} // namespace app
diff --git a/src/app/ui/rgbmap_algorithm_selector.h b/src/app/ui/rgbmap_algorithm_selector.h
new file mode 100644
index 000000000..983bb33da
--- /dev/null
+++ b/src/app/ui/rgbmap_algorithm_selector.h
@@ -0,0 +1,26 @@
+// Aseprite
+// Copyright (C) 2020 Igara Studio S.A.
+//
+// This program is distributed under the terms of
+// the End-User License Agreement for Aseprite.
+
+#ifndef APP_UI_MAP_ALGORITHM_SELECTOR_H_INCLUDED
+#define APP_UI_MAP_ALGORITHM_SELECTOR_H_INCLUDED
+#pragma once
+
+#include "doc/rgbmap_algorithm.h"
+#include "ui/combobox.h"
+
+namespace app {
+
+ class RgbMapAlgorithmSelector : public ui::ComboBox {
+ public:
+ RgbMapAlgorithmSelector();
+
+ doc::RgbMapAlgorithm algorithm();
+ void algorithm(doc::RgbMapAlgorithm mapAlgo);
+ };
+
+} // namespace app
+
+#endif
diff --git a/src/doc/CMakeLists.txt b/src/doc/CMakeLists.txt
index c603b352a..3806c781c 100644
--- a/src/doc/CMakeLists.txt
+++ b/src/doc/CMakeLists.txt
@@ -1,5 +1,5 @@
# Aseprite Document Library
-# Copyright (C) 2020 Igara Studio S.A.
+# Copyright (C) 2019-2020 Igara Studio S.A.
# Copyright (C) 2001-2018 David Capello
if(WIN32)
@@ -49,11 +49,12 @@ add_library(doc-lib
mask_io.cpp
object.cpp
object.cpp
+ octree_map.cpp
palette.cpp
palette_io.cpp
primitives.cpp
remap.cpp
- rgbmap.cpp
+ rgbmap_rgb5a3.cpp
selected_frames.cpp
selected_layers.cpp
slice.cpp
diff --git a/src/doc/doc.h b/src/doc/doc.h
index 17203c193..a58c20674 100644
--- a/src/doc/doc.h
+++ b/src/doc/doc.h
@@ -1,5 +1,5 @@
// Aseprite Document Library
-// Copyright (C) 2019 Igara Studio S.A.
+// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@@ -33,6 +33,7 @@
#include "doc/primitives_fast.h"
#include "doc/remap.h"
#include "doc/rgbmap.h"
+#include "doc/rgbmap_rgb5a3.h"
#include "doc/slice.h"
#include "doc/slices.h"
#include "doc/sprite.h"
diff --git a/src/doc/object_type.h b/src/doc/object_type.h
index 31f897375..d09084500 100644
--- a/src/doc/object_type.h
+++ b/src/doc/object_type.h
@@ -1,5 +1,5 @@
// Aseprite Document Library
-// Copyright (C) 2019 Igara Studio S.A.
+// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
@@ -12,20 +12,25 @@
namespace doc {
enum class ObjectType {
- Unknown,
- Image,
- Palette,
- RgbMap,
- Path,
- Mask,
- Cel,
- CelData,
- LayerImage,
- LayerGroup,
- Sprite,
- Document,
- Tag,
- Slice,
+ Unknown = 0,
+ Image = 1,
+ Palette = 2,
+
+ // Deprecated values, we cannot re-use these indexes because
+ // backup sessions use them (check readLayer() function in
+ // src/app/crash/read_document.cpp).
+ //RgbMap = 3,
+ //Path = 4,
+
+ Mask = 5,
+ Cel = 6,
+ CelData = 7,
+ LayerImage = 8,
+ LayerGroup = 9,
+ Sprite = 10,
+ Document = 11,
+ Tag = 12,
+ Slice = 13,
};
} // namespace doc
diff --git a/src/doc/octree_map.cpp b/src/doc/octree_map.cpp
new file mode 100644
index 000000000..1bf03a1bd
--- /dev/null
+++ b/src/doc/octree_map.cpp
@@ -0,0 +1,311 @@
+// Aseprite
+// Copyright (c) 2020 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/octree_map.h"
+
+#include "doc/palette.h"
+
+#define MID_VALUE_COLOR ((rgba_r_mask / 2) + 1)
+#define MIN_LEVEL_OCTREE_DEEP 3
+
+namespace doc {
+
+void OctreeNode::addColor(color_t c, int level, OctreeNode* parent,
+ int paletteIndex, int levelDeep)
+{
+ m_parent = parent;
+ if (level >= levelDeep) {
+ m_leafColor.add(c);
+ m_paletteIndex = paletteIndex;
+ return;
+ }
+ int index = getOctet(c, level);
+ if (!m_children) {
+ m_children.reset(new std::array());
+ }
+ (*m_children)[index].addColor(c, level + 1, this, paletteIndex, levelDeep);
+}
+
+void OctreeNode::fillOrphansNodes(const Palette* palette,
+ const color_t upstreamBranchColor,
+ const int level)
+{
+ for (int i=0; i<8; i++) {
+ if ((*m_children)[i].m_children)
+ (*m_children)[i].fillOrphansNodes(
+ palette,
+ upstreamBranchColor + octetToBranchColor(i, level),
+ level + 1);
+ else if (!((*m_children)[i].isLeaf())) {
+ // Here the node IS NOT a Leaf and HAS NOT children
+ // So, we must assign palette index to the current node
+ // to fill the "map holes" (i.e "death tree branches")
+ // BUT, if the level is low (a few bits to identify a color)
+ // 0, 1, 2, or 3, we need to create branchs/Leaves until
+ // the desired minimum color MSB bits.
+ if (level < MIN_LEVEL_OCTREE_DEEP) {
+ (*m_children)[i].fillMostSignificantNodes(level);
+ i--;
+ continue;
+ }
+ int currentBranchColorAdd = octetToBranchColor(i, level);
+ int midColorAdd = MID_VALUE_COLOR >> (level + 1);
+ midColorAdd = ((midColorAdd) << rgba_r_shift)
+ + ((midColorAdd) << rgba_g_shift)
+ + ((midColorAdd) << rgba_b_shift);
+ color_t branchColorMed = rgba_a_mask
+ + upstreamBranchColor
+ + currentBranchColorAdd
+ + midColorAdd;
+ int indexMed = palette->findBestfit2(rgba_getr(branchColorMed),
+ rgba_getg(branchColorMed),
+ rgba_getb(branchColorMed));
+ (*m_children)[i].paletteIndex(indexMed);
+ }
+ }
+}
+
+void OctreeNode::fillMostSignificantNodes(int level)
+{
+ if (level < MIN_LEVEL_OCTREE_DEEP) {
+ m_children.reset(new std::array());
+ level++;
+ for (int i=0; i<8; i++)
+ (*m_children)[i].fillMostSignificantNodes(level);
+ }
+}
+
+int OctreeNode::mapColor(int r, int g, int b, int level) const
+{
+ int indexLevel = ( (b >> (7 - level)) & 1) * 4
+ + ((g >> (7 - level)) & 1) * 2
+ + ((r >> (7 - level)) & 1);
+ if ((*m_children)[indexLevel].m_children)
+ return (*m_children)[indexLevel].mapColor(r, g, b, level+1);
+ return (*m_children)[indexLevel].m_paletteIndex;
+}
+
+void OctreeNode::collectLeafNodes(std::vector* leavesVector, int& paletteIndex)
+{
+ for (int i=0; i<8; i++) {
+ if ((*m_children)[i].isLeaf()) {
+ (*m_children)[i].paletteIndex(paletteIndex);
+ leavesVector->push_back(&(*m_children)[i]);
+ paletteIndex++;
+ }
+ else if ((*m_children)[i].m_children)
+ (*m_children)[i].collectLeafNodes(leavesVector, paletteIndex);
+ }
+}
+
+// removeLeaves(): remove leaves from a common parent
+// auxParentVector: i/o addreess of an auxiliary parent leaf Vector from outside this function.
+// rootLeavesVector: i/o address of the m_root->m_leavesVector
+int OctreeNode::removeLeaves(std::vector& auxParentVector,
+ std::vector& rootLeavesVector,
+ int octreeDeep)
+{
+ // Apply to OctreeNode which has children which are leaf nodes
+ int result = 0;
+ for (int i=octreeDeep; i>=0; i--) {
+ if ((*m_children)[i].isLeaf()) {
+ m_leafColor.add((*m_children)[i].getLeafColor());
+ result++;
+ if (rootLeavesVector[rootLeavesVector.size()-1] == &((*m_children)[i]))
+ rootLeavesVector.pop_back();
+ }
+ }
+ auxParentVector.push_back(this);
+ return result - 1;
+}
+
+OctreeMap::OctreeMap()
+ : m_palette(nullptr)
+ , m_modifications(0)
+{
+}
+
+bool OctreeMap::makePalette(Palette* palette,
+ const int colorCount,
+ const int levelDeep)
+{
+ if (m_root.children()) {
+ // We create paletteIndex to get a "global like" variable, in collectLeafNodes
+ // function, the purpose is having a incremental variable in the stack memory
+ // sharend between all recursive calls of collectLeafNodes.
+ int paletteIndex = 0;
+ m_root.collectLeafNodes(&m_leavesVector, paletteIndex);
+ }
+
+ // If we can improve the octree accuracy, makePalette returns false, then
+ // outside from this function we must re-construct the octreeMap all again with
+ // deep level equal to 8.
+ if (levelDeep == 7 && m_leavesVector.size() < colorCount)
+ return false;
+
+
+ std::vector auxLeavesVector; // auxiliary collapsed node accumulator
+ bool keepReducingMap = true;
+
+ for (int level = levelDeep; level > -1; level--) {
+ for (int i=m_leavesVector.size()-1; i>=0; i--) {
+ if (m_leavesVector.size() + auxLeavesVector.size() <= colorCount) {
+ for (int j=0; j < auxLeavesVector.size(); j++)
+ m_leavesVector.push_back(auxLeavesVector[auxLeavesVector.size() - 1 - j]);
+ keepReducingMap = false;
+ break;
+ }
+ else if (m_leavesVector.size() == 0) {
+ // When colorCount is < 8, auxLeavesVector->size() could reach the 8 size,
+ // if this is true and we don't stop the regular removeLeaves algorithm,
+ // the 8 remains colors will collapse in one.
+ // So, we have to reduce color with other method:
+ // Sort colors by pixelCount (most pixelCount on front of sortedVector),
+ // then:
+ // Blend in pairs from the least pixelCount colors.
+ if (auxLeavesVector.size() <= 8 && colorCount < 8 && colorCount > 0) {
+ // Sort colors:
+ std::vector sortedVector;
+ int auxVectorSize = auxLeavesVector.size();
+ for (int k=0; k < auxVectorSize; k++) {
+ int maximumCount = -1;
+ int maximumIndex = -1;
+ for (int j=0; j < auxLeavesVector.size(); j++) {
+ if (auxLeavesVector[j]->getLeafColor().pixelCount() > maximumCount) {
+ maximumCount = auxLeavesVector[j]->getLeafColor().pixelCount();
+ maximumIndex = j;
+ }
+ }
+ sortedVector.push_back(auxLeavesVector[maximumIndex]);
+ auxLeavesVector.erase(auxLeavesVector.begin() + maximumIndex);
+ }
+ // End Sort colors.
+ // Blend colors:
+ for(;;) {
+ if (sortedVector.size() <= colorCount) {
+ for (int k=0; kgetLeafColor()
+ .add(sortedVector[sortedVector.size()-1]->getLeafColor());
+ sortedVector.pop_back();
+ }
+ // End Blend colors:
+ keepReducingMap = false;
+ break;
+ }
+ else
+ break;
+ }
+
+ m_leavesVector.back()->parent()->removeLeaves(auxLeavesVector, m_leavesVector);
+ }
+ if (keepReducingMap) {
+ // Copy collapsed leaves to m_leavesVector
+ int auxLeavesVectorSize = auxLeavesVector.size();
+ for (int i=0; iresize(leafCount);
+ else{
+ palette->resize(leafCount + 1);
+ palette->setEntry(0, m_maskColor);
+ aux = 1;
+ }
+
+ for (int i=0; isetEntry(i+aux, m_leavesVector[i]->getLeafColor().normalizeColor().LeafColorToColor());
+
+ return true;
+}
+
+void OctreeMap::fillOrphansNodes(const Palette* palette)
+{
+ m_root.fillOrphansNodes(palette, 0, 0);
+}
+
+void OctreeMap::feedWithImage(const Image* image,
+ const color_t maskColor,
+ const int levelDeep)
+{
+ ASSERT(image);
+ ASSERT(image->pixelFormat() == IMAGE_RGB);
+ uint32_t color;
+ const LockImageBits bits(image);
+ auto it = bits.begin(), end = bits.end();
+
+ for (; it != end; ++it) {
+ color = *it;
+ if (rgba_geta(color) > 0)
+ addColor(color, levelDeep);
+ }
+
+ m_maskColor = maskColor;
+}
+
+int OctreeMap::mapColor(color_t rgba) const
+{
+ if (m_root.children())
+ return m_root.mapColor(rgba_getr(rgba),
+ rgba_getg(rgba),
+ rgba_getb(rgba), 0);
+ else
+ return -1;
+}
+
+void OctreeMap::regenerateMap(const Palette* palette, const int maskIndex)
+{
+ ASSERT(palette);
+ if (!palette)
+ return;
+
+ // Skip useless regenerations
+ if (m_palette == palette &&
+ m_modifications == palette->getModifications() &&
+ m_maskIndex == maskIndex)
+ return;
+
+ m_root = OctreeNode();
+ m_leavesVector.clear();
+ m_maskIndex = maskIndex;
+ int maskColorBestFitIndex;
+ if (maskIndex < 0) {
+ m_maskColor = 0x00ffffff;
+ maskColorBestFitIndex = -1;
+ }
+ else {
+ m_maskColor = palette->getEntry(maskIndex);
+ maskColorBestFitIndex = palette->findBestfit(rgba_getr(m_maskColor),
+ rgba_getg(m_maskColor),
+ rgba_getb(m_maskColor), 255, maskIndex);
+ }
+
+ for (int i=0; isize(); i++) {
+ if (i == maskIndex) {
+ m_root.addColor(palette->entry(i), 0, &m_root, maskColorBestFitIndex, 8);
+ continue;
+ }
+ m_root.addColor(palette->entry(i), 0, &m_root, i, 8);
+ }
+ m_root.fillOrphansNodes(palette, 0, 0);
+
+ m_palette = palette;
+ m_modifications = palette->getModifications();
+}
+
+} // namespace doc
diff --git a/src/doc/octree_map.h b/src/doc/octree_map.h
new file mode 100644
index 000000000..9243cf913
--- /dev/null
+++ b/src/doc/octree_map.h
@@ -0,0 +1,176 @@
+// Aseprite
+// Copyright (c) 2020 Igara Studio S.A.
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifndef DOC_OCTREEMAP_H_INCLUDED
+#define DOC_OCTREEMAP_H_INCLUDED
+#pragma once
+
+#include "doc/image_impl.h"
+#include "doc/palette.h"
+#include "doc/rgbmap.h"
+
+#include
+#include
+#include
+
+namespace doc {
+
+class OctreeNode {
+private:
+ class LeafColor {
+ public:
+ LeafColor() :
+ m_r(0),
+ m_g(0),
+ m_b(0),
+ m_pixelCount(0)
+ {}
+
+ LeafColor(int r, int g, int b, int pixelCount) :
+ m_r((double)r),
+ m_g((double)g),
+ m_b((double)b),
+ m_pixelCount(pixelCount)
+ {}
+
+ void add(color_t c)
+ {
+ m_r += rgba_getr(c);
+ m_g += rgba_getg(c);
+ m_b += rgba_getb(c);
+ m_pixelCount++;
+ }
+
+ void add(LeafColor leafColor)
+ {
+ m_r += leafColor.m_r;
+ m_g += leafColor.m_g;
+ m_b += leafColor.m_b;
+ m_pixelCount += leafColor.m_pixelCount;
+ }
+
+ LeafColor normalizeColor()
+ {
+ int auxR = (((int)m_r) % m_pixelCount > m_pixelCount / 2)? 1 : 0;
+ int auxG = (((int)m_g) % m_pixelCount > m_pixelCount / 2)? 1 : 0;
+ int auxB = (((int)m_b) % m_pixelCount > m_pixelCount / 2)? 1 : 0;
+ return LeafColor(m_r / m_pixelCount + auxR,
+ m_g / m_pixelCount + auxG,
+ m_b / m_pixelCount + auxB,
+ m_pixelCount);
+ }
+
+ color_t LeafColorToColor()
+ {
+ return 0xff000000 + (((int)m_b) << 16) + (((int)m_g) << 8) + (int)m_r;
+ }
+
+ int pixelCount() { return m_pixelCount; }
+
+private:
+ double m_r;
+ double m_g;
+ double m_b;
+ int m_pixelCount;
+ };
+
+public:
+ OctreeNode()
+ {
+ m_paletteIndex = 0;
+ m_children.reset(nullptr);
+ m_parent = nullptr;
+ }
+
+ static int getOctet(color_t c, int level)
+ {
+ int aux = c >> (7 - level);
+ int octet = aux & 1;
+ aux = aux >> (7);
+ octet += (aux & 2);
+ return octet + ((aux >> 7) & 4);
+ }
+
+ static color_t octetToBranchColor(int octet, int level)
+ {
+ int auxR = (octet & 1) << (7 - level);
+ int auxG = (octet & 2) << (14 - level);
+ int auxB = (octet & 4) << (21 - level);
+ return auxR + auxG + auxB;
+ }
+
+ OctreeNode* parent() const { return m_parent; }
+ std::array* children() const { return m_children.get(); }
+ LeafColor getLeafColor() const { return m_leafColor; }
+
+ void addColor(color_t c, int level, OctreeNode* parent,
+ int paletteIndex = 0, int levelDeep = 7);
+
+ void fillOrphansNodes(const Palette* palette,
+ const color_t upstreamBranchColor,
+ const int level);
+
+ void fillMostSignificantNodes(int level);
+
+ int mapColor(int r, int g, int b, int level) const;
+
+ void collectLeafNodes(std::vector* leavesVector, int& paletteIndex);
+
+ // removeLeaves(): remove leaves from a common parent
+ // auxParentVector: i/o addreess of an auxiliary parent leaf Vector from outside.
+ // rootLeavesVector: i/o address of the m_root->m_leavesVector
+ int removeLeaves(std::vector& auxParentVector,
+ std::vector& rootLeavesVector,
+ int octreeDeep = 7);
+
+private:
+ bool isLeaf() { return m_leafColor.pixelCount() > 0; }
+ void paletteIndex(int index) { m_paletteIndex = index; }
+
+ LeafColor m_leafColor;
+ int m_paletteIndex;
+ std::unique_ptr> m_children;
+ OctreeNode* m_parent;
+};
+
+class OctreeMap : public RgbMap {
+public:
+ OctreeMap();
+
+ void addColor(color_t color, int levelDeep = 7)
+ {
+ m_root.addColor(color, 0, &m_root, 0, levelDeep);
+ }
+
+ // makePalette returns true if a 7 level octreeDeep is OK, and false
+ // if we can add ONE level deep.
+ bool makePalette(Palette* palette,
+ const int colorCount,
+ const int levelDeep = 7);
+
+ void feedWithImage(const Image* image,
+ const color_t maskColor,
+ const int levelDeep = 7);
+
+ // RgbMap impl
+ void regenerateMap(const Palette* palette, const int maskIndex) override;
+ int mapColor(color_t rgba) const override;
+
+ int getModifications() const { return m_modifications; };
+
+private:
+ void fillOrphansNodes(const Palette* palette);
+
+ OctreeNode m_root;
+ std::vector m_leavesVector;
+ const Palette* m_palette;
+ int m_modifications;
+ int m_maskIndex;
+ color_t m_maskColor;
+};
+
+} // namespace doc
+#endif
diff --git a/src/doc/palette.cpp b/src/doc/palette.cpp
index e1487089c..9894928d9 100644
--- a/src/doc/palette.cpp
+++ b/src/doc/palette.cpp
@@ -272,6 +272,31 @@ int Palette::findBestfit(int r, int g, int b, int a, int mask_index) const
return bestfit;
}
+int Palette::findBestfit2(int r, int g, int b) const
+{
+ ASSERT(r >= 0 && r <= 255);
+ ASSERT(g >= 0 && g <= 255);
+ ASSERT(b >= 0 && b <= 255);
+
+ int bestfit = 0;
+ int lowest = std::numeric_limits::max();
+ int size = m_colors.size();
+
+ for (int i=0; i
+#include "doc/color.h"
namespace doc {
class Palette;
- // It acts like a cache for Palette:findBestfit() calls.
- class RgbMap : public Object {
- // Bit activated on m_map entries that aren't yet calculated.
- const int INVALID = 256;
-
+ // Matches a RGBA value with an index in a color palette (doc::Palette).
+ class RgbMap {
public:
- RgbMap();
+ virtual ~RgbMap() { }
- bool match(const Palette* palette) const;
- void regenerate(const Palette* palette, int mask_index);
+ virtual void regenerateMap(const Palette* palette, const int maskIndex) = 0;
- int mapColor(int r, int g, int b, int a) const {
+ // Should return the best index in a palette that matches the given RGBA values.
+ virtual int mapColor(const color_t rgba) const = 0;
+
+ 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);
- // bits -> bbbbbgggggrrrrraaa
- int i = (a>>5) | ((b>>3) << 3) | ((g>>3) << 8) | ((r>>3) << 13);
- int v = m_map[i];
- return (v & INVALID) ? generateEntry(i, r, g, b, a): v;
+ return mapColor(rgba(r, g, b, a));
}
- int maskIndex() const { return m_maskIndex; }
-
- 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(RgbMap);
};
} // namespace doc
diff --git a/src/doc/rgbmap_algorithm.h b/src/doc/rgbmap_algorithm.h
new file mode 100644
index 000000000..c8f686ce8
--- /dev/null
+++ b/src/doc/rgbmap_algorithm.h
@@ -0,0 +1,21 @@
+// Aseprite Document Library
+// Copyright (c) 2020 Igara Studio S.A.
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifndef DOC_RGBMAP_ALGORITHM_H_INCLUDED
+#define DOC_RGBMAP_ALGORITHM_H_INCLUDED
+#pragma once
+
+namespace doc {
+
+ enum class RgbMapAlgorithm {
+ RGB5A3,
+ OCTREE,
+ DEFAULT = RGB5A3
+ };
+
+} // namespace doc
+
+#endif
diff --git a/src/doc/rgbmap.cpp b/src/doc/rgbmap_rgb5a3.cpp
similarity index 64%
rename from src/doc/rgbmap.cpp
rename to src/doc/rgbmap_rgb5a3.cpp
index 4a39dffb7..5f0ec2653 100644
--- a/src/doc/rgbmap.cpp
+++ b/src/doc/rgbmap_rgb5a3.cpp
@@ -1,4 +1,5 @@
// Aseprite Document Library
+// Copyright (c) 2020 Igara Studio S.A.
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
@@ -8,7 +9,7 @@
#include "config.h"
#endif
-#include "doc/rgbmap.h"
+#include "doc/rgbmap_rgb5a3.h"
#include "doc/color_scales.h"
#include "doc/palette.h"
@@ -21,33 +22,32 @@ namespace doc {
#define ASIZE 8
#define MAPSIZE (RSIZE*GSIZE*BSIZE*ASIZE)
-RgbMap::RgbMap()
- : Object(ObjectType::RgbMap)
- , m_map(MAPSIZE)
- , m_palette(NULL)
+RgbMapRGB5A3::RgbMapRGB5A3()
+ : m_map(MAPSIZE)
+ , m_palette(nullptr)
, m_modifications(0)
, m_maskIndex(0)
{
}
-bool RgbMap::match(const Palette* palette) const
+void RgbMapRGB5A3::regenerateMap(const Palette* palette, int maskIndex)
{
- return (m_palette == palette &&
- m_modifications == palette->getModifications());
-}
+ // Skip useless regenerations
+ if (m_palette == palette &&
+ m_modifications == palette->getModifications() &&
+ m_maskIndex == maskIndex)
+ return;
-void RgbMap::regenerate(const Palette* palette, int mask_index)
-{
m_palette = palette;
m_modifications = palette->getModifications();
- m_maskIndex = mask_index;
+ m_maskIndex = maskIndex;
// Mark all entries as invalid (need to be regenerated)
for (uint16_t& entry : m_map)
entry |= INVALID;
}
-int RgbMap::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] =
m_palette->findBestfit(
diff --git a/src/doc/rgbmap_rgb5a3.h b/src/doc/rgbmap_rgb5a3.h
new file mode 100644
index 000000000..05def365b
--- /dev/null
+++ b/src/doc/rgbmap_rgb5a3.h
@@ -0,0 +1,59 @@
+// Aseprite Document Library
+// Copyright (c) 2020 Igara Studio S.A.
+// Copyright (c) 2001-2016 David Capello
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifndef DOC_RGBMAP_RGB5A3_H_INCLUDED
+#define DOC_RGBMAP_RGB5A3_H_INCLUDED
+#pragma once
+
+#include "base/debug.h"
+#include "base/disable_copying.h"
+#include "doc/object.h"
+#include "doc/rgbmap.h"
+
+#include
+
+namespace doc {
+
+ class Palette;
+
+ // It acts like a cache for Palette:findBestfit() calls.
+ class RgbMapRGB5A3 : public RgbMap {
+ // Bit activated on m_map entries that aren't yet calculated.
+ const int INVALID = 256;
+
+ public:
+ RgbMapRGB5A3();
+
+ // RgbMap impl
+ void regenerateMap(const Palette* palette, int maskIndex) override;
+ int mapColor(const color_t rgba) const override {
+ const int r = rgba_getr(rgba);
+ const int g = rgba_getg(rgba);
+ const int b = rgba_getb(rgba);
+ const int a = rgba_geta(rgba);
+ // bits -> bbbbbgggggrrrrraaa
+ const int i = (a>>5) | ((b>>3) << 3) | ((g>>3) << 8) | ((r>>3) << 13);
+ const int v = m_map[i];
+ return (v & INVALID) ? generateEntry(i, r, g, b, a): v;
+ }
+
+ int maskIndex() const { return m_maskIndex; }
+
+ 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);
+ };
+
+} // namespace doc
+
+#endif
diff --git a/src/doc/sprite.cpp b/src/doc/sprite.cpp
index 05c5936e2..b1572d3d1 100644
--- a/src/doc/sprite.cpp
+++ b/src/doc/sprite.cpp
@@ -18,10 +18,11 @@
#include "doc/cels_range.h"
#include "doc/image_impl.h"
#include "doc/layer.h"
+#include "doc/octree_map.h"
#include "doc/palette.h"
#include "doc/primitives.h"
#include "doc/remap.h"
-#include "doc/rgbmap.h"
+#include "doc/rgbmap_rgb5a3.h"
#include "doc/tag.h"
#include
@@ -31,9 +32,7 @@
namespace doc {
-//////////////////////////////////////////////////////////////////////
-// Constructors/Destructor
-
+static RgbMapAlgorithm g_rgbMapAlgorithm = RgbMapAlgorithm::DEFAULT;
static gfx::Rect g_defaultGridBounds(0, 0, 16, 16);
// static
@@ -48,6 +47,21 @@ void Sprite::SetDefaultGridBounds(const gfx::Rect& defGridBounds)
g_defaultGridBounds = defGridBounds;
}
+// static
+RgbMapAlgorithm Sprite::DefaultRgbMapAlgorithm()
+{
+ return g_rgbMapAlgorithm;
+}
+
+// static
+void Sprite::SetDefaultRgbMapAlgorithm(const RgbMapAlgorithm mapAlgo)
+{
+ g_rgbMapAlgorithm = mapAlgo;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Constructors/Destructor
+
Sprite::Sprite(const ImageSpec& spec,
int ncolors)
: Object(ObjectType::Sprite)
@@ -58,7 +72,6 @@ Sprite::Sprite(const ImageSpec& spec,
, m_frlens(1, 100) // First frame with 100 msecs of duration
, m_root(new LayerGroup(this))
, m_gridBounds(Sprite::DefaultGridBounds())
- , m_rgbMap(nullptr) // Initial RGB map
, m_tags(this)
, m_slices(this)
{
@@ -98,9 +111,6 @@ Sprite::~Sprite()
for (; it != end; ++it)
delete *it; // palette
}
-
- // Destroy RGB map
- delete m_rgbMap;
}
// static
@@ -345,27 +355,46 @@ void Sprite::deletePalette(frame_t frame)
}
}
-RgbMap* Sprite::rgbMap(frame_t frame) const
+Sprite::RgbMapFor Sprite::rgbMapForSprite() const
{
- return rgbMap(frame, backgroundLayer() ? RgbMapFor::OpaqueLayer:
- RgbMapFor::TransparentLayer);
+ return backgroundLayer() ? RgbMapFor::OpaqueLayer:
+ RgbMapFor::TransparentLayer;
}
-RgbMap* Sprite::rgbMap(frame_t frame, RgbMapFor forLayer) const
+RgbMap* Sprite::rgbMap(const frame_t frame) const
+{
+ return rgbMap(frame, rgbMapForSprite());
+}
+
+RgbMap* Sprite::rgbMap(const frame_t frame,
+ const RgbMapFor forLayer) const
+{
+ return rgbMap(frame,
+ forLayer,
+ g_rgbMapAlgorithm);
+}
+
+RgbMap* Sprite::rgbMap(const frame_t frame,
+ const RgbMapFor forLayer,
+ const RgbMapAlgorithm mapAlgo) const
{
int maskIndex = (forLayer == RgbMapFor::OpaqueLayer ?
-1: transparentColor());
- if (m_rgbMap == NULL) {
- m_rgbMap = new RgbMap();
- m_rgbMap->regenerate(palette(frame), maskIndex);
- }
- else if (!m_rgbMap->match(palette(frame)) ||
- m_rgbMap->maskIndex() != maskIndex) {
- m_rgbMap->regenerate(palette(frame), maskIndex);
+ if (!m_rgbMap || m_rgbMapAlgorithm != mapAlgo) {
+ m_rgbMapAlgorithm = mapAlgo;
+ switch (m_rgbMapAlgorithm) {
+ case RgbMapAlgorithm::RGB5A3: m_rgbMap.reset(new RgbMapRGB5A3); break;
+ case RgbMapAlgorithm::OCTREE: m_rgbMap.reset(new OctreeMap); break;
+ default:
+ m_rgbMap.reset(nullptr);
+ ASSERT(false);
+ return nullptr;
+ }
}
- return m_rgbMap;
+ m_rgbMap->regenerateMap(palette(frame), maskIndex);
+ return m_rgbMap.get();
}
//////////////////////////////////////////////////////////////////////
diff --git a/src/doc/sprite.h b/src/doc/sprite.h
index 1e6915676..2e63dee54 100644
--- a/src/doc/sprite.h
+++ b/src/doc/sprite.h
@@ -1,5 +1,5 @@
// Aseprite Document Library
-// Copyright (C) 2018-2019 Igara Studio S.A.
+// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@@ -21,10 +21,12 @@
#include "doc/object.h"
#include "doc/pixel_format.h"
#include "doc/pixel_ratio.h"
+#include "doc/rgbmap_algorithm.h"
#include "doc/slices.h"
#include "doc/tags.h"
#include "gfx/rect.h"
+#include
#include
#define DOC_SPRITE_MAX_WIDTH 65535
@@ -42,6 +44,7 @@ namespace doc {
class Palette;
class Remap;
class RgbMap;
+ class RgbMapRGB5A3;
class SelectedFrames;
typedef std::vector PalettesList;
@@ -100,8 +103,11 @@ namespace doc {
color_t transparentColor() const { return m_spec.maskColor(); }
void setTransparentColor(color_t color);
+ // Defaults
static gfx::Rect DefaultGridBounds();
static void SetDefaultGridBounds(const gfx::Rect& defGridBounds);
+ static RgbMapAlgorithm DefaultRgbMapAlgorithm();
+ static void SetDefaultRgbMapAlgorithm(const RgbMapAlgorithm mapAlgo);
const gfx::Rect& gridBounds() const { return m_gridBounds; }
void setGridBounds(const gfx::Rect& rc) { m_gridBounds = rc; }
@@ -131,8 +137,13 @@ namespace doc {
void deletePalette(frame_t frame);
- RgbMap* rgbMap(frame_t frame) const;
- RgbMap* rgbMap(frame_t frame, RgbMapFor forLayer) const;
+ RgbMapFor rgbMapForSprite() const;
+ RgbMap* rgbMap(const frame_t frame) const;
+ RgbMap* rgbMap(const frame_t frame,
+ const RgbMapFor forLayer) const;
+ RgbMap* rgbMap(const frame_t frame,
+ const RgbMapFor forLayer,
+ const RgbMapAlgorithm mapAlgo) const;
////////////////////////////////////////
// Frames
@@ -199,7 +210,8 @@ namespace doc {
gfx::Rect m_gridBounds; // grid settings
// Current rgb map
- mutable RgbMap* m_rgbMap;
+ mutable RgbMapAlgorithm m_rgbMapAlgorithm;
+ mutable std::unique_ptr m_rgbMap;
Tags m_tags;
Slices m_slices;
diff --git a/src/filters/brightness_contrast_filter.cpp b/src/filters/brightness_contrast_filter.cpp
index 30b2b5a2f..3d67f3d09 100644
--- a/src/filters/brightness_contrast_filter.cpp
+++ b/src/filters/brightness_contrast_filter.cpp
@@ -138,10 +138,7 @@ void BrightnessContrastFilter::applyToIndexed(FilterManager* filterMgr)
color_t c = pal->getEntry(*(src_address++));
applyFilterToRgb(target, c);
- *(dst_address++) = rgbmap->mapColor(rgba_getr(c),
- rgba_getg(c),
- rgba_getb(c),
- rgba_geta(c));
+ *(dst_address++) = rgbmap->mapColor(c);
}
}
diff --git a/src/filters/hue_saturation_filter.cpp b/src/filters/hue_saturation_filter.cpp
index f3cd8ee0f..9cfe6975c 100644
--- a/src/filters/hue_saturation_filter.cpp
+++ b/src/filters/hue_saturation_filter.cpp
@@ -168,10 +168,7 @@ void HueSaturationFilter::applyToIndexed(FilterManager* filterMgr)
color_t c = pal->getEntry(*(src_address++));
applyFilterToRgb(target, c);
- *(dst_address++) = rgbmap->mapColor(rgba_getr(c),
- rgba_getg(c),
- rgba_getb(c),
- rgba_geta(c));
+ *(dst_address++) = rgbmap->mapColor(c);
}
}
diff --git a/src/render/quantization.cpp b/src/render/quantization.cpp
index 498838bc0..4129e87d9 100644
--- a/src/render/quantization.cpp
+++ b/src/render/quantization.cpp
@@ -13,10 +13,10 @@
#include "doc/image_impl.h"
#include "doc/layer.h"
+#include "doc/octree_map.h"
#include "doc/palette.h"
#include "doc/primitives.h"
#include "doc/remap.h"
-#include "doc/rgbmap.h"
#include "doc/sprite.h"
#include "render/dithering.h"
#include "render/error_diffusion.h"
@@ -42,9 +42,14 @@ Palette* create_palette_from_sprite(
const bool withAlpha,
Palette* palette,
TaskDelegate* delegate,
- const bool newBlend)
+ const bool newBlend,
+ const RgbMapAlgorithm mappingAlgorithm)
{
PaletteOptimizer optimizer;
+ OctreeMap octreemap;
+ const color_t maskColor = (sprite->backgroundLayer()
+ && sprite->allLayersCount() == 1) ? 0x00FFFFFF:
+ sprite->transparentColor();
if (!palette)
palette = new Palette(fromFrame, 256);
@@ -58,7 +63,15 @@ Palette* create_palette_from_sprite(
render.setNewBlend(newBlend);
for (frame_t frame=fromFrame; frame<=toFrame; ++frame) {
render.renderSprite(flat_image.get(), sprite, frame);
- optimizer.feedWithImage(flat_image.get(), withAlpha);
+
+ switch (mappingAlgorithm) {
+ case RgbMapAlgorithm::RGB5A3:
+ optimizer.feedWithImage(flat_image.get(), withAlpha);
+ break;
+ case RgbMapAlgorithm::OCTREE:
+ octreemap.feedWithImage(flat_image.get(), maskColor);
+ break;
+ }
if (delegate) {
if (!delegate->continueTask())
@@ -69,12 +82,35 @@ Palette* create_palette_from_sprite(
}
}
- // Generate an optimized palette
- optimizer.calculate(
- palette,
- // Transparent color is needed if we have transparent layers
- (sprite->backgroundLayer() &&
- sprite->allLayersCount() == 1 ? -1: sprite->transparentColor()));
+ switch (mappingAlgorithm) {
+ case RgbMapAlgorithm::RGB5A3:
+ // Generate an optimized palette
+ optimizer.calculate(
+ palette,
+ // Transparent color is needed if we have transparent layers
+ (sprite->backgroundLayer() &&
+ sprite->allLayersCount() == 1 ? -1: sprite->transparentColor()));
+ break;
+ case RgbMapAlgorithm::OCTREE:
+ if (!octreemap.makePalette(palette, palette->size())) {
+ // We can use an 8-bit deep octree map, instead of 7-bit of the
+ // first attempt.
+ octreemap = OctreeMap();
+ for (frame_t frame=fromFrame; frame<=toFrame; ++frame) {
+ render.renderSprite(flat_image.get(), sprite, frame);
+ octreemap.feedWithImage(flat_image.get(), maskColor , 8);
+ if (delegate) {
+ if (!delegate->continueTask())
+ return nullptr;
+
+ delegate->notifyTaskProgress(
+ double(frame-fromFrame+1) / double(toFrame-fromFrame+1));
+ }
+ }
+ octreemap.makePalette(palette, palette->size(), 8);
+ }
+ break;
+ }
return palette;
}
@@ -179,7 +215,7 @@ Image* convert_pixel_format(
if (a == 0)
*dst_it = new_mask_color;
else if (rgbmap)
- *dst_it = rgbmap->mapColor(r, g, b, a);
+ *dst_it = rgbmap->mapColor(c);
else
*dst_it = palette->findBestfit(r, g, b, a, new_mask_color);
}
diff --git a/src/render/quantization.h b/src/render/quantization.h
index 214a2ad38..1ab8627f1 100644
--- a/src/render/quantization.h
+++ b/src/render/quantization.h
@@ -11,6 +11,7 @@
#include "doc/frame.h"
#include "doc/pixel_format.h"
+#include "doc/rgbmap_algorithm.h"
#include "render/color_histogram.h"
#include
@@ -45,7 +46,8 @@ namespace render {
const bool withAlpha,
doc::Palette* newPalette, // Can be NULL to create a new palette
TaskDelegate* delegate,
- const bool newBlend);
+ const bool newBlend,
+ const RgbMapAlgorithm mappingAlgorithm);
// Changes the image pixel format. The dithering method is used only
// when you want to convert from RGB to Indexed.