mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-29 19:20:09 +00:00
Add OctreeMap as an alternative RgbMap implementation to RgbMapRGB53A
There is a new experimental option to switch between both RgbMap implementations.
This commit is contained in:
parent
ad31c9b7a8
commit
886fdf8b3f
@ -190,6 +190,7 @@
|
||||
<option id="load_wintab_driver" type="bool" default="true" />
|
||||
<option id="flash_layer" type="bool" default="false" />
|
||||
<option id="nonactive_layers_opacity" type="int" default="255" />
|
||||
<option id="rgbmap_algorithm" type="doc::RgbMapAlgorithm" default="doc::RgbMapAlgorithm::DEFAULT" />
|
||||
</section>
|
||||
<section id="news">
|
||||
<option id="cache_file" type="std::string" />
|
||||
|
@ -971,6 +971,11 @@ 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?
|
||||
|
@ -489,6 +489,10 @@
|
||||
<label text="@.non_active_layer_opacity" />
|
||||
<slider id="nonactive_layers_opacity" min="0" max="255" width="128" />
|
||||
</hbox>
|
||||
<hbox>
|
||||
<label text="@rgbmap_algorithm_selector.label" />
|
||||
<hbox id="rgbmap_algorithm_placeholder" />
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
</panel>
|
||||
|
@ -366,6 +366,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
|
||||
|
@ -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"
|
||||
@ -337,6 +338,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);
|
||||
|
||||
@ -632,6 +637,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
|
||||
manager()->getDisplay()
|
||||
@ -1417,6 +1423,7 @@ private:
|
||||
int m_restoreUIScaling;
|
||||
std::vector<os::ColorSpacePtr> m_colorSpaces;
|
||||
std::string m_templateTextForDisplayCS;
|
||||
RgbMapAlgorithmSelector m_rgbmapAlgorithmSelector;
|
||||
};
|
||||
|
||||
class OptionsCommand : public Command {
|
||||
|
@ -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.
|
||||
@ -18,6 +18,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"
|
||||
@ -179,6 +180,17 @@ void Param<filters::ColorCurve>::fromString(const std::string& value)
|
||||
setValue(curve);
|
||||
}
|
||||
|
||||
template<>
|
||||
void Param<doc::RgbMapAlgorithm>::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
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@ -307,6 +319,15 @@ void Param<filters::ColorCurve>::fromLua(lua_State* L, int index)
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Param<doc::RgbMapAlgorithm>::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();
|
||||
|
@ -1181,7 +1181,7 @@ private:
|
||||
framePalette = framePaletteRef.get();
|
||||
|
||||
rgbmapRef.reset(new RgbMapRGB5A3);
|
||||
rgbmapRef->regenerate(framePalette, m_transparentIndex);
|
||||
rgbmapRef->regenerateMap(framePalette, m_transparentIndex);
|
||||
rgbmap = rgbmapRef.get();
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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/tiled_mode.h"
|
||||
#include "gfx/rect.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.
|
||||
@ -168,6 +168,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::TiledMode)
|
||||
FOR_ENUM(render::OnionskinPosition)
|
||||
|
||||
|
40
src/app/ui/rgbmap_algorithm_selector.cpp
Normal file
40
src/app/ui/rgbmap_algorithm_selector.cpp
Normal file
@ -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
|
26
src/app/ui/rgbmap_algorithm_selector.h
Normal file
26
src/app/ui/rgbmap_algorithm_selector.h
Normal file
@ -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
|
@ -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,6 +49,7 @@ add_library(doc-lib
|
||||
mask_io.cpp
|
||||
object.cpp
|
||||
object.cpp
|
||||
octree_map.cpp
|
||||
palette.cpp
|
||||
palette_io.cpp
|
||||
primitives.cpp
|
||||
|
297
src/doc/octree_map.cpp
Normal file
297
src/doc/octree_map.cpp
Normal file
@ -0,0 +1,297 @@
|
||||
// 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<OctreeNode, 8>());
|
||||
}
|
||||
(*m_children)[index].addColor(c, level + 1, this, paletteIndex, levelDeep);
|
||||
}
|
||||
|
||||
void OctreeNode::fillOrphansNodes(const Palette* palette,
|
||||
color_t upstreamBranchColor, 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<OctreeNode, 8>());
|
||||
level++;
|
||||
for (int i=0; i<8; i++)
|
||||
(*m_children)[i].fillMostSignificantNodes(level);
|
||||
}
|
||||
}
|
||||
|
||||
int OctreeNode::mapColor(int r, int g, int b, int level)
|
||||
{
|
||||
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<OctreeNode*>* 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<OctreeNode*>& auxParentVector,
|
||||
std::vector<OctreeNode*>& 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_root(new OctreeNode)
|
||||
, m_leavesVector(new std::vector<OctreeNode*>())
|
||||
, m_palette(nullptr)
|
||||
, m_modifications(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool OctreeMap::makePalette(Palette* palette, int colorCount, int levelDeep)
|
||||
{
|
||||
|
||||
if (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;
|
||||
root()->collectLeafNodes(m_leavesVector.get(), 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<OctreeNode*> 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<OctreeNode*> 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; k<sortedVector.size(); k++)
|
||||
m_leavesVector->push_back(sortedVector[k]);
|
||||
break;
|
||||
}
|
||||
sortedVector[sortedVector.size()-2]->getLeafColor()
|
||||
.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; i<auxLeavesVectorSize; i++)
|
||||
m_leavesVector->push_back(auxLeavesVector[auxLeavesVector.size() - 1 - i]);
|
||||
auxLeavesVector.clear();
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
int leafCount = m_leavesVector->size();
|
||||
int aux = 0;
|
||||
if (m_maskColor == 0x00FFFFFF)
|
||||
palette->resize(leafCount);
|
||||
else{
|
||||
palette->resize(leafCount + 1);
|
||||
palette->setEntry(0, m_maskColor);
|
||||
aux = 1;
|
||||
}
|
||||
|
||||
for (int i=0; i<leafCount; i++)
|
||||
palette->setEntry(i+aux, (*m_leavesVector)[i]->getLeafColor().normalizeColor().LeafColorToColor());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OctreeMap::fillOrphansNodes(Palette* palette)
|
||||
{
|
||||
m_root->fillOrphansNodes(palette, 0, 0);
|
||||
}
|
||||
|
||||
void OctreeMap::feedWithImage(Image* image, color_t maskColor, int levelDeep)
|
||||
{
|
||||
ASSERT(image);
|
||||
ASSERT(image->pixelFormat() == IMAGE_RGB);
|
||||
uint32_t color;
|
||||
const LockImageBits<RgbTraits> bits(image);
|
||||
LockImageBits<RgbTraits>::const_iterator 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 (rgba_geta(rgba) == 0 && m_maskColor >= 0)
|
||||
return m_maskColor;
|
||||
else if (m_root->children())
|
||||
return m_root->mapColor(rgba_getr(rgba),
|
||||
rgba_getg(rgba),
|
||||
rgba_geta(rgba), 0);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
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.reset(new OctreeNode());
|
||||
m_leavesVector.reset(new std::vector<OctreeNode*>());
|
||||
for (int i=0; i<palette->size(); i++)
|
||||
m_root->addColor(palette->entry(i), 0, m_root.get(), i, 8);
|
||||
m_root->fillOrphansNodes(palette, 0, 0);
|
||||
m_palette = palette;
|
||||
m_modifications = palette->getModifications();
|
||||
m_maskIndex = maskIndex;
|
||||
if (maskIndex < 0)
|
||||
m_maskColor = 0x00ffffff;
|
||||
else
|
||||
m_maskColor = palette->getEntry(maskIndex);
|
||||
}
|
||||
|
||||
} // namespace doc
|
169
src/doc/octree_map.h
Normal file
169
src/doc/octree_map.h
Normal file
@ -0,0 +1,169 @@
|
||||
// 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 <vector>
|
||||
#include <array>
|
||||
|
||||
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<OctreeNode, 8>* 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, color_t upstreamBranchColor, int level);
|
||||
|
||||
void fillMostSignificantNodes(int level);
|
||||
|
||||
int mapColor(int r, int g, int b, int level);
|
||||
|
||||
void collectLeafNodes(std::vector<OctreeNode*>* 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<OctreeNode*>& auxParentVector,
|
||||
std::vector<OctreeNode*>& 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<std::array<OctreeNode, 8>> 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.get(), 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);
|
||||
|
||||
void feedWithImage(Image* image, color_t maskColor, 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:
|
||||
OctreeNode* root() const { return m_root.get(); };
|
||||
void fillOrphansNodes(Palette* palette);
|
||||
|
||||
std::unique_ptr<OctreeNode> m_root;
|
||||
std::unique_ptr<std::vector<OctreeNode*>> m_leavesVector;
|
||||
const Palette* m_palette;
|
||||
int m_modifications;
|
||||
int m_maskIndex;
|
||||
color_t m_maskColor;
|
||||
};
|
||||
|
||||
} // namespace doc
|
||||
#endif
|
@ -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<int>::max();
|
||||
int size = m_colors.size();
|
||||
|
||||
for (int i=0; i<size; ++i) {
|
||||
color_t rgb = m_colors[i];
|
||||
int rDiff = r - rgba_getr(rgb);
|
||||
int gDiff = g - rgba_getg(rgb);
|
||||
int bDiff = b - rgba_getb(rgb);
|
||||
|
||||
int diff = rDiff * rDiff * 900 + gDiff * gDiff * 3481 + bDiff * bDiff * 121;
|
||||
if (diff < lowest) {
|
||||
lowest = diff;
|
||||
bestfit = i;
|
||||
}
|
||||
}
|
||||
return bestfit;
|
||||
}
|
||||
|
||||
void Palette::applyRemap(const Remap& remap)
|
||||
{
|
||||
Palette original(*this);
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2020 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -89,6 +90,7 @@ namespace doc {
|
||||
|
||||
int findExactMatch(int r, int g, int b, int a, int mask_index) const;
|
||||
int findBestfit(int r, int g, int b, int a, int mask_index) const;
|
||||
int findBestfit2(int r, int g, int b) const;
|
||||
|
||||
void applyRemap(const Remap& remap);
|
||||
|
||||
|
@ -13,11 +13,15 @@
|
||||
|
||||
namespace doc {
|
||||
|
||||
class Palette;
|
||||
|
||||
// Matches a RGBA value with an index in a color palette (doc::Palette).
|
||||
class RgbMap {
|
||||
public:
|
||||
virtual ~RgbMap() { }
|
||||
|
||||
virtual void regenerateMap(const Palette* palette, const int maskIndex) = 0;
|
||||
|
||||
// Should return the best index in a palette that matches the given RGBA values.
|
||||
virtual int mapColor(const color_t rgba) const = 0;
|
||||
|
||||
|
21
src/doc/rgbmap_algorithm.h
Normal file
21
src/doc/rgbmap_algorithm.h
Normal file
@ -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
|
@ -24,23 +24,23 @@ namespace doc {
|
||||
|
||||
RgbMapRGB5A3::RgbMapRGB5A3()
|
||||
: m_map(MAPSIZE)
|
||||
, m_palette(NULL)
|
||||
, m_palette(nullptr)
|
||||
, m_modifications(0)
|
||||
, m_maskIndex(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool RgbMapRGB5A3::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 RgbMapRGB5A3::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)
|
||||
|
@ -28,10 +28,8 @@ namespace doc {
|
||||
public:
|
||||
RgbMapRGB5A3();
|
||||
|
||||
bool match(const Palette* palette) const;
|
||||
void regenerate(const Palette* palette, int mask_index);
|
||||
|
||||
// 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);
|
||||
|
@ -18,6 +18,7 @@
|
||||
#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"
|
||||
@ -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
|
||||
@ -356,16 +366,20 @@ RgbMap* Sprite::rgbMap(frame_t frame, RgbMapFor forLayer) const
|
||||
int maskIndex = (forLayer == RgbMapFor::OpaqueLayer ?
|
||||
-1: transparentColor());
|
||||
|
||||
if (m_rgbMap == NULL) {
|
||||
m_rgbMap = new RgbMapRGB5A3;
|
||||
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 != g_rgbMapAlgorithm) {
|
||||
m_rgbMapAlgorithm = g_rgbMapAlgorithm;
|
||||
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();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
@ -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 <memory>
|
||||
#include <vector>
|
||||
|
||||
#define DOC_SPRITE_MAX_WIDTH 65535
|
||||
@ -101,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; }
|
||||
@ -200,7 +205,8 @@ namespace doc {
|
||||
gfx::Rect m_gridBounds; // grid settings
|
||||
|
||||
// Current rgb map
|
||||
mutable RgbMapRGB5A3* m_rgbMap;
|
||||
mutable RgbMapAlgorithm m_rgbMapAlgorithm;
|
||||
mutable std::unique_ptr<RgbMap> m_rgbMap;
|
||||
|
||||
Tags m_tags;
|
||||
Slices m_slices;
|
||||
|
Loading…
x
Reference in New Issue
Block a user