Merge branch 'palette-with-alpha'

This commit is contained in:
David Capello 2015-07-02 18:16:04 -03:00
commit 48541af433
59 changed files with 1090 additions and 637 deletions

View File

@ -36,6 +36,22 @@
<value id="IF_VISIBLE" value="1" />
<value id="ALWAYS" value="2" />
</enum>
<enum id="EyedropperChannel">
<value id="COLOR_ALPHA" value="0" />
<value id="COLOR" value="1" />
<value id="ALPHA" value="2" />
<value id="RGBA" value="3" />
<value id="RGB" value="4" />
<value id="HSVA" value="5" />
<value id="HSV" value="6" />
<value id="GRAYA" value="7" />
<value id="GRAY" value="8" />
<value id="INDEX" value="9" />
</enum>
<enum id="EyedropperSample">
<value id="ALL_LAYERS" value="0" />
<value id="CURRENT_LAYER" value="1" />
</enum>
</types>
<global>
@ -59,7 +75,6 @@
<option id="zoom_from_center_with_keys" type="bool" default="false" />
<option id="show_scrollbars" type="bool" default="true" migrate="Options.ShowScrollbars" />
<option id="right_click_mode" type="RightClickMode" default="RightClickMode::PAINT_BGCOLOR" migrate="Options.RightClickMode" />
<option id="grab_alpha" type="bool" default="false" migrate="Options.GrabAlpha" />
<option id="auto_select_layer" type="bool" default="false" migrate="Options.AutoSelectLayer" />
<option id="cursor_color" type="app::Color" default="app::Color::fromMask()" migrate="Tools.CursorColor" />
</section>
@ -98,6 +113,13 @@
<option id="transparent_color" type="app::Color" />
<option id="rotation_algorithm" type="app::tools::RotationAlgorithm" default="app::tools::RotationAlgorithm::DEFAULT" />
</section>
<section id="quantization">
<option id="with_alpha" type="bool" default="true" />
</section>
<section id="eyedropper" text="Editor">
<option id="channel" type="EyedropperChannel" default="EyedropperChannel::COLOR_ALPHA" />
<option id="sample" type="EyedropperSample" default="EyedropperSample::ALL_LAYERS" />
</section>
</global>
<tool>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -372,6 +372,9 @@
<part id="canvas_s" x="112" y="128" w="16" h="16" />
<part id="canvas_se" x="128" y="128" w="16" h="16" />
<part id="canvas_empty" x="96" y="96" w="1" h="1" />
<part id="ink_default" x="144" y="144" w="16" h="16" />
<part id="ink_composite" x="160" y="144" w="16" h="16" />
<part id="ink_lock_alpha" x="176" y="144" w="16" h="16" />
</parts>
<stylesheet>

View File

@ -9,6 +9,8 @@
<radio id="current_palette" text="Replace current palette" group="1" cell_hspan="3" />
<radio id="current_range" text="Replace current range" group="1" cell_hspan="3" />
<check id="alpha_channel" text="Create entries with alpha component" cell_hspan="3" />
<separator horizontal="true" cell_hspan="3" />
<box horizontal="true" homogeneous="true" cell_hspan="3" cell_align="right">

View File

@ -35,30 +35,33 @@ Color Color::fromMask()
}
// static
Color Color::fromRgb(int r, int g, int b)
Color Color::fromRgb(int r, int g, int b, int a)
{
Color color(Color::RgbType);
color.m_value.rgb.r = r;
color.m_value.rgb.g = g;
color.m_value.rgb.b = b;
color.m_value.rgb.a = a;
return color;
}
// static
Color Color::fromHsv(int h, int s, int v)
Color Color::fromHsv(int h, int s, int v, int a)
{
Color color(Color::HsvType);
color.m_value.hsv.h = h;
color.m_value.hsv.s = s;
color.m_value.hsv.v = v;
color.m_value.hsv.a = a;
return color;
}
// static
Color Color::fromGray(int g)
Color Color::fromGray(int g, int a)
{
Color color(Color::GrayType);
color.m_value.gray = g;
color.m_value.gray.g = g;
color.m_value.gray.a = a;
return color;
}
@ -83,13 +86,15 @@ Color Color::fromImage(PixelFormat pixelFormat, color_t c)
if (rgba_geta(c) > 0) {
color = Color::fromRgb(rgba_getr(c),
rgba_getg(c),
rgba_getb(c));
rgba_getb(c),
rgba_geta(c));
}
break;
case IMAGE_GRAYSCALE:
if (graya_geta(c) > 0) {
color = Color::fromGray(graya_getv(c));
color = Color::fromGray(graya_getv(c),
graya_geta(c));
}
break;
@ -117,26 +122,26 @@ Color Color::fromString(const std::string& str)
if (str != "mask") {
if (str.find("rgb{") == 0 ||
str.find("hsv{") == 0) {
int c = 0, table[3] = { 0, 0, 0 };
str.find("hsv{") == 0 ||
str.find("gray{") == 0) {
int c = 0, table[4] = { 0, 0, 0, 255 };
std::string::size_type i = 4, j;
while ((j = str.find_first_of(",}", i)) != std::string::npos) {
std::string element = str.substr(i, j - i);
if (c < 3)
if (c < 4)
table[c++] = std::strtol(element.c_str(), NULL, 10);
if (c >= 3)
if (c >= 4)
break;
i = j+1;
}
if (str[0] == 'r')
color = Color::fromRgb(table[0], table[1], table[2]);
else
color = Color::fromHsv(table[0], table[1], table[2]);
}
else if (str.find("gray{") == 0) {
color = Color::fromGray(std::strtol(str.c_str()+5, NULL, 10));
color = Color::fromRgb(table[0], table[1], table[2], table[3]);
else if (str[0] == 'h')
color = Color::fromHsv(table[0], table[1], table[2], table[3]);
else if (str[0] == 'g')
color = Color::fromGray(table[0], c >= 2 ? table[1]: 255);
}
else if (str.find("index{") == 0) {
color = Color::fromIndex(std::strtol(str.c_str()+6, NULL, 10));
@ -160,18 +165,22 @@ std::string Color::toString() const
result << "rgb{"
<< m_value.rgb.r << ","
<< m_value.rgb.g << ","
<< m_value.rgb.b << "}";
<< m_value.rgb.b << ","
<< m_value.rgb.a << "}";
break;
case Color::HsvType:
result << "hsv{"
<< m_value.hsv.h << ","
<< m_value.hsv.s << ","
<< m_value.hsv.v << "}";
<< m_value.hsv.v << ","
<< m_value.hsv.a << "}";
break;
case Color::GrayType:
result << "gray{" << m_value.gray << "}";
result << "gray{"
<< m_value.gray.g << ","
<< m_value.gray.a << "}";
break;
case Color::IndexType:
@ -226,7 +235,7 @@ std::string Color::toHumanReadableString(PixelFormat pixelFormat, HumanReadableS
break;
case Color::GrayType:
result << "Gray " << m_value.gray;
result << "Gray " << m_value.gray.g;
break;
case Color::IndexType: {
@ -288,7 +297,7 @@ std::string Color::toHumanReadableString(PixelFormat pixelFormat, HumanReadableS
break;
case Color::GrayType:
result << "Gry-" << m_value.gray;
result << "Gry-" << m_value.gray.g;
break;
case Color::IndexType:
@ -318,16 +327,20 @@ bool Color::operator==(const Color& other) const
return
m_value.rgb.r == other.m_value.rgb.r &&
m_value.rgb.g == other.m_value.rgb.g &&
m_value.rgb.b == other.m_value.rgb.b;
m_value.rgb.b == other.m_value.rgb.b &&
m_value.rgb.a == other.m_value.rgb.a;
case Color::HsvType:
return
m_value.hsv.h == other.m_value.hsv.h &&
m_value.hsv.s == other.m_value.hsv.s &&
m_value.hsv.v == other.m_value.hsv.v;
m_value.hsv.v == other.m_value.hsv.v &&
m_value.hsv.a == other.m_value.hsv.a;
case Color::GrayType:
return m_value.gray == other.m_value.gray;
return
m_value.gray.g == other.m_value.gray.g &&
m_value.gray.a == other.m_value.gray.a;
case Color::IndexType:
return m_value.index == other.m_value.index;
@ -370,7 +383,7 @@ int Color::getRed() const
double(m_value.hsv.v) / 100.0)).red();
case Color::GrayType:
return m_value.gray;
return m_value.gray.g;
case Color::IndexType: {
int i = m_value.index;
@ -402,7 +415,7 @@ int Color::getGreen() const
double(m_value.hsv.v) / 100.0)).green();
case Color::GrayType:
return m_value.gray;
return m_value.gray.g;
case Color::IndexType: {
int i = m_value.index;
@ -434,7 +447,7 @@ int Color::getBlue() const
double(m_value.hsv.v) / 100.0)).blue();
case Color::GrayType:
return m_value.gray;
return m_value.gray.g;
case Color::IndexType: {
int i = m_value.index;
@ -538,7 +551,7 @@ int Color::getValue() const
return m_value.hsv.v;
case Color::GrayType:
return 100 * m_value.gray / 255;
return 100 * m_value.gray.g / 255;
case Color::IndexType: {
int i = m_value.index;
@ -574,7 +587,7 @@ int Color::getGray() const
return 255 * m_value.hsv.v / 100;
case Color::GrayType:
return m_value.gray;
return m_value.gray.g;
case Color::IndexType: {
int i = m_value.index;
@ -602,13 +615,14 @@ int Color::getIndex() const
return 0;
case Color::RgbType:
return get_current_palette()->findBestfit(getRed(), getGreen(), getBlue());
case Color::HsvType:
return get_current_palette()->findBestfit(getRed(), getGreen(), getBlue());
case Color::GrayType:
return m_value.gray;
case Color::GrayType: {
int i = get_current_palette()->findExactMatch(getRed(), getGreen(), getBlue(), getAlpha());
if (i >= 0)
return i;
else
return get_current_palette()->findBestfit(getRed(), getGreen(), getBlue(), getAlpha(), 0);
}
case Color::IndexType:
return m_value.index;
@ -619,4 +633,34 @@ int Color::getIndex() const
return -1;
}
int Color::getAlpha() const
{
switch (getType()) {
case Color::MaskType:
return 0;
case Color::RgbType:
return m_value.rgb.a;
case Color::HsvType:
return m_value.hsv.a;
case Color::GrayType:
return m_value.gray.a;
case Color::IndexType: {
int i = m_value.index;
if (i >= 0 && i < get_current_palette()->size())
return rgba_geta(get_current_palette()->getEntry(i));
else
return 0;
}
}
ASSERT(false);
return -1;
}
} // namespace app

View File

@ -42,9 +42,9 @@ namespace app {
Color() : m_type(MaskType) { }
static Color fromMask();
static Color fromRgb(int r, int g, int b);
static Color fromHsv(int h, int s, int v); // h=[0,360], s=[0,100], v=[0,100]
static Color fromGray(int g);
static Color fromRgb(int r, int g, int b, int a = 255);
static Color fromHsv(int h, int s, int v, int a = 255); // h=[0,360], s=[0,100], v=[0,100]
static Color fromGray(int g, int a = 255);
static Color fromIndex(int index);
static Color fromImage(PixelFormat pixelFormat, color_t c);
@ -74,6 +74,7 @@ namespace app {
int getValue() const;
int getGray() const;
int getIndex() const;
int getAlpha() const;
private:
Color(Type type) : m_type(type) { }
@ -84,12 +85,14 @@ namespace app {
// Color value
union {
struct {
int r, g, b;
int r, g, b, a;
} rgb;
struct {
int h, s, v;
int h, s, v, a;
} hsv;
int gray;
struct {
int g, a;
} gray;
int index;
} m_value;
};

View File

@ -52,14 +52,16 @@ gfx::Color color_utils::color_for_ui(const app::Color& color)
c = gfx::rgba(
color.getRed(),
color.getGreen(),
color.getBlue(), 255);
color.getBlue(),
color.getAlpha());
break;
case app::Color::GrayType:
c = gfx::rgba(
color.getGray(),
color.getGray(),
color.getGray(), 255);
color.getGray(),
color.getAlpha());
break;
case app::Color::IndexType: {
@ -70,7 +72,8 @@ gfx::Color color_utils::color_for_ui(const app::Color& color)
c = gfx::rgba(
rgba_getr(_c),
rgba_getg(_c),
rgba_getb(_c), 255);
rgba_getb(_c),
color.getAlpha());
break;
}
@ -116,10 +119,10 @@ doc::color_t color_utils::color_for_target_mask(const app::Color& color, const C
else {
switch (colorTarget.pixelFormat()) {
case IMAGE_RGB:
c = doc::rgba(color.getRed(), color.getGreen(), color.getBlue(), 255);
c = doc::rgba(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
break;
case IMAGE_GRAYSCALE:
c = doc::graya(color.getGray(), 255);
c = doc::graya(color.getGray(), color.getAlpha());
break;
case IMAGE_INDEXED:
if (color.getType() == app::Color::IndexType) {
@ -130,6 +133,7 @@ doc::color_t color_utils::color_for_target_mask(const app::Color& color, const C
color.getRed(),
color.getGreen(),
color.getBlue(),
color.getAlpha(),
colorTarget.isTransparent() ?
colorTarget.maskColor(): // Don't return the mask color
-1); // Return any color, we are in a background layer.

View File

@ -9,12 +9,14 @@
#include "config.h"
#endif
#include "app/app.h"
#include "app/cmd/set_palette.h"
#include "app/commands/command.h"
#include "app/console.h"
#include "app/context.h"
#include "app/context_access.h"
#include "app/modules/palettes.h"
#include "app/pref/preferences.h"
#include "app/transaction.h"
#include "app/ui/color_bar.h"
#include "app/ui_context.h"
@ -70,6 +72,8 @@ void ColorQuantizationCommand::onExecute(Context* context)
curPalette = sprite->palette(frame);
window.newPalette()->setSelected(true);
window.alphaChannel()->setSelected(
App::instance()->preferences().quantization.withAlpha());
window.ncolors()->setText("256");
ColorBar::instance()->getPaletteView()->getSelectedEntries(entries);
@ -92,6 +96,9 @@ void ColorQuantizationCommand::onExecute(Context* context)
if (window.getKiller() != window.ok())
return;
bool withAlpha = window.alphaChannel()->isSelected();
App::instance()->preferences().quantization.withAlpha(withAlpha);
bool createPal = false;
if (window.newPalette()->isSelected()) {
int n = window.ncolors()->getTextInt();
@ -107,7 +114,8 @@ void ColorQuantizationCommand::onExecute(Context* context)
return;
Palette tmpPalette(frame, entries.picks());
render::create_palette_from_rgb(sprite, 0, sprite->lastFrame(), &tmpPalette);
render::create_palette_from_rgb(sprite, 0, sprite->lastFrame(),
withAlpha, &tmpPalette);
base::UniquePtr<Palette> newPalette(
new Palette(createPal ? tmpPalette:

View File

@ -12,7 +12,7 @@
#include "app/app.h"
#include "app/color.h"
#include "app/color_picker.h"
#include "app/commands/command.h"
#include "app/commands/cmd_eyedropper.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/modules/editors.h"
@ -32,21 +32,6 @@ namespace app {
using namespace ui;
class EyedropperCommand : public Command {
/**
* True means "pick background color", false the foreground color.
*/
bool m_background;
public:
EyedropperCommand();
Command* clone() const override { return new EyedropperCommand(*this); }
protected:
void onLoadParams(const Params& params) override;
void onExecute(Context* context) override;
};
EyedropperCommand::EyedropperCommand()
: Command("Eyedropper",
"Eyedropper",
@ -55,6 +40,113 @@ EyedropperCommand::EyedropperCommand()
m_background = false;
}
void EyedropperCommand::pickSample(const doc::Site& site,
const gfx::Point& pixelPos,
app::Color& color)
{
// Check if we've to grab alpha channel or the merged color.
Preferences& pref = Preferences::instance();
bool allLayers =
(pref.eyedropper.sample() == app::gen::EyedropperSample::ALL_LAYERS);
ColorPicker picker;
picker.pickColor(site,
pixelPos,
(allLayers ?
ColorPicker::FromComposition:
ColorPicker::FromActiveLayer));
app::gen::EyedropperChannel channel =
pref.eyedropper.channel();
app::Color picked = picker.color();
switch (channel) {
case app::gen::EyedropperChannel::COLOR_ALPHA:
color = picked;
break;
case app::gen::EyedropperChannel::COLOR:
if (picked.getAlpha() > 0)
color = app::Color::fromRgb(picked.getRed(),
picked.getGreen(),
picked.getBlue(),
color.getAlpha());
break;
case app::gen::EyedropperChannel::ALPHA:
switch (color.getType()) {
case app::Color::RgbType:
case app::Color::IndexType:
color = app::Color::fromRgb(color.getRed(),
color.getGreen(),
color.getBlue(),
picked.getAlpha());
break;
case app::Color::HsvType:
color = app::Color::fromHsv(color.getHue(),
color.getSaturation(),
color.getValue(),
picked.getAlpha());
break;
case app::Color::GrayType:
color = app::Color::fromGray(color.getGray(),
picked.getAlpha());
break;
}
break;
case app::gen::EyedropperChannel::RGBA:
if (picked.getType() == app::Color::RgbType)
color = picked;
else
color = app::Color::fromRgb(picked.getRed(),
picked.getGreen(),
picked.getBlue(),
picked.getAlpha());
break;
case app::gen::EyedropperChannel::RGB:
if (picked.getAlpha() > 0)
color = app::Color::fromRgb(picked.getRed(),
picked.getGreen(),
picked.getBlue(),
color.getAlpha());
break;
case app::gen::EyedropperChannel::HSVA:
if (picked.getType() == app::Color::HsvType)
color = picked;
else
color = app::Color::fromHsv(picked.getHue(),
picked.getSaturation(),
picked.getValue(),
picked.getAlpha());
break;
case app::gen::EyedropperChannel::HSV:
if (picked.getAlpha() > 0)
color = app::Color::fromHsv(picked.getHue(),
picked.getSaturation(),
picked.getValue(),
color.getAlpha());
break;
case app::gen::EyedropperChannel::GRAYA:
if (picked.getType() == app::Color::GrayType)
color = picked;
else
color = app::Color::fromGray(picked.getGray(),
picked.getAlpha());
break;
case app::gen::EyedropperChannel::GRAY:
if (picked.getAlpha() > 0)
color = app::Color::fromGray(picked.getGray(),
color.getAlpha());
break;
case app::gen::EyedropperChannel::INDEX:
color = app::Color::fromIndex(picked.getIndex());
break;
}
}
void EyedropperCommand::onLoadParams(const Params& params)
{
std::string target = params.get("target");
@ -82,27 +174,18 @@ void EyedropperCommand::onExecute(Context* context)
// Pixel position to get
gfx::Point pixelPos = editor->screenToEditor(ui::get_mouse_position());
// Check if we've to grab alpha channel or the merged color.
// Start with fg/bg color
Preferences& pref = Preferences::instance();
bool grabAlpha = pref.editor.grabAlpha();
app::Color color =
m_background ? pref.colorBar.bgColor():
pref.colorBar.fgColor();
ColorPicker picker;
picker.pickColor(editor->getSite(),
pixelPos,
grabAlpha ?
ColorPicker::FromActiveLayer:
ColorPicker::FromComposition);
if (grabAlpha) {
tools::ToolBox* toolBox = App::instance()->getToolBox();
for (auto tool : *toolBox)
pref.tool(tool).opacity(picker.alpha());
}
pickSample(editor->getSite(), pixelPos, color);
if (m_background)
pref.colorBar.bgColor(picker.color());
pref.colorBar.bgColor(color);
else
pref.colorBar.fgColor(picker.color());
pref.colorBar.fgColor(color);
}
Command* CommandFactory::createEyedropperCommand()

View File

@ -0,0 +1,41 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_COMMANDS_CMD_EYEDROPPER_H_INCLUDED
#define APP_COMMANDS_CMD_EYEDROPPER_H_INCLUDED
#pragma once
#include "app/color.h"
#include "app/commands/command.h"
namespace doc {
class Site;
}
namespace app {
class EyedropperCommand : public Command {
public:
EyedropperCommand();
Command* clone() const override { return new EyedropperCommand(*this); }
// Returns the color in the given sprite pos.
void pickSample(const doc::Site& site,
const gfx::Point& pixelPos,
app::Color& color);
protected:
void onLoadParams(const Params& params) override;
void onExecute(Context* context) override;
// True means "pick background color", false the foreground color.
bool m_background;
};
} // namespace app
#endif // APP_COMMANDS_CMD_EYEDROPPER_H_INCLUDED

View File

@ -497,7 +497,7 @@ void PaletteEntryEditor::setAbsolutePaletteEntryChannel(ColorSliders::Channel ch
int picksCount = entries.picks();
uint32_t src_color;
int r, g, b;
int r, g, b, a;
Palette* palette = get_current_palette();
for (int c=0; c<palette->size(); c++) {
@ -509,6 +509,7 @@ void PaletteEntryEditor::setAbsolutePaletteEntryChannel(ColorSliders::Channel ch
r = rgba_getr(src_color);
g = rgba_getg(src_color);
b = rgba_getb(src_color);
a = rgba_geta(src_color);
switch (m_type) {
@ -518,6 +519,7 @@ void PaletteEntryEditor::setAbsolutePaletteEntryChannel(ColorSliders::Channel ch
r = color.getRed();
g = color.getGreen();
b = color.getBlue();
a = color.getAlpha();
}
// Modify one channel a set of entries
else {
@ -531,6 +533,9 @@ void PaletteEntryEditor::setAbsolutePaletteEntryChannel(ColorSliders::Channel ch
case ColorSliders::Blue:
b = color.getBlue();
break;
case ColorSliders::Alpha:
a = color.getAlpha();
break;
}
}
break;
@ -561,6 +566,9 @@ void PaletteEntryEditor::setAbsolutePaletteEntryChannel(ColorSliders::Channel ch
case ColorSliders::Value:
hsv.value(double(color.getValue()) / 100.0);
break;
case ColorSliders::Alpha:
a = color.getAlpha();
break;
}
}
@ -573,7 +581,7 @@ void PaletteEntryEditor::setAbsolutePaletteEntryChannel(ColorSliders::Channel ch
break;
}
palette->setEntry(c, doc::rgba(r, g, b, 255));
palette->setEntry(c, doc::rgba(r, g, b, a));
}
}
@ -586,7 +594,7 @@ void PaletteEntryEditor::setRelativePaletteEntryChannel(ColorSliders::Channel ch
m_relDeltas[channel] = delta;
uint32_t src_color;
int r, g, b;
int r, g, b, a;
Palette* palette = get_current_palette();
for (int c=0; c<palette->size(); c++) {
@ -598,6 +606,7 @@ void PaletteEntryEditor::setRelativePaletteEntryChannel(ColorSliders::Channel ch
r = rgba_getr(src_color);
g = rgba_getg(src_color);
b = rgba_getb(src_color);
a = rgba_geta(src_color);
switch (m_type) {
@ -605,6 +614,7 @@ void PaletteEntryEditor::setRelativePaletteEntryChannel(ColorSliders::Channel ch
r = MID(0, r+m_relDeltas[ColorSliders::Red], 255);
g = MID(0, g+m_relDeltas[ColorSliders::Green], 255);
b = MID(0, b+m_relDeltas[ColorSliders::Blue], 255);
a = MID(0, a+m_relDeltas[ColorSliders::Alpha], 255);
break;
case app::Color::HsvType: {
@ -627,12 +637,13 @@ void PaletteEntryEditor::setRelativePaletteEntryChannel(ColorSliders::Channel ch
r = rgb.red();
g = rgb.green();
b = rgb.blue();
a = MID(0, a+m_relDeltas[ColorSliders::Alpha], 255);
break;
}
}
palette->setEntry(c, doc::rgba(r, g, b, 255));
palette->setEntry(c, doc::rgba(r, g, b, a));
}
}

View File

@ -28,6 +28,7 @@
#include "doc/cel.h"
#include "doc/cels_range.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/mask.h"
#include "doc/primitives.h"
#include "doc/sprite.h"
@ -95,10 +96,12 @@ protected:
ImageRef new_image(Image::create(image->pixelFormat(), MAX(1, w), MAX(1, h)));
doc::algorithm::fixup_image_transparent_colors(image);
doc::algorithm::resize_image(image, new_image.get(),
doc::algorithm::resize_image(
image, new_image.get(),
m_resize_method,
m_sprite->palette(cel->frame()),
m_sprite->rgbMap(cel->frame()));
m_sprite->rgbMap(cel->frame()),
(cel->layer()->isBackground() ? -1: m_sprite->transparentColor()));
api.replaceImage(m_sprite, cel->imageRef(), new_image);
}
@ -125,10 +128,12 @@ protected:
gfx::Rect(
scale_x(m_document->mask()->bounds().x-1),
scale_y(m_document->mask()->bounds().y-1), MAX(1, w), MAX(1, h)));
algorithm::resize_image(old_bitmap.get(), new_mask->bitmap(),
m_resize_method,
m_sprite->palette(0), // Ignored
m_sprite->rgbMap(0)); // Ignored
algorithm::resize_image(
old_bitmap.get(), new_mask->bitmap(),
m_resize_method,
m_sprite->palette(0), // Ignored
m_sprite->rgbMap(0), // Ignored
-1); // Ignored
// Reshrink
new_mask->intersect(new_mask->bounds());

View File

@ -61,17 +61,15 @@ FilterTargetButtons::FilterTargetButtons(int imgtype, bool withChannels)
case IMAGE_INDEXED:
r = check_button_new("R", 2, 0, 0, 0);
g = check_button_new("G", 0, 0, 0, 0);
b = check_button_new("B", 0, (imgtype == IMAGE_RGB) ? 0: 2, 0, 0);
b = check_button_new("B", 0, 0, 0, 0);
a = check_button_new("A", 0, 2, 0, 0);
r->setId("r");
g->setId("g");
b->setId("b");
a->setId("a");
if (imgtype == IMAGE_RGB) {
a = check_button_new("A", 0, 2, 0, 0);
a->setId("a");
}
else {
if (imgtype == IMAGE_INDEXED) {
index = check_button_new("Index", 0, 0, 0, 0);
index->setId("i");
}

View File

@ -144,7 +144,8 @@ class AseFormat : public FileFormat {
FILE_SUPPORT_FRAMES |
FILE_SUPPORT_PALETTES |
FILE_SUPPORT_FRAME_TAGS |
FILE_SUPPORT_BIG_PALETTES;
FILE_SUPPORT_BIG_PALETTES |
FILE_SUPPORT_PALETTE_WITH_ALPHA;
}
bool onLoad(FileOp* fop) override;
@ -666,9 +667,7 @@ static Palette* ase_file_read_palette_chunk(FILE* f, Palette* prevPal, frame_t f
int g = fgetc(f);
int b = fgetc(f);
int a = fgetc(f);
// TODO don't ignore alpha
pal->setEntry(c, rgba(r, g, b, 255));
pal->setEntry(c, rgba(r, g, b, a));
// Skip name
if (flags & 1) {

View File

@ -332,6 +332,22 @@ FileOp* fop_to_save_document(const Context* context, const Document* document,
}
}
// Palette with alpha
if (!fop->format->support(FILE_SUPPORT_PALETTE_WITH_ALPHA)) {
bool done = false;
for (Palette* pal : fop->document->sprite()->getPalettes()) {
for (int c=0; c<pal->size(); ++c) {
if (rgba_geta(pal->getEntry(c)) < 255) {
warnings += "<<- Palette with alpha channel";
done = true;
break;
}
}
if (done)
break;
}
}
// Show the confirmation alert
if (!warnings.empty()) {
// Interative
@ -724,7 +740,7 @@ void fop_post_load(FileOp* fop)
sprite->palette(frame_t(0))->isBlack()) {
base::SharedPtr<Palette> palette(
render::create_palette_from_rgb(
sprite, frame_t(0), sprite->lastFrame(), nullptr));
sprite, frame_t(0), sprite->lastFrame(), true, nullptr));
sprite->resetPalettes();
sprite->setPalette(palette.get(), false);
@ -740,6 +756,16 @@ void fop_sequence_set_format_options(FileOp* fop, const base::SharedPtr<FormatOp
fop->seq.format_options = format_options;
}
void fop_sequence_set_ncolors(FileOp* fop, int ncolors)
{
fop->seq.palette->resize(ncolors);
}
int fop_sequence_get_ncolors(FileOp* fop)
{
return fop->seq.palette->size();
}
void fop_sequence_set_color(FileOp *fop, int index, int r, int g, int b)
{
fop->seq.palette->setEntry(index, rgba(r, g, b, 255));
@ -760,6 +786,25 @@ void fop_sequence_get_color(FileOp *fop, int index, int *r, int *g, int *b)
*b = rgba_getb(c);
}
void fop_sequence_set_alpha(FileOp* fop, int index, int a)
{
int c = fop->seq.palette->getEntry(index);
int r = rgba_getr(c);
int g = rgba_getg(c);
int b = rgba_getb(c);
fop->seq.palette->setEntry(index, rgba(r, g, b, a));
}
void fop_sequence_get_alpha(FileOp* fop, int index, int* a)
{
ASSERT(index >= 0);
if (index >= 0 && index < fop->seq.palette->size())
*a = rgba_geta(fop->seq.palette->getEntry(index));
else
*a = 0;
}
Image* fop_sequence_image(FileOp* fop, PixelFormat pixelFormat, int w, int h)
{
Sprite* sprite;

View File

@ -133,8 +133,12 @@ namespace app {
void fop_post_load(FileOp* fop);
void fop_sequence_set_format_options(FileOp* fop, const base::SharedPtr<FormatOptions>& format_options);
void fop_sequence_set_ncolors(FileOp* fop, int ncolors);
int fop_sequence_get_ncolors(FileOp* fop);
void fop_sequence_set_color(FileOp* fop, int index, int r, int g, int b);
void fop_sequence_get_color(FileOp* fop, int index, int *r, int *g, int *b);
void fop_sequence_set_alpha(FileOp* fop, int index, int a);
void fop_sequence_get_alpha(FileOp* fop, int index, int* a);
Image* fop_sequence_image(FileOp* fi, PixelFormat pixelFormat, int w, int h);
void fop_error(FileOp* fop, const char *error, ...);

View File

@ -27,6 +27,7 @@
#define FILE_SUPPORT_GET_FORMAT_OPTIONS 0x00000800
#define FILE_SUPPORT_FRAME_TAGS 0x00001000
#define FILE_SUPPORT_BIG_PALETTES 0x00002000 // Palettes w/more than 256 colors
#define FILE_SUPPORT_PALETTE_WITH_ALPHA 0x00004000
namespace app {

View File

@ -641,7 +641,7 @@ bool GifFormat::onSave(FileOp* fop)
for (frame_t frame_num(0); frame_num<sprite->totalFrames(); ++frame_num) {
clear_image(buffer_image, background_color);
render.renderSprite(buffer_image, sprite, frame_num);
optimizer.feedWithImage(buffer_image);
optimizer.feedWithImage(buffer_image, false);
}
current_palette.makeBlack();
@ -667,7 +667,7 @@ bool GifFormat::onSave(FileOp* fop)
std::vector<Image*> imgarray(1);
imgarray[0] = buffer_image;
render::create_palette_from_images(imgarray, &current_palette, has_background);
render::create_palette_from_images(imgarray, &current_palette, has_background, false);
rgbmap.regenerate(&current_palette, transparent_index);
}
break;

View File

@ -39,7 +39,8 @@ class PngFormat : public FileFormat {
FILE_SUPPORT_GRAY |
FILE_SUPPORT_GRAYA |
FILE_SUPPORT_INDEXED |
FILE_SUPPORT_SEQUENCES;
FILE_SUPPORT_SEQUENCES |
FILE_SUPPORT_PALETTE_WITH_ALPHA;
}
bool onLoad(FileOp* fop) override;
@ -184,39 +185,34 @@ bool PngFormat::onLoad(FileOp* fop)
return false;
}
// Transparent palette entries
std::vector<uint8_t> pal_alphas(256, 255);
int mask_entry = -1;
// Read the palette
if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE &&
png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette)) {
int c;
fop_sequence_set_ncolors(fop, num_palette);
for (c = 0; c < num_palette; c++) {
for (int c=0; c<num_palette; ++c) {
fop_sequence_set_color(fop, c,
palette[c].red,
palette[c].green,
palette[c].blue);
}
for (; c < 256; c++) {
fop_sequence_set_color(fop, c, 0, 0, 0);
}
// Read alpha values for palette entries
png_bytep trans = NULL; // Transparent palette entries
int num_trans = 0;
int mask_entry = -1;
png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, NULL);
for (int i = 0; i < num_trans; ++i) {
pal_alphas[i] = trans[i];
fop_sequence_set_alpha(fop, i, trans[i]);
if (pal_alphas[i] < 128) {
if (trans[i] < 255) {
fop->seq.has_alpha = true; // Is a transparent sprite
if (mask_entry < 0)
mask_entry = i;
if (trans[i] == 0) {
if (mask_entry < 0)
mask_entry = i;
}
}
}
@ -225,8 +221,6 @@ bool PngFormat::onLoad(FileOp* fop)
fop->document->sprite()->setTransparentColor(mask_entry);
}
mask_entry = fop->document->sprite()->transparentColor();
/* Allocate the memory to hold the image using the fields of info_ptr. */
/* The easiest way to read the image: */
@ -290,18 +284,10 @@ bool PngFormat::onLoad(FileOp* fop)
else if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE) {
uint8_t* src_address = row_pointer;
uint8_t* dst_address = (uint8_t*)image->getPixelAddress(0, y);
unsigned int x, c;
unsigned int x;
for (x=0; x<width; x++) {
c = *(src_address++);
if (pal_alphas[c] < 128) {
*(dst_address++) = mask_entry;
}
else {
*(dst_address++) = c;
}
}
for (x=0; x<width; x++)
*(dst_address++) = *(src_address++);
}
fop_progress(fop,
@ -398,37 +384,44 @@ bool PngFormat::onSave(FileOp* fop)
if (image->pixelFormat() == IMAGE_INDEXED) {
int c, r, g, b;
int pal_size = fop_sequence_get_ncolors(fop);
ASSERT(pal_size > 0 && pal_size <= PNG_MAX_PALETTE_LENGTH);
pal_size = MID(1, pal_size, PNG_MAX_PALETTE_LENGTH);
#if PNG_MAX_PALETTE_LENGTH != 256
#error PNG_MAX_PALETTE_LENGTH should be 256
#endif
// Save the color palette.
palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color));
for (c = 0; c < PNG_MAX_PALETTE_LENGTH; c++) {
palette = (png_colorp)png_malloc(png_ptr, pal_size * sizeof(png_color));
for (c = 0; c < pal_size; c++) {
fop_sequence_get_color(fop, c, &r, &g, &b);
palette[c].red = r;
palette[c].green = g;
palette[c].blue = b;
}
png_set_PLTE(png_ptr, info_ptr, palette, PNG_MAX_PALETTE_LENGTH);
png_set_PLTE(png_ptr, info_ptr, palette, pal_size);
// If the sprite does not have a (visible) background layer, we
// include the alpha information of palette entries to indicate
// which is the transparent color.
// put alpha=0 to the transparent color.
int mask_entry = -1;
if (fop->document->sprite()->backgroundLayer() == NULL ||
!fop->document->sprite()->backgroundLayer()->isVisible()) {
int mask_entry = fop->document->sprite()->transparentColor();
int num_trans = mask_entry+1;
png_bytep trans = (png_bytep)png_malloc(png_ptr, num_trans);
for (c = 0; c < num_trans; ++c)
trans[c] = (c == mask_entry ? 0: 255);
png_set_tRNS(png_ptr, info_ptr, trans, num_trans, NULL);
png_free(png_ptr, trans);
mask_entry = fop->document->sprite()->transparentColor();
}
int num_trans = pal_size;
png_bytep trans = (png_bytep)png_malloc(png_ptr, num_trans);
for (c=0; c<num_trans; ++c) {
int alpha = 255;
fop_sequence_get_alpha(fop, c, &alpha);
trans[c] = (c == mask_entry ? 0: alpha);
}
png_set_tRNS(png_ptr, info_ptr, trans, num_trans, NULL);
png_free(png_ptr, trans);
}
/* Write the file header information. */

View File

@ -62,33 +62,38 @@ static void rectgrid(ui::Graphics* g, const gfx::Rect& rc, const gfx::Size& tile
}
}
static void draw_color(ui::Graphics* g, const Rect& rc, const app::Color& color)
void draw_color(ui::Graphics* g, const Rect& rc, const app::Color& color)
{
if (rc.w < 1 || rc.h < 1)
return;
app::Color::Type type = color.getType();
int alpha = color.getAlpha();
if (type == app::Color::MaskType) {
rectgrid(g, rc, gfx::Size(rc.w/4, rc.h/2));
return;
if (alpha < 255) {
if (rc.w == rc.h)
rectgrid(g, rc, gfx::Size(rc.w/2, rc.h/2));
else
rectgrid(g, rc, gfx::Size(rc.w/4, rc.h/2));
}
else if (type == app::Color::IndexType) {
int index = color.getIndex();
if (index >= 0 && index < get_current_palette()->size()) {
if (alpha > 0) {
if (type == app::Color::IndexType) {
int index = color.getIndex();
if (index >= 0 && index < get_current_palette()->size()) {
g->fillRect(color_utils::color_for_ui(color), rc);
}
else {
g->fillRect(gfx::rgba(0, 0, 0), rc);
g->drawLine(gfx::rgba(255, 255, 255),
gfx::Point(rc.x+rc.w-2, rc.y+1),
gfx::Point(rc.x+1, rc.y+rc.h-2));
}
}
else
g->fillRect(color_utils::color_for_ui(color), rc);
}
else {
g->fillRect(gfx::rgba(0, 0, 0), rc);
g->drawLine(gfx::rgba(255, 255, 255),
gfx::Point(rc.x+rc.w-2, rc.y+1),
gfx::Point(rc.x+1, rc.y+rc.h-2));
}
return;
}
g->fillRect(color_utils::color_for_ui(color), rc);
}
void draw_color_button(ui::Graphics* g,

View File

@ -18,6 +18,9 @@
namespace app {
using namespace doc;
void draw_color(ui::Graphics* g,
const gfx::Rect& rc, const app::Color& color);
void draw_color_button(ui::Graphics* g,
const gfx::Rect& rc, const app::Color& color,
bool hot, bool drag);

View File

@ -10,6 +10,7 @@
#include "app/tools/shading_options.h"
#include "doc/blend_funcs.h"
#include "doc/image_impl.h"
#include "doc/layer.h"
#include "doc/palette.h"
#include "doc/rgbmap.h"
#include "doc/sprite.h"
@ -241,14 +242,22 @@ public:
m_palette(get_current_palette()),
m_rgbmap(loop->getRgbMap()),
m_opacity(loop->getOpacity()),
m_color(m_palette->getEntry(loop->getPrimaryColor())) {
m_color(m_palette->getEntry(loop->getPrimaryColor())),
m_maskColor(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) {
}
void processPixel(int x, int y) {
color_t c = rgba_blender_normal(m_palette->getEntry(*m_srcAddress), m_color, m_opacity);
color_t c = *m_srcAddress;
if (c == m_maskColor)
c = m_palette->getEntry(c) & rgba_rgb_mask; // Alpha = 0
else
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_getb(c),
rgba_geta(c));
}
private:
@ -256,6 +265,7 @@ private:
const RgbMap* m_rgbmap;
int m_opacity;
color_t m_color;
color_t m_maskColor;
};
//////////////////////////////////////////////////////////////////////
@ -387,24 +397,27 @@ public:
m_opacity(loop->getOpacity()),
m_tiledMode(loop->getTiledMode()),
m_srcImage(loop->getSrcImage()),
m_area(get_current_palette()) {
m_area(get_current_palette(),
loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) {
}
void processPixel(int x, int y) {
m_area.reset();
get_neighboring_pixels<IndexedTraits>(m_srcImage, x, y, 3, 3, 1, 1, m_tiledMode, m_area);
if (m_area.count > 0 && m_area.a/9 >= 128) {
if (m_area.count > 0) {
m_area.r /= m_area.count;
m_area.g /= m_area.count;
m_area.b /= m_area.count;
m_area.a /= 9;
uint32_t color32 = m_palette->getEntry(*m_srcAddress);
m_area.r = rgba_getr(color32) + (m_area.r-rgba_getr(color32)) * m_opacity / 255;
m_area.g = rgba_getg(color32) + (m_area.g-rgba_getg(color32)) * m_opacity / 255;
m_area.b = rgba_getb(color32) + (m_area.b-rgba_getb(color32)) * m_opacity / 255;
m_area.a = rgba_geta(color32) + (m_area.a-rgba_geta(color32)) * m_opacity / 255;
*m_dstAddress = m_rgbmap->mapColor(m_area.r, m_area.g, m_area.b);
*m_dstAddress = m_rgbmap->mapColor(m_area.r, m_area.g, m_area.b, m_area.a);
}
else {
*m_dstAddress = *m_srcAddress;
@ -415,20 +428,27 @@ private:
struct GetPixelsDelegate {
const Palette* pal;
int count, r, g, b, a;
color_t maskColor;
GetPixelsDelegate(const Palette* pal) : pal(pal) { }
GetPixelsDelegate(const Palette* pal,
color_t maskColor)
: pal(pal), maskColor(maskColor) { }
void reset() { count = r = g = b = a = 0; }
void operator()(IndexedTraits::pixel_t color)
{
a += (color == 0 ? 0: 255);
if (color == maskColor)
return;
uint32_t color32 = pal->getEntry(color);
r += rgba_getr(color32);
g += rgba_getg(color32);
b += rgba_getb(color32);
count++;
if (rgba_geta(color32) > 0) {
r += rgba_getr(color32);
g += rgba_getg(color32);
b += rgba_getb(color32);
a += rgba_geta(color32);
++count;
}
}
};
@ -510,7 +530,7 @@ public:
m_palette->getEntry(*m_srcAddress), m_color2, m_opacity);
*m_dstAddress = m_rgbmap->mapColor(
rgba_getr(c), rgba_getg(c), rgba_getb(c));
rgba_getr(c), rgba_getg(c), rgba_getb(c), rgba_geta(c));
}
}
}
@ -610,7 +630,8 @@ void JumbleInkProcessing<IndexedTraits>::processPixel(int x, int y)
if (rgba_geta(c) >= 128)
*m_dstAddress = m_rgbmap->mapColor(rgba_getr(c),
rgba_getg(c),
rgba_getb(c));
rgba_getb(c),
rgba_geta(c));
else
*m_dstAddress = 0;
}
@ -692,7 +713,8 @@ public:
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_getb(c),
rgba_geta(c));
}
private:
@ -819,7 +841,7 @@ void BrushInkProcessing<IndexedTraits>::processPixel(int x, int y) {
switch (m_brushImage->pixelFormat()) {
case IMAGE_RGB: {
c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
c = m_palette->findBestfit(rgba_getr(c), rgba_getg(c), rgba_getb(c));
c = m_palette->findBestfit(rgba_getr(c), rgba_getg(c), rgba_getb(c), rgba_geta(c), 0);
break;
}
case IMAGE_INDEXED: {
@ -828,8 +850,7 @@ void BrushInkProcessing<IndexedTraits>::processPixel(int x, int y) {
}
case IMAGE_GRAYSCALE: {
c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
c = graya_getv(c);
c = m_palette->findBestfit(c, c, c);
c = m_palette->findBestfit(graya_getv(c), graya_getv(c), graya_getv(c), graya_geta(c), 0);
break;
}
case IMAGE_BITMAP: {

View File

@ -14,7 +14,8 @@ namespace tools {
enum class InkType {
DEFAULT = 0,
SET_ALPHA = 1,
REPLACE_PIXEL = 0,
ALPHA_COMPOSITING = 1,
LOCK_ALPHA = 2,
};

View File

@ -62,11 +62,7 @@ public:
case Opaque: m_proc = ink_processing[INK_OPAQUE][depth]; break;
case SetAlpha: m_proc = ink_processing[INK_SETALPHA][depth]; break;
case LockAlpha: m_proc = ink_processing[INK_LOCKALPHA][depth]; break;
default:
m_proc = (loop->getOpacity() == 255 ?
ink_processing[INK_OPAQUE][depth]:
ink_processing[INK_TRANSPARENT][depth]);
break;
default: m_proc = ink_processing[INK_TRANSPARENT][depth]; break;
}
}
}

View File

@ -805,11 +805,13 @@ void ColorBar::onFixWarningClick(ColorButton* colorButton, ui::Button* warningIc
color_t color = doc::rgba(
appColor.getRed(),
appColor.getGreen(),
appColor.getBlue(), 255);
appColor.getBlue(),
appColor.getAlpha());
int index = newPalette->findExactMatch(
appColor.getRed(),
appColor.getGreen(),
appColor.getBlue());
appColor.getBlue(),
appColor.getAlpha());
// It should be -1, because the user has pressed the warning
// button that is available only when the color isn't in the
@ -853,7 +855,8 @@ void ColorBar::updateWarningIcon(const app::Color& color, ui::Button* warningIco
int index = get_current_palette()->findExactMatch(
color.getRed(),
color.getGreen(),
color.getBlue());
color.getBlue(),
color.getAlpha());
warningIcon->setVisible(index < 0);
warningIcon->getParent()->layout();

View File

@ -95,7 +95,7 @@ bool ColorButton::onProcessMessage(Message* msg)
break;
case kMouseEnterMessage:
StatusBar::instance()->showColor(0, "", m_color, 255);
StatusBar::instance()->showColor(0, "", m_color);
break;
case kMouseLeaveMessage:

View File

@ -159,20 +159,27 @@ void ColorSelector::onColorHexEntryChange(const app::Color& color)
void ColorSelector::onColorTypeClick()
{
app::Color newColor;
app::Color newColor = getColor();
switch (m_colorType.selectedItem()) {
case INDEX_MODE:
newColor = app::Color::fromIndex(getColor().getIndex());
newColor = app::Color::fromIndex(newColor.getIndex());
break;
case RGB_MODE:
newColor = app::Color::fromRgb(getColor().getRed(), getColor().getGreen(), getColor().getBlue());
newColor = app::Color::fromRgb(newColor.getRed(),
newColor.getGreen(),
newColor.getBlue(),
newColor.getAlpha());
break;
case HSB_MODE:
newColor = app::Color::fromHsv(getColor().getHue(), getColor().getSaturation(), getColor().getValue());
newColor = app::Color::fromHsv(newColor.getHue(),
newColor.getSaturation(),
newColor.getValue(),
newColor.getAlpha());
break;
case GRAY_MODE:
newColor = app::Color::fromGray(getColor().getGray());
newColor = app::Color::fromGray(newColor.getGray(),
newColor.getAlpha());
break;
case MASK_MODE:
newColor = app::Color::fromMask();
@ -194,10 +201,11 @@ void ColorSelector::findBestfitIndex(const app::Color& color)
int r = color.getRed();
int g = color.getGreen();
int b = color.getBlue();
int a = color.getAlpha();
// Search for the closest color to the RGB values
int i = get_current_palette()->findBestfit(r, g, b);
if (i >= 0 && i < 256) {
int i = get_current_palette()->findBestfit(r, g, b, a, 0);
if (i >= 0) {
m_colorPalette.deselect();
m_colorPalette.selectColor(i);
}

View File

@ -237,6 +237,7 @@ RgbSliders::RgbSliders()
addSlider(Red, "R", 0, 255);
addSlider(Green, "G", 0, 255);
addSlider(Blue, "B", 0, 255);
addSlider(Alpha, "A", 0, 255);
}
void RgbSliders::onSetColor(const app::Color& color)
@ -244,13 +245,15 @@ void RgbSliders::onSetColor(const app::Color& color)
setAbsSliderValue(0, color.getRed());
setAbsSliderValue(1, color.getGreen());
setAbsSliderValue(2, color.getBlue());
setAbsSliderValue(3, color.getAlpha());
}
app::Color RgbSliders::getColorFromSliders()
{
return app::Color::fromRgb(getAbsSliderValue(0),
getAbsSliderValue(1),
getAbsSliderValue(2));
getAbsSliderValue(2),
getAbsSliderValue(3));
}
//////////////////////////////////////////////////////////////////////
@ -262,6 +265,7 @@ HsvSliders::HsvSliders()
addSlider(Hue, "H", 0, 360);
addSlider(Saturation, "S", 0, 100);
addSlider(Value, "B", 0, 100);
addSlider(Alpha, "A", 0, 255);
}
void HsvSliders::onSetColor(const app::Color& color)
@ -269,13 +273,15 @@ void HsvSliders::onSetColor(const app::Color& color)
setAbsSliderValue(0, color.getHue());
setAbsSliderValue(1, color.getSaturation());
setAbsSliderValue(2, color.getValue());
setAbsSliderValue(3, color.getAlpha());
}
app::Color HsvSliders::getColorFromSliders()
{
return app::Color::fromHsv(getAbsSliderValue(0),
getAbsSliderValue(1),
getAbsSliderValue(2));
getAbsSliderValue(2),
getAbsSliderValue(3));
}
//////////////////////////////////////////////////////////////////////
@ -284,18 +290,20 @@ app::Color HsvSliders::getColorFromSliders()
GraySlider::GraySlider()
: ColorSliders()
{
addSlider(Gray, "V", 0, 255);
addSlider(Gray, "V", 0, 255);
addSlider(Alpha, "A", 0, 255);
}
void GraySlider::onSetColor(const app::Color& color)
{
setAbsSliderValue(0, color.getGray());
setAbsSliderValue(1, color.getAlpha());
}
app::Color GraySlider::getColorFromSliders()
{
return app::Color::fromGray(getAbsSliderValue(0));
return app::Color::fromGray(getAbsSliderValue(0),
getAbsSliderValue(1));
}
} // namespace app

View File

@ -31,7 +31,8 @@ namespace app {
public:
enum Channel { Red, Green, Blue,
Hue, Saturation, Value,
Gray };
Gray,
Alpha };
enum Mode { Absolute, Relative };
ColorSliders();

View File

@ -135,7 +135,7 @@ bool ColorSpectrum::onProcessMessage(ui::Message* msg)
app::Color color = pickColor(mouseMsg->position());
if (color != app::Color::fromMask()) {
StatusBar::instance()->showColor(0, "", color, 255);
StatusBar::instance()->showColor(0, "", color);
if (hasCapture())
ColorChange(color, mouseMsg->buttons());
}

View File

@ -42,6 +42,7 @@
#include "ui/int_entry.h"
#include "ui/label.h"
#include "ui/listitem.h"
#include "ui/menu.h"
#include "ui/popup_window.h"
#include "ui/preferred_size_event.h"
#include "ui/theme.h"
@ -147,6 +148,7 @@ private:
void closePopup() {
m_popupWindow.closeWindow(NULL);
deselectItems();
}
void onBrushChange(const BrushRef& brush) {
@ -319,66 +321,67 @@ protected:
}
};
class ContextBar::InkTypeField : public ComboBox
class ContextBar::InkTypeField : public ButtonSet
{
public:
InkTypeField() : m_lock(false) {
// The same order as in InkType
addItem("Default Ink");
#if 0
addItem("Opaque");
#endif
addItem("Set Alpha");
addItem("Lock Alpha");
#if 0
addItem("Merge");
addItem("Shading");
addItem("Replace");
addItem("Erase");
addItem("Selection");
addItem("Blur");
addItem("Jumble");
#endif
InkTypeField(ContextBar* owner) : ButtonSet(1)
, m_owner(owner) {
addItem(
static_cast<SkinTheme*>(getTheme())->get_part(PART_INK_DEFAULT));
}
void setInkType(InkType inkType) {
int index = 0;
int part = PART_INK_DEFAULT;
switch (inkType) {
case InkType::DEFAULT: index = 0; break;
case InkType::SET_ALPHA: index = 1; break;
case InkType::LOCK_ALPHA: index = 2; break;
case InkType::REPLACE_PIXEL: part = PART_INK_DEFAULT; break;
case InkType::ALPHA_COMPOSITING: part = PART_INK_COMPOSITE; break;
case InkType::LOCK_ALPHA: part = PART_INK_LOCK_ALPHA; break;
}
m_lock = true;
setSelectedItemIndex(index);
m_lock = false;
getItem(0)->setIcon(
static_cast<SkinTheme*>(getTheme())->get_part(part));
}
protected:
void onChange() override {
ComboBox::onChange();
void onItemChange() override {
ButtonSet::onItemChange();
if (m_lock)
return;
gfx::Rect bounds = getBounds();
InkType inkType = InkType::DEFAULT;
switch (getSelectedItemIndex()) {
case 0: inkType = InkType::DEFAULT; break;
case 1: inkType = InkType::SET_ALPHA; break;
case 2: inkType = InkType::LOCK_ALPHA; break;
}
Menu menu;
MenuItem
replace("Replace Pixel"),
alphacompo("Alpha Compositing"),
lockalpha("Lock Alpha");
menu.addChild(&replace);
menu.addChild(&alphacompo);
menu.addChild(&lockalpha);
Tool* tool = App::instance()->activeTool();
switch (Preferences::instance().tool(tool).ink()) {
case tools::InkType::REPLACE_PIXEL: replace.setSelected(true); break;
case tools::InkType::ALPHA_COMPOSITING: alphacompo.setSelected(true); break;
case tools::InkType::LOCK_ALPHA: lockalpha.setSelected(true); break;
}
replace.Click.connect(Bind<void>(&InkTypeField::selectInk, this, InkType::REPLACE_PIXEL));
alphacompo.Click.connect(Bind<void>(&InkTypeField::selectInk, this, InkType::ALPHA_COMPOSITING));
lockalpha.Click.connect(Bind<void>(&InkTypeField::selectInk, this, InkType::LOCK_ALPHA));
menu.showPopup(gfx::Point(bounds.x, bounds.y+bounds.h));
deselectItems();
}
void selectInk(InkType inkType) {
Tool* tool = App::instance()->activeTool();
Preferences::instance().tool(tool).ink(inkType);
m_owner->updateForCurrentTool();
}
void onCloseListBox() override {
releaseFocus();
}
bool m_lock;
ContextBar* m_owner;
};
class ContextBar::InkOpacityField : public IntEntry
@ -742,21 +745,51 @@ protected:
}
};
class ContextBar::GrabAlphaField : public CheckBox
class ContextBar::EyedropperField : public HBox
{
public:
GrabAlphaField() : CheckBox("Grab Alpha") {
setup_mini_font(this);
EyedropperField() {
m_channel.addItem("Color+Alpha");
m_channel.addItem("Color");
m_channel.addItem("Alpha");
m_channel.addItem("RGB+Alpha");
m_channel.addItem("RGB");
m_channel.addItem("HSB+Alpha");
m_channel.addItem("HSB");
m_channel.addItem("Gray+Alpha");
m_channel.addItem("Gray");
m_channel.addItem("Best fit Index");
m_sample.addItem("All Layers");
m_sample.addItem("Current Layer");
addChild(new Label("Pick:"));
addChild(&m_channel);
addChild(new Label("Sample:"));
addChild(&m_sample);
m_channel.Change.connect(Bind<void>(&EyedropperField::onChannelChange, this));
m_sample.Change.connect(Bind<void>(&EyedropperField::onSampleChange, this));
}
protected:
void onClick(Event& ev) override {
CheckBox::onClick(ev);
Preferences::instance().editor.grabAlpha(isSelected());
releaseFocus();
void updateFromPreferences(app::Preferences::Eyedropper& prefEyedropper) {
m_channel.setSelectedItemIndex((int)prefEyedropper.channel());
m_sample.setSelectedItemIndex((int)prefEyedropper.sample());
}
private:
void onChannelChange() {
Preferences::instance().eyedropper.channel(
(app::gen::EyedropperChannel)m_channel.getSelectedItemIndex());
}
void onSampleChange() {
Preferences::instance().eyedropper.sample(
(app::gen::EyedropperSample)m_sample.getSelectedItemIndex());
}
ComboBox m_channel;
ComboBox m_sample;
};
class ContextBar::AutoSelectLayerField : public CheckBox
@ -802,12 +835,12 @@ ContextBar::ContextBar()
addChild(m_contiguous = new ContiguousField());
addChild(m_stopAtGrid = new StopAtGridField());
addChild(m_inkType = new InkTypeField());
addChild(m_inkType = new InkTypeField(this));
addChild(m_opacityLabel = new Label("Opacity:"));
addChild(m_inkOpacityLabel = new Label("Opacity:"));
addChild(m_inkOpacity = new InkOpacityField());
addChild(m_grabAlpha = new GrabAlphaField());
addChild(m_eyedropperField = new EyedropperField());
addChild(m_autoSelectLayer = new AutoSelectLayerField());
@ -832,7 +865,7 @@ ContextBar::ContextBar()
m_freehandBox->addChild(m_freehandAlgo = new FreehandAlgorithmField());
setup_mini_font(m_toleranceLabel);
setup_mini_font(m_opacityLabel);
setup_mini_font(m_inkOpacityLabel);
TooltipManager* tooltipManager = new TooltipManager();
addChild(tooltipManager);
@ -840,17 +873,13 @@ ContextBar::ContextBar()
tooltipManager->addTooltipFor(m_brushType, "Brush Type", BOTTOM);
tooltipManager->addTooltipFor(m_brushSize, "Brush Size (in pixels)", BOTTOM);
tooltipManager->addTooltipFor(m_brushAngle, "Brush Angle (in degrees)", BOTTOM);
tooltipManager->addTooltipFor(m_inkOpacity, "Opacity (Alpha value in RGBA)", BOTTOM);
tooltipManager->addTooltipFor(m_inkType, "Ink", BOTTOM);
tooltipManager->addTooltipFor(m_inkOpacity, "Opacity (paint intensity)", BOTTOM);
tooltipManager->addTooltipFor(m_sprayWidth, "Spray Width", BOTTOM);
tooltipManager->addTooltipFor(m_spraySpeed, "Spray Speed", BOTTOM);
tooltipManager->addTooltipFor(m_transparentColor, "Transparent Color", BOTTOM);
tooltipManager->addTooltipFor(m_rotAlgo, "Rotation Algorithm", BOTTOM);
tooltipManager->addTooltipFor(m_freehandAlgo, "Freehand trace algorithm", BOTTOM);
tooltipManager->addTooltipFor(m_grabAlpha,
"When checked the tool picks the color from the active layer, and its alpha\n"
"component is used to setup the opacity level of all drawing tools.\n\n"
"When unchecked -the default behavior- the color is picked\n"
"from the composition of all sprite layers.", LEFT | TOP);
m_brushType->setupTooltips(tooltipManager);
m_selectionMode->setupTooltips(tooltipManager);
@ -939,6 +968,25 @@ void ContextBar::updateForTool(tools::Tool* tool)
m_brushPatternField->setBrushPattern(
preferences.brush.pattern());
// Tool ink
bool isPaint = tool &&
(tool->getInk(0)->isPaint() ||
tool->getInk(1)->isPaint());
bool isEffect = tool &&
(tool->getInk(0)->isEffect() ||
tool->getInk(1)->isEffect());
// True if the current tool support opacity slider
bool supportOpacity = (isPaint || isEffect);
// True if it makes sense to change the ink property for the current
// tool.
bool hasInk = tool &&
((tool->getInk(0)->isPaint() && !tool->getInk(0)->isEffect()) ||
(tool->getInk(1)->isPaint() && !tool->getInk(1)->isEffect()));
bool hasInkWithOpacity = false;
if (toolPref) {
m_tolerance->setTextf("%d", toolPref->tolerance());
m_contiguous->setSelected(toolPref->contiguous());
@ -948,22 +996,19 @@ void ContextBar::updateForTool(tools::Tool* tool)
m_inkType->setInkType(toolPref->ink());
m_inkOpacity->setTextf("%d", toolPref->opacity());
hasInkWithOpacity =
((isPaint && toolPref->ink() != tools::InkType::REPLACE_PIXEL) ||
(isEffect));
m_freehandAlgo->setFreehandAlgorithm(toolPref->freehandAlgorithm());
m_sprayWidth->setValue(toolPref->spray.width());
m_spraySpeed->setValue(toolPref->spray.speed());
}
m_grabAlpha->setSelected(preferences.editor.grabAlpha());
m_eyedropperField->updateFromPreferences(preferences.eyedropper);
m_autoSelectLayer->setSelected(preferences.editor.autoSelectLayer());
// True if the current tool needs opacity options
bool hasOpacity = tool &&
(tool->getInk(0)->isPaint() ||
tool->getInk(0)->isEffect() ||
tool->getInk(1)->isPaint() ||
tool->getInk(1)->isEffect());
// True if we have an image as brush
bool hasImageBrush = (activeBrush()->type() == kImageBrushType);
@ -977,10 +1022,6 @@ void ContextBar::updateForTool(tools::Tool* tool)
(tool->getInk(0)->isCelMovement() ||
tool->getInk(1)->isCelMovement());
// True if it makes sense to change the ink property for the current
// tool.
bool hasInk = hasOpacity;
// True if the current tool is floodfill
bool isFloodfill = tool &&
(tool->getPointShape(0)->isFloodFill() ||
@ -1005,16 +1046,16 @@ void ContextBar::updateForTool(tools::Tool* tool)
tool->getController(1)->isFreehand());
// Show/Hide fields
m_brushType->setVisible(hasOpacity && (!isFloodfill || (isFloodfill && hasImageBrush)));
m_brushSize->setVisible(hasOpacity && !isFloodfill && !hasImageBrush);
m_brushAngle->setVisible(hasOpacity && !isFloodfill && !hasImageBrush);
m_brushPatternField->setVisible(hasOpacity && hasImageBrush);
m_opacityLabel->setVisible(hasOpacity);
m_brushType->setVisible(supportOpacity && (!isFloodfill || (isFloodfill && hasImageBrush)));
m_brushSize->setVisible(supportOpacity && !isFloodfill && !hasImageBrush);
m_brushAngle->setVisible(supportOpacity && !isFloodfill && !hasImageBrush);
m_brushPatternField->setVisible(supportOpacity && hasImageBrush);
m_inkType->setVisible(hasInk && !hasImageBrush);
m_inkOpacity->setVisible(hasOpacity);
m_grabAlpha->setVisible(isEyedropper);
m_inkOpacityLabel->setVisible(hasInkWithOpacity && supportOpacity);
m_inkOpacity->setVisible(hasInkWithOpacity && supportOpacity);
m_eyedropperField->setVisible(isEyedropper);
m_autoSelectLayer->setVisible(isMove);
m_freehandBox->setVisible(isFreehand && hasOpacity);
m_freehandBox->setVisible(isFreehand && supportOpacity);
m_toleranceLabel->setVisible(hasTolerance);
m_tolerance->setVisible(hasTolerance);
m_contiguous->setVisible(hasTolerance);

View File

@ -106,7 +106,7 @@ namespace app {
class RotAlgorithmField;
class FreehandAlgorithmField;
class BrushPatternField;
class GrabAlphaField;
class EyedropperField;
class DropPixelsField;
class AutoSelectLayerField;
@ -118,9 +118,9 @@ namespace app {
ContiguousField* m_contiguous;
StopAtGridField* m_stopAtGrid;
InkTypeField* m_inkType;
ui::Label* m_opacityLabel;
ui::Label* m_inkOpacityLabel;
InkOpacityField* m_inkOpacity;
GrabAlphaField* m_grabAlpha;
EyedropperField* m_eyedropperField;
AutoSelectLayerField* m_autoSelectLayer;
ui::Box* m_freehandBox;
FreehandAlgorithmField* m_freehandAlgo;

View File

@ -916,49 +916,21 @@ tools::Ink* Editor::getCurrentEditorInk()
break;
}
}
else {
// Only paint tools can have different inks
else if (ink->isPaint() && !ink->isEffect()) {
tools::InkType inkType = Preferences::instance().tool(tool).ink();
const char* id = NULL;
switch (inkType) {
case tools::InkType::DEFAULT:
// Do nothing
case tools::InkType::REPLACE_PIXEL:
id = tools::WellKnownInks::PaintOpaque;
break;
case tools::InkType::SET_ALPHA:
id = tools::WellKnownInks::PaintSetAlpha;
case tools::InkType::ALPHA_COMPOSITING:
id = tools::WellKnownInks::Paint;
break;
case tools::InkType::LOCK_ALPHA:
id = tools::WellKnownInks::PaintLockAlpha;
break;
#if 0
case tools::InkType::OPAQUE:
id = tools::WellKnownInks::PaintOpaque;
break;
case tools::InkType::MERGE:
id = tools::WellKnownInks::Paint;
break;
case tools::InkType::SHADING:
id = tools::WellKnownInks::Shading;
break;
case tools::InkType::REPLACE:
if (!m_secondaryButton)
id = tools::WellKnownInks::ReplaceBgWithFg;
else
id = tools::WellKnownInks::ReplaceFgWithBg;
break;
case tools::InkType::ERASER:
id = tools::WellKnownInks::Eraser;
break;
case tools::InkType::SELECTION:
id = tools::WellKnownInks::Selection;
break;
case tools::InkType::BLUR:
id = tools::WellKnownInks::Blur;
break;
case tools::InkType::JUMBLE:
id = tools::WellKnownInks::Jumble;
break;
#endif
}
if (id)

View File

@ -14,6 +14,7 @@
#include "app/app.h"
#include "app/color_picker.h"
#include "app/commands/commands.h"
#include "app/commands/cmd_eyedropper.h"
#include "app/commands/params.h"
#include "app/ini_file.h"
#include "app/pref/preferences.h"
@ -363,18 +364,14 @@ bool StandbyState::onUpdateStatusBar(Editor* editor)
}
// For eye-dropper
else if (ink->isEyedropper()) {
bool grabAlpha = Preferences::instance().editor.grabAlpha();
ColorPicker picker;
picker.pickColor(editor->getSite(),
spritePos,
grabAlpha ?
ColorPicker::FromActiveLayer:
ColorPicker::FromComposition);
EyedropperCommand cmd;
app::Color color = Preferences::instance().colorBar.fgColor();
cmd.pickSample(editor->getSite(), spritePos, color);
char buf[256];
sprintf(buf, "- Pos %d %d", spritePos.x, spritePos.y);
StatusBar::instance()->showColor(0, buf, picker.color(), picker.alpha());
StatusBar::instance()->showColor(0, buf, color);
}
else {
Mask* mask =

View File

@ -14,6 +14,7 @@
#include "app/color_utils.h"
#include "app/commands/commands.h"
#include "app/modules/editors.h"
#include "app/modules/gfx.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/ui/editor/editor.h"
@ -311,8 +312,8 @@ bool PaletteView::onProcessMessage(Message* msg)
int idx = m_hot.color;
idx = MID(0, idx, currentPalette()->size()-1);
StatusBar::instance()->showColor(0, "",
app::Color::fromIndex(idx), 255);
StatusBar::instance()->showColor(
0, "", app::Color::fromIndex(idx));
MouseButtons buttons = mouseMsg->buttons();
if (hasCapture() && ((idx != m_currentEntry) ||
@ -422,37 +423,44 @@ void PaletteView::onPaint(ui::PaintEvent& ev)
// Draw palette entries
for (int i=0; i<palette->size(); ++i) {
gfx::Rect box = getPaletteEntryBounds(i);
gfx::Color color = gfx::rgba(
rgba_getr(palette->getEntry(i)),
rgba_getg(palette->getEntry(i)),
rgba_getb(palette->getEntry(i)));
doc::color_t palColor = palette->getEntry(i);
app::Color appColor = app::Color::fromRgb(
rgba_getr(palColor),
rgba_getg(palColor),
rgba_getb(palColor),
rgba_geta(palColor));
gfx::Color gfxColor = gfx::rgba(
rgba_getr(palColor),
rgba_getg(palColor),
rgba_getb(palColor),
rgba_geta(palColor));
g->drawRect(gfx::rgba(0, 0, 0), gfx::Rect(box).enlarge(guiscale()));
g->fillRect(color, box);
draw_color(g, box, appColor);
switch (m_style) {
case SelectOneColor:
if (m_currentEntry == i)
g->fillRect(color_utils::blackandwhite_neg(color),
g->fillRect(color_utils::blackandwhite_neg(gfxColor),
gfx::Rect(box.getCenter(), gfx::Size(1, 1)));
break;
case FgBgColors:
if (fgIndex == i) {
gfx::Color neg = color_utils::blackandwhite_neg(color);
gfx::Color neg = color_utils::blackandwhite_neg(gfxColor);
for (int i=0; i<m_boxsize/2; ++i)
g->drawHLine(neg, box.x, box.y+i, m_boxsize/2-i);
}
if (bgIndex == i) {
gfx::Color neg = color_utils::blackandwhite_neg(color);
gfx::Color neg = color_utils::blackandwhite_neg(gfxColor);
for (int i=0; i<m_boxsize/4; ++i)
g->drawHLine(neg, box.x+box.w-(i+1), box.y+box.h-m_boxsize/4+i, i+1);
}
if (transparentIndex == i)
g->fillRect(color_utils::blackandwhite_neg(color),
g->fillRect(color_utils::blackandwhite_neg(gfxColor),
gfx::Rect(box.getCenter(), gfx::Size(1, 1)));
break;
}
@ -804,7 +812,7 @@ int PaletteView::findExactIndex(const app::Color& color) const
case Color::RgbType:
case Color::HsvType:
case Color::GrayType:
return currentPalette()->findExactMatch(color.getRed(), color.getGreen(), color.getBlue());
return currentPalette()->findExactMatch(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
case Color::IndexType:
return color.getIndex();

View File

@ -176,6 +176,10 @@ namespace app {
PART_FREEHAND_ALGO_DOTS,
PART_FREEHAND_ALGO_DOTS_SELECTED,
PART_INK_DEFAULT,
PART_INK_COMPOSITE,
PART_INK_LOCK_ALPHA,
PARTS
};

View File

@ -278,6 +278,9 @@ SkinTheme::SkinTheme()
sheet_mapping["freehand_algo_pixel_perfect_selected"] = PART_FREEHAND_ALGO_PIXEL_PERFECT_SELECTED;
sheet_mapping["freehand_algo_dots"] = PART_FREEHAND_ALGO_DOTS;
sheet_mapping["freehand_algo_dots_selected"] = PART_FREEHAND_ALGO_DOTS_SELECTED;
sheet_mapping["ink_default"] = PART_INK_DEFAULT;
sheet_mapping["ink_composite"] = PART_INK_COMPOSITE;
sheet_mapping["ink_lock_alpha"] = PART_INK_LOCK_ALPHA;
}
SkinTheme::~SkinTheme()

View File

@ -286,12 +286,11 @@ void StatusBar::showTip(int msecs, const char *format, ...)
invalidate();
}
void StatusBar::showColor(int msecs, const char* text, const app::Color& color, int alpha)
void StatusBar::showColor(int msecs, const char* text, const app::Color& color)
{
if (setStatusText(msecs, text)) {
m_state = SHOW_COLOR;
m_color = color;
m_alpha = alpha;
}
}
@ -382,9 +381,9 @@ void StatusBar::onPaint(ui::PaintEvent& ev)
// Draw color description
std::string str = m_color.toHumanReadableString(app_get_current_pixel_format(),
app::Color::LongHumanReadableString);
if (m_alpha < 255) {
if (m_color.getAlpha() < 255) {
char buf[256];
sprintf(buf, " \xCE\xB1%d", m_alpha);
sprintf(buf, " \xCE\xB1%d", m_color.getAlpha());
str += buf;
}

View File

@ -54,7 +54,7 @@ namespace app {
bool setStatusText(int msecs, const char *format, ...);
void showTip(int msecs, const char *format, ...);
void showColor(int msecs, const char* text, const Color& color, int alpha);
void showColor(int msecs, const char* text, const Color& color);
void showTool(int msecs, tools::Tool* tool);
protected:
@ -87,7 +87,6 @@ namespace app {
// Showing a color
Color m_color;
int m_alpha;
// Box of main commands
ui::Widget* m_commandsBox;

View File

@ -17,7 +17,6 @@ add_library(doc-lib
cel_data_io.cpp
cel_io.cpp
cels_range.cpp
color_scales.cpp
compressed_image.cpp
context.cpp
conversion_she.cpp

View File

@ -18,7 +18,7 @@
namespace doc {
namespace algorithm {
void resize_image(const Image* src, Image* dst, ResizeMethod method, const Palette* pal, const RgbMap* rgbmap)
void resize_image(const Image* src, Image* dst, ResizeMethod method, const Palette* pal, const RgbMap* rgbmap, color_t maskColor)
{
switch (method) {
@ -111,15 +111,23 @@ void resize_image(const Image* src, Image* dst, ResizeMethod method, const Palet
break;
}
case IMAGE_INDEXED: {
int r = int((rgba_getr(pal->getEntry(color[0]))*u2 + rgba_getr(pal->getEntry(color[1]))*u1)*v2 +
(rgba_getr(pal->getEntry(color[2]))*u2 + rgba_getr(pal->getEntry(color[3]))*u1)*v1);
int g = int((rgba_getg(pal->getEntry(color[0]))*u2 + rgba_getg(pal->getEntry(color[1]))*u1)*v2 +
(rgba_getg(pal->getEntry(color[2]))*u2 + rgba_getg(pal->getEntry(color[3]))*u1)*v1);
int b = int((rgba_getb(pal->getEntry(color[0]))*u2 + rgba_getb(pal->getEntry(color[1]))*u1)*v2 +
(rgba_getb(pal->getEntry(color[2]))*u2 + rgba_getb(pal->getEntry(color[3]))*u1)*v1);
int a = int(((color[0] == 0 ? 0: 255)*u2 + (color[1] == 0 ? 0: 255)*u1)*v2 +
((color[2] == 0 ? 0: 255)*u2 + (color[3] == 0 ? 0: 255)*u1)*v1);
dst_color = a > 127 ? rgbmap->mapColor(r, g, b): 0;
// Convert index to RGBA values
for (int i=0; i<4; ++i) {
if (color[i] == maskColor)
color[i] = pal->getEntry(color[i]) & rgba_rgb_mask; // Set alpha = 0
else
color[i] = pal->getEntry(color[i]);
}
int r = int((rgba_getr(color[0])*u2 + rgba_getr(color[1])*u1)*v2 +
(rgba_getr(color[2])*u2 + rgba_getr(color[3])*u1)*v1);
int g = int((rgba_getg(color[0])*u2 + rgba_getg(color[1])*u1)*v2 +
(rgba_getg(color[2])*u2 + rgba_getg(color[3])*u1)*v1);
int b = int((rgba_getb(color[0])*u2 + rgba_getb(color[1])*u1)*v2 +
(rgba_getb(color[2])*u2 + rgba_getb(color[3])*u1)*v1);
int a = int((rgba_geta(color[0])*u2 + rgba_geta(color[1])*u1)*v2 +
(rgba_geta(color[2])*u2 + rgba_geta(color[3])*u1)*v1);
dst_color = rgbmap->mapColor(r, g, b, a);
break;
}
}

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -8,6 +8,7 @@
#define DOC_ALGORITHM_RESIZE_IMAGE_H_INCLUDED
#pragma once
#include "doc/color.h"
#include "gfx/fwd.h"
namespace doc {
@ -27,7 +28,8 @@ namespace doc {
// Warning: If you are using the RESIZE_METHOD_BILINEAR, it is
// recommended to use 'fixup_image_transparent_colors' function
// over the source image 'src' BEFORE using this routine.
void resize_image(const Image* src, Image* dst, ResizeMethod method, const Palette* palette, const RgbMap* rgbmap);
void resize_image(const Image* src, Image* dst, ResizeMethod method, const Palette* palette, const RgbMap* rgbmap,
color_t maskColor);
// It does not modify the image to the human eye, but internally
// tries to fixup all colors that are completelly transparent

View File

@ -1,48 +0,0 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
//
// 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 "base/clamp.h"
// Based on Allegro _rgb_scale_5 and _rgb_scale_6 tables
namespace doc {
int scale_5bits_to_8bits(int channel5bits)
{
static int scale[32] = {
0, 8, 16, 24, 33, 41, 49, 57,
66, 74, 82, 90, 99, 107, 115, 123,
132, 140, 148, 156, 165, 173, 181, 189,
198, 206, 214, 222, 231, 239, 247, 255
};
ASSERT(channel5bits >= 0);
ASSERT(channel5bits < 32);
return scale[channel5bits];
}
int scale_6bits_to_8bits(int channel6bits)
{
static int scale[64] = {
0, 4, 8, 12, 16, 20, 24, 28,
32, 36, 40, 44, 48, 52, 56, 60,
65, 69, 73, 77, 81, 85, 89, 93,
97, 101, 105, 109, 113, 117, 121, 125,
130, 134, 138, 142, 146, 150, 154, 158,
162, 166, 170, 174, 178, 182, 186, 190,
195, 199, 203, 207, 211, 215, 219, 223,
227, 231, 235, 239, 243, 247, 251, 255
};
ASSERT(channel6bits >= 0);
ASSERT(channel6bits < 64);
return scale[channel6bits];
}
} // namespace doc

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -15,8 +15,64 @@
namespace doc {
int scale_5bits_to_8bits(int channel5bits);
int scale_6bits_to_8bits(int channel6bits);
inline int scale_2bits_to_8bits(int channel2bits) {
static int scale[4] = {
0, 85, 170, 255
};
ASSERT(channel2bits >= 0);
ASSERT(channel2bits < 4);
return scale[channel2bits];
}
inline int scale_3bits_to_8bits(int channel3bits) {
static int scale[8] = {
0, 36, 72, 109,
145, 182, 218, 255
};
ASSERT(channel3bits >= 0);
ASSERT(channel3bits < 8);
return scale[channel3bits];
}
inline int scale_4bits_to_8bits(int channel4bits) {
static int scale[16] = {
0, 16, 34, 51,
68, 85, 102, 119,
136, 153, 170, 187,
204, 221, 238, 255
};
ASSERT(channel4bits >= 0);
ASSERT(channel4bits < 16);
return scale[channel4bits];
}
inline int scale_5bits_to_8bits(int channel5bits) {
static int scale[32] = {
0, 8, 16, 24, 33, 41, 49, 57,
66, 74, 82, 90, 99, 107, 115, 123,
132, 140, 148, 156, 165, 173, 181, 189,
198, 206, 214, 222, 231, 239, 247, 255
};
ASSERT(channel5bits >= 0);
ASSERT(channel5bits < 32);
return scale[channel5bits];
}
inline int scale_6bits_to_8bits(int channel6bits) {
static int scale[64] = {
0, 4, 8, 12, 16, 20, 24, 28,
32, 36, 40, 44, 48, 52, 56, 60,
65, 69, 73, 77, 81, 85, 89, 93,
97, 101, 105, 109, 113, 117, 121, 125,
130, 134, 138, 142, 146, 150, 154, 158,
162, 166, 170, 174, 178, 182, 186, 190,
195, 199, 203, 207, 211, 215, 219, 223,
227, 231, 235, 239, 243, 247, 251, 255
};
ASSERT(channel6bits >= 0);
ASSERT(channel6bits < 64);
return scale[channel6bits];
}
} // namespace doc

View File

@ -142,9 +142,9 @@ void Palette::makeBlack()
// Creates a linear ramp in the palette.
void Palette::makeGradient(int from, int to)
{
int r, g, b;
int r1, g1, b1;
int r2, g2, b2;
int r, g, b, a;
int r1, g1, b1, a1;
int r2, g2, b2, a2;
int i, n;
ASSERT(from >= 0 && from <= 255);
@ -160,22 +160,27 @@ void Palette::makeGradient(int from, int to)
r1 = rgba_getr(getEntry(from));
g1 = rgba_getg(getEntry(from));
b1 = rgba_getb(getEntry(from));
a1 = rgba_geta(getEntry(from));
r2 = rgba_getr(getEntry(to));
g2 = rgba_getg(getEntry(to));
b2 = rgba_getb(getEntry(to));
a2 = rgba_geta(getEntry(to));
for (i=from+1; i<to; ++i) {
r = r1 + (r2-r1) * (i-from) / n;
g = g1 + (g2-g1) * (i-from) / n;
b = b1 + (b2-b1) * (i-from) / n;
setEntry(i, rgba(r, g, b, 255));
a = a1 + (a2-a1) * (i-from) / n;
setEntry(i, rgba(r, g, b, a));
}
}
int Palette::findExactMatch(int r, int g, int b) const
int Palette::findExactMatch(int r, int g, int b, int a) const
{
for (int i=0; i<(int)m_colors.size(); ++i)
if (getEntry(i) == rgba(r, g, b, 255))
if (getEntry(i) == rgba(r, g, b, a))
return i;
return -1;
@ -184,61 +189,64 @@ int Palette::findExactMatch(int r, int g, int b) const
//////////////////////////////////////////////////////////////////////
// Based on Allegro's bestfit_color
static unsigned int col_diff[3*128];
static std::vector<unsigned int> col_diff;
static void bestfit_init()
static void initBestfit()
{
int i, k;
col_diff.resize(4*128, 0);
for (i=1; i<64; i++) {
k = i * i;
col_diff[0 +i] = col_diff[0 +128-i] = k * (59 * 59);
col_diff[128+i] = col_diff[128+128-i] = k * (30 * 30);
col_diff[256+i] = col_diff[256+128-i] = k * (11 * 11);
for (int i=1; i<64; ++i) {
int k = i * i;
col_diff[0 +i] = col_diff[0 +128-i] = k * 59 * 59;
col_diff[128+i] = col_diff[128+128-i] = k * 30 * 30;
col_diff[256+i] = col_diff[256+128-i] = k * 11 * 11;
col_diff[384+i] = col_diff[384+128-i] = k * 8 * 8;
}
}
int Palette::findBestfit(int r, int g, int b, int mask_index) const
int Palette::findBestfit(int r, int g, int b, int a, int mask_index) const
{
#ifdef __GNUC__
register int bestfit asm("%eax");
#else
register int bestfit;
#endif
int i, coldiff, lowest;
ASSERT(r >= 0 && r <= 255);
ASSERT(g >= 0 && g <= 255);
ASSERT(b >= 0 && b <= 255);
ASSERT(a >= 0 && a <= 255);
if (col_diff[1] == 0)
bestfit_init();
bestfit = 0;
lowest = std::numeric_limits<int>::max();
if (col_diff.empty())
initBestfit();
r >>= 3;
g >>= 3;
b >>= 3;
a >>= 3;
i = 0;
while (i < size()) {
// Mask index is like alpha = 0, so we can use it as transparent color.
if (a == 0 && mask_index >= 0)
return mask_index;
int bestfit = 0;
int lowest = std::numeric_limits<int>::max();
int size = MIN(256, m_colors.size());
for (int i=0; i<size; ++i) {
color_t rgb = m_colors[i];
coldiff = (col_diff + 0) [ ((rgba_getg(rgb)>>3) - g) & 0x7F ];
int coldiff = col_diff[((rgba_getg(rgb)>>3) - g) & 0x7F];
if (coldiff < lowest) {
coldiff += (col_diff + 128) [ ((rgba_getr(rgb)>>3) - r) & 0x7F ];
coldiff += col_diff[128 + (((rgba_getr(rgb)>>3) - r) & 0x7F)];
if (coldiff < lowest) {
coldiff += (col_diff + 256) [ ((rgba_getb(rgb)>>3) - b) & 0x7F ];
if (coldiff < lowest && i != mask_index) {
bestfit = i;
if (coldiff == 0)
return bestfit;
lowest = coldiff;
coldiff += col_diff[256 + (((rgba_getb(rgb)>>3) - b) & 0x7F)];
if (coldiff < lowest) {
coldiff += col_diff[384 + (((rgba_geta(rgb)>>3) - a) & 0x7F)];
if (coldiff < lowest && i != mask_index) {
if (coldiff == 0)
return i;
bestfit = i;
lowest = coldiff;
}
}
}
}
i++;
}
return bestfit;

View File

@ -77,8 +77,8 @@ namespace doc {
void makeGradient(int from, int to);
int findExactMatch(int r, int g, int b) const;
int findBestfit(int r, int g, int b, int mask_index = 0) const;
int findExactMatch(int r, int g, int b, int a) const;
int findBestfit(int r, int g, int b, int a, int mask_index) const;
private:
frame_t m_frame;

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -72,7 +72,7 @@ TEST(ResizeImage, NearestNeighborInterp)
Image* dst_expected = create_image_from_data(IMAGE_RGB, test_image_scaled_9x9_nearest, 9, 9);
Image* dst = Image::create(IMAGE_RGB, 9, 9);
algorithm::resize_image(src, dst, algorithm::RESIZE_METHOD_NEAREST_NEIGHBOR, NULL, NULL);
algorithm::resize_image(src, dst, algorithm::RESIZE_METHOD_NEAREST_NEIGHBOR, NULL, NULL, -1);
ASSERT_EQ(0, count_diff_between_images(dst, dst_expected));
}

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -15,7 +15,11 @@
namespace doc {
#define MAPSIZE 32*32*32
#define RSIZE 32
#define GSIZE 32
#define BSIZE 32
#define ASIZE 8
#define MAPSIZE (RSIZE*GSIZE*BSIZE*ASIZE)
RgbMap::RgbMap()
: Object(ObjectType::RgbMap)
@ -36,26 +40,23 @@ void RgbMap::regenerate(const Palette* palette, int mask_index)
m_palette = palette;
m_modifications = palette->getModifications();
// TODO This is slow for 256 colors 32*32*32*8 findBestfit calls
int i = 0;
for (int r=0; r<32; ++r) {
for (int g=0; g<32; ++g) {
for (int b=0; b<32; ++b) {
m_map[i++] =
palette->findBestfit(
scale_5bits_to_8bits(r),
scale_5bits_to_8bits(g),
scale_5bits_to_8bits(b), mask_index);
for (int r=0; r<RSIZE; ++r) {
for (int g=0; g<GSIZE; ++g) {
for (int b=0; b<BSIZE; ++b) {
for (int a=0; a<ASIZE; ++a) {
m_map[i++] =
palette->findBestfit(
scale_5bits_to_8bits(r),
scale_5bits_to_8bits(g),
scale_5bits_to_8bits(b),
scale_3bits_to_8bits(a), mask_index);
}
}
}
}
}
int RgbMap::mapColor(int r, int g, int b) const
{
ASSERT(r >= 0 && r < 256);
ASSERT(g >= 0 && g < 256);
ASSERT(b >= 0 && b < 256);
return m_map[((r>>3) << 10) + ((g>>3) << 5) + (b>>3)];
}
} // namespace doc

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -24,7 +24,14 @@ namespace doc {
bool match(const Palette* palette) const;
void regenerate(const Palette* palette, int mask_index);
int mapColor(int r, int g, int b) const;
int mapColor(int r, int g, int b, 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
return m_map[(a>>5) | ((b>>3) << 3) | ((g>>3) << 8) | ((r>>3) << 13)];
}
private:
std::vector<uint8_t> m_map;

View File

@ -114,7 +114,7 @@ void ColorCurveFilter::applyToIndexed(FilterManager* filterMgr)
Target target = filterMgr->getTarget();
const Palette* pal = filterMgr->getIndexedData()->getPalette();
const RgbMap* rgbmap = filterMgr->getIndexedData()->getRgbMap();
int x, c, r, g, b;
int x, c, r, g, b, a;
for (x=0; x<w; x++) {
if (filterMgr->skipPixel()) {
@ -129,15 +129,18 @@ void ColorCurveFilter::applyToIndexed(FilterManager* filterMgr)
c = m_cmap[c];
}
else {
r = rgba_getr(pal->getEntry(c));
g = rgba_getg(pal->getEntry(c));
b = rgba_getb(pal->getEntry(c));
c = pal->getEntry(c);
r = rgba_getr(c);
g = rgba_getg(c);
b = rgba_getb(c);
a = rgba_geta(c);
if (target & TARGET_RED_CHANNEL) r = m_cmap[r];
if (target & TARGET_RED_CHANNEL ) r = m_cmap[r];
if (target & TARGET_GREEN_CHANNEL) g = m_cmap[g];
if (target & TARGET_BLUE_CHANNEL) b = m_cmap[b];
if (target & TARGET_BLUE_CHANNEL ) b = m_cmap[b];
if (target & TARGET_ALPHA_CHANNEL) a = m_cmap[a];
c = rgbmap->mapColor(r, g, b);
c = rgbmap->mapColor(r, g, b, a);
}
*(dst_address++) = MID(0, c, pal->size()-1);

View File

@ -87,22 +87,28 @@ namespace {
struct GetPixelsDelegateIndexed : public GetPixelsDelegate {
const Palette* pal;
int r, g, b, index;
int r, g, b, a, index;
GetPixelsDelegateIndexed(const Palette* pal) : pal(pal) { }
void reset(const ConvolutionMatrix* matrix) {
GetPixelsDelegate::reset(matrix);
r = g = b = index = 0;
r = g = b = a = index = 0;
}
void operator()(GrayscaleTraits::pixel_t color)
void operator()(IndexedTraits::pixel_t color)
{
if (*matrixData) {
r += rgba_getr(pal->getEntry(color)) * (*matrixData);
g += rgba_getg(pal->getEntry(color)) * (*matrixData);
b += rgba_getb(pal->getEntry(color)) * (*matrixData);
index += color * (*matrixData);
color_t rgba = pal->getEntry(color);
if (rgba_geta(rgba) == 0)
div -= *matrixData;
else {
r += rgba_getr(rgba) * (*matrixData);
g += rgba_getg(rgba) * (*matrixData);
b += rgba_getb(rgba) * (*matrixData);
a += rgba_geta(rgba) * (*matrixData);
}
}
matrixData++;
}
@ -297,28 +303,37 @@ void ConvolutionMatrixFilter::applyToIndexed(FilterManager* filterMgr)
*(dst_address++) = delegate.index;
}
else {
color = pal->getEntry(color);
if (target & TARGET_RED_CHANNEL) {
delegate.r = delegate.r / delegate.div + m_matrix->getBias();
delegate.r = MID(0, delegate.r, 255);
}
else
delegate.r = rgba_getr(pal->getEntry(color));
delegate.r = rgba_getr(color);
if (target & TARGET_GREEN_CHANNEL) {
delegate.g = delegate.g / delegate.div + m_matrix->getBias();
delegate.g = MID(0, delegate.g, 255);
}
else
delegate.g = rgba_getg(pal->getEntry(color));
delegate.g = rgba_getg(color);
if (target & TARGET_BLUE_CHANNEL) {
delegate.b = delegate.b / delegate.div + m_matrix->getBias();
delegate.b = MID(0, delegate.b, 255);
}
else
delegate.b = rgba_getb(pal->getEntry(color));
delegate.b = rgba_getb(color);
*(dst_address++) = rgbmap->mapColor(delegate.r, delegate.g, delegate.b);
if (target & TARGET_ALPHA_CHANNEL) {
delegate.a = delegate.a / delegate.div + m_matrix->getBias();
delegate.a = MID(0, delegate.a, 255);
}
else
delegate.a = rgba_geta(color);
*(dst_address++) = rgbmap->mapColor(delegate.r, delegate.g, delegate.b, delegate.a);
}
}
}

View File

@ -92,7 +92,7 @@ void InvertColorFilter::applyToIndexed(FilterManager* filterMgr)
const RgbMap* rgbmap = filterMgr->getIndexedData()->getRgbMap();
int w = filterMgr->getWidth();
Target target = filterMgr->getTarget();
int x, c, r, g, b;
int x, c, r, g, b, a;
for (x=0; x<w; x++) {
if (filterMgr->skipPixel()) {
@ -106,15 +106,18 @@ void InvertColorFilter::applyToIndexed(FilterManager* filterMgr)
if (target & TARGET_INDEX_CHANNEL)
c ^= 0xff;
else {
r = rgba_getr(pal->getEntry(c));
g = rgba_getg(pal->getEntry(c));
b = rgba_getb(pal->getEntry(c));
c = pal->getEntry(c);
r = rgba_getr(c);
g = rgba_getg(c);
b = rgba_getb(c);
a = rgba_geta(c);
if (target & TARGET_RED_CHANNEL ) r ^= 0xff;
if (target & TARGET_GREEN_CHANNEL) g ^= 0xff;
if (target & TARGET_BLUE_CHANNEL ) b ^= 0xff;
if (target & TARGET_ALPHA_CHANNEL) a ^= 0xff;
c = rgbmap->mapColor(r, g, b);
c = rgbmap->mapColor(r, g, b, a);
}
*(dst_address++) = c;

View File

@ -78,9 +78,11 @@ namespace {
channel[0][c] = color;
}
else {
channel[0][c] = rgba_getr(pal->getEntry(color));
channel[1][c] = rgba_getg(pal->getEntry(color));
channel[2][c] = rgba_getb(pal->getEntry(color));
color_t rgb = pal->getEntry(color);
channel[0][c] = rgba_getr(rgb);
channel[1][c] = rgba_getg(rgb);
channel[2][c] = rgba_getb(rgb);
channel[3][c] = rgba_geta(rgb);
}
c++;
}
@ -222,7 +224,7 @@ void MedianFilter::applyToIndexed(FilterManager* filterMgr)
const Palette* pal = filterMgr->getIndexedData()->getPalette();
const RgbMap* rgbmap = filterMgr->getIndexedData()->getRgbMap();
Target target = filterMgr->getTarget();
int color, r, g, b;
int color, r, g, b, a;
GetPixelsDelegateIndexed delegate(pal, m_channel, target);
int x = filterMgr->x();
int x2 = x+filterMgr->getWidth();
@ -245,13 +247,14 @@ void MedianFilter::applyToIndexed(FilterManager* filterMgr)
}
else {
color = get_pixel_fast<IndexedTraits>(src, x, y);
color = pal->getEntry(color);
if (target & TARGET_RED_CHANNEL) {
std::sort(m_channel[0].begin(), m_channel[0].end());
r = m_channel[0][m_ncolors/2];
}
else
r = rgba_getr(pal->getEntry(color));
r = rgba_getr(color);
if (target & TARGET_GREEN_CHANNEL) {
std::sort(m_channel[1].begin(), m_channel[1].end());
@ -265,9 +268,16 @@ void MedianFilter::applyToIndexed(FilterManager* filterMgr)
b = m_channel[2][m_ncolors/2];
}
else
b = rgba_getb(pal->getEntry(color));
b = rgba_getb(color);
*(dst_address++) = rgbmap->mapColor(r, g, b);
if (target & TARGET_ALPHA_CHANNEL) {
std::sort(m_channel[3].begin(), m_channel[3].end());
a = m_channel[3][m_ncolors/2];
}
else
a = rgba_geta(color);
*(dst_address++) = rgbmap->mapColor(r, g, b, a);
}
}
}

View File

@ -20,34 +20,35 @@
namespace render {
using namespace doc;
template<int RBits, int GBits, int BBits> // Number of bits for each component in the histogram
template<int RBits, // Number of bits for each component in the histogram
int GBits,
int BBits,
int ABits>
class ColorHistogram {
public:
// Number of elements in histogram for each RGB component
enum {
RElements = 1 << RBits,
GElements = 1 << GBits,
BElements = 1 << BBits
BElements = 1 << BBits,
AElements = 1 << ABits
};
ColorHistogram()
: m_histogram(RElements*GElements*BElements, 0)
, m_useHighPrecision(true)
{
: m_histogram(RElements*GElements*BElements*AElements, 0)
, m_useHighPrecision(true) {
}
// Returns the number of points in the specified histogram
// entry. Each index (i, j, k) is in the range of the
// histogram i=[0,RElements), etc.
std::size_t at(int i, int j, int k) const
{
return m_histogram[histogramIndex(i, j, k)];
// entry. Each rgba-index is in the range of the histogram, e.g.
// r=[0,RElements), g=[0,GElements), etc.
std::size_t at(int r, int g, int b, int a) const {
return m_histogram[histogramIndex(r, g, b, a)];
}
// Add the specified "color" in the histogram as many times as the
// specified value in "count".
void addSamples(uint32_t color, std::size_t count = 1)
{
void addSamples(uint32_t color, std::size_t count = 1) {
int i = histogramIndex(color);
if (m_histogram[i] < std::numeric_limits<std::size_t>::max()-count) // Avoid overflow
@ -79,8 +80,7 @@ namespace render {
// with the more important colors in the histogram. Returns the
// number of used entries in the palette (maybe the range [from,to]
// is more than necessary).
int createOptimizedPalette(Palette* palette, int from, int to)
{
int createOptimizedPalette(Palette* palette, int from, int to) {
// Can we use the high-precision table?
if (m_useHighPrecision && int(m_highPrecision.size()) <= (to-from+1)) {
for (int i=0; i<(int)m_highPrecision.size(); ++i)
@ -105,16 +105,19 @@ namespace render {
// Converts input color in a index for the histogram. It reduces
// each 8-bit component to the resolution given in the template
// parameters.
std::size_t histogramIndex(uint32_t color) const
{
std::size_t histogramIndex(uint32_t color) const {
return histogramIndex((rgba_getr(color) >> (8 - RBits)),
(rgba_getg(color) >> (8 - GBits)),
(rgba_getb(color) >> (8 - BBits)));
(rgba_getb(color) >> (8 - BBits)),
(rgba_geta(color) >> (8 - ABits)));
}
std::size_t histogramIndex(int i, int j, int k) const
{
return i | (j << RBits) | (k << (RBits+GBits));
std::size_t histogramIndex(int r, int g, int b, int a) const {
return
r
| (g << RBits)
| (b << (RBits+GBits))
| (a << (RBits+GBits+BBits));
}
// 3D histogram (the index in the histogram is calculated through histogramIndex() function).

View File

@ -21,40 +21,46 @@ namespace render {
// These classes are used as parameters for some Box's generic
// member functions, so we can access to a different axis using
// the same generic function (i=Red channel in RAxisGetter, etc.).
struct RAxisGetter { static std::size_t at(const Histogram& h, int i, int j, int k) { return h.at(i, j, k); } };
struct GAxisGetter { static std::size_t at(const Histogram& h, int i, int j, int k) { return h.at(j, i, k); } };
struct BAxisGetter { static std::size_t at(const Histogram& h, int i, int j, int k) { return h.at(j, k, i); } };
struct RAxisGetter { static std::size_t at(const Histogram& h, int i, int j, int k, int l) { return h.at(i, j, k, l); } };
struct GAxisGetter { static std::size_t at(const Histogram& h, int i, int j, int k, int l) { return h.at(j, i, k, l); } };
struct BAxisGetter { static std::size_t at(const Histogram& h, int i, int j, int k, int l) { return h.at(j, k, i, l); } };
struct AAxisGetter { static std::size_t at(const Histogram& h, int i, int j, int k, int l) { return h.at(j, k, l, i); } };
// These classes are used as template parameter to split a Box
// along an axis (see splitAlongAxis)
struct RAxisSplitter {
static Box box1(const Box& box, int r) { return Box(box.r1, box.g1, box.b1, r, box.g2, box.b2); }
static Box box2(const Box& box, int r) { return Box(r, box.g1, box.b1, box.r2, box.g2, box.b2); }
static Box box1(const Box& box, int r) { return Box(box.r1, box.g1, box.b1, box.a1, r, box.g2, box.b2, box.a2); }
static Box box2(const Box& box, int r) { return Box(r, box.g1, box.b1, box.a1, box.r2, box.g2, box.b2, box.a2); }
};
struct GAxisSplitter {
static Box box1(const Box& box, int g) { return Box(box.r1, box.g1, box.b1, box.r2, g, box.b2); }
static Box box2(const Box& box, int g) { return Box(box.r1, g, box.b1, box.r2, box.g2, box.b2); }
static Box box1(const Box& box, int g) { return Box(box.r1, box.g1, box.b1, box.a1, box.r2, g, box.b2, box.a2); }
static Box box2(const Box& box, int g) { return Box(box.r1, g, box.b1, box.a1, box.r2, box.g2, box.b2, box.a2); }
};
struct BAxisSplitter {
static Box box1(const Box& box, int b) { return Box(box.r1, box.g1, box.b1, box.r2, box.g2, b ); }
static Box box2(const Box& box, int b) { return Box(box.r1, box.g1, b, box.r2, box.g2, box.b2); }
static Box box1(const Box& box, int b) { return Box(box.r1, box.g1, box.b1, box.a1, box.r2, box.g2, b, box.a2); }
static Box box2(const Box& box, int b) { return Box(box.r1, box.g1, b, box.a1, box.r2, box.g2, box.b2, box.a2); }
};
struct AAxisSplitter {
static Box box1(const Box& box, int a) { return Box(box.r1, box.g1, box.b1, box.a1, box.r2, box.g2, box.b2, a ); }
static Box box2(const Box& box, int a) { return Box(box.r1, box.g1, box.b1, a, box.r2, box.g2, box.b2, box.a2); }
};
public:
Box(int r1, int g1, int b1,
int r2, int g2, int b2)
: r1(r1), g1(g1), b1(b1)
, r2(r2), g2(g2), b2(b2)
Box(int r1, int g1, int b1, int a1,
int r2, int g2, int b2, int a2)
: r1(r1), g1(g1), b1(b1), a1(a1)
, r2(r2), g2(g2), b2(b2), a2(a2)
, points(0)
, volume(calculateVolume()) { }
, volume(calculateVolume()) {
}
// Shrinks each plane of the box to a position where there are
// points in the histogram.
void shrink(const Histogram& histogram)
{
axisShrink<RAxisGetter>(histogram, r1, r2, g1, g2, b1, b2);
axisShrink<GAxisGetter>(histogram, g1, g2, r1, r2, b1, b2);
axisShrink<BAxisGetter>(histogram, b1, b2, r1, r2, g1, g2);
void shrink(const Histogram& histogram) {
axisShrink<RAxisGetter>(histogram, r1, r2, g1, g2, b1, b2, a1, a2);
axisShrink<GAxisGetter>(histogram, g1, g2, r1, r2, b1, b2, a1, a2);
axisShrink<BAxisGetter>(histogram, b1, b2, r1, r2, g1, g2, a1, a2);
axisShrink<AAxisGetter>(histogram, a1, a2, r1, r2, g1, g2, b1, b2);
// Calculate number of points inside the box (this is done by
// first time here, because the Box ctor didn't calculate it).
@ -64,37 +70,47 @@ namespace render {
volume = calculateVolume();
}
bool split(const Histogram& histogram, std::priority_queue<Box>& boxes) const
{
bool split(const Histogram& histogram, std::priority_queue<Box>& boxes) const {
// Split along the largest dimension of the box.
if ((r2-r1) >= (g2-g1) && (r2-r1) >= (b2-b1)) {
return splitAlongAxis<RAxisGetter, RAxisSplitter>(histogram, boxes, r1, r2, g1, g2, b1, b2);
if ((r2-r1) >= (g2-g1) &&
(r2-r1) >= (b2-b1) &&
(r2-r1) >= (a2-a1)) {
return splitAlongAxis<RAxisGetter, RAxisSplitter>(histogram, boxes, r1, r2, g1, g2, b1, b2, a1, a2);
}
else if ((g2-g1) >= (r2-r1) && (g2-g1) >= (b2-b1)) {
return splitAlongAxis<GAxisGetter, GAxisSplitter>(histogram, boxes, g1, g2, r1, r2, b1, b2);
if ((g2-g1) >= (r2-r1) &&
(g2-g1) >= (b2-b1) &&
(g2-g1) >= (a2-a1)) {
return splitAlongAxis<GAxisGetter, GAxisSplitter>(histogram, boxes, g1, g2, r1, r2, b1, b2, a1, a2);
}
else {
return splitAlongAxis<BAxisGetter, BAxisSplitter>(histogram, boxes, b1, b2, r1, r2, g1, g2);
if ((b2-b1) >= (r2-r1) &&
(b2-b1) >= (g2-g1) &&
(b2-b1) >= (a2-a1)) {
return splitAlongAxis<BAxisGetter, BAxisSplitter>(histogram, boxes, b1, b2, r1, r2, g1, g2, a1, a2);
}
return splitAlongAxis<AAxisGetter, AAxisSplitter>(histogram, boxes, a1, a2, r1, r2, g1, g2, b1, b2);
}
// Returns the color enclosed by the box calculating the mean of
// all histogram's points inside the box.
uint32_t meanColor(const Histogram& histogram) const
{
std::size_t r = 0, g = 0, b = 0;
uint32_t meanColor(const Histogram& histogram) const {
std::size_t r = 0, g = 0, b = 0, a = 0;
std::size_t count = 0;
int i, j, k;
int i, j, k, l;
for (i=r1; i<=r2; ++i)
for (j=g1; j<=g2; ++j)
for (k=b1; k<=b2; ++k) {
int c = histogram.at(i, j, k);
r += c * i;
g += c * j;
b += c * k;
count += c;
}
for (k=b1; k<=b2; ++k)
for (l=a1; l<=a2; ++l) {
int c = histogram.at(i, j, k, l);
r += c * i;
g += c * j;
b += c * k;
a += c * l;
count += c;
}
// No colors in the box? This should not be possible.
ASSERT(count > 0 && "Box without histogram points, you must fill the histogram before using this function.");
@ -104,12 +120,12 @@ namespace render {
// Returns the mean.
return doc::rgba((255 * r / (Histogram::RElements-1)) / count,
(255 * g / (Histogram::GElements-1)) / count,
(255 * b / (Histogram::BElements-1)) / count, 255);
(255 * b / (Histogram::BElements-1)) / count,
(255 * a / (Histogram::AElements-1)) / count);
}
// The boxes will be sort in the priority_queue by volume.
bool operator<(const Box& other) const
{
bool operator<(const Box& other) const {
return volume < other.volume;
}
@ -119,21 +135,20 @@ namespace render {
// value returned by this function is cached in the "volume"
// variable member of Box class to avoid multiplying several
// times.
int calculateVolume() const
{
return (r2-r1+1) * (g2-g1+1) * (b2-b1+1);
int calculateVolume() const {
return (r2-r1+1) * (g2-g1+1) * (b2-b1+1) * (a2-a1+1);
}
// Returns the number of histogram's points inside the box bounds.
std::size_t countPoints(const Histogram& histogram) const
{
std::size_t countPoints(const Histogram& histogram) const {
std::size_t count = 0;
int i, j, k;
int i, j, k, l;
for (i=r1; i<=r2; ++i)
for (j=g1; j<=g2; ++j)
for (k=b1; k<=b2; ++k)
count += histogram.at(i, j, k);
for (l=a1; l<=a2; ++l)
count += histogram.at(i, j, k, l);
return count;
}
@ -145,16 +160,19 @@ namespace render {
static void axisShrink(const Histogram& histogram,
int& i1, int& i2,
const int& j1, const int& j2,
const int& k1, const int& k2)
const int& k1, const int& k2,
const int& l1, const int& l2)
{
int j, k;
int j, k, l;
// Shrink i1.
for (; i1<i2; ++i1) {
for (j=j1; j<=j2; ++j) {
for (k=k1; k<=k2; ++k) {
if (AxisGetter::at(histogram, i1, j, k) > 0)
goto doneA;
for (l=l1; l<=l2; ++l) {
if (AxisGetter::at(histogram, i1, j, k, l) > 0)
goto doneA;
}
}
}
}
@ -164,8 +182,10 @@ namespace render {
for (; i2>i1; --i2) {
for (j=j1; j<=j2; ++j) {
for (k=k1; k<=k2; ++k) {
if (AxisGetter::at(histogram, i2, j, k) > 0)
goto doneB;
for (l=l1; l<=l2; ++l) {
if (AxisGetter::at(histogram, i2, j, k, l) > 0)
goto doneB;
}
}
}
}
@ -183,13 +203,13 @@ namespace render {
std::priority_queue<Box>& boxes,
const int& i1, const int& i2,
const int& j1, const int& j2,
const int& k1, const int& k2) const
{
const int& k1, const int& k2,
const int& l1, const int& l2) const {
// These two variables will be used to count how many points are
// in each side of the box if we split it in "i" position.
std::size_t totalPoints1 = 0;
std::size_t totalPoints2 = this->points;
int i, j, k;
int i, j, k, l;
// We will try to split the box along the "i" axis. Imagine a
// plane which its normal vector is "i" axis, so we will try to
@ -202,7 +222,8 @@ namespace render {
// We count all points in "i" plane.
for (j=j1; j<=j2; ++j)
for (k=k1; k<=k2; ++k)
planePoints += AxisGetter::at(histogram, i, j, k);
for (l=l1; l<=l2; ++l)
planePoints += AxisGetter::at(histogram, i, j, k, l);
// As we move the plane to split through "i" axis One side is getting more points,
totalPoints1 += planePoints;
@ -234,8 +255,8 @@ namespace render {
return false;
}
int r1, g1, b1; // Min point (closest to origin)
int r2, g2, b2; // Max point
int r1, g1, b1, a1; // Min point (closest to origin)
int r2, g2, b2, a2; // Max point
std::size_t points; // Number of points in the space which enclose this box
int volume;
}; // end of class Box
@ -250,10 +271,11 @@ namespace render {
std::priority_queue<Box<Histogram> > boxes;
// First we start with one big box containing all histogram's samples.
boxes.push(Box<Histogram>(0, 0, 0,
boxes.push(Box<Histogram>(0, 0, 0, 0,
Histogram::RElements-1,
Histogram::GElements-1,
Histogram::BElements-1));
Histogram::BElements-1,
Histogram::AElements-1));
// Then we split each box until we reach the maximum specified by
// the user (maxBoxes) or until there aren't more boxes to split.

View File

@ -60,12 +60,13 @@ namespace render {
3, 1 };
class OrderedDither {
static int colorDistance(int r1, int g1, int b1,
int r2, int g2, int b2) {
static int colorDistance(int r1, int g1, int b1, int a1,
int r2, int g2, int b2, int a2) {
// The factor for RGB components came from doc::rba_luma()
return int((r1-r2) * (r1-r2) * 21 + // 2126
(g1-g2) * (g1-g2) * 71 + // 7152
(b1-b2) * (b1-b2) * 7); // 722
(b1-b2) * (b1-b2) * 7 + // 722
(a1-a2) * (a1-a2));
}
public:
@ -80,7 +81,7 @@ namespace render {
const doc::RgbMap* rgbmap,
const doc::Palette* palette) {
// Alpha=0, output transparent color
if (!doc::rgba_geta(color))
if (m_transparentIndex >= 0 && !doc::rgba_geta(color))
return m_transparentIndex;
// Get the nearest color in the palette with the given RGB
@ -88,14 +89,16 @@ namespace render {
int r = doc::rgba_getr(color);
int g = doc::rgba_getg(color);
int b = doc::rgba_getb(color);
int a = doc::rgba_geta(color);
doc::color_t nearest1idx =
(rgbmap ? rgbmap->mapColor(r, g, b):
palette->findBestfit(r, g, b, m_transparentIndex));
(rgbmap ? rgbmap->mapColor(r, g, b, a):
palette->findBestfit(r, g, b, a, m_transparentIndex));
doc::color_t nearest1rgb = palette->getEntry(nearest1idx);
int r1 = doc::rgba_getr(nearest1rgb);
int g1 = doc::rgba_getg(nearest1rgb);
int b1 = doc::rgba_getb(nearest1rgb);
int a1 = doc::rgba_geta(nearest1rgb);
// Between the original color ('color' parameter) and 'nearest'
// index, we have an error (r1-r, g1-g, b1-b). Here we try to
@ -104,12 +107,14 @@ namespace render {
int r2 = r - (r1-r);
int g2 = g - (g1-g);
int b2 = b - (b1-b);
int a2 = a - (a1-a);
r2 = MID(0, r2, 255);
g2 = MID(0, g2, 255);
b2 = MID(0, b2, 255);
a2 = MID(0, a2, 255);
doc::color_t nearest2idx =
(rgbmap ? rgbmap->mapColor(r2, g2, b2):
palette->findBestfit(r2, g2, b2, m_transparentIndex));
(rgbmap ? rgbmap->mapColor(r2, g2, b2, a2):
palette->findBestfit(r2, g2, b2, a2, m_transparentIndex));
// If both possible RGB colors use the same index, we cannot
// make any dither with these two colors.
@ -120,12 +125,13 @@ namespace render {
r2 = doc::rgba_getr(nearest2rgb);
g2 = doc::rgba_getg(nearest2rgb);
b2 = doc::rgba_getb(nearest2rgb);
a2 = doc::rgba_geta(nearest2rgb);
// Here we calculate the distance between the original 'color'
// and 'nearest1rgb'. The maximum possible distance is given by
// the distance between 'nearest1rgb' and 'nearest2rgb'.
int d = colorDistance(r1, g1, b1, r, g, b);
int D = colorDistance(r1, g1, b1, r2, g2, b2);
int d = colorDistance(r1, g1, b1, a1, r, g, b, a);
int D = colorDistance(r1, g1, b1, a1, r2, g2, b2, a2);
if (D == 0)
return nearest1idx;

View File

@ -36,6 +36,7 @@ Palette* create_palette_from_rgb(
const Sprite* sprite,
frame_t fromFrame,
frame_t toFrame,
bool withAlpha,
Palette* palette)
{
PaletteOptimizer optimizer;
@ -43,7 +44,7 @@ Palette* create_palette_from_rgb(
if (!palette)
palette = new Palette(fromFrame, 256);
bool has_background_layer = (sprite->backgroundLayer() != nullptr);
bool hasBackgroundLayer = (sprite->backgroundLayer() != nullptr);
// Add a flat image with the current sprite's frame rendered
ImageRef flat_image(Image::create(IMAGE_RGB,
@ -53,11 +54,11 @@ Palette* create_palette_from_rgb(
render::Render render;
for (frame_t frame=fromFrame; frame<=toFrame; ++frame) {
render.renderSprite(flat_image.get(), sprite, frame);
optimizer.feedWithImage(flat_image.get());
optimizer.feedWithImage(flat_image.get(), withAlpha);
}
// Generate an optimized palette
optimizer.calculate(palette, has_background_layer);
optimizer.calculate(palette, hasBackgroundLayer);
return palette;
}
@ -85,7 +86,7 @@ Image* convert_pixel_format(
}
color_t c;
int r, g, b;
int r, g, b, a;
switch (image->pixelFormat()) {
@ -137,11 +138,12 @@ Image* convert_pixel_format(
r = rgba_getr(c);
g = rgba_getg(c);
b = rgba_getb(c);
a = rgba_geta(c);
if (rgba_geta(c) == 0)
*dst_it = 0;
if (a == 0)
*dst_it = 0; // TODO why 0 is mask color and not a param?
else
*dst_it = rgbmap->mapColor(r, g, b);
*dst_it = rgbmap->mapColor(r, g, b, a);
}
ASSERT(dst_it == dst_end);
break;
@ -192,11 +194,13 @@ Image* convert_pixel_format(
for (; src_it != src_end; ++src_it, ++dst_it) {
ASSERT(dst_it != dst_end);
c = *src_it;
a = graya_geta(c);
c = graya_getv(c);
if (graya_geta(c) == 0)
*dst_it = 0;
if (a == 0)
*dst_it = 0; // TODO why 0 is mask color and not a param?
else
*dst_it = graya_getv(c);
*dst_it = rgbmap->mapColor(c, c, c, a);
}
ASSERT(dst_it == dst_end);
break;
@ -226,9 +230,7 @@ Image* convert_pixel_format(
if (!is_background && c == image->maskColor())
*dst_it = 0;
else
*dst_it = rgba(rgba_getr(palette->getEntry(c)),
rgba_getg(palette->getEntry(c)),
rgba_getb(palette->getEntry(c)), 255);
*dst_it = palette->getEntry(c);
}
ASSERT(dst_it == dst_end);
break;
@ -249,12 +251,14 @@ Image* convert_pixel_format(
if (!is_background && c == image->maskColor())
*dst_it = 0;
else {
r = rgba_getr(palette->getEntry(c));
g = rgba_getg(palette->getEntry(c));
b = rgba_getb(palette->getEntry(c));
c = palette->getEntry(c);
r = rgba_getr(c);
g = rgba_getg(c);
b = rgba_getb(c);
a = rgba_geta(c);
g = 255 * Hsv(Rgb(r, g, b)).valueInt() / 100;
*dst_it = graya(g, 255);
*dst_it = graya(g, a);
}
}
ASSERT(dst_it == dst_end);
@ -277,11 +281,12 @@ Image* convert_pixel_format(
if (!is_background && c == image->maskColor())
*dst_it = dstMaskColor;
else {
r = rgba_getr(palette->getEntry(c));
g = rgba_getg(palette->getEntry(c));
b = rgba_getb(palette->getEntry(c));
*dst_it = rgbmap->mapColor(r, g, b);
c = palette->getEntry(c);
r = rgba_getr(c);
g = rgba_getg(c);
b = rgba_getb(c);
a = rgba_geta(c);
*dst_it = rgbmap->mapColor(r, g, b, a);
}
}
ASSERT(dst_it == dst_end);
@ -300,7 +305,7 @@ Image* convert_pixel_format(
// Creation of optimized palette for RGB images
// by David Capello
void PaletteOptimizer::feedWithImage(Image* image)
void PaletteOptimizer::feedWithImage(Image* image, bool withAlpha)
{
uint32_t color;
@ -314,9 +319,10 @@ void PaletteOptimizer::feedWithImage(Image* image)
for (; it != end; ++it) {
color = *it;
if (rgba_geta(color) > 0) {
color |= rgba(0, 0, 0, 255);
if (!withAlpha)
color |= rgba(0, 0, 0, 255);
m_histogram.addSamples(color, 1);
}
}
@ -332,8 +338,13 @@ void PaletteOptimizer::feedWithImage(Image* image)
color = *it;
if (graya_geta(color) > 0) {
color = graya_getv(color);
m_histogram.addSamples(rgba(color, color, color, 255), 1);
if (!withAlpha)
color = graya(graya_getv(color), 255);
m_histogram.addSamples(rgba(graya_getv(color),
graya_getv(color),
graya_getv(color),
graya_geta(color)), 1);
}
}
}
@ -346,25 +357,27 @@ void PaletteOptimizer::feedWithImage(Image* image)
}
}
void PaletteOptimizer::calculate(Palette* palette, bool has_background_layer)
void PaletteOptimizer::calculate(Palette* palette, bool hasBackgroundLayer)
{
// If the sprite has a background layer, the first entry can be
// used, in other case the 0 indexed will be the mask color, so it
// will not be used later in the color conversion (from RGB to
// Indexed).
int first_usable_entry = (has_background_layer ? 0: 1);
int first_usable_entry = (hasBackgroundLayer ? 0: 1);
int used_colors = m_histogram.createOptimizedPalette(
palette, first_usable_entry, palette->size()-1);
palette->resize(MAX(1, first_usable_entry+used_colors));
}
void create_palette_from_images(const std::vector<Image*>& images, Palette* palette, bool has_background_layer)
void create_palette_from_images(const std::vector<Image*>& images, Palette* palette,
bool hasBackgroundLayer,
bool withAlpha)
{
PaletteOptimizer optimizer;
for (int i=0; i<(int)images.size(); ++i)
optimizer.feedWithImage(images[i]);
optimizer.feedWithImage(images[i], withAlpha);
optimizer.calculate(palette, has_background_layer);
optimizer.calculate(palette, hasBackgroundLayer);
}
} // namespace render

View File

@ -26,26 +26,28 @@ namespace doc {
namespace render {
using namespace doc;
class PaletteOptimizer {
public:
void feedWithImage(Image* image);
void calculate(Palette* palette, bool has_background_layer);
class PaletteOptimizer {
public:
void feedWithImage(Image* image, bool withAlpha);
void calculate(Palette* palette, bool hasBackgroundLayer);
private:
ColorHistogram<5, 6, 5> m_histogram;
ColorHistogram<5, 6, 5, 5> m_histogram;
};
void create_palette_from_images(
const std::vector<Image*>& images,
Palette* palette,
bool has_background_layer);
bool hasBackgroundLayer,
bool withAlpha);
// Creates a new palette suitable to quantize the given RGB sprite to Indexed color.
Palette* create_palette_from_rgb(
const Sprite* sprite,
frame_t fromFrame,
frame_t toFrame,
Palette* newPalette); // Can be NULL to create a new palette
bool withAlpha,
Palette* newPalette); // Can be NULL to create a new palette
// Changes the image pixel format. The dithering method is used only
// when you want to convert from RGB to Indexed.

View File

@ -492,10 +492,18 @@ void Render::renderSprite(
switch (m_bgType) {
case BgType::CHECKED:
if (bgLayer && bgLayer->isVisible())
if (bgLayer && bgLayer->isVisible() && rgba_geta(bg_color) == 255) {
fill_rect(dstImage, area.dstBounds(), bg_color);
else
}
else {
renderBackground(dstImage, area, zoom);
if (bgLayer && bgLayer->isVisible() && rgba_geta(bg_color) > 0) {
blend_rect(dstImage, area.dst.x, area.dst.y,
area.dst.x+area.size.w-1,
area.dst.y+area.size.h-1,
bg_color, 255);
}
}
break;
case BgType::TRANSPARENT: