1// Aseprite
2// Copyright (C) 2019-2022 Igara Studio S.A.
3// Copyright (C) 2017-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "filters/hue_saturation_filter.h"
13
14#include "doc/image.h"
15#include "doc/palette.h"
16#include "doc/palette_picks.h"
17#include "doc/rgbmap.h"
18#include "filters/filter_indexed_data.h"
19#include "filters/filter_manager.h"
20#include "gfx/hsl.h"
21#include "gfx/hsv.h"
22#include "gfx/rgb.h"
23
24#include <cmath>
25
26namespace filters {
27
28using namespace doc;
29
30const char* HueSaturationFilter::getName()
31{
32 return "Hue Saturation Color";
33}
34
35HueSaturationFilter::HueSaturationFilter()
36 : m_mode(Mode::HSL_MUL)
37 , m_h(0.0)
38 , m_s(0.0)
39 , m_l(0.0)
40 , m_a(0.0)
41{
42}
43
44void HueSaturationFilter::setMode(Mode mode)
45{
46 m_mode = mode;
47}
48
49void HueSaturationFilter::setHue(double h)
50{
51 m_h = h;
52}
53
54void HueSaturationFilter::setSaturation(double s)
55{
56 m_s = s;
57}
58
59void HueSaturationFilter::setLightness(double l)
60{
61 m_l = l;
62}
63
64void HueSaturationFilter::setAlpha(double a)
65{
66 m_a = a;
67}
68
69void HueSaturationFilter::applyToRgba(FilterManager* filterMgr)
70{
71 FilterIndexedData* fid = filterMgr->getIndexedData();
72 const Palette* pal = fid->getPalette();
73 Palette* newPal = (m_usePaletteOnRGB ? fid->getNewPalette(): nullptr);
74 const uint32_t* src_address = (uint32_t*)filterMgr->getSourceAddress();
75 uint32_t* dst_address = (uint32_t*)filterMgr->getDestinationAddress();
76 const int w = filterMgr->getWidth();
77 const Target target = filterMgr->getTarget();
78
79 for (int x=0; x<w; x++) {
80 if (filterMgr->skipPixel()) {
81 ++src_address;
82 ++dst_address;
83 continue;
84 }
85
86 color_t c = *(src_address++);
87
88 if (newPal) {
89 int i =
90 pal->findExactMatch(rgba_getr(c),
91 rgba_getg(c),
92 rgba_getb(c),
93 rgba_geta(c), -1);
94 if (i >= 0)
95 c = newPal->getEntry(i);
96 }
97 else {
98 applyFilterToRgb(target, c);
99 }
100
101 *(dst_address++) = c;
102 }
103}
104
105void HueSaturationFilter::applyToGrayscale(FilterManager* filterMgr)
106{
107 const uint16_t* src_address = (uint16_t*)filterMgr->getSourceAddress();
108 uint16_t* dst_address = (uint16_t*)filterMgr->getDestinationAddress();
109 const int w = filterMgr->getWidth();
110 const Target target = filterMgr->getTarget();
111
112 for (int x=0; x<w; x++) {
113 if (filterMgr->skipPixel()) {
114 ++src_address;
115 ++dst_address;
116 continue;
117 }
118
119 color_t c = *(src_address++);
120 int k = graya_getv(c);
121 int a = graya_geta(c);
122
123 {
124 gfx::Hsl hsl(gfx::Rgb(k, k, k));
125
126 double l = hsl.lightness()*(1.0+m_l);
127 l = std::clamp(l, 0.0, 1.0);
128
129 hsl.lightness(l);
130 gfx::Rgb rgb(hsl);
131
132 if (target & TARGET_GRAY_CHANNEL) k = rgb.red();
133
134 if (a && (target & TARGET_ALPHA_CHANNEL)) {
135 a = a*(1.0+m_a);
136 a = std::clamp(a, 0, 255);
137 }
138 }
139
140 *(dst_address++) = graya(k, a);
141 }
142}
143
144void HueSaturationFilter::applyToIndexed(FilterManager* filterMgr)
145{
146 // Apply filter to pixels if there is selection (in other case, the
147 // change is global, so we have already applied the filter to the
148 // palette).
149 if (!filterMgr->isMaskActive())
150 return;
151
152 // Apply filter to color region
153 FilterIndexedData* fid = filterMgr->getIndexedData();
154 const Target target = filterMgr->getTarget();
155 const Palette* pal = fid->getPalette();
156 const RgbMap* rgbmap = fid->getRgbMap();
157 const uint8_t* src_address = (uint8_t*)filterMgr->getSourceAddress();
158 uint8_t* dst_address = (uint8_t*)filterMgr->getDestinationAddress();
159 const int w = filterMgr->getWidth();
160
161 for (int x=0; x<w; x++) {
162 if (filterMgr->skipPixel()) {
163 ++src_address;
164 ++dst_address;
165 continue;
166 }
167
168 color_t c = pal->getEntry(*(src_address++));
169 applyFilterToRgb(target, c);
170 *(dst_address++) = rgbmap->mapColor(c);
171 }
172}
173
174void HueSaturationFilter::onApplyToPalette(FilterManager* filterMgr,
175 const PalettePicks& picks)
176{
177 FilterIndexedData* fid = filterMgr->getIndexedData();
178 const Target target = filterMgr->getTarget();
179 const Palette* pal = fid->getPalette();
180 Palette* newPal = fid->getNewPalette();
181
182 int i = 0;
183 for (bool state : picks) {
184 if (!state) {
185 ++i;
186 continue;
187 }
188
189 color_t c = pal->getEntry(i);
190 applyFilterToRgb(target, c);
191 newPal->setEntry(i, c);
192 ++i;
193 }
194}
195
196template<class T,
197 double (T::*get_lightness)() const,
198 void (T::*set_lightness)(double)>
199void HueSaturationFilter::applyFilterToRgbT(const Target target,
200 doc::color_t& c,
201 bool multiply)
202{
203 int r = rgba_getr(c);
204 int g = rgba_getg(c);
205 int b = rgba_getb(c);
206 int a = rgba_geta(c);
207
208 T hsl(gfx::Rgb(r, g, b));
209
210 double h = hsl.hue() + m_h;
211 while (h < 0.0) h += 360.0;
212 h = std::fmod(h, 360.0);
213
214 double s = (multiply ? hsl.saturation()*(1.0+m_s):
215 hsl.saturation() + m_s);
216 s = std::clamp(s, 0.0, 1.0);
217
218 double l = (multiply ? (hsl.*get_lightness)()*(1.0+m_l):
219 (hsl.*get_lightness)() + m_l);
220 l = std::clamp(l, 0.0, 1.0);
221
222 hsl.hue(h);
223 hsl.saturation(s);
224 (hsl.*set_lightness)(l);
225 gfx::Rgb rgb(hsl);
226
227 if (target & TARGET_RED_CHANNEL ) r = rgb.red();
228 if (target & TARGET_GREEN_CHANNEL) g = rgb.green();
229 if (target & TARGET_BLUE_CHANNEL ) b = rgb.blue();
230 if (a && (target & TARGET_ALPHA_CHANNEL)) {
231 a = a*(1.0+m_a);
232 a = std::clamp(a, 0, 255);
233 }
234
235 c = rgba(r, g, b, a);
236}
237
238void HueSaturationFilter::applyFilterToRgb(const Target target, doc::color_t& color)
239{
240 switch (m_mode) {
241 case Mode::HSV_MUL:
242 applyFilterToRgbT<gfx::Hsv,
243 &gfx::Hsv::value,
244 &gfx::Hsv::value>(target, color, true);
245 break;
246 case Mode::HSL_MUL:
247 applyFilterToRgbT<gfx::Hsl,
248 &gfx::Hsl::lightness,
249 &gfx::Hsl::lightness>(target, color, true);
250 break;
251 case Mode::HSV_ADD:
252 applyFilterToRgbT<gfx::Hsv,
253 &gfx::Hsv::value,
254 &gfx::Hsv::value>(target, color, false);
255 break;
256 case Mode::HSL_ADD:
257 applyFilterToRgbT<gfx::Hsl,
258 &gfx::Hsl::lightness,
259 &gfx::Hsl::lightness>(target, color, false);
260 break;
261 }
262}
263
264} // namespace filters
265