1// Aseprite
2// Copyright (C) 2019-2022 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#include "app/color_utils.h"
9#include "app/util/wrap_point.h"
10#include "app/util/wrap_value.h"
11#include "doc/blend_funcs.h"
12#include "doc/blend_internals.h"
13#include "doc/image_impl.h"
14#include "doc/layer.h"
15#include "doc/palette.h"
16#include "doc/remap.h"
17#include "doc/rgbmap.h"
18#include "doc/sprite.h"
19#include "filters/neighboring_pixels.h"
20#include "gfx/hsv.h"
21#include "gfx/rgb.h"
22#include "render/dithering.h"
23#include "render/gradient.h"
24
25namespace app {
26namespace tools {
27
28using namespace gfx;
29using namespace filters;
30
31class BaseInkProcessing {
32public:
33 virtual ~BaseInkProcessing() { }
34 virtual void processScanline(int x1, int y, int x2, ToolLoop* loop) = 0;
35 virtual void prepareForStrokes(ToolLoop* loop, Strokes& strokes) { }
36 virtual void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) { }
37 virtual void prepareVForPointShape(ToolLoop* loop, int y) { }
38 virtual void prepareUForPointShapeWholeScanline(ToolLoop* loop, int x1) { }
39 virtual void prepareUForPointShapeSlicedScanline(ToolLoop* loop, bool leftSlice, int x1) { }
40};
41
42typedef std::unique_ptr<BaseInkProcessing> InkProcessingPtr;
43
44namespace {
45
46//////////////////////////////////////////////////////////////////////
47// Ink Processing
48//////////////////////////////////////////////////////////////////////
49
50template<typename Derived>
51class InkProcessing : public BaseInkProcessing {
52public:
53 void processScanline(int x1, int y, int x2, ToolLoop* loop) override {
54 int x;
55
56 // Use mask
57 if (loop->useMask()) {
58 Point maskOrigin(loop->getMaskOrigin());
59 const Rect& maskBounds(loop->getMask()->bounds());
60
61 if ((y < maskOrigin.y) || (y >= maskOrigin.y+maskBounds.h))
62 return;
63
64 if (x1 < maskOrigin.x)
65 x1 = maskOrigin.x;
66
67 if (x2 > maskOrigin.x+maskBounds.w-1)
68 x2 = maskOrigin.x+maskBounds.w-1;
69
70 if (Image* bitmap = loop->getMask()->bitmap()) {
71 static_cast<Derived*>(this)->initIterators(loop, x1, y);
72
73 for (x=x1; x<=x2; ++x) {
74 if (bitmap->getPixel(x-maskOrigin.x, y-maskOrigin.y))
75 static_cast<Derived*>(this)->processPixel(x, y);
76
77 static_cast<Derived*>(this)->moveIterators();
78 }
79 return;
80 }
81 }
82
83 static_cast<Derived*>(this)->initIterators(loop, x1, y);
84 for (x=x1; x<=x2; ++x) {
85 static_cast<Derived*>(this)->processPixel(x, y);
86 static_cast<Derived*>(this)->moveIterators();
87 }
88 }
89};
90
91template<typename Derived, typename ImageTraits>
92class SimpleInkProcessing : public InkProcessing<Derived> {
93public:
94 void initIterators(ToolLoop* loop, int x1, int y) {
95 m_dstAddress = (typename ImageTraits::address_t)loop->getDstImage()->getPixelAddress(x1, y);
96 }
97
98 void moveIterators() {
99 ++m_dstAddress;
100 }
101
102protected:
103 typename ImageTraits::address_t m_dstAddress;
104};
105
106template<typename Derived, typename ImageTraits>
107class DoubleInkProcessing : public InkProcessing<Derived> {
108public:
109 void initIterators(ToolLoop* loop, int x1, int y) {
110 m_srcAddress = (typename ImageTraits::address_t)loop->getSrcImage()->getPixelAddress(x1, y);
111 m_dstAddress = (typename ImageTraits::address_t)loop->getDstImage()->getPixelAddress(x1, y);
112 }
113
114 void moveIterators() {
115 ++m_srcAddress;
116 ++m_dstAddress;
117 }
118
119protected:
120 typename ImageTraits::address_t m_srcAddress;
121 typename ImageTraits::address_t m_dstAddress;
122};
123
124//////////////////////////////////////////////////////////////////////
125// Copy Ink
126//////////////////////////////////////////////////////////////////////
127
128template<typename ImageTraits>
129class CopyInkProcessing : public SimpleInkProcessing<CopyInkProcessing<ImageTraits>, ImageTraits> {
130public:
131 CopyInkProcessing(ToolLoop* loop) {
132 }
133
134 void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
135 m_color = loop->getPrimaryColor();
136
137 if (loop->getLayer()->isBackground()) {
138 switch (loop->sprite()->pixelFormat()) {
139 case IMAGE_RGB: m_color |= rgba_a_mask; break;
140 case IMAGE_GRAYSCALE: m_color |= graya_a_mask; break;
141 }
142 }
143 }
144
145 void processPixel(int x, int y) {
146 *this->m_dstAddress = m_color;
147 }
148
149private:
150 color_t m_color;
151};
152
153//////////////////////////////////////////////////////////////////////
154// LockAlpha Ink
155//////////////////////////////////////////////////////////////////////
156
157template<typename ImageTraits>
158class LockAlphaInkProcessing : public DoubleInkProcessing<LockAlphaInkProcessing<ImageTraits>, ImageTraits> {
159public:
160 LockAlphaInkProcessing(ToolLoop* loop)
161 : m_opacity(loop->getOpacity()) {
162 }
163
164 void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
165 m_color = loop->getPrimaryColor();
166 }
167
168 void processPixel(int x, int y) {
169 // Do nothing
170 }
171
172private:
173 color_t m_color;
174 const int m_opacity;
175};
176
177template<>
178void LockAlphaInkProcessing<RgbTraits>::processPixel(int x, int y) {
179 color_t result = rgba_blender_normal(*m_srcAddress, m_color, m_opacity);
180 *m_dstAddress = doc::rgba(
181 rgba_getr(result),
182 rgba_getg(result),
183 rgba_getb(result),
184 rgba_geta(*m_srcAddress));
185}
186
187template<>
188void LockAlphaInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
189 color_t result = graya_blender_normal(*m_srcAddress, m_color, m_opacity);
190 *m_dstAddress = graya(
191 graya_getv(result),
192 graya_geta(*m_srcAddress));
193}
194
195template<>
196class LockAlphaInkProcessing<IndexedTraits> : public DoubleInkProcessing<LockAlphaInkProcessing<IndexedTraits>, IndexedTraits> {
197public:
198 LockAlphaInkProcessing(ToolLoop* loop)
199 : m_palette(loop->getPalette())
200 , m_rgbmap(loop->getRgbMap())
201 , m_opacity(loop->getOpacity())
202 , m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) {
203 }
204
205 void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
206 m_color = m_palette->getEntry(loop->getPrimaryColor());
207 }
208
209 void processPixel(int x, int y) {
210 color_t c = *m_srcAddress;
211 if (int(c) == m_maskIndex)
212 c = m_palette->getEntry(c) & rgba_rgb_mask; // Alpha = 0
213 else
214 c = m_palette->getEntry(c);
215
216 color_t result = rgba_blender_normal(c, m_color, m_opacity);
217 // TODO should we use m_rgbmap->mapColor instead?
218 *m_dstAddress = m_palette->findBestfit(
219 rgba_getr(result),
220 rgba_getg(result),
221 rgba_getb(result),
222 rgba_geta(c), m_maskIndex);
223 }
224
225private:
226 const Palette* m_palette;
227 const RgbMap* m_rgbmap;
228 color_t m_color;
229 const int m_opacity;
230 const int m_maskIndex;
231};
232
233//////////////////////////////////////////////////////////////////////
234// Transparent Ink
235//////////////////////////////////////////////////////////////////////
236
237template<typename ImageTraits>
238class TransparentInkProcessing : public DoubleInkProcessing<TransparentInkProcessing<ImageTraits>, ImageTraits> {
239public:
240 TransparentInkProcessing(ToolLoop* loop) {
241 m_opacity = loop->getOpacity();
242 }
243
244 void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
245 m_color = loop->getPrimaryColor();
246 }
247
248 void processPixel(int x, int y) {
249 // Do nothing
250 }
251
252private:
253 color_t m_color;
254 int m_opacity;
255};
256
257template<>
258void TransparentInkProcessing<RgbTraits>::processPixel(int x, int y) {
259 *m_dstAddress = rgba_blender_normal(*m_srcAddress, m_color, m_opacity);
260}
261
262template<>
263void TransparentInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
264 *m_dstAddress = graya_blender_normal(*m_srcAddress, m_color, m_opacity);
265}
266
267template<>
268class TransparentInkProcessing<IndexedTraits> : public DoubleInkProcessing<TransparentInkProcessing<IndexedTraits>, IndexedTraits> {
269public:
270 TransparentInkProcessing(ToolLoop* loop) :
271 m_palette(loop->getPalette()),
272 m_rgbmap(loop->getRgbMap()),
273 m_opacity(loop->getOpacity()),
274 m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()),
275 m_colorIndex(loop->getFgColor()) {
276 }
277
278 void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
279 m_color = m_palette->getEntry(loop->getPrimaryColor());
280 }
281
282 void processPixel(int x, int y) {
283 if (m_colorIndex == m_maskIndex)
284 return;
285
286 color_t c = *m_srcAddress;
287 if (int(c) == m_maskIndex)
288 c = m_palette->getEntry(c) & rgba_rgb_mask; // Alpha = 0
289 else
290 c = m_palette->getEntry(c);
291
292 c = rgba_blender_normal(c, m_color, m_opacity);
293 *m_dstAddress = m_rgbmap->mapColor(c);
294 }
295
296private:
297 const Palette* m_palette;
298 const RgbMap* m_rgbmap;
299 const int m_opacity;
300 color_t m_color;
301 const int m_maskIndex;
302 int m_colorIndex;
303};
304
305//////////////////////////////////////////////////////////////////////
306// Merge Ink
307//////////////////////////////////////////////////////////////////////
308
309template<typename ImageTraits>
310class MergeInkProcessing : public DoubleInkProcessing<MergeInkProcessing<ImageTraits>, ImageTraits> {
311public:
312 MergeInkProcessing(ToolLoop* loop) {
313 m_opacity = loop->getOpacity();
314 }
315
316 void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
317 m_color = loop->getPrimaryColor();
318 }
319
320 void processPixel(int x, int y) {
321 // Do nothing
322 }
323
324private:
325 color_t m_color;
326 int m_opacity;
327};
328
329template<>
330void MergeInkProcessing<RgbTraits>::processPixel(int x, int y) {
331 *m_dstAddress = rgba_blender_merge(*m_srcAddress, m_color, m_opacity);
332}
333
334template<>
335void MergeInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
336 *m_dstAddress = graya_blender_merge(*m_srcAddress, m_color, m_opacity);
337}
338
339template<>
340class MergeInkProcessing<IndexedTraits> : public DoubleInkProcessing<MergeInkProcessing<IndexedTraits>, IndexedTraits> {
341public:
342 MergeInkProcessing(ToolLoop* loop) :
343 m_palette(loop->getPalette()),
344 m_rgbmap(loop->getRgbMap()),
345 m_opacity(loop->getOpacity()),
346 m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) {
347 }
348
349 void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
350 m_color = (int(loop->getPrimaryColor()) == m_maskIndex ?
351 (m_palette->getEntry(loop->getPrimaryColor()) & rgba_rgb_mask):
352 (m_palette->getEntry(loop->getPrimaryColor())));
353 }
354
355 void processPixel(int x, int y) {
356 color_t c = *m_srcAddress;
357 if (int(c) == m_maskIndex)
358 c = m_palette->getEntry(c) & rgba_rgb_mask; // Alpha = 0
359 else
360 c = m_palette->getEntry(c);
361
362 c = rgba_blender_merge(c, m_color, m_opacity);
363 *m_dstAddress = m_rgbmap->mapColor(c);
364 }
365
366private:
367 const Palette* m_palette;
368 const RgbMap* m_rgbmap;
369 const int m_opacity;
370 const int m_maskIndex;
371 color_t m_color;
372};
373
374//////////////////////////////////////////////////////////////////////
375// Blur Ink
376//////////////////////////////////////////////////////////////////////
377
378template<typename ImageTraits>
379class BlurInkProcessing : public DoubleInkProcessing<BlurInkProcessing<ImageTraits>, ImageTraits> {
380public:
381 BlurInkProcessing(ToolLoop* loop) {
382 }
383 void processPixel(int x, int y) {
384 // Do nothing (it's specialized for each case)
385 }
386};
387
388template<>
389class BlurInkProcessing<RgbTraits> : public DoubleInkProcessing<BlurInkProcessing<RgbTraits>, RgbTraits> {
390public:
391 BlurInkProcessing(ToolLoop* loop) :
392 m_opacity(loop->getOpacity()),
393 m_tiledMode(loop->getTiledMode()),
394 m_srcImage(loop->getSrcImage()) {
395 }
396
397 void processPixel(int x, int y) {
398 m_area.reset();
399 get_neighboring_pixels<RgbTraits>(m_srcImage, x, y, 3, 3, 1, 1, m_tiledMode, m_area);
400
401 if (m_area.count > 0) {
402 m_area.r /= m_area.count;
403 m_area.g /= m_area.count;
404 m_area.b /= m_area.count;
405 m_area.a /= 9;
406 *m_dstAddress =
407 rgba_blender_merge(*m_srcAddress,
408 doc::rgba(m_area.r, m_area.g, m_area.b, m_area.a),
409 m_opacity);
410 }
411 else {
412 *m_dstAddress = *m_srcAddress;
413 }
414 }
415
416private:
417 struct GetPixelsDelegate {
418 int count, r, g, b, a;
419
420 void reset() { count = r = g = b = a = 0; }
421
422 void operator()(RgbTraits::pixel_t color) {
423 if (rgba_geta(color) != 0) {
424 r += rgba_getr(color);
425 g += rgba_getg(color);
426 b += rgba_getb(color);
427 a += rgba_geta(color);
428 ++count;
429 }
430 }
431 };
432
433 int m_opacity;
434 TiledMode m_tiledMode;
435 const Image* m_srcImage;
436 GetPixelsDelegate m_area;
437};
438
439template<>
440class BlurInkProcessing<GrayscaleTraits> : public DoubleInkProcessing<BlurInkProcessing<GrayscaleTraits>, GrayscaleTraits> {
441public:
442 BlurInkProcessing(ToolLoop* loop) :
443 m_opacity(loop->getOpacity()),
444 m_tiledMode(loop->getTiledMode()),
445 m_srcImage(loop->getSrcImage()) {
446 }
447
448 void processPixel(int x, int y) {
449 m_area.reset();
450 get_neighboring_pixels<GrayscaleTraits>(m_srcImage, x, y, 3, 3, 1, 1, m_tiledMode, m_area);
451
452 if (m_area.count > 0) {
453 m_area.v /= m_area.count;
454 m_area.a /= 9;
455 *m_dstAddress =
456 graya_blender_merge(*m_srcAddress,
457 graya(m_area.v, m_area.a),
458 m_opacity);
459 }
460 else {
461 *m_dstAddress = *m_srcAddress;
462 }
463 }
464
465private:
466 struct GetPixelsDelegate {
467 int count, v, a;
468
469 void reset() { count = v = a = 0; }
470
471 void operator()(GrayscaleTraits::pixel_t color)
472 {
473 if (graya_geta(color) > 0) {
474 v += graya_getv(color);
475 a += graya_geta(color);
476 ++count;
477 }
478 }
479 };
480
481 int m_opacity;
482 TiledMode m_tiledMode;
483 const Image* m_srcImage;
484 GetPixelsDelegate m_area;
485};
486
487template<>
488class BlurInkProcessing<IndexedTraits> : public DoubleInkProcessing<BlurInkProcessing<IndexedTraits>, IndexedTraits> {
489public:
490 BlurInkProcessing(ToolLoop* loop) :
491 m_palette(loop->getPalette()),
492 m_rgbmap(loop->getRgbMap()),
493 m_opacity(loop->getOpacity()),
494 m_tiledMode(loop->getTiledMode()),
495 m_srcImage(loop->getSrcImage()),
496 m_area(loop->getPalette(),
497 loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor()) {
498 }
499
500 void processPixel(int x, int y) {
501 m_area.reset();
502 get_neighboring_pixels<IndexedTraits>(m_srcImage, x, y, 3, 3, 1, 1, m_tiledMode, m_area);
503
504 if (m_area.count > 0) {
505 m_area.r /= m_area.count;
506 m_area.g /= m_area.count;
507 m_area.b /= m_area.count;
508 m_area.a /= 9;
509
510 const color_t c =
511 rgba_blender_merge(m_palette->getEntry(*m_srcAddress),
512 doc::rgba(m_area.r, m_area.g, m_area.b, m_area.a),
513 m_opacity);
514
515 *m_dstAddress = m_rgbmap->mapColor(c);
516 }
517 else {
518 *m_dstAddress = *m_srcAddress;
519 }
520 }
521
522private:
523 struct GetPixelsDelegate {
524 const Palette* pal;
525 int count, r, g, b, a;
526 color_t maskColor;
527
528 GetPixelsDelegate(const Palette* pal,
529 color_t maskColor)
530 : pal(pal), maskColor(maskColor) { }
531
532 void reset() { count = r = g = b = a = 0; }
533
534 void operator()(IndexedTraits::pixel_t color)
535 {
536 if (color == maskColor)
537 return;
538
539 uint32_t color32 = pal->getEntry(color);
540 if (rgba_geta(color32) > 0) {
541 r += rgba_getr(color32);
542 g += rgba_getg(color32);
543 b += rgba_getb(color32);
544 a += rgba_geta(color32);
545 ++count;
546 }
547 }
548 };
549
550 const Palette* m_palette;
551 const RgbMap* m_rgbmap;
552 int m_opacity;
553 TiledMode m_tiledMode;
554 const Image* m_srcImage;
555 GetPixelsDelegate m_area;
556};
557
558//////////////////////////////////////////////////////////////////////
559// Replace Ink
560//////////////////////////////////////////////////////////////////////
561
562template<typename ImageTraits>
563class ReplaceInkProcessing : public DoubleInkProcessing<ReplaceInkProcessing<ImageTraits>, ImageTraits> {
564public:
565 ReplaceInkProcessing(ToolLoop* loop) {
566 m_color1 = loop->getPrimaryColor();
567 m_color2 = loop->getSecondaryColor();
568 m_opacity = loop->getOpacity();
569 }
570
571 void processPixel(int x, int y) {
572 // Do nothing (it's specialized for each case)
573 }
574
575private:
576 color_t m_color1;
577 color_t m_color2;
578 int m_opacity;
579};
580
581template<>
582void ReplaceInkProcessing<RgbTraits>::processPixel(int x, int y) {
583 color_t src = (*m_srcAddress);
584
585 // Colors (m_srcAddress and m_color1) match if:
586 // * They are both completely transparent (alpha == 0)
587 // * Or they are not transparent and the RGB values are the same
588 if ((rgba_geta(src) == 0 && rgba_geta(m_color1) == 0) ||
589 (rgba_geta(src) > 0 && rgba_geta(m_color1) > 0 &&
590 ((src & rgba_rgb_mask) == (m_color1 & rgba_rgb_mask)))) {
591 *m_dstAddress = rgba_blender_merge(src, m_color2, m_opacity);
592 }
593}
594
595template<>
596void ReplaceInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
597 color_t src = (*m_srcAddress);
598
599 if ((graya_geta(src) == 0 && graya_geta(m_color1) == 0) ||
600 (graya_geta(src) > 0 && graya_geta(m_color1) > 0 &&
601 ((src & graya_v_mask) == (m_color1 & graya_v_mask)))) {
602 *m_dstAddress = graya_blender_merge(src, m_color2, m_opacity);
603 }
604}
605
606template<>
607class ReplaceInkProcessing<IndexedTraits> : public DoubleInkProcessing<ReplaceInkProcessing<IndexedTraits>, IndexedTraits> {
608public:
609 ReplaceInkProcessing(ToolLoop* loop) {
610 m_palette = loop->getPalette();
611 m_rgbmap = loop->getRgbMap();
612 m_color1 = loop->getPrimaryColor();
613 m_color2 = loop->getSecondaryColor();
614 m_opacity = loop->getOpacity();
615 if (m_opacity < 255)
616 m_color2 = m_palette->getEntry(m_color2);
617 }
618
619 void processPixel(int x, int y) {
620 if (*m_srcAddress == m_color1) {
621 if (m_opacity == 255)
622 *m_dstAddress = m_color2;
623 else {
624 color_t c = rgba_blender_normal(
625 m_palette->getEntry(*m_srcAddress), m_color2, m_opacity);
626
627 *m_dstAddress = m_rgbmap->mapColor(c);
628 }
629 }
630 }
631
632private:
633 const Palette* m_palette;
634 const RgbMap* m_rgbmap;
635 color_t m_color1;
636 color_t m_color2;
637 int m_opacity;
638};
639
640template<>
641void ReplaceInkProcessing<TilemapTraits>::processPixel(int x, int y) {
642 color_t c = *m_srcAddress;
643 *m_dstAddress = (c == m_color1 ? m_color2: c);
644}
645
646//////////////////////////////////////////////////////////////////////
647// Jumble Ink
648//////////////////////////////////////////////////////////////////////
649
650template<typename ImageTraits>
651class JumbleInkProcessing : public DoubleInkProcessing<JumbleInkProcessing<ImageTraits>, ImageTraits> {
652public:
653 JumbleInkProcessing(ToolLoop* loop) :
654 m_palette(loop->getPalette()),
655 m_rgbmap(loop->getRgbMap()),
656 m_speed(loop->getSpeed() / 4),
657 m_opacity(loop->getOpacity()),
658 m_tiledMode(loop->getTiledMode()),
659 m_srcImage(loop->getSrcImage()),
660 m_srcImageWidth(m_srcImage->width()),
661 m_srcImageHeight(m_srcImage->height()) {
662 }
663
664 void processPixel(int x, int y) {
665 // Do nothing (it's specialized for each case)
666 }
667
668private:
669 void pickColorFromArea(int x, int y) {
670 gfx::Point pt(x + (rand() % 3)-1 - m_speed.x,
671 y + (rand() % 3)-1 - m_speed.y);
672
673 pt = wrap_point(m_tiledMode,
674 gfx::Size(m_srcImageWidth,
675 m_srcImageHeight),
676 pt, false);
677
678 pt.x = std::clamp(pt.x, 0, m_srcImageWidth-1);
679 pt.y = std::clamp(pt.y, 0, m_srcImageHeight-1);
680
681 m_color = get_pixel(m_srcImage, pt.x, pt.y);
682 }
683
684 const Palette* m_palette;
685 const RgbMap* m_rgbmap;
686 Point m_speed;
687 int m_opacity;
688 TiledMode m_tiledMode;
689 const Image* m_srcImage;
690 int m_srcImageWidth;
691 int m_srcImageHeight;
692 color_t m_color;
693};
694
695template<>
696void JumbleInkProcessing<RgbTraits>::processPixel(int x, int y)
697{
698 pickColorFromArea(x, y);
699 *m_dstAddress = rgba_blender_merge(*m_srcAddress, m_color, m_opacity);
700}
701
702template<>
703void JumbleInkProcessing<GrayscaleTraits>::processPixel(int x, int y)
704{
705 pickColorFromArea(x, y);
706 *m_dstAddress = graya_blender_merge(*m_srcAddress, m_color, m_opacity);
707}
708
709template<>
710void JumbleInkProcessing<IndexedTraits>::processPixel(int x, int y)
711{
712 pickColorFromArea(x, y);
713
714 color_t tc = (m_color != 0 ? m_palette->getEntry(m_color): 0);
715 color_t c = rgba_blender_merge(*m_srcAddress != 0 ?
716 m_palette->getEntry(*m_srcAddress): 0,
717 tc, m_opacity);
718
719 if (rgba_geta(c) >= 128)
720 *m_dstAddress = m_rgbmap->mapColor(c);
721 else
722 *m_dstAddress = 0;
723}
724
725//////////////////////////////////////////////////////////////////////
726// Shading Ink Helper used by ShadingInkProcessing and
727// BrushShadingInkProcessing (it does only the shading of one pixel
728// using some auxiliary structures)
729//////////////////////////////////////////////////////////////////////
730
731template<typename ImageTraits>
732class PixelShadingInkHelper {
733public:
734 PixelShadingInkHelper(ToolLoop* loop) {
735 static_assert(false && sizeof(ImageTraits), "Use specialized cases");
736 }
737};
738
739template<>
740class PixelShadingInkHelper<RgbTraits> {
741public:
742 using pixel_t = RgbTraits::pixel_t;
743
744 PixelShadingInkHelper(ToolLoop* loop)
745 : m_shadePalette(0, 1)
746 , m_left(loop->getMouseButton() == ToolLoop::Left)
747 {
748 const Shade shade = loop->getShade();
749 m_shadePalette.resize(shade.size());
750 int i = 0;
751 for (app::Color color : shade) {
752 m_shadePalette.setEntry(
753 i++, color_utils::color_for_layer(color, loop->getLayer()));
754 }
755 }
756
757 int findIndex(uint8_t r, uint8_t g, uint8_t b, uint8_t a) const {
758 return m_shadePalette.findExactMatch(r, g, b, a, -1);
759 }
760
761 pixel_t operator()(const pixel_t src) const {
762 int i = findIndex(rgba_getr(src),
763 rgba_getg(src),
764 rgba_getb(src),
765 rgba_geta(src));
766 if (i < 0)
767 return src;
768
769 if (m_left) {
770 if (i > 0)
771 --i;
772 }
773 else {
774 if (i < m_shadePalette.size()-1)
775 ++i;
776 }
777 return m_shadePalette.getEntry(i);
778 }
779
780private:
781 Palette m_shadePalette;
782 bool m_left;
783};
784
785template<>
786class PixelShadingInkHelper<GrayscaleTraits> {
787public:
788 using pixel_t = GrayscaleTraits::pixel_t;
789
790 PixelShadingInkHelper(ToolLoop* loop)
791 : m_shadePalette(0, 1)
792 , m_left(loop->getMouseButton() == ToolLoop::Left)
793 {
794 Shade shade = loop->getShade();
795 m_shadePalette.resize(shade.size());
796
797 // As the colors are going to a palette, we need RGB colors
798 // (instead of Grayscale)
799 const ColorTarget target(
800 (loop->getLayer()->isBackground() ? ColorTarget::BackgroundLayer:
801 ColorTarget::TransparentLayer),
802 IMAGE_RGB, 0);
803
804 int i = 0;
805 for (app::Color color : shade) {
806 m_shadePalette.setEntry(
807 i++, color_utils::color_for_target(color, target));
808 }
809 }
810
811 int findIndex(uint8_t r, uint8_t g, uint8_t b, uint8_t a) const {
812 return m_shadePalette.findExactMatch(r, g, b, a, -1);
813 }
814
815 pixel_t operator()(const pixel_t src) const {
816 int i = findIndex(graya_getv(src),
817 graya_getv(src),
818 graya_getv(src),
819 graya_geta(src));
820 if (i < 0)
821 return src;
822
823 if (m_left) {
824 if (i > 0)
825 --i;
826 }
827 else {
828 if (i < m_shadePalette.size()-1)
829 ++i;
830 }
831
832 color_t rgba = m_shadePalette.getEntry(i);
833 return graya(rgba_getr(rgba), rgba_geta(rgba));
834 }
835
836private:
837 Palette m_shadePalette;
838 bool m_left;
839};
840
841template<>
842class PixelShadingInkHelper<IndexedTraits> {
843public:
844 using pixel_t = IndexedTraits::pixel_t;
845
846 PixelShadingInkHelper(ToolLoop* loop) :
847 m_palette(loop->getPalette()),
848 m_remap(loop->getShadingRemap()),
849 m_left(loop->getMouseButton() == ToolLoop::Left) {
850 }
851
852 int findIndex(uint8_t r, uint8_t g, uint8_t b, uint8_t a) const {
853 return m_palette->findExactMatch(r, g, b, a, -1);
854 }
855
856 pixel_t operator()(pixel_t i) const {
857 if (m_remap) {
858 i = (*m_remap)[i];
859 }
860 else {
861 if (m_left) {
862 if (i > 0)
863 --i;
864 }
865 else {
866 if (i < m_palette->size()-1)
867 ++i;
868 }
869 }
870 return i;
871 }
872
873private:
874 const Palette* m_palette;
875 const Remap* m_remap;
876 bool m_left;
877};
878
879//////////////////////////////////////////////////////////////////////
880// Shading Ink
881//////////////////////////////////////////////////////////////////////
882
883template<typename ImageTraits>
884class ShadingInkProcessing : public DoubleInkProcessing<ShadingInkProcessing<ImageTraits>, ImageTraits> {
885public:
886 using Base = DoubleInkProcessing<ShadingInkProcessing<ImageTraits>, ImageTraits>;
887
888 ShadingInkProcessing(ToolLoop* loop) : m_shading(loop) { }
889 void processPixel(int x, int y) {
890 *Base::m_dstAddress = m_shading(*Base::m_srcAddress);
891 }
892private:
893 PixelShadingInkHelper<ImageTraits> m_shading;
894};
895
896//////////////////////////////////////////////////////////////////////
897// Gradient Ink
898//////////////////////////////////////////////////////////////////////
899
900static ImageBufferPtr tmpGradientBuffer; // TODO non-thread safe
901
902class GradientRenderer {
903public:
904 GradientRenderer(ToolLoop* loop)
905 : m_tiledMode(loop->getTiledMode())
906 {
907 if (!tmpGradientBuffer)
908 tmpGradientBuffer.reset(new ImageBuffer(1));
909
910 m_tmpImage.reset(
911 Image::create(IMAGE_RGB,
912 loop->getDstImage()->width(),
913 loop->getDstImage()->height(),
914 tmpGradientBuffer));
915 m_tmpImage->clear(0);
916 }
917
918 void renderRgbaGradient(ToolLoop* loop, Strokes& strokes,
919 // RGBA colors
920 color_t c0, color_t c1) {
921 if (strokes.empty() || strokes[0].size() < 2) {
922 m_tmpImage->clear(0);
923 return;
924 }
925
926 const gfx::Point u = strokes[0].firstPoint().toPoint();
927 const gfx::Point v = strokes[0].lastPoint().toPoint();
928
929 // The image position for the gradient depends on the first user
930 // click. The gradient depends on the first clicked tile.
931 gfx::Point imgPos(0, 0);
932 if (int(m_tiledMode) & int(TiledMode::X_AXIS)) {
933 const int w = loop->sprite()->width();
934 imgPos.x = u.x / w;
935 imgPos.x *= w;
936 }
937 if (int(m_tiledMode) & int(TiledMode::Y_AXIS)) {
938 const int h = loop->sprite()->height();
939 imgPos.y = u.y / h;
940 imgPos.y *= h;
941 }
942
943 render::render_rgba_gradient(
944 m_tmpImage.get(), imgPos, u, v, c0, c1,
945 loop->getDitheringMatrix(),
946 loop->getGradientType());
947 }
948
949protected:
950 ImageRef m_tmpImage;
951 RgbTraits::address_t m_tmpAddress;
952 TiledMode m_tiledMode;
953};
954
955template<typename ImageTraits>
956class GradientInkProcessing : public GradientRenderer,
957 public DoubleInkProcessing<GradientInkProcessing<ImageTraits>, ImageTraits> {
958public:
959 typedef DoubleInkProcessing<GradientInkProcessing<ImageTraits>, ImageTraits> base;
960
961 GradientInkProcessing(ToolLoop* loop)
962 : GradientRenderer(loop)
963 , m_opacity(loop->getOpacity())
964 , m_palette(loop->getPalette())
965 , m_rgbmap(loop->getRgbMap())
966 , m_maskIndex(loop->getLayer()->isBackground() ? -1: loop->sprite()->transparentColor())
967 {
968 }
969
970 void processScanline(int x1, int y, int x2, ToolLoop* loop) override {
971 m_tmpAddress = (RgbTraits::address_t)m_tmpImage->getPixelAddress(x1, y);
972 base::processScanline(x1, y, x2, loop);
973 }
974
975 void prepareForStrokes(ToolLoop* loop, Strokes& strokes) override {
976 // Do nothing
977 }
978
979 void processPixel(int x, int y) {
980 // Do nothing (it's specialized for each case)
981 }
982
983private:
984 const int m_opacity;
985 const Palette* m_palette;
986 const RgbMap* m_rgbmap;
987 const int m_maskIndex;
988};
989
990template<>
991void GradientInkProcessing<RgbTraits>::prepareForStrokes(ToolLoop* loop, Strokes& strokes)
992{
993 color_t c0 = loop->getPrimaryColor();
994 color_t c1 = loop->getSecondaryColor();
995
996 renderRgbaGradient(loop, strokes, c0, c1);
997}
998
999template<>
1000void GradientInkProcessing<RgbTraits>::processPixel(int x, int y)
1001{
1002 *m_dstAddress = rgba_blender_normal(*m_srcAddress,
1003 *m_tmpAddress,
1004 m_opacity);
1005 ++m_tmpAddress;
1006}
1007
1008template<>
1009void GradientInkProcessing<GrayscaleTraits>::prepareForStrokes(ToolLoop* loop, Strokes& strokes)
1010{
1011 color_t c0 = loop->getPrimaryColor();
1012 color_t c1 = loop->getSecondaryColor();
1013 int v0 = int(doc::graya_getv(c0));
1014 int a0 = int(doc::graya_geta(c0));
1015 int v1 = int(doc::graya_getv(c1));
1016 int a1 = int(doc::graya_geta(c1));
1017 c0 = doc::rgba(v0, v0, v0, a0);
1018 c1 = doc::rgba(v1, v1, v1, a1);
1019
1020 renderRgbaGradient(loop, strokes, c0, c1);
1021}
1022
1023template<>
1024void GradientInkProcessing<GrayscaleTraits>::processPixel(int x, int y)
1025{
1026 doc::color_t c = *m_tmpAddress;
1027 int a = doc::rgba_geta(c);
1028 int v = doc::rgba_getr(c);
1029
1030 *m_dstAddress = graya_blender_normal(*m_srcAddress,
1031 doc::graya(v, a),
1032 m_opacity);
1033 ++m_tmpAddress;
1034}
1035
1036template<>
1037void GradientInkProcessing<IndexedTraits>::prepareForStrokes(ToolLoop* loop, Strokes& strokes)
1038{
1039 color_t c0 = m_palette->getEntry(loop->getPrimaryColor());
1040 color_t c1 = m_palette->getEntry(loop->getSecondaryColor());
1041
1042 renderRgbaGradient(loop, strokes, c0, c1);
1043}
1044
1045template<>
1046void GradientInkProcessing<IndexedTraits>::processPixel(int x, int y)
1047{
1048 doc::color_t c = *m_tmpAddress;
1049 doc::color_t c0 = *m_srcAddress;
1050 if (int(c0) == m_maskIndex)
1051 c0 = m_palette->getEntry(c0) & rgba_rgb_mask; // Alpha = 0
1052 else
1053 c0 = m_palette->getEntry(c0);
1054 c = rgba_blender_normal(c0, c, m_opacity);
1055
1056 *m_dstAddress = m_rgbmap->mapColor(c);
1057
1058 ++m_tmpAddress;
1059}
1060
1061
1062//////////////////////////////////////////////////////////////////////
1063// Xor Ink
1064//////////////////////////////////////////////////////////////////////
1065
1066template<typename ImageTraits>
1067class XorInkProcessing : public DoubleInkProcessing<XorInkProcessing<ImageTraits>, ImageTraits> {
1068public:
1069 XorInkProcessing(ToolLoop* loop) { }
1070 void processPixel(int x, int y) { }
1071};
1072
1073template<>
1074void XorInkProcessing<RgbTraits>::processPixel(int x, int y) {
1075 *m_dstAddress = rgba_blender_neg_bw(*m_srcAddress, 0, 255);
1076}
1077
1078template<>
1079void XorInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
1080 *m_dstAddress = graya_blender_neg_bw(*m_srcAddress, 0, 255);
1081}
1082
1083template<>
1084class XorInkProcessing<IndexedTraits> : public DoubleInkProcessing<XorInkProcessing<IndexedTraits>, IndexedTraits> {
1085public:
1086 XorInkProcessing(ToolLoop* loop) :
1087 m_palette(loop->getPalette()),
1088 m_rgbmap(loop->getRgbMap()) {
1089 }
1090
1091 void processPixel(int x, int y) {
1092 color_t c = rgba_blender_neg_bw(m_palette->getEntry(*m_srcAddress), 0, 255);
1093 *m_dstAddress = m_rgbmap->mapColor(c);
1094 }
1095
1096private:
1097 const Palette* m_palette;
1098 const RgbMap* m_rgbmap;
1099};
1100
1101//////////////////////////////////////////////////////////////////////
1102// Brush Ink - Base
1103//////////////////////////////////////////////////////////////////////
1104
1105// TODO In all cases where we get the brush index and use that index
1106// in m_palette->getEntry(index), the color is converted to the
1107// sprite palette or to the grayscale palette (for grayscale
1108// sprites), we might want to save the original palette in the
1109// brush to use that one in these cases (not sure if this does
1110// applies when we select a new foreground/background color in
1111// the color bar and the brush color changes, or if this should
1112// be a new optional flag/parameter to save on each brush)
1113template<typename ImageTraits>
1114class BrushInkProcessingBase : public DoubleInkProcessing<BrushInkProcessingBase<ImageTraits>, ImageTraits> {
1115public:
1116 BrushInkProcessingBase(ToolLoop* loop) {
1117 m_fgColor = loop->getPrimaryColor();
1118 m_bgColor = loop->getSecondaryColor();
1119 m_palette = loop->getPalette();
1120 m_brush = loop->getBrush();
1121 m_brushImage = (m_brush->patternImage() ? m_brush->patternImage():
1122 m_brush->image());
1123 m_brushMask = m_brush->maskBitmap();
1124 m_patternAlign = m_brush->pattern();
1125 m_opacity = loop->getOpacity();
1126 m_width = m_brush->bounds().w;
1127 m_height = m_brush->bounds().h;
1128 m_u = (m_brush->patternOrigin().x - loop->getCelOrigin().x) % m_width;
1129 m_v = (m_brush->patternOrigin().y - loop->getCelOrigin().y) % m_height;
1130
1131 if (loop->sprite()->colorMode() == ColorMode::INDEXED) {
1132 if (loop->getLayer()->isTransparent())
1133 m_transparentColor = loop->sprite()->transparentColor();
1134 else
1135 m_transparentColor = -1;
1136 }
1137 else
1138 m_transparentColor = 0;
1139 }
1140
1141 void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
1142 if ((m_patternAlign == BrushPattern::ALIGNED_TO_DST && firstPoint) ||
1143 (m_patternAlign == BrushPattern::PAINT_BRUSH)) {
1144 m_u = ((m_brush->patternOrigin().x % loop->sprite()->width()) - loop->getCelOrigin().x) % m_width;
1145 m_v = ((m_brush->patternOrigin().y % loop->sprite()->height()) - loop->getCelOrigin().y) % m_height;
1146 }
1147 }
1148
1149 void prepareVForPointShape(ToolLoop* loop, int y) override {
1150 if (m_patternAlign == doc::BrushPattern::ALIGNED_TO_SRC) {
1151 m_v = (m_brush->patternOrigin().y - loop->getCelOrigin().y) % m_height;
1152 if (m_v < 0) m_v += m_height;
1153 }
1154 else {
1155 int spriteH = loop->sprite()->height();
1156 if (y/spriteH > 0)
1157 // 'y' is outside of the center tile.
1158 m_v = (m_brush->patternOrigin().y + m_height - (y/spriteH) * spriteH) % m_height;
1159 else
1160 // 'y' is inside of the center tile.
1161 m_v = ((m_brush->patternOrigin().y % spriteH) - loop->getCelOrigin().y) % m_height;
1162 }
1163 }
1164
1165 void prepareUForPointShapeWholeScanline(ToolLoop* loop, int x1) override {
1166 if (m_patternAlign == doc::BrushPattern::ALIGNED_TO_SRC) {
1167 m_u = (m_brush->patternOrigin().x - loop->getCelOrigin().x) % m_width;
1168 if (m_u < 0) m_u += m_height;
1169 }
1170 else {
1171 m_u = ((m_brush->patternOrigin().x % loop->sprite()->width()) - loop->getCelOrigin().x ) % m_width;
1172 if (x1/loop->sprite()->width() > 0)
1173 m_u = (m_brush->patternOrigin().x + m_width - (x1/loop->sprite()->width()) * loop->sprite()->width()) % m_width;
1174 }
1175 }
1176
1177 void prepareUForPointShapeSlicedScanline(ToolLoop* loop, bool leftSlice, int x1) override {
1178 if (m_patternAlign == doc::BrushPattern::ALIGNED_TO_SRC) {
1179 m_u = (m_brush->patternOrigin().x - loop->getCelOrigin().x) % m_width;
1180 if (m_u < 0) m_u += m_height;
1181 return;
1182 }
1183 else {
1184 if (leftSlice)
1185 m_u = ((m_brush->patternOrigin().x % loop->sprite()->width()) - loop->getCelOrigin().x ) % m_width;
1186 else
1187 m_u = (m_brush->patternOrigin().x + m_width - (x1/loop->sprite()->width() + 1) * loop->sprite()->width()) % m_width;
1188 }
1189 }
1190
1191 bool preProcessPixel(int x, int y, color_t* result) {
1192 // Do nothing
1193 return true;
1194 }
1195
1196 // TODO Remove this virtual function in some way. At the moment we
1197 // need it because InkProcessing expects that its Derived
1198 // template parameter has a processPixel() member function.
1199 virtual void processPixel(int x, int y) {
1200 // Do nothing
1201 }
1202
1203protected:
1204 bool alignPixelPoint(int& x0, int& y0) {
1205 int x = (x0 - m_u) % m_width;
1206 int y = (y0 - m_v) % m_height;
1207 if (x < 0) x = m_width - ((-x) % m_width);
1208 if (y < 0) y = m_height - ((-y) % m_height);
1209
1210 if (m_brushMask && !get_pixel_fast<BitmapTraits>(m_brushMask, x, y))
1211 return false;
1212
1213 if (m_brush->patternImage()) {
1214 const int w = m_brush->patternImage()->width();
1215 const int h = m_brush->patternImage()->height();
1216 x = x0 % w;
1217 y = y0 % h;
1218 if (x < 0) x = w - ((-x) % w);
1219 if (y < 0) y = h - ((-y) % h);
1220 }
1221
1222 x0 = x;
1223 y0 = y;
1224 return true;
1225 }
1226
1227 color_t m_fgColor;
1228 color_t m_bgColor;
1229 const Palette* m_palette;
1230 const Brush* m_brush;
1231 const Image* m_brushImage;
1232 const Image* m_brushMask;
1233 BrushPattern m_patternAlign;
1234 int m_opacity;
1235 int m_u, m_v, m_width, m_height;
1236 // When we have a image brush from an INDEXED sprite, we need to know
1237 // which is the background color in order to translate to transparent color
1238 // in a RGBA sprite.
1239 color_t m_transparentColor;
1240};
1241
1242template<>
1243bool BrushInkProcessingBase<RgbTraits>::preProcessPixel(int x, int y, color_t* result) {
1244 if (!alignPixelPoint(x, y))
1245 return false;
1246
1247 color_t c;
1248 switch (m_brushImage->pixelFormat()) {
1249 case IMAGE_RGB: {
1250 c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
1251
1252 // We blend the previous image brush pixel with a pixel from the
1253 // image preview (*m_dstAddress). Yes, dstImage, in that way we
1254 // can overlap image brush self printed areas (auto compose
1255 // colors). Doing this, we avoid eraser action of the pixels
1256 // with alpha <255 in the image brush.
1257 c = rgba_blender_normal(*m_dstAddress, c, m_opacity);
1258 break;
1259 }
1260 case IMAGE_INDEXED: {
1261 c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
1262 if (m_transparentColor == c)
1263 c = 0;
1264 else
1265 c = m_palette->getEntry(c);
1266 c = rgba_blender_normal(*m_dstAddress, c, m_opacity);
1267 break;
1268 }
1269 case IMAGE_GRAYSCALE: {
1270 c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
1271 c = doc::rgba(graya_getv(c), graya_getv(c), graya_getv(c), graya_geta(c));
1272 c = rgba_blender_normal(*m_dstAddress, c, m_opacity);
1273 break;
1274 }
1275 case IMAGE_BITMAP: {
1276 // TODO In which circuntance is possible this case?
1277 c = get_pixel_fast<BitmapTraits>(m_brushImage, x, y);
1278 c = c ? m_fgColor: m_bgColor;
1279 break;
1280 }
1281 default:
1282 ASSERT(false);
1283 return false;
1284 }
1285 *result = c;
1286 return true;
1287}
1288
1289template<>
1290bool BrushInkProcessingBase<GrayscaleTraits>::preProcessPixel(int x, int y, color_t* result) {
1291 if (!alignPixelPoint(x, y))
1292 return false;
1293
1294 color_t c;
1295 switch (m_brushImage->pixelFormat()) {
1296 case IMAGE_RGB: {
1297 c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
1298 c = graya(rgba_luma(c), rgba_geta(c));
1299 c = graya_blender_normal(*m_dstAddress, c, m_opacity);
1300 break;
1301 }
1302 case IMAGE_INDEXED: {
1303 c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
1304 if (m_transparentColor == c)
1305 c = 0;
1306 else
1307 c = m_palette->getEntry(c);
1308 c = graya(rgba_luma(c), rgba_geta(c));
1309 c = graya_blender_normal(*m_dstAddress, c, m_opacity);
1310 break;
1311 }
1312 case IMAGE_GRAYSCALE: {
1313 c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
1314 c = graya_blender_normal(*m_dstAddress, c, m_opacity);
1315 break;
1316 }
1317 case IMAGE_BITMAP: {
1318 // TODO In which circuntance is possible this case?
1319 c = get_pixel_fast<BitmapTraits>(m_brushImage, x, y);
1320 c = c ? m_fgColor: m_bgColor;
1321 break;
1322 }
1323 default:
1324 ASSERT(false);
1325 return false;
1326 }
1327 *result = c;
1328 return true;
1329}
1330
1331template<>
1332bool BrushInkProcessingBase<IndexedTraits>::preProcessPixel(int x, int y, color_t* result) {
1333 if (!alignPixelPoint(x, y))
1334 return false;
1335
1336 color_t c;
1337 switch (m_brushImage->pixelFormat()) {
1338 case IMAGE_RGB: {
1339 c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
1340 color_t d = m_palette->getEntry(*m_dstAddress);
1341 c = rgba_blender_normal(d, c, m_opacity);
1342 c = m_palette->findBestfit(rgba_getr(c),
1343 rgba_getg(c),
1344 rgba_getb(c),
1345 rgba_geta(c), m_transparentColor);
1346 break;
1347 }
1348 case IMAGE_INDEXED: {
1349 c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
1350 if (c == m_transparentColor)
1351 return false;
1352
1353 color_t f = m_palette->getEntry(c);
1354
1355 // Keep original index in special opaque case
1356 if (rgba_geta(f) == 255 && m_opacity == 255)
1357 break;
1358
1359 color_t b = m_palette->getEntry(*m_dstAddress);
1360 c = rgba_blender_normal(b, f, m_opacity);
1361 c = m_palette->findBestfit(rgba_getr(c),
1362 rgba_getg(c),
1363 rgba_getb(c),
1364 rgba_geta(c), m_transparentColor);
1365 break;
1366 }
1367 case IMAGE_GRAYSCALE: {
1368 c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
1369 color_t b = m_palette->getEntry(*m_dstAddress);
1370 b = graya(rgba_luma(b),
1371 rgba_geta(b));
1372 c = graya_blender_normal(b, c, m_opacity);
1373 c = m_palette->findBestfit(graya_getv(c),
1374 graya_getv(c),
1375 graya_getv(c),
1376 graya_geta(c), m_transparentColor);
1377 break;
1378 }
1379 case IMAGE_BITMAP: {
1380 // TODO In which circuntance is possible this case?
1381 c = get_pixel_fast<BitmapTraits>(m_brushImage, x, y);
1382 c = c ? m_fgColor: m_bgColor;
1383 break;
1384 }
1385 default:
1386 ASSERT(false);
1387 return false;
1388 }
1389 if (c != m_transparentColor) {
1390 *result = c;
1391 return true;
1392 }
1393 return false;
1394}
1395
1396//////////////////////////////////////////////////////////////////////
1397// Brush Ink - Simple ink type
1398//////////////////////////////////////////////////////////////////////
1399
1400template<typename ImageTraits>
1401class BrushSimpleInkProcessing : public BrushInkProcessingBase<ImageTraits> {
1402public:
1403 BrushSimpleInkProcessing(ToolLoop* loop) : BrushInkProcessingBase<ImageTraits>(loop) {
1404 }
1405
1406 void processPixel(int x, int y) override {
1407 // Do nothing
1408 }
1409};
1410
1411template<>
1412void BrushSimpleInkProcessing<RgbTraits>::processPixel(int x, int y) {
1413 color_t c;
1414 if (preProcessPixel(x, y, &c))
1415 *m_dstAddress = c;
1416}
1417
1418template<>
1419void BrushSimpleInkProcessing<IndexedTraits>::processPixel(int x, int y) {
1420 color_t c;
1421 if (preProcessPixel(x, y, &c))
1422 *m_dstAddress = c;
1423}
1424
1425template<>
1426void BrushSimpleInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
1427 color_t c;
1428 if (preProcessPixel(x, y, &c))
1429 *m_dstAddress = c;
1430}
1431
1432//////////////////////////////////////////////////////////////////////
1433// Brush Ink - Lock Alpha ink type
1434//////////////////////////////////////////////////////////////////////
1435
1436template<typename ImageTraits>
1437class BrushLockAlphaInkProcessing : public BrushInkProcessingBase<ImageTraits> {
1438public:
1439 BrushLockAlphaInkProcessing(ToolLoop* loop) : BrushInkProcessingBase<ImageTraits>(loop) {
1440 }
1441
1442 void processPixel(int x, int y) override {
1443 //Do nothing
1444 }
1445};
1446
1447template<>
1448void BrushLockAlphaInkProcessing<RgbTraits>::processPixel(int x, int y) {
1449 color_t c;
1450 if (preProcessPixel(x, y, &c))
1451 *m_dstAddress = doc::rgba(rgba_getr(c), rgba_getg(c), rgba_getb(c), rgba_geta(*m_srcAddress));
1452}
1453
1454template<>
1455void BrushLockAlphaInkProcessing<IndexedTraits>::processPixel(int x, int y) {
1456 if (*m_srcAddress != m_transparentColor) {
1457 color_t c;
1458 if (preProcessPixel(x, y, &c))
1459 *m_dstAddress = c;
1460 }
1461}
1462
1463template<>
1464void BrushLockAlphaInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
1465 color_t c;
1466 if (preProcessPixel(x, y, &c))
1467 *m_dstAddress = graya(graya_getv(c), graya_geta(*m_srcAddress));
1468}
1469
1470//////////////////////////////////////////////////////////////////////
1471// Brush Ink - Eraser Tool
1472//////////////////////////////////////////////////////////////////////
1473
1474template<typename ImageTraits>
1475class BrushEraserInkProcessing : public BrushInkProcessingBase<ImageTraits> {
1476public:
1477 BrushEraserInkProcessing(ToolLoop* loop) : BrushInkProcessingBase<ImageTraits>(loop) {
1478 }
1479
1480 void processPixel(int x, int y) override {
1481 // Do nothing
1482 }
1483};
1484
1485template<>
1486void BrushEraserInkProcessing<RgbTraits>::processPixel(int x, int y) {
1487 if (!alignPixelPoint(x, y))
1488 return;
1489
1490 color_t c;
1491 switch (m_brushImage->pixelFormat()) {
1492 case IMAGE_RGB: {
1493 c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
1494 int t;
1495 c = doc::rgba(rgba_getr(*m_srcAddress),
1496 rgba_getg(*m_srcAddress),
1497 rgba_getb(*m_srcAddress),
1498 MUL_UN8(rgba_geta(*m_dstAddress), 255 - rgba_geta(c), t));
1499 break;
1500 }
1501 case IMAGE_INDEXED: {
1502 c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
1503 if (m_transparentColor == c)
1504 c = 0;
1505 else
1506 c = m_palette->getEntry(c);
1507 int t;
1508 c = doc::rgba(rgba_getr(*m_srcAddress),
1509 rgba_getg(*m_srcAddress),
1510 rgba_getb(*m_srcAddress),
1511 MUL_UN8(rgba_geta(*m_dstAddress), 255 - rgba_geta(c), t));
1512 break;
1513 }
1514 case IMAGE_GRAYSCALE: {
1515 c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
1516 int t;
1517 c = doc::rgba(rgba_getr(*m_srcAddress),
1518 rgba_getg(*m_srcAddress),
1519 rgba_getb(*m_srcAddress),
1520 MUL_UN8(rgba_geta(*m_dstAddress), 255 - graya_geta(c), t));
1521 break;
1522 }
1523 case IMAGE_BITMAP: {
1524 // TODO In which circuntance is possible this case?
1525 c = get_pixel_fast<BitmapTraits>(m_brushImage, x, y);
1526 c = c ? m_bgColor : *m_srcAddress;
1527 break;
1528 }
1529 default:
1530 ASSERT(false);
1531 return;
1532 }
1533 *m_dstAddress = c;;
1534}
1535
1536template<>
1537void BrushEraserInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
1538 if (!alignPixelPoint(x, y))
1539 return;
1540
1541 color_t c;
1542 switch (m_brushImage->pixelFormat()) {
1543 case IMAGE_RGB: {
1544 c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
1545 int t;
1546 c = graya(graya_getv(*m_srcAddress),
1547 MUL_UN8(graya_geta(*m_dstAddress), 255 - rgba_geta(c), t));
1548 break;
1549 }
1550 case IMAGE_INDEXED: {
1551 c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
1552 if (m_transparentColor == c)
1553 c = 0;
1554 else {
1555 c = m_palette->getEntry(c);
1556 }
1557 int t;
1558 c = graya(graya_getv(*m_srcAddress),
1559 MUL_UN8(graya_geta(*m_dstAddress), 255 - rgba_geta(c), t));
1560 break;
1561 }
1562 case IMAGE_GRAYSCALE: {
1563 c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
1564 int t;
1565 c = graya(graya_getv(*m_srcAddress),
1566 MUL_UN8(graya_geta(*m_dstAddress), 255 - graya_geta(c), t));
1567 break;
1568 }
1569 case IMAGE_BITMAP: {
1570 // TODO In which circuntance is possible this case?
1571 c = get_pixel_fast<BitmapTraits>(m_brushImage, x, y);
1572 c = c ? m_bgColor : *m_srcAddress;
1573 break;
1574 }
1575 default:
1576 ASSERT(false);
1577 return;
1578 }
1579 *m_dstAddress = c;
1580}
1581
1582template<>
1583void BrushEraserInkProcessing<IndexedTraits>::processPixel(int x, int y) {
1584 if (!alignPixelPoint(x, y))
1585 return;
1586
1587 color_t c;
1588 switch (m_brushImage->pixelFormat()) {
1589 case IMAGE_RGB: {
1590 c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
1591 c = m_palette->findBestfit(rgba_getr(c),
1592 rgba_getg(c),
1593 rgba_getb(c),
1594 rgba_geta(c), m_transparentColor);
1595 break;
1596 }
1597 case IMAGE_INDEXED: {
1598 c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
1599 break;
1600 }
1601 case IMAGE_GRAYSCALE: {
1602 c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
1603 c = m_palette->findBestfit(graya_getv(c),
1604 graya_getv(c),
1605 graya_getv(c),
1606 graya_geta(c), m_transparentColor);
1607 break;
1608 }
1609 case IMAGE_BITMAP: {
1610 // TODO In which circuntance is possible this case?
1611 c = get_pixel_fast<BitmapTraits>(m_brushImage, x, y);
1612 c = c ? m_fgColor: m_bgColor;
1613 break;
1614 }
1615 default:
1616 ASSERT(false);
1617 return;
1618 }
1619 if (c != m_transparentColor) {
1620 *m_dstAddress = m_transparentColor;
1621 }
1622}
1623
1624//////////////////////////////////////////////////////////////////////
1625// Brush Ink - Shading ink type
1626//////////////////////////////////////////////////////////////////////
1627
1628template<typename ImageTraits>
1629class BrushShadingInkProcessing : public BrushInkProcessingBase<ImageTraits> {
1630public:
1631 using pixel_t = typename ImageTraits::pixel_t;
1632
1633 BrushShadingInkProcessing(ToolLoop* loop)
1634 : BrushInkProcessingBase<ImageTraits>(loop)
1635 , m_shading(loop) {
1636 }
1637
1638 void processPixel(int x, int y) override {
1639 // Do nothing
1640 }
1641
1642private:
1643 PixelShadingInkHelper<ImageTraits> m_shading;
1644};
1645
1646template <>
1647void BrushShadingInkProcessing<RgbTraits>::processPixel(int x, int y) {
1648 if (!alignPixelPoint(x, y))
1649 return;
1650
1651 switch (m_brushImage->pixelFormat()) {
1652 case IMAGE_RGB: {
1653 auto c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
1654 int iBrush = m_shading.findIndex(rgba_getr(c),
1655 rgba_getg(c),
1656 rgba_getb(c),
1657 rgba_geta(c));
1658 if (rgba_geta(c) != 0 && iBrush >= 0)
1659 *m_dstAddress = m_shading(*m_srcAddress);
1660 break;
1661 }
1662 case IMAGE_INDEXED: {
1663 auto c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
1664 if (m_transparentColor != c)
1665 *m_dstAddress = m_shading(*m_srcAddress);
1666 break;
1667 }
1668 case IMAGE_GRAYSCALE: {
1669 auto c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
1670 if (graya_geta(c) != 0)
1671 *m_dstAddress = m_shading(*m_srcAddress);
1672 break;
1673 }
1674 default:
1675 ASSERT(false);
1676 return;
1677 }
1678};
1679
1680template <>
1681void BrushShadingInkProcessing<IndexedTraits>::processPixel(int x, int y) {
1682 if (!alignPixelPoint(x, y))
1683 return;
1684
1685 switch (m_brushImage->pixelFormat()) {
1686 case IMAGE_RGB: {
1687 auto c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
1688 int iBrush = m_shading.findIndex(rgba_getr(c),
1689 rgba_getg(c),
1690 rgba_getb(c),
1691 rgba_geta(c));
1692 if (rgba_geta(c) != 0 && iBrush >= 0)
1693 *m_dstAddress = m_shading(*m_srcAddress);
1694 break;
1695 }
1696 case IMAGE_INDEXED: {
1697 auto c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
1698 if (m_transparentColor != c)
1699 *m_dstAddress = m_shading(*m_srcAddress);
1700 break;
1701 }
1702 case IMAGE_GRAYSCALE: {
1703 auto c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
1704 if (graya_geta(c) != 0)
1705 *m_dstAddress = m_shading(*m_srcAddress);
1706 break;
1707 }
1708 default:
1709 ASSERT(false);
1710 return;
1711 }
1712};
1713
1714template <>
1715void BrushShadingInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
1716 if (!alignPixelPoint(x, y))
1717 return;
1718
1719 switch (m_brushImage->pixelFormat()) {
1720 case IMAGE_RGB: {
1721 auto c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
1722 int iBrush = m_shading.findIndex(rgba_getr(c),
1723 rgba_getg(c),
1724 rgba_getb(c),
1725 rgba_geta(c));
1726 if (rgba_geta(c) != 0 && iBrush >= 0)
1727 *m_dstAddress = m_shading(*m_srcAddress);
1728 break;
1729 }
1730 case IMAGE_INDEXED: {
1731 auto c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
1732 if (m_transparentColor != c)
1733 *m_dstAddress = m_shading(*m_srcAddress);
1734 break;
1735 }
1736 case IMAGE_GRAYSCALE: {
1737 auto c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
1738 if (graya_geta(c) != 0)
1739 *m_dstAddress = m_shading(*m_srcAddress);
1740 break;
1741 }
1742 default:
1743 ASSERT(false);
1744 return;
1745 }
1746};
1747
1748//////////////////////////////////////////////////////////////////////
1749// Brush Ink - Copy Alpha+Color ink type
1750//////////////////////////////////////////////////////////////////////
1751
1752template<typename ImageTraits>
1753class BrushCopyInkProcessing : public BrushInkProcessingBase<ImageTraits> {
1754public:
1755 BrushCopyInkProcessing(ToolLoop* loop) : BrushInkProcessingBase<ImageTraits>(loop) {
1756 }
1757
1758 void processPixel(int x, int y) override {
1759 //Do nothing
1760 }
1761};
1762
1763template<>
1764void BrushCopyInkProcessing<RgbTraits>::processPixel(int x, int y) {
1765 if (!alignPixelPoint(x, y))
1766 return;
1767
1768 color_t c;
1769 switch (m_brushImage->pixelFormat()) {
1770 case IMAGE_RGB: {
1771 c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
1772 if (rgba_geta(c) == 0)
1773 return;
1774 break;
1775 }
1776 case IMAGE_INDEXED: {
1777 c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
1778 if (c == m_transparentColor) {
1779 *m_dstAddress = *m_srcAddress;
1780 return;
1781 }
1782 c = m_palette->getEntry(c);
1783 break;
1784 }
1785 case IMAGE_GRAYSCALE: {
1786 c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
1787 if (graya_geta(c) == 0)
1788 return;
1789 c = doc::rgba(graya_getv(c), graya_getv(c), graya_getv(c), graya_geta(c));
1790 break;
1791 }
1792 case IMAGE_BITMAP: {
1793 // TODO In which circuntance is possible this case?
1794 c = get_pixel_fast<BitmapTraits>(m_brushImage, x, y);
1795 c = c ? m_fgColor: m_bgColor;
1796 break;
1797 }
1798 default:
1799 ASSERT(false);
1800 return;
1801 }
1802 *m_dstAddress = c;
1803}
1804
1805template<>
1806void BrushCopyInkProcessing<IndexedTraits>::processPixel(int x, int y) {
1807 if (!alignPixelPoint(x, y))
1808 return;
1809
1810 color_t c;
1811 switch (m_brushImage->pixelFormat()) {
1812 case IMAGE_RGB: {
1813 c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
1814 if (rgba_geta(c) == 0)
1815 return;
1816 c = m_palette->findBestfit(rgba_getr(c),
1817 rgba_getg(c),
1818 rgba_getb(c),
1819 rgba_geta(c), m_transparentColor);
1820 if (c == 0)
1821 c = *m_srcAddress;
1822 break;
1823 }
1824 case IMAGE_INDEXED: {
1825 c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
1826 if (c == m_transparentColor)
1827 c = *m_srcAddress;
1828 break;
1829 }
1830 case IMAGE_GRAYSCALE: {
1831 c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
1832 if (graya_geta(c) == 0)
1833 return;
1834 c = m_palette->findBestfit(graya_getv(c),
1835 graya_getv(c),
1836 graya_getv(c),
1837 graya_geta(c), m_transparentColor);
1838 break;
1839 }
1840 case IMAGE_BITMAP: {
1841 // TODO In which circuntance is possible this case?
1842 c = get_pixel_fast<BitmapTraits>(m_brushImage, x, y);
1843 c = c ? m_fgColor: m_bgColor;
1844 break;
1845 }
1846 default:
1847 ASSERT(false);
1848 return;
1849 }
1850 *m_dstAddress = c;
1851}
1852
1853template<>
1854void BrushCopyInkProcessing<GrayscaleTraits>::processPixel(int x, int y) {
1855 if (!alignPixelPoint(x, y))
1856 return;
1857
1858 color_t c;
1859 switch (m_brushImage->pixelFormat()) {
1860 case IMAGE_RGB: {
1861 c = get_pixel_fast<RgbTraits>(m_brushImage, x, y);
1862 if (rgba_geta(c) == 0)
1863 return;
1864 c = graya(rgba_luma(c), rgba_geta(c));
1865 break;
1866 }
1867 case IMAGE_INDEXED: {
1868 c = get_pixel_fast<IndexedTraits>(m_brushImage, x, y);
1869 if (c == m_transparentColor) {
1870 *m_dstAddress = *m_srcAddress;
1871 return;
1872 }
1873 c = m_palette->getEntry(c);
1874 c = graya(rgba_luma(c), rgba_geta(c));
1875 break;
1876 }
1877 case IMAGE_GRAYSCALE: {
1878 c = get_pixel_fast<GrayscaleTraits>(m_brushImage, x, y);
1879 if (graya_geta(c) == 0)
1880 return;
1881 break;
1882 }
1883 case IMAGE_BITMAP: {
1884 // TODO In which circuntance is possible this case?
1885 c = get_pixel_fast<BitmapTraits>(m_brushImage, x, y);
1886 c = c ? m_fgColor: m_bgColor;
1887 break;
1888 }
1889 default:
1890 ASSERT(false);
1891 return;
1892 }
1893 *m_dstAddress = c;
1894}
1895
1896//////////////////////////////////////////////////////////////////////
1897
1898template<template<typename> class T>
1899BaseInkProcessing* get_ink_proc(ToolLoop* loop)
1900{
1901 switch (loop->sprite()->pixelFormat()) {
1902 case IMAGE_RGB: return new T<RgbTraits>(loop);
1903 case IMAGE_GRAYSCALE: return new T<GrayscaleTraits>(loop);
1904 case IMAGE_INDEXED: return new T<IndexedTraits>(loop);
1905 }
1906 ASSERT(false);
1907 return nullptr;
1908}
1909
1910template<template<typename> class T>
1911BaseInkProcessing* get_ink_proc2(ToolLoop* loop)
1912{
1913 switch (loop->getDstImage()->pixelFormat()) {
1914 case IMAGE_RGB: return new T<RgbTraits>(loop);
1915 case IMAGE_GRAYSCALE: return new T<GrayscaleTraits>(loop);
1916 case IMAGE_INDEXED: return new T<IndexedTraits>(loop);
1917 case IMAGE_TILEMAP: return new T<TilemapTraits>(loop);
1918 }
1919 ASSERT(false);
1920 return nullptr;
1921}
1922
1923} // anonymous namespace
1924} // namespace tools
1925} // namespace app
1926