diff --git a/data/pref.xml b/data/pref.xml index aa7fa6213..8068e00fd 100644 --- a/data/pref.xml +++ b/data/pref.xml @@ -36,6 +36,22 @@ + + + + + + + + + + + + + + + + @@ -59,7 +75,6 @@ diff --git a/data/skins/default/sheet.png b/data/skins/default/sheet.png index 590efd825..ebe610d1b 100644 Binary files a/data/skins/default/sheet.png and b/data/skins/default/sheet.png differ diff --git a/data/skins/default/skin.xml b/data/skins/default/skin.xml index 967333803..140a1467f 100644 --- a/data/skins/default/skin.xml +++ b/data/skins/default/skin.xml @@ -372,6 +372,9 @@ + + + diff --git a/data/widgets/palette_from_sprite.xml b/data/widgets/palette_from_sprite.xml index a39f65c12..ab3d46017 100644 --- a/data/widgets/palette_from_sprite.xml +++ b/data/widgets/palette_from_sprite.xml @@ -9,6 +9,8 @@ + + diff --git a/src/app/color.cpp b/src/app/color.cpp index 23677cafb..92cec8c38 100644 --- a/src/app/color.cpp +++ b/src/app/color.cpp @@ -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 diff --git a/src/app/color.h b/src/app/color.h index fb71695d0..711ec725b 100644 --- a/src/app/color.h +++ b/src/app/color.h @@ -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; }; diff --git a/src/app/color_utils.cpp b/src/app/color_utils.cpp index 8cf1c7560..5398184c0 100644 --- a/src/app/color_utils.cpp +++ b/src/app/color_utils.cpp @@ -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. diff --git a/src/app/commands/cmd_color_quantization.cpp b/src/app/commands/cmd_color_quantization.cpp index 70b50fa90..ef38d16fe 100644 --- a/src/app/commands/cmd_color_quantization.cpp +++ b/src/app/commands/cmd_color_quantization.cpp @@ -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 newPalette( new Palette(createPal ? tmpPalette: diff --git a/src/app/commands/cmd_eyedropper.cpp b/src/app/commands/cmd_eyedropper.cpp index fbcc0c54a..207a8e9a0 100644 --- a/src/app/commands/cmd_eyedropper.cpp +++ b/src/app/commands/cmd_eyedropper.cpp @@ -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() diff --git a/src/app/commands/cmd_eyedropper.h b/src/app/commands/cmd_eyedropper.h new file mode 100644 index 000000000..c2886b3de --- /dev/null +++ b/src/app/commands/cmd_eyedropper.h @@ -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 diff --git a/src/app/commands/cmd_palette_editor.cpp b/src/app/commands/cmd_palette_editor.cpp index 53e7afd92..8e2411ccc 100644 --- a/src/app/commands/cmd_palette_editor.cpp +++ b/src/app/commands/cmd_palette_editor.cpp @@ -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; csize(); 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; csize(); 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)); } } diff --git a/src/app/commands/cmd_sprite_size.cpp b/src/app/commands/cmd_sprite_size.cpp index 6d27e4409..37a2f48ef 100644 --- a/src/app/commands/cmd_sprite_size.cpp +++ b/src/app/commands/cmd_sprite_size.cpp @@ -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()); diff --git a/src/app/commands/filters/filter_target_buttons.cpp b/src/app/commands/filters/filter_target_buttons.cpp index 31599a216..0c2c4c633 100644 --- a/src/app/commands/filters/filter_target_buttons.cpp +++ b/src/app/commands/filters/filter_target_buttons.cpp @@ -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"); } diff --git a/src/app/file/ase_format.cpp b/src/app/file/ase_format.cpp index ff4506bea..de48b91fc 100644 --- a/src/app/file/ase_format.cpp +++ b/src/app/file/ase_format.cpp @@ -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) { diff --git a/src/app/file/file.cpp b/src/app/file/file.cpp index 3316b7df4..8436e7499 100644 --- a/src/app/file/file.cpp +++ b/src/app/file/file.cpp @@ -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; csize(); ++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( 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::SharedPtrseq.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; diff --git a/src/app/file/file.h b/src/app/file/file.h index 7121f142f..8e5babfeb 100644 --- a/src/app/file/file.h +++ b/src/app/file/file.h @@ -133,8 +133,12 @@ namespace app { void fop_post_load(FileOp* fop); void fop_sequence_set_format_options(FileOp* fop, const base::SharedPtr& 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, ...); diff --git a/src/app/file/file_format.h b/src/app/file/file_format.h index 9485020c5..e6f179079 100644 --- a/src/app/file/file_format.h +++ b/src/app/file/file_format.h @@ -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 { diff --git a/src/app/file/gif_format.cpp b/src/app/file/gif_format.cpp index b5f1df973..9f093372b 100644 --- a/src/app/file/gif_format.cpp +++ b/src/app/file/gif_format.cpp @@ -641,7 +641,7 @@ bool GifFormat::onSave(FileOp* fop) for (frame_t frame_num(0); frame_numtotalFrames(); ++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 imgarray(1); imgarray[0] = buffer_image; - render::create_palette_from_images(imgarray, ¤t_palette, has_background); + render::create_palette_from_images(imgarray, ¤t_palette, has_background, false); rgbmap.regenerate(¤t_palette, transparent_index); } break; diff --git a/src/app/file/png_format.cpp b/src/app/file/png_format.cpp index 6abf5e556..2e9960817 100644 --- a/src/app/file/png_format.cpp +++ b/src/app/file/png_format.cpp @@ -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 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; cseq.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; xpixelFormat() == 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= 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, diff --git a/src/app/modules/gfx.h b/src/app/modules/gfx.h index 43f647464..afc060d50 100644 --- a/src/app/modules/gfx.h +++ b/src/app/modules/gfx.h @@ -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); diff --git a/src/app/tools/ink_processing.h b/src/app/tools/ink_processing.h index 5135cb7b5..36ec7fe83 100644 --- a/src/app/tools/ink_processing.h +++ b/src/app/tools/ink_processing.h @@ -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(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::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::processPixel(int x, int y) { switch (m_brushImage->pixelFormat()) { case IMAGE_RGB: { c = get_pixel_fast(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::processPixel(int x, int y) { } case IMAGE_GRAYSCALE: { c = get_pixel_fast(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: { diff --git a/src/app/tools/ink_type.h b/src/app/tools/ink_type.h index 6acadcdc9..a7934fc45 100644 --- a/src/app/tools/ink_type.h +++ b/src/app/tools/ink_type.h @@ -14,7 +14,8 @@ namespace tools { enum class InkType { DEFAULT = 0, - SET_ALPHA = 1, + REPLACE_PIXEL = 0, + ALPHA_COMPOSITING = 1, LOCK_ALPHA = 2, }; diff --git a/src/app/tools/inks.h b/src/app/tools/inks.h index 9cad96d41..8132f2f60 100644 --- a/src/app/tools/inks.h +++ b/src/app/tools/inks.h @@ -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; } } } diff --git a/src/app/ui/color_bar.cpp b/src/app/ui/color_bar.cpp index 0e5f9bfb5..739e4efa1 100644 --- a/src/app/ui/color_bar.cpp +++ b/src/app/ui/color_bar.cpp @@ -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(); diff --git a/src/app/ui/color_button.cpp b/src/app/ui/color_button.cpp index 306ebdc9b..e2204d3bd 100644 --- a/src/app/ui/color_button.cpp +++ b/src/app/ui/color_button.cpp @@ -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: diff --git a/src/app/ui/color_selector.cpp b/src/app/ui/color_selector.cpp index 9b58eeaed..152ec92b7 100644 --- a/src/app/ui/color_selector.cpp +++ b/src/app/ui/color_selector.cpp @@ -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); } diff --git a/src/app/ui/color_sliders.cpp b/src/app/ui/color_sliders.cpp index 7c9dd33b8..7302cb579 100644 --- a/src/app/ui/color_sliders.cpp +++ b/src/app/ui/color_sliders.cpp @@ -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 diff --git a/src/app/ui/color_sliders.h b/src/app/ui/color_sliders.h index c60817df8..8c0a6a398 100644 --- a/src/app/ui/color_sliders.h +++ b/src/app/ui/color_sliders.h @@ -31,7 +31,8 @@ namespace app { public: enum Channel { Red, Green, Blue, Hue, Saturation, Value, - Gray }; + Gray, + Alpha }; enum Mode { Absolute, Relative }; ColorSliders(); diff --git a/src/app/ui/color_spectrum.cpp b/src/app/ui/color_spectrum.cpp index 525c1187b..76e9ea9ef 100644 --- a/src/app/ui/color_spectrum.cpp +++ b/src/app/ui/color_spectrum.cpp @@ -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()); } diff --git a/src/app/ui/context_bar.cpp b/src/app/ui/context_bar.cpp index 8459bfcec..10b34069d 100644 --- a/src/app/ui/context_bar.cpp +++ b/src/app/ui/context_bar.cpp @@ -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(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(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(&InkTypeField::selectInk, this, InkType::REPLACE_PIXEL)); + alphacompo.Click.connect(Bind(&InkTypeField::selectInk, this, InkType::ALPHA_COMPOSITING)); + lockalpha.Click.connect(Bind(&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(&EyedropperField::onChannelChange, this)); + m_sample.Change.connect(Bind(&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); diff --git a/src/app/ui/context_bar.h b/src/app/ui/context_bar.h index de172e3ed..f30fa213c 100644 --- a/src/app/ui/context_bar.h +++ b/src/app/ui/context_bar.h @@ -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; diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 6f42482de..734f4fcc7 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -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) diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp index ba2d0e141..972fa2852 100644 --- a/src/app/ui/editor/standby_state.cpp +++ b/src/app/ui/editor/standby_state.cpp @@ -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 = diff --git a/src/app/ui/palette_view.cpp b/src/app/ui/palette_view.cpp index 59580ac58..4f1ab5d19 100644 --- a/src/app/ui/palette_view.cpp +++ b/src/app/ui/palette_view.cpp @@ -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; isize(); ++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; idrawHLine(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; idrawHLine(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(); diff --git a/src/app/ui/skin/skin_parts.h b/src/app/ui/skin/skin_parts.h index 7b589a884..118c337b1 100644 --- a/src/app/ui/skin/skin_parts.h +++ b/src/app/ui/skin/skin_parts.h @@ -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 }; diff --git a/src/app/ui/skin/skin_theme.cpp b/src/app/ui/skin/skin_theme.cpp index c1bbd9fd0..fb3fce864 100644 --- a/src/app/ui/skin/skin_theme.cpp +++ b/src/app/ui/skin/skin_theme.cpp @@ -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() diff --git a/src/app/ui/status_bar.cpp b/src/app/ui/status_bar.cpp index f37edd196..36c9a4b2d 100644 --- a/src/app/ui/status_bar.cpp +++ b/src/app/ui/status_bar.cpp @@ -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; } diff --git a/src/app/ui/status_bar.h b/src/app/ui/status_bar.h index 15d2a74c0..84c887fd6 100644 --- a/src/app/ui/status_bar.h +++ b/src/app/ui/status_bar.h @@ -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; diff --git a/src/doc/CMakeLists.txt b/src/doc/CMakeLists.txt index b21d72613..63eea5556 100644 --- a/src/doc/CMakeLists.txt +++ b/src/doc/CMakeLists.txt @@ -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 diff --git a/src/doc/algorithm/resize_image.cpp b/src/doc/algorithm/resize_image.cpp index c3565d6b2..16fed961e 100644 --- a/src/doc/algorithm/resize_image.cpp +++ b/src/doc/algorithm/resize_image.cpp @@ -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; } } diff --git a/src/doc/algorithm/resize_image.h b/src/doc/algorithm/resize_image.h index c3b1ac1a9..ee7a08589 100644 --- a/src/doc/algorithm/resize_image.h +++ b/src/doc/algorithm/resize_image.h @@ -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 diff --git a/src/doc/color_scales.cpp b/src/doc/color_scales.cpp deleted file mode 100644 index 5a5039b5c..000000000 --- a/src/doc/color_scales.cpp +++ /dev/null @@ -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 diff --git a/src/doc/color_scales.h b/src/doc/color_scales.h index 1f79310c9..aa52d43eb 100644 --- a/src/doc/color_scales.h +++ b/src/doc/color_scales.h @@ -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 diff --git a/src/doc/palette.cpp b/src/doc/palette.cpp index 6f91143ae..f92623e2e 100644 --- a/src/doc/palette.cpp +++ b/src/doc/palette.cpp @@ -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 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::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::max(); + int size = MIN(256, m_colors.size()); + + for (int i=0; i>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; diff --git a/src/doc/palette.h b/src/doc/palette.h index f5505a029..167a9f96c 100644 --- a/src/doc/palette.h +++ b/src/doc/palette.h @@ -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; diff --git a/src/doc/resize_image_tests.cpp b/src/doc/resize_image_tests.cpp index 7c04b36cb..0e9fb426a 100644 --- a/src/doc/resize_image_tests.cpp +++ b/src/doc/resize_image_tests.cpp @@ -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. @@ -56,7 +56,7 @@ color_t test_image_scaled_9x9_bilinear[81] = 0x000000, 0x000000, 0x565656, 0xa9a9a9, 0xffffff, 0xa9a9a9, 0x565656, 0x000000, 0x000000 }; -Image* create_image_from_data(PixelFormat format, color_t* data, int width, int height) +Image* create_image_from_data(PixelFormat format, color_t* data, int width, int height) { Image* new_image = Image::create(format, width, height); for (int i = 0; i < width * height; i++) { @@ -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)); } diff --git a/src/doc/rgbmap.cpp b/src/doc/rgbmap.cpp index b53158b1d..112fb8c3e 100644 --- a/src/doc/rgbmap.cpp +++ b/src/doc/rgbmap.cpp @@ -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; rfindBestfit( + 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 diff --git a/src/doc/rgbmap.h b/src/doc/rgbmap.h index 1bf228131..aed8b5002 100644 --- a/src/doc/rgbmap.h +++ b/src/doc/rgbmap.h @@ -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 m_map; diff --git a/src/filters/color_curve_filter.cpp b/src/filters/color_curve_filter.cpp index 17dcdec27..52735d831 100644 --- a/src/filters/color_curve_filter.cpp +++ b/src/filters/color_curve_filter.cpp @@ -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; xskipPixel()) { @@ -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); diff --git a/src/filters/convolution_matrix_filter.cpp b/src/filters/convolution_matrix_filter.cpp index d720ce9c6..5212828f1 100644 --- a/src/filters/convolution_matrix_filter.cpp +++ b/src/filters/convolution_matrix_filter.cpp @@ -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); } } } diff --git a/src/filters/invert_color_filter.cpp b/src/filters/invert_color_filter.cpp index 352652414..f55d78623 100644 --- a/src/filters/invert_color_filter.cpp +++ b/src/filters/invert_color_filter.cpp @@ -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; xskipPixel()) { @@ -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; diff --git a/src/filters/median_filter.cpp b/src/filters/median_filter.cpp index ae6b0dbf4..2a9b52de9 100644 --- a/src/filters/median_filter.cpp +++ b/src/filters/median_filter.cpp @@ -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(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); } } } diff --git a/src/render/color_histogram.h b/src/render/color_histogram.h index ac421a914..499928844 100644 --- a/src/render/color_histogram.h +++ b/src/render/color_histogram.h @@ -20,34 +20,35 @@ namespace render { using namespace doc; - template // Number of bits for each component in the histogram + template 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::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). diff --git a/src/render/median_cut.h b/src/render/median_cut.h index f4b21258d..b63e6fc20 100644 --- a/src/render/median_cut.h +++ b/src/render/median_cut.h @@ -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(histogram, r1, r2, g1, g2, b1, b2); - axisShrink(histogram, g1, g2, r1, r2, b1, b2); - axisShrink(histogram, b1, b2, r1, r2, g1, g2); + void shrink(const Histogram& histogram) { + axisShrink(histogram, r1, r2, g1, g2, b1, b2, a1, a2); + axisShrink(histogram, g1, g2, r1, r2, b1, b2, a1, a2); + axisShrink(histogram, b1, b2, r1, r2, g1, g2, a1, a2); + axisShrink(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& boxes) const - { + bool split(const Histogram& histogram, std::priority_queue& boxes) const { // Split along the largest dimension of the box. - if ((r2-r1) >= (g2-g1) && (r2-r1) >= (b2-b1)) { - return splitAlongAxis(histogram, boxes, r1, r2, g1, g2, b1, b2); + if ((r2-r1) >= (g2-g1) && + (r2-r1) >= (b2-b1) && + (r2-r1) >= (a2-a1)) { + return splitAlongAxis(histogram, boxes, r1, r2, g1, g2, b1, b2, a1, a2); } - else if ((g2-g1) >= (r2-r1) && (g2-g1) >= (b2-b1)) { - return splitAlongAxis(histogram, boxes, g1, g2, r1, r2, b1, b2); + + if ((g2-g1) >= (r2-r1) && + (g2-g1) >= (b2-b1) && + (g2-g1) >= (a2-a1)) { + return splitAlongAxis(histogram, boxes, g1, g2, r1, r2, b1, b2, a1, a2); } - else { - return splitAlongAxis(histogram, boxes, b1, b2, r1, r2, g1, g2); + + if ((b2-b1) >= (r2-r1) && + (b2-b1) >= (g2-g1) && + (b2-b1) >= (a2-a1)) { + return splitAlongAxis(histogram, boxes, b1, b2, r1, r2, g1, g2, a1, a2); } + + return splitAlongAxis(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 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& 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 > boxes; // First we start with one big box containing all histogram's samples. - boxes.push(Box(0, 0, 0, + boxes.push(Box(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. diff --git a/src/render/ordered_dither.h b/src/render/ordered_dither.h index 965ea889a..57a9d31dc 100644 --- a/src/render/ordered_dither.h +++ b/src/render/ordered_dither.h @@ -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; diff --git a/src/render/quantization.cpp b/src/render/quantization.cpp index dbab3e3ba..ef0c19ecc 100644 --- a/src/render/quantization.cpp +++ b/src/render/quantization.cpp @@ -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& images, Palette* palette, bool has_background_layer) +void create_palette_from_images(const std::vector& 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 diff --git a/src/render/quantization.h b/src/render/quantization.h index 1a53119f2..472e318ad 100644 --- a/src/render/quantization.h +++ b/src/render/quantization.h @@ -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& 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. diff --git a/src/render/render.cpp b/src/render/render.cpp index ae54417b2..3c96c9cc2 100644 --- a/src/render/render.cpp +++ b/src/render/render.cpp @@ -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: