Add Hue/Saturation filter (fix #1186)

Added new gfx::Hsl class to handle HSL color model(related to #707 and #1102)
This commit is contained in:
David Capello 2017-05-24 19:07:10 -03:00
parent b4ea90a266
commit 239ac42378
16 changed files with 606 additions and 25 deletions

View File

@ -27,7 +27,6 @@
</key>
<!-- Edit -->
<key command="Undo" shortcut="Ctrl+Z" mac="Cmd+Z" />
<key command="Undo" shortcut="Ctrl+U" mac="Cmd+U" />
<key command="Redo" shortcut="Ctrl+Y" mac="Cmd+Y" />
<key command="Redo" shortcut="Ctrl+R" mac="Cmd+R" />
<key command="Redo" shortcut="Ctrl+Shift+Z" mac="Cmd+Shift+Z" />
@ -49,6 +48,7 @@
<param name="orientation" value="vertical" />
</key>
<key command="ReplaceColor" shortcut="Shift+R" />
<key command="HueSaturation" shortcut="Ctrl+U" mac="Cmd+U" />
<key command="ConvolutionMatrix" shortcut="F9" />
<key command="ColorCurve" shortcut="Ctrl+M" mac="Cmd+M" />
<key command="ColorCurve" shortcut="F10" />
@ -627,7 +627,8 @@
<item command="NewSpriteFromSelection" text="&amp;New Sprite from Selection" />
<separator />
<item command="ReplaceColor" text="R&amp;eplace Color..." />
<item command="InvertColor" text="&amp;Invert" />
<item command="InvertColor" text="&amp;Invert..." />
<item command="HueSaturation" text="Ad&amp;just Hue/Saturation..." />
<menu text="F&amp;X" id="fx_popup">
<item command="ConvolutionMatrix" text="Convolution &amp;Matrix" />
<item command="ColorCurve" text="&amp;Color Curve" />

View File

@ -173,6 +173,11 @@ recent_files = Recent files:
recent_folders = Recent folders:
news = News:
[hue_saturation]
h = H:
s = S:
l = L:
[import_sprite_sheet]
title = Import Sprite Sheet
select_file = Select File

View File

@ -0,0 +1,11 @@
<!-- Aseprite -->
<!-- Copyright (C) 2017 by David Capello -->
<gui>
<vbox expansive="true" id="hue_saturation">
<grid expansive="true" columns="2">
<label text="@.h" /><slider min="-180" max="180" id="hue" width="128" />
<label text="@.s" /><slider min="-100" max="100" id="saturation" />
<label text="@.l" /><slider min="-100" max="100" id="lightness" />
</grid>
</vbox>
</gui>

View File

@ -344,6 +344,7 @@ add_library(app-lib
commands/filters/cmd_color_curve.cpp
commands/filters/cmd_convolution_matrix.cpp
commands/filters/cmd_despeckle.cpp
commands/filters/cmd_hue_saturation.cpp
commands/filters/cmd_invert_color.cpp
commands/filters/cmd_replace_color.cpp
commands/filters/color_curve_editor.cpp

View File

@ -56,6 +56,7 @@ FOR_EACH_COMMAND(GotoPreviousLayer)
FOR_EACH_COMMAND(GotoPreviousTab)
FOR_EACH_COMMAND(GridSettings)
FOR_EACH_COMMAND(Home)
FOR_EACH_COMMAND(HueSaturation)
FOR_EACH_COMMAND(ImportSpriteSheet)
FOR_EACH_COMMAND(InvertColor)
FOR_EACH_COMMAND(InvertMask)

View File

@ -0,0 +1,111 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/color.h"
#include "app/commands/command.h"
#include "app/commands/filters/filter_manager_impl.h"
#include "app/commands/filters/filter_window.h"
#include "app/context.h"
#include "app/ini_file.h"
#include "app/modules/gui.h"
#include "app/ui/color_button.h"
#include "base/bind.h"
#include "doc/image.h"
#include "doc/mask.h"
#include "doc/sprite.h"
#include "filters/hue_saturation_filter.h"
#include "ui/button.h"
#include "ui/label.h"
#include "ui/slider.h"
#include "ui/widget.h"
#include "ui/window.h"
#include "hue_saturation.xml.h"
namespace app {
static const char* ConfigSection = "HueSaturation";
class HueSaturationWindow : public FilterWindow {
public:
HueSaturationWindow(HueSaturationFilter& filter,
FilterManagerImpl& filterMgr)
: FilterWindow("Hue/Saturation", ConfigSection, &filterMgr,
WithChannelsSelector,
WithoutTiledCheckBox)
, m_filter(filter)
{
getContainer()->addChild(&m_controls);
m_controls.hue()->setValue(0);
m_controls.saturation()->setValue(0);
m_controls.lightness()->setValue(0);
m_controls.hue()->Change.connect(base::Bind(&HueSaturationWindow::onChangeControls, this));
m_controls.saturation()->Change.connect(base::Bind(&HueSaturationWindow::onChangeControls, this));
m_controls.lightness()->Change.connect(base::Bind(&HueSaturationWindow::onChangeControls, this));
}
private:
void onChangeControls() {
m_filter.setHue(double(m_controls.hue()->getValue()));
m_filter.setSaturation(m_controls.saturation()->getValue() / 100.0);
m_filter.setLightness(m_controls.lightness()->getValue() / 100.0);
restartPreview();
}
HueSaturationFilter& m_filter;
app::gen::HueSaturation m_controls;
};
class HueSaturationCommand : public Command {
public:
HueSaturationCommand();
Command* clone() const override { return new HueSaturationCommand(*this); }
protected:
bool onEnabled(Context* context) override;
void onExecute(Context* context) override;
};
HueSaturationCommand::HueSaturationCommand()
: Command("HueSaturation",
"Hue Saturation",
CmdRecordableFlag)
{
}
bool HueSaturationCommand::onEnabled(Context* context)
{
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::HasActiveSprite);
}
void HueSaturationCommand::onExecute(Context* context)
{
HueSaturationFilter filter;
FilterManagerImpl filterMgr(context, &filter);
filterMgr.setTarget(TARGET_RED_CHANNEL |
TARGET_GREEN_CHANNEL |
TARGET_BLUE_CHANNEL |
TARGET_GRAY_CHANNEL);
HueSaturationWindow window(filter, filterMgr);
window.doModal();
}
Command* CommandFactory::createHueSaturationCommand()
{
return new HueSaturationCommand;
}
} // namespace app

View File

@ -1,11 +1,12 @@
# Aseprite
# Copyright (C) 2001-2016 David Capello
# Copyright (C) 2001-2017 David Capello
add_library(filters-lib
color_curve.cpp
color_curve_filter.cpp
convolution_matrix.cpp
convolution_matrix_filter.cpp
hue_saturation_filter.cpp
invert_color_filter.cpp
median_filter.cpp
replace_color_filter.cpp)

View File

@ -0,0 +1,192 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "filters/hue_saturation_filter.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/rgbmap.h"
#include "filters/filter_indexed_data.h"
#include "filters/filter_manager.h"
#include "gfx/hsl.h"
#include "gfx/rgb.h"
#include <cmath>
namespace filters {
using namespace doc;
const char* HueSaturationFilter::getName()
{
return "Hue Saturation Color";
}
HueSaturationFilter::HueSaturationFilter()
: m_h(0.0)
, m_s(0.0)
, m_l(0.0)
{
}
void HueSaturationFilter::setHue(double h)
{
m_h = h;
}
void HueSaturationFilter::setSaturation(double s)
{
m_s = s;
}
void HueSaturationFilter::setLightness(double l)
{
m_l = l;
}
void HueSaturationFilter::applyToRgba(FilterManager* filterMgr)
{
const uint32_t* src_address = (uint32_t*)filterMgr->getSourceAddress();
uint32_t* dst_address = (uint32_t*)filterMgr->getDestinationAddress();
int w = filterMgr->getWidth();
Target target = filterMgr->getTarget();
int x, c, r, g, b, a;
for (x=0; x<w; x++) {
if (filterMgr->skipPixel()) {
++src_address;
++dst_address;
continue;
}
c = *(src_address++);
r = rgba_getr(c);
g = rgba_getg(c);
b = rgba_getb(c);
a = rgba_geta(c);
{
gfx::Hsl hsl(gfx::Rgb(r, g, b));
double h = hsl.hue() + m_h;
while (h < 0.0)
h += 360.0;
h = std::fmod(h, 360.0);
double s = hsl.saturation() + m_s;
s = MID(0.0, s, 1.0);
double l = hsl.lightness() + m_l;
l = MID(0.0, l, 1.0);
hsl.hue(h);
hsl.saturation(s);
hsl.lightness(l);
gfx::Rgb rgb(hsl);
if (target & TARGET_RED_CHANNEL ) r = rgb.red();
if (target & TARGET_GREEN_CHANNEL) g = rgb.green();
if (target & TARGET_BLUE_CHANNEL ) b = rgb.blue();
}
*(dst_address++) = rgba(r, g, b, a);
}
}
void HueSaturationFilter::applyToGrayscale(FilterManager* filterMgr)
{
const uint16_t* src_address = (uint16_t*)filterMgr->getSourceAddress();
uint16_t* dst_address = (uint16_t*)filterMgr->getDestinationAddress();
int w = filterMgr->getWidth();
Target target = filterMgr->getTarget();
int x, c, k, a;
for (x=0; x<w; x++) {
if (filterMgr->skipPixel()) {
++src_address;
++dst_address;
continue;
}
c = *(src_address++);
k = graya_getv(c);
a = graya_geta(c);
{
gfx::Hsl hsl(gfx::Rgb(k, k, k));
double l = hsl.lightness() + m_l;
l = MID(0.0, l, 1.0);
hsl.lightness(l);
gfx::Rgb rgb(hsl);
if (target & TARGET_GRAY_CHANNEL) k = rgb.red();
}
*(dst_address++) = graya(k, a);
}
}
void HueSaturationFilter::applyToIndexed(FilterManager* filterMgr)
{
const uint8_t* src_address = (uint8_t*)filterMgr->getSourceAddress();
uint8_t* dst_address = (uint8_t*)filterMgr->getDestinationAddress();
const Palette* pal = filterMgr->getIndexedData()->getPalette();
const RgbMap* rgbmap = filterMgr->getIndexedData()->getRgbMap();
int w = filterMgr->getWidth();
Target target = filterMgr->getTarget();
int x, c, r, g, b, a;
for (x=0; x<w; x++) {
if (filterMgr->skipPixel()) {
++src_address;
++dst_address;
continue;
}
c = *(src_address++);
c = pal->getEntry(c);
r = rgba_getr(c);
g = rgba_getg(c);
b = rgba_getb(c);
a = rgba_geta(c);
{
gfx::Hsl hsl(gfx::Rgb(r, g, b));
double h = hsl.hue() + m_h;
while (h < 0.0) h += 360.0;
h = std::fmod(h, 360.0);
double s = hsl.saturation() + m_s;
s = MID(0.0, s, 1.0);
double l = hsl.lightness() + m_l;
l = MID(0.0, l, 1.0);
hsl.hue(h);
hsl.saturation(s);
hsl.lightness(l);
gfx::Rgb rgb(hsl);
if (target & TARGET_RED_CHANNEL ) r = rgb.red();
if (target & TARGET_GREEN_CHANNEL) g = rgb.green();
if (target & TARGET_BLUE_CHANNEL ) b = rgb.blue();
}
c = rgbmap->mapColor(r, g, b, a);
*(dst_address++) = c;
}
}
} // namespace filters

View File

@ -0,0 +1,35 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef FILTERS_HUE_SATURATION_FILTER_H_INCLUDED
#define FILTERS_HUE_SATURATION_FILTER_H_INCLUDED
#pragma once
#include "filters/filter.h"
namespace filters {
class HueSaturationFilter : public Filter {
public:
HueSaturationFilter();
void setHue(double h);
void setSaturation(double s);
void setLightness(double v);
// Filter implementation
const char* getName();
void applyToRgba(FilterManager* filterMgr);
void applyToGrayscale(FilterManager* filterMgr);
void applyToIndexed(FilterManager* filterMgr);
private:
double m_h, m_s, m_l;
};
} // namespace filters
#endif

View File

@ -1,7 +1,8 @@
# Aseprite
# Copyright (C) 2001-2016 David Capello
# Copyright (C) 2001-2017 David Capello
add_library(gfx-lib
hsl.cpp
hsv.cpp
packing_rects.cpp
region.cpp

92
src/gfx/hsl.cpp Normal file
View File

@ -0,0 +1,92 @@
// Aseprite Gfx Library
// Copyright (C) 2017 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 "gfx/hsl.h"
#include "gfx/rgb.h"
#include <cmath>
namespace gfx {
using namespace std;
Hsl::Hsl(double hue, double saturation, double lightness)
: m_hue(hue)
, m_saturation(saturation)
, m_lightness(lightness)
{
while (m_hue < 0.0)
m_hue += 360.0;
m_hue = std::fmod(m_hue, 360.0);
assert(hue >= 0.0 && hue <= 360.0);
assert(saturation >= 0.0 && saturation <= 1.0);
assert(lightness >= 0.0 && lightness <= 1.0);
}
Hsl::Hsl(const Rgb& rgb)
{
int M = rgb.maxComponent();
int m = rgb.minComponent();
int c = M - m;
double chroma = double(c) / 255.0;
double hue_prime = 0.0;
double h, s, l;
double r, g, b;
l = double((M + m) / 255.0) / 2.0;
if (c == 0) {
h = 0.0; // Undefined Hue because max == min
s = 0.0;
}
else {
r = double(rgb.red()) / 255.0;
g = double(rgb.green()) / 255.0;
b = double(rgb.blue()) / 255.0;
s = chroma / (1-std::fabs(2.0*l-1.0));
if (M == rgb.red()) {
hue_prime = (g - b) / chroma;
while (hue_prime < 0.0)
hue_prime += 6.0;
hue_prime = std::fmod(hue_prime, 6.0);
}
else if (M == rgb.green()) {
hue_prime = ((b - r) / chroma) + 2.0;
}
else if (M == rgb.blue()) {
hue_prime = ((r - g) / chroma) + 4.0;
}
h = hue_prime * 60.0;
}
m_hue = h;
m_saturation = s;
m_lightness = l;
}
int Hsl::hueInt() const
{
return int(floor(m_hue + 0.5));
}
int Hsl::saturationInt() const
{
return int(floor(m_saturation*100.0 + 0.5));
}
int Hsl::lightnessInt() const
{
return int(floor(m_lightness*100.0 + 0.5));
}
} // namespace gfx

84
src/gfx/hsl.h Normal file
View File

@ -0,0 +1,84 @@
// Aseprite Gfx Library
// Copyright (C) 2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef GFX_HSL_H_INCLUDED
#define GFX_HSL_H_INCLUDED
#pragma once
#include <cassert>
namespace gfx {
class Rgb;
class Hsl {
public:
Hsl()
: m_hue(0.0)
, m_saturation(0.0)
, m_lightness(0.0)
{ }
Hsl(double hue, double saturation, double lightness);
Hsl(const Hsl& hsl)
: m_hue(hsl.hue())
, m_saturation(hsl.saturation())
, m_lightness(hsl.lightness())
{ }
// RGB to HSL conversion
explicit Hsl(const Rgb& rgb);
// Returns color's hue, a value from 0 to 360
double hue() const { return m_hue; }
// Returns color's saturation, a value from 0 to 100
double saturation() const { return m_saturation; }
// Returns color's lightness, a value from 0 to 100
double lightness() const { return m_lightness; }
// Integer getters, hue=[0,360), saturation=[0,100], value=[0,100]
int hueInt() const;
int saturationInt() const;
int lightnessInt() const;
void hue(double hue) {
assert(hue >= 0.0 && hue <= 360.0);
m_hue = hue;
}
void saturation(double saturation) {
assert(saturation >= 0.0 && saturation <= 1.0);
m_saturation = saturation;
}
void lightness(double lightness) {
assert(lightness >= 0.0 && lightness <= 1.0);
m_lightness = lightness;
}
// The comparison is done through the integer value of each component.
bool operator==(const Hsl& other) const {
return (hueInt() == other.hueInt() &&
saturationInt() == other.saturationInt() &&
lightnessInt() == other.lightnessInt());
}
bool operator!=(const Hsl& other) const {
return !operator==(other);
}
private:
double m_hue;
double m_saturation;
double m_lightness;
};
} // namespace gfx
#endif

View File

@ -1,5 +1,5 @@
// Aseprite Gfx Library
// Copyright (C) 2001-2013 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -23,7 +23,7 @@ Hsv::Hsv(double hue, double saturation, double value)
{
while (m_hue < 0.0)
m_hue += 360.0;
m_hue = fmod(hue, 360.0);
m_hue = std::fmod(m_hue, 360.0);
assert(hue >= 0.0 && hue <= 360.0);
assert(saturation >= 0.0 && saturation <= 1.0);
@ -58,7 +58,7 @@ Hsv::Hsv(const Rgb& rgb)
while (hue_prime < 0.0)
hue_prime += 6.0;
hue_prime = fmod(hue_prime, 6.0);
hue_prime = std::fmod(hue_prime, 6.0);
}
else if (M == rgb.green()) {
hue_prime = ((b - r) / chroma) + 2.0;

View File

@ -1,5 +1,5 @@
// Aseprite Gfx Library
// Copyright (C) 2001-2013 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -14,8 +14,7 @@ namespace gfx {
class Rgb;
class Hsv
{
class Hsv {
public:
Hsv()
: m_hue(0.0)
@ -35,19 +34,13 @@ public:
explicit Hsv(const Rgb& rgb);
// Returns color's hue, a value from 0 to 360
double hue() const {
return m_hue;
}
double hue() const { return m_hue; }
// Returns color's saturation, a value from 0 to 100
double saturation() const {
return m_saturation;
}
double saturation() const { return m_saturation; }
// Returns color's brightness, a value from 0 to 100
double value() const {
return m_value;
}
double value() const { return m_value; }
// Integer getters, hue=[0,360), saturation=[0,100], value=[0,100]
int hueInt() const;

View File

@ -1,10 +1,12 @@
// Aseprite Gfx Library
// Copyright (C) 2001-2013 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#include "gfx/rgb.h"
#include "gfx/hsl.h"
#include "gfx/hsv.h"
#include <cmath>
@ -17,7 +19,7 @@ Rgb::Rgb(const Hsv& hsv)
{
double chroma = hsv.value() * hsv.saturation();
double hue_prime = hsv.hue() / 60.0;
double x = chroma * (1.0 - fabs(fmod(hue_prime, 2.0) - 1.0));
double x = chroma * (1.0 - std::fabs(std::fmod(hue_prime, 2.0) - 1.0));
double r, g, b;
r = g = b = 0.0;
@ -63,6 +65,56 @@ Rgb::Rgb(const Hsv& hsv)
m_blue = int(b*255.0+0.5);
}
Rgb::Rgb(const Hsl& hsl)
{
double chroma = (1.0 - std::fabs(2.0*hsl.lightness() - 1.0)) * hsl.saturation();
double hue_prime = hsl.hue() / 60.0;
double x = chroma * (1.0 - std::fabs(std::fmod(hue_prime, 2.0) - 1.0));
double r, g, b;
r = g = b = 0.0;
switch (int(hue_prime)) {
case 6:
case 0:
r = chroma;
g = x;
break;
case 1:
r = x;
g = chroma;
break;
case 2:
g = chroma;
b = x;
break;
case 3:
g = x;
b = chroma;
break;
case 4:
b = chroma;
r = x;
break;
case 5:
b = x;
r = chroma;
break;
}
double m = hsl.lightness() - chroma/2.0;
r += m;
g += m;
b += m;
m_red = int(r*255.0+0.5);
m_green = int(g*255.0+0.5);
m_blue = int(b*255.0+0.5);
}
int Rgb::maxComponent() const
{
if (m_red > m_green)

View File

@ -1,5 +1,5 @@
// Aseprite Gfx Library
// Copyright (C) 2001-2013 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -13,9 +13,9 @@
namespace gfx {
class Hsv;
class Hsl;
class Rgb
{
class Rgb {
public:
Rgb()
: m_red(0)
@ -39,8 +39,9 @@ public:
, m_blue(rgb.blue())
{ }
// HSV to RGB conversion
// Conversions
explicit Rgb(const Hsv& hsv);
explicit Rgb(const Hsl& hsl);
int red() const {
return m_red;