1// Aseprite Render Library
2// Copyright (c) 2019-2020 Igara Studio S.A.
3// Copyright (c) 2017 David Capello
4//
5// This file is released under the terms of the MIT license.
6// Read LICENSE.txt for more information.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "render/gradient.h"
13
14#include "base/vector2d.h"
15#include "doc/image.h"
16#include "doc/image_impl.h"
17#include "render/dithering_matrix.h"
18
19namespace render {
20
21void render_rgba_gradient(
22 doc::Image* img,
23 const gfx::Point imgPos,
24 const gfx::Point p0,
25 const gfx::Point p1,
26 doc::color_t c0,
27 doc::color_t c1,
28 const render::DitheringMatrix& matrix,
29 const GradientType type)
30{
31 switch (type) {
32 case GradientType::Linear:
33 render_rgba_linear_gradient(img, imgPos, p0, p1, c0, c1, matrix);
34 break;
35 case GradientType::Radial:
36 render_rgba_radial_gradient(img, imgPos, p0, p1, c0, c1, matrix);
37 break;
38 }
39}
40
41void render_rgba_linear_gradient(
42 doc::Image* img,
43 const gfx::Point imgPos,
44 const gfx::Point p0,
45 const gfx::Point p1,
46 doc::color_t c0,
47 doc::color_t c1,
48 const render::DitheringMatrix& matrix)
49{
50 ASSERT(img->pixelFormat() == doc::IMAGE_RGB);
51 if (img->pixelFormat() != doc::IMAGE_RGB) {
52 return;
53 }
54
55 // If there is no vector defining the gradient (just one point),
56 // the "gradient" will be just "c0"
57 if (p0 == p1) {
58 img->clear(c0);
59 return;
60 }
61
62 base::Vector2d<double>
63 u(p0.x, p0.y),
64 v(p1.x, p1.y), w;
65 w = v - u;
66 const double wmag = w.magnitude();
67 w = w.normalize();
68
69 // As we use non-premultiplied RGB values, we need correct RGB
70 // values on each stop. So in case that one color has alpha=0
71 // (complete transparent), use the RGB values of the
72 // non-transparent color in the other stop point.
73 if (doc::rgba_geta(c0) == 0 &&
74 doc::rgba_geta(c1) != 0) {
75 c0 = (c1 & doc::rgba_rgb_mask);
76 }
77 else if (doc::rgba_geta(c0) != 0 &&
78 doc::rgba_geta(c1) == 0) {
79 c1 = (c0 & doc::rgba_rgb_mask);
80 }
81
82 const double r0 = double(doc::rgba_getr(c0)) / 255.0;
83 const double g0 = double(doc::rgba_getg(c0)) / 255.0;
84 const double b0 = double(doc::rgba_getb(c0)) / 255.0;
85 const double a0 = double(doc::rgba_geta(c0)) / 255.0;
86
87 const double r1 = double(doc::rgba_getr(c1)) / 255.0;
88 const double g1 = double(doc::rgba_getg(c1)) / 255.0;
89 const double b1 = double(doc::rgba_getb(c1)) / 255.0;
90 const double a1 = double(doc::rgba_geta(c1)) / 255.0;
91
92 doc::LockImageBits<doc::RgbTraits> bits(img);
93 auto it = bits.begin();
94 const int width = img->width();
95 const int height = img->height();
96
97 if (matrix.rows() == 1 && matrix.cols() == 1) {
98 for (int y=0; y<height; ++y) {
99 for (int x=0; x<width; ++x, ++it) {
100 base::Vector2d<double> q(imgPos.x+x,
101 imgPos.y+y);
102 q -= u;
103 double f = (q * w) / wmag;
104
105 doc::color_t c;
106 if (f < 0.0) c = c0;
107 else if (f > 1.0) c = c1;
108 else {
109 c = doc::rgba(int(255.0 * (r0 + f*(r1-r0))),
110 int(255.0 * (g0 + f*(g1-g0))),
111 int(255.0 * (b0 + f*(b1-b0))),
112 int(255.0 * (a0 + f*(a1-a0))));
113 }
114
115 *it = c;
116 }
117 }
118 }
119 else {
120 for (int y=0; y<height; ++y) {
121 for (int x=0; x<width; ++x, ++it) {
122 base::Vector2d<double> q(imgPos.x+x,
123 imgPos.y+y);
124 q -= u;
125 double f = (q * w) / wmag;
126
127 *it = (f*(matrix.maxValue()+2) < matrix(y, x)+1 ? c0: c1);
128 }
129 }
130 }
131}
132
133void render_rgba_radial_gradient(
134 doc::Image* img,
135 const gfx::Point imgPos,
136 const gfx::Point p0,
137 const gfx::Point p1,
138 doc::color_t c0,
139 doc::color_t c1,
140 const render::DitheringMatrix& matrix)
141{
142 ASSERT(img->pixelFormat() == doc::IMAGE_RGB);
143 if (img->pixelFormat() != doc::IMAGE_RGB) {
144 return;
145 }
146
147 base::Vector2d<double>
148 u(p0.x, p0.y),
149 v(p1.x, p1.y), w;
150 w = (v - u) / 2;
151
152 // If there is no vector defining the gradient (just one point),
153 // the "gradient" will be just a solid color ("c1")
154 if (std::fabs(w.x) <= 0.000001 ||
155 std::fabs(w.y) <= 0.000001) {
156 img->clear(c1);
157 return;
158 }
159
160 // As we use non-premultiplied RGB values, we need correct RGB
161 // values on each stop. So in case that one color has alpha=0
162 // (complete transparent), use the RGB values of the
163 // non-transparent color in the other stop point.
164 if (doc::rgba_geta(c0) == 0 &&
165 doc::rgba_geta(c1) != 0) {
166 c0 = (c1 & doc::rgba_rgb_mask);
167 }
168 else if (doc::rgba_geta(c0) != 0 &&
169 doc::rgba_geta(c1) == 0) {
170 c1 = (c0 & doc::rgba_rgb_mask);
171 }
172
173 const double r0 = double(doc::rgba_getr(c0)) / 255.0;
174 const double g0 = double(doc::rgba_getg(c0)) / 255.0;
175 const double b0 = double(doc::rgba_getb(c0)) / 255.0;
176 const double a0 = double(doc::rgba_geta(c0)) / 255.0;
177
178 const double r1 = double(doc::rgba_getr(c1)) / 255.0;
179 const double g1 = double(doc::rgba_getg(c1)) / 255.0;
180 const double b1 = double(doc::rgba_getb(c1)) / 255.0;
181 const double a1 = double(doc::rgba_geta(c1)) / 255.0;
182
183 doc::LockImageBits<doc::RgbTraits> bits(img);
184 auto it = bits.begin();
185 const int width = img->width();
186 const int height = img->height();
187
188 if (matrix.rows() == 1 && matrix.cols() == 1) {
189 for (int y=0; y<height; ++y) {
190 for (int x=0; x<width; ++x, ++it) {
191 base::Vector2d<double> q(imgPos.x+x,
192 imgPos.y+y);
193 q -= (u+v)/2;
194 q.x /= std::fabs(w.x);
195 q.y /= std::fabs(w.y);
196 double f = std::sqrt(q.x*q.x + q.y*q.y);
197
198 doc::color_t c;
199 if (f < 0.0) c = c0;
200 else if (f > 1.0) c = c1;
201 else {
202 c = doc::rgba(int(255.0 * (r0 + f*(r1-r0))),
203 int(255.0 * (g0 + f*(g1-g0))),
204 int(255.0 * (b0 + f*(b1-b0))),
205 int(255.0 * (a0 + f*(a1-a0))));
206 }
207
208 *it = c;
209 }
210 }
211 }
212 else {
213 for (int y=0; y<height; ++y) {
214 for (int x=0; x<width; ++x, ++it) {
215 base::Vector2d<double> q(imgPos.x+x,
216 imgPos.y+y);
217 q -= (u+v)/2;
218 q.x /= std::fabs(w.x);
219 q.y /= std::fabs(w.y);
220 double f = std::sqrt(q.x*q.x + q.y*q.y);
221
222 *it = (f*(matrix.maxValue()+2) < matrix(y, x)+1 ? c0: c1);
223 }
224 }
225 }
226}
227
228template<typename ImageTraits>
229static void create_dithering_pattern_templ(
230 doc::Image* pattern,
231 const render::DitheringMatrix& matrix,
232 const float f,
233 const doc::color_t c0,
234 const doc::color_t c1)
235{
236 const int w = pattern->width();
237 const int h = pattern->height();
238
239 doc::LockImageBits<ImageTraits> dstBits(pattern);
240 auto dst = dstBits.begin();
241 for (int y=0; y<h; ++y) {
242 for (int x=0; x<w; ++x, ++dst)
243 *dst = (f*(matrix.maxValue()+2) < matrix(y, x)+1 ? c0: c1);
244 }
245}
246
247void convert_bitmap_brush_to_dithering_brush(
248 doc::Brush* brush,
249 const doc::PixelFormat pixelFormat,
250 const render::DitheringMatrix& matrix,
251 const float f,
252 const doc::color_t c0,
253 const doc::color_t c1)
254{
255 // Create a pattern
256 doc::ImageRef pattern(
257 doc::Image::create(pixelFormat,
258 matrix.cols(), matrix.rows()));
259
260 switch (pixelFormat) {
261 case doc::IMAGE_RGB:
262 create_dithering_pattern_templ<doc::RgbTraits>(
263 pattern.get(), matrix, f, c0, c1);
264 break;
265 case doc::IMAGE_GRAYSCALE:
266 create_dithering_pattern_templ<doc::GrayscaleTraits>(
267 pattern.get(), matrix, f, c0, c1);
268 break;
269 case doc::IMAGE_INDEXED:
270 create_dithering_pattern_templ<doc::IndexedTraits>(
271 pattern.get(), matrix, f, c0, c1);
272 break;
273 }
274
275 doc::ImageRef copy(doc::Image::createCopy(brush->image()));
276 brush->setImage(copy.get(), copy.get());
277 brush->setPatternImage(pattern);
278 brush->setPattern(doc::BrushPattern::PAINT_BRUSH);
279}
280
281} // namespace render
282