1// Aseprite Document Library
2// Copyright (C) 2019-2022 Igara Studio S.A.
3// Copyright (C) 2001-2016 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 "doc/brush.h"
13
14#include "base/pi.h"
15#include "doc/algo.h"
16#include "doc/algorithm/polygon.h"
17#include "doc/blend_internals.h"
18#include "doc/image.h"
19#include "doc/image_impl.h"
20#include "doc/primitives.h"
21
22#include <algorithm>
23#include <cmath>
24
25namespace doc {
26
27static int generation = 0;
28
29Brush::Brush()
30{
31 m_type = kCircleBrushType;
32 m_size = 1;
33 m_angle = 0;
34 m_pattern = BrushPattern::DEFAULT_FOR_UI;
35 m_gen = 0;
36
37 regenerate();
38}
39
40Brush::Brush(BrushType type, int size, int angle)
41{
42 m_type = type;
43 m_size = size;
44 m_angle = angle;
45 m_pattern = BrushPattern::DEFAULT_FOR_UI;
46 m_gen = 0;
47
48 regenerate();
49}
50
51Brush::Brush(const Brush& brush)
52{
53 m_type = brush.m_type;
54 m_size = brush.m_size;
55 m_angle = brush.m_angle;
56 m_image = brush.m_image;
57 m_maskBitmap = brush.m_maskBitmap;
58 m_pattern = brush.m_pattern;
59 m_patternOrigin = brush.m_patternOrigin;
60 m_gen = 0;
61
62 regenerate();
63}
64
65Brush::~Brush()
66{
67 clean();
68}
69
70void Brush::setType(BrushType type)
71{
72 m_type = type;
73 if (m_type != kImageBrushType)
74 regenerate();
75 else
76 clean();
77}
78
79void Brush::setSize(int size)
80{
81 m_size = size;
82 regenerate();
83}
84
85void Brush::setAngle(int angle)
86{
87 m_angle = angle;
88 regenerate();
89}
90
91void Brush::setImage(const Image* image,
92 const Image* maskBitmap)
93{
94 m_type = kImageBrushType;
95 m_image.reset(Image::createCopy(image));
96 if (maskBitmap)
97 m_maskBitmap.reset(Image::createCopy(maskBitmap));
98 else {
99 int w = image->width();
100 int h = image->height();
101 m_maskBitmap.reset(Image::create(IMAGE_BITMAP, w, h));
102 LockImageBits<BitmapTraits> bits(m_maskBitmap.get());
103 auto pos = bits.begin();
104 for (int v=0; v<h; ++v)
105 for (int u=0; u<w; ++u, ++pos)
106 *pos = (get_pixel(image, u, v) != image->maskColor());
107 }
108
109 m_backupImage.reset();
110 m_mainColor.reset();
111 m_bgColor.reset();
112
113 resetBounds();
114}
115
116template<class ImageTraits,
117 color_t color_mask,
118 color_t alpha_mask,
119 color_t alpha_shift>
120static void replace_image_colors(
121 Image* image,
122 Image* maskBitmap,
123 const bool useMain, color_t mainColor,
124 const bool useBg, color_t bgColor)
125{
126 LockImageBits<ImageTraits> bits(image, Image::ReadWriteLock);
127 const LockImageBits<BitmapTraits> maskBits(maskBitmap);
128 bool hasAlpha = false; // True if "image" has a pixel with alpha < 255
129 color_t srcMainColor, srcBgColor;
130 srcMainColor = srcBgColor = 0;
131
132 auto mask_it = maskBits.begin();
133 for (const auto& pixel : bits) {
134 if (!*mask_it)
135 continue;
136
137 if ((pixel & alpha_mask) != alpha_mask) { // If alpha != 255
138 hasAlpha = true;
139 }
140 else if (srcBgColor == 0) {
141 srcMainColor = srcBgColor = pixel;
142 }
143 else if (pixel != srcBgColor && srcMainColor == srcBgColor) {
144 srcMainColor = pixel;
145 }
146
147 ++mask_it;
148 }
149
150 int t;
151 if (hasAlpha) {
152 if (useMain || useBg) {
153 const color_t color = (useMain ? mainColor: useBg);
154 for (auto& pixel : bits) {
155 color_t a1 = (pixel & alpha_mask) >> alpha_shift;
156 const color_t a2 = (color & alpha_mask) >> alpha_shift;
157 a1 = MUL_UN8(a1, a2, t);
158 pixel =
159 (a1 << alpha_shift) |
160 (color & color_mask);
161 }
162 }
163 }
164 else {
165 for (auto& pixel : bits) {
166 color_t color;
167 if (useMain && ((pixel != srcBgColor) || (srcMainColor == srcBgColor)))
168 color = mainColor;
169 else if (useBg && (pixel == srcBgColor))
170 color = bgColor;
171 else
172 continue;
173
174 color_t a1 = (pixel & alpha_mask) >> alpha_shift;
175 color_t a2 = (color & alpha_mask) >> alpha_shift;
176 a1 = MUL_UN8(a1, a2, t);
177 pixel =
178 (a1 << alpha_shift) |
179 (color & color_mask);
180 }
181 }
182}
183
184static void replace_image_colors_indexed(
185 Image* image,
186 Image* maskBitmap,
187 const bool useMain, const color_t mainColor,
188 const bool useBg, const color_t bgColor)
189{
190 LockImageBits<IndexedTraits> bits(image, Image::ReadWriteLock);
191 const LockImageBits<BitmapTraits> maskBits(maskBitmap);
192 bool hasAlpha = false; // True if "image" has a pixel with the mask color
193 color_t maskColor = image->maskColor();
194 color_t srcMainColor, srcBgColor;
195 srcMainColor = srcBgColor = maskColor;
196
197 auto mask_it = maskBits.begin();
198 for (const auto& pixel : bits) {
199 if (!*mask_it)
200 continue;
201
202 if (pixel == maskColor) {
203 hasAlpha = true;
204 }
205 else if (srcBgColor == maskColor) {
206 srcMainColor = srcBgColor = pixel;
207 }
208 else if (pixel != srcBgColor && srcMainColor == srcBgColor) {
209 srcMainColor = pixel;
210 }
211
212 ++mask_it;
213 }
214
215 if (hasAlpha) {
216 for (auto& pixel : bits) {
217 if (pixel != maskColor) {
218 if (useMain)
219 pixel = mainColor;
220 else if (useBg)
221 pixel = bgColor;
222 }
223 }
224 }
225 else {
226 for (auto& pixel : bits) {
227 if (useMain && ((pixel != srcBgColor) || (srcMainColor == srcBgColor))) {
228 pixel = mainColor;
229 }
230 else if (useBg && (pixel == srcBgColor)) {
231 pixel = bgColor;
232 }
233 }
234 }
235}
236
237void Brush::setImageColor(ImageColor imageColor, color_t color)
238{
239 ASSERT(m_image);
240 if (!m_image)
241 return;
242
243 if (!m_backupImage)
244 m_backupImage.reset(Image::createCopy(m_image.get()));
245 else
246 m_image.reset(Image::createCopy(m_backupImage.get()));
247
248 ASSERT(m_maskBitmap);
249
250 switch (imageColor) {
251 case ImageColor::MainColor:
252 m_mainColor.reset(new color_t(color));
253 break;
254 case ImageColor::BackgroundColor:
255 m_bgColor.reset(new color_t(color));
256 break;
257 }
258
259 switch (m_image->pixelFormat()) {
260
261 case IMAGE_RGB:
262 replace_image_colors<RgbTraits, rgba_rgb_mask, rgba_a_mask, rgba_a_shift>(
263 m_image.get(), m_maskBitmap.get(),
264 (m_mainColor ? true: false), (m_mainColor ? *m_mainColor: 0),
265 (m_bgColor ? true: false), (m_bgColor ? *m_bgColor: 0));
266 break;
267
268 case IMAGE_GRAYSCALE:
269 replace_image_colors<GrayscaleTraits, graya_v_mask, graya_a_mask, graya_a_shift>(
270 m_image.get(), m_maskBitmap.get(),
271 (m_mainColor ? true: false), (m_mainColor ? *m_mainColor: 0),
272 (m_bgColor ? true: false), (m_bgColor ? *m_bgColor: 0));
273 break;
274
275 case IMAGE_INDEXED:
276 replace_image_colors_indexed(
277 m_image.get(), m_maskBitmap.get(),
278 (m_mainColor ? true: false), (m_mainColor ? *m_mainColor: 0),
279 (m_bgColor ? true: false), (m_bgColor ? *m_bgColor: 0));
280 break;
281 }
282}
283
284void Brush::resetImageColors()
285{
286 if (m_backupImage)
287 m_image.reset(Image::createCopy(m_backupImage.get()));
288}
289
290void Brush::setCenter(const gfx::Point& center)
291{
292 m_center = center;
293 m_bounds = gfx::Rect(-m_center,
294 gfx::Size(m_image->width(),
295 m_image->height()));
296}
297
298// Cleans the brush's data (image and region).
299void Brush::clean()
300{
301 m_gen = ++generation;
302 m_image.reset();
303 m_maskBitmap.reset();
304 m_backupImage.reset();
305}
306
307static void algo_hline(int x1, int y, int x2, void *data)
308{
309 draw_hline(reinterpret_cast<Image*>(data), x1, y, x2, BitmapTraits::max_value);
310}
311
312// Regenerates the brush bitmap and its rectangle's region.
313void Brush::regenerate()
314{
315 clean();
316
317 ASSERT(m_size > 0);
318
319 int size = m_size;
320 if (m_type == kSquareBrushType && m_angle != 0 && m_size > 2)
321 size = (int)std::sqrt((double)2*m_size*m_size)+2;
322
323 m_image.reset(Image::create(IMAGE_BITMAP, size, size));
324 m_maskBitmap.reset();
325
326 resetBounds();
327
328 if (size == 1) {
329 clear_image(m_image.get(), BitmapTraits::max_value);
330 }
331 else {
332 clear_image(m_image.get(), BitmapTraits::min_value);
333
334 switch (m_type) {
335
336 case kCircleBrushType:
337 fill_ellipse(m_image.get(), 0, 0, size-1, size-1, 0, 0, BitmapTraits::max_value);
338 break;
339
340 case kSquareBrushType:
341 if (m_angle == 0 || size <= 2) {
342 clear_image(m_image.get(), BitmapTraits::max_value);
343 }
344 else {
345 double a = PI * m_angle / 180;
346 int c = size/2;
347 int r = m_size/2;
348 int d = m_size;
349 int x1 = int(c + r*cos(a-PI/2) + r*cos(a-PI));
350 int y1 = int(c - r*sin(a-PI/2) - r*sin(a-PI));
351 int x2 = int(x1 + d*cos(a));
352 int y2 = int(y1 - d*sin(a));
353 int x3 = int(x2 + d*cos(a+PI/2));
354 int y3 = int(y2 - d*sin(a+PI/2));
355 int x4 = int(x3 + d*cos(a+PI));
356 int y4 = int(y3 - d*sin(a+PI));
357 int points[8] = { x1, y1, x2, y2, x3, y3, x4, y4 };
358
359 doc::algorithm::polygon(4, points, m_image.get(), algo_hline);
360 }
361 break;
362
363 case kLineBrushType: {
364 const double a = PI * m_angle / 180;
365 const double r = m_size / 2.0;
366 const int cx = m_center.x;
367 const int cy = m_center.y;
368 const int dx = int(r*cos(-a));
369 const int dy = int(r*sin(-a));
370
371 draw_line(m_image.get(), cx, cy, cx+dx, cy+dy, BitmapTraits::max_value);
372 draw_line(m_image.get(), cx, cy, cx-dx, cy-dy, BitmapTraits::max_value);
373 break;
374 }
375 }
376 }
377}
378
379void Brush::resetBounds()
380{
381 m_center = gfx::Point(std::max(0, m_image->width()/2),
382 std::max(0, m_image->height()/2));
383 m_bounds = gfx::Rect(-m_center,
384 gfx::Size(m_image->width(),
385 m_image->height()));
386}
387
388} // namespace doc
389