From 696bb9a53794e4137aad3b7ab8bf7724b6a200df Mon Sep 17 00:00:00 2001 From: Gaspar Capello Date: Tue, 14 Apr 2020 20:01:25 -0300 Subject: [PATCH] Add possibility to use OctreeMap in create_palette_from_image() --- src/app/commands/cmd_color_quantization.cpp | 8 +-- src/app/commands/cmd_new_file.cpp | 3 +- src/app/file/file.cpp | 3 +- src/app/file/file_op_config.cpp | 1 + src/app/file/file_op_config.h | 6 ++- src/doc/octree_map.cpp | 15 ++++-- src/doc/octree_map.h | 17 +++++-- src/render/quantization.cpp | 54 +++++++++++++++++---- src/render/quantization.h | 6 ++- 9 files changed, 86 insertions(+), 27 deletions(-) diff --git a/src/app/commands/cmd_color_quantization.cpp b/src/app/commands/cmd_color_quantization.cpp index 5bf34b858..a249ca380 100644 --- a/src/app/commands/cmd_color_quantization.cpp +++ b/src/app/commands/cmd_color_quantization.cpp @@ -140,13 +140,15 @@ void ColorQuantizationCommand::onExecute(Context* ctx) SpriteJob job(reader, "Color Quantization"); const bool newBlend = Preferences::instance().experimental.newBlend(); + const RgbMapAlgorithm algorithm = Preferences::instance().experimental.rgbmapAlgorithm(); 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/file/file.cpp b/src/app/file/file.cpp index f43cb51a1..41a632aa4 100644 --- a/src/app/file/file.cpp +++ b/src/app/file/file.cpp @@ -907,7 +907,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/doc/octree_map.cpp b/src/doc/octree_map.cpp index 614344cc8..4105e8f9b 100644 --- a/src/doc/octree_map.cpp +++ b/src/doc/octree_map.cpp @@ -34,7 +34,8 @@ void OctreeNode::addColor(color_t c, int level, OctreeNode* parent, } void OctreeNode::fillOrphansNodes(const Palette* palette, - color_t upstreamBranchColor, int level) + const color_t upstreamBranchColor, + const int level) { for (int i=0; i<8; i++) { if ((*m_children)[i].m_children) @@ -131,7 +132,9 @@ OctreeMap::OctreeMap() { } -bool OctreeMap::makePalette(Palette* palette, int colorCount, int levelDeep) +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 @@ -231,18 +234,20 @@ bool OctreeMap::makePalette(Palette* palette, int colorCount, int levelDeep) return true; } -void OctreeMap::fillOrphansNodes(Palette* palette) +void OctreeMap::fillOrphansNodes(const Palette* palette) { m_root.fillOrphansNodes(palette, 0, 0); } -void OctreeMap::feedWithImage(Image* image, color_t maskColor, int levelDeep) +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); - LockImageBits::const_iterator it = bits.begin(), end = bits.end(); + auto it = bits.begin(), end = bits.end(); for (; it != end; ++it) { color = *it; diff --git a/src/doc/octree_map.h b/src/doc/octree_map.h index 6cbc3f222..9243cf913 100644 --- a/src/doc/octree_map.h +++ b/src/doc/octree_map.h @@ -109,7 +109,9 @@ public: void addColor(color_t c, int level, OctreeNode* parent, int paletteIndex = 0, int levelDeep = 7); - void fillOrphansNodes(const Palette* palette, color_t upstreamBranchColor, int level); + void fillOrphansNodes(const Palette* palette, + const color_t upstreamBranchColor, + const int level); void fillMostSignificantNodes(int level); @@ -143,10 +145,15 @@ public: m_root.addColor(color, 0, &m_root, 0, levelDeep); } - // makePalette return true if a 7 level octreeDeep is OK, and false if we can add ONE level deep. - bool makePalette(Palette* palette, int colorCount, int leveleep = 7); + // 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(Image* image, color_t maskColor, 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; @@ -155,7 +162,7 @@ public: int getModifications() const { return m_modifications; }; private: - void fillOrphansNodes(Palette* palette); + void fillOrphansNodes(const Palette* palette); OctreeNode m_root; std::vector m_leavesVector; diff --git a/src/render/quantization.cpp b/src/render/quantization.cpp index 657ced0b1..7617861cd 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 "gfx/hsv.h" #include "gfx/rgb.h" @@ -44,9 +44,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); @@ -60,7 +65,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()) @@ -71,12 +84,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; } diff --git a/src/render/quantization.h b/src/render/quantization.h index 7e8876840..27fe354c8 100644 --- a/src/render/quantization.h +++ b/src/render/quantization.h @@ -1,5 +1,5 @@ // Aseprite Rener 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. @@ -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.