1// Aseprite
2// Copyright (C) 2018-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/tools/ink_processing.h"
9
10#include "app/app.h" // TODO avoid to include this file
11#include "app/color_utils.h"
12#include "app/context.h"
13#include "app/doc.h"
14#include "app/doc_undo.h"
15#include "app/tools/pick_ink.h"
16#include "app/transformation.h"
17#include "doc/mask.h"
18#include "doc/tile.h"
19#include "gfx/region.h"
20
21namespace app {
22namespace tools {
23
24class BaseInk : public Ink {
25public:
26 BaseInk() { }
27 BaseInk(const BaseInk& other) { }
28
29 void inkHline(int x1, int y, int x2, ToolLoop* loop) override {
30 ASSERT(m_proc);
31 m_proc->processScanline(x1, y, x2, loop);
32 }
33
34 void prepareForPointShape(ToolLoop* loop, bool firstPoint, int x, int y) override {
35 ASSERT(m_proc);
36 m_proc->prepareForPointShape(loop, firstPoint, x, y);
37 }
38
39 void prepareVForPointShape(ToolLoop* loop, int y) override {
40 ASSERT(m_proc);
41 m_proc->prepareVForPointShape(loop, y);
42 }
43
44 void prepareUForPointShapeWholeScanline(ToolLoop* loop, int x1) override {
45 ASSERT(m_proc);
46 m_proc->prepareUForPointShapeWholeScanline(loop, x1);
47 }
48
49 void prepareUForPointShapeSlicedScanline(ToolLoop* loop, bool leftSlice, int x1) override {
50 ASSERT(m_proc);
51 m_proc->prepareUForPointShapeSlicedScanline(loop, leftSlice, x1);
52 }
53
54protected:
55 void setProc(BaseInkProcessing* proc) {
56 m_proc.reset(proc);
57 }
58
59 BaseInkProcessing* proc() {
60 return m_proc.get();
61 }
62
63private:
64 InkProcessingPtr m_proc;
65};
66
67// Ink used for tools which paint with primary/secondary
68// (or foreground/background colors)
69class PaintInk : public BaseInk {
70public:
71 enum Type { Simple, WithFg, WithBg, AlphaCompositing, Copy, LockAlpha};
72
73private:
74 Type m_type;
75
76public:
77 PaintInk(Type type) : m_type(type) { }
78
79 Ink* clone() override { return new PaintInk(*this); }
80
81 bool isPaint() const override { return true; }
82
83 void prepareInk(ToolLoop* loop) override {
84 switch (m_type) {
85
86 case Simple:
87 // Do nothing, use the default colors
88 break;
89
90 case WithFg:
91 case WithBg:
92 {
93 int color = (m_type == WithFg ? loop->getFgColor():
94 loop->getBgColor());
95 loop->setPrimaryColor(color);
96 loop->setSecondaryColor(color);
97 }
98 break;
99 }
100
101 // TODO support different ink types for tilemaps (even custom brushes,
102 // and custom inks script-driven)
103 if (loop->getDstImage()->pixelFormat() == IMAGE_TILEMAP) {
104 setProc(new CopyInkProcessing<TilemapTraits>(loop));
105 }
106 // Custom brushes
107 else if (loop->getBrush()->type() == doc::kImageBrushType) {
108 switch (m_type) {
109 case Simple:
110 setProc(get_ink_proc<BrushSimpleInkProcessing>(loop));
111 break;
112 case LockAlpha:
113 setProc(get_ink_proc<BrushLockAlphaInkProcessing>(loop));
114 break;
115 case Copy:
116 setProc(get_ink_proc<BrushCopyInkProcessing>(loop));
117 break;
118 default:
119 setProc(get_ink_proc<BrushSimpleInkProcessing>(loop));
120 break;
121 }
122 }
123 else {
124 switch (m_type) {
125 case Simple:
126 case AlphaCompositing: {
127 bool opaque = false;
128
129 // Opacity is set to 255 when InkType=Simple in ToolLoopBase()
130 if (loop->getOpacity() == 255 &&
131 loop->getDynamics().gradient == DynamicSensor::Static) {
132 color_t color = loop->getPrimaryColor();
133
134 switch (loop->sprite()->pixelFormat()) {
135 case IMAGE_RGB:
136 opaque = (rgba_geta(color) == 255);
137 break;
138 case IMAGE_GRAYSCALE:
139 opaque = (graya_geta(color) == 255);
140 break;
141 case IMAGE_INDEXED:
142 // Simple ink for indexed is better to use always
143 // opaque if opacity == 255.
144 if (m_type == Simple)
145 opaque = true;
146 else if (color == loop->sprite()->transparentColor() &&
147 loop->getLayer()->isTransparent()) {
148 opaque = false;
149 }
150 else {
151 color = loop->getPalette()->getEntry(color);
152 opaque = (rgba_geta(color) == 255);
153 }
154 break;
155 }
156 }
157
158 // Use a faster ink, direct copy
159 if (opaque)
160 setProc(get_ink_proc<CopyInkProcessing>(loop));
161 else
162 setProc(get_ink_proc<TransparentInkProcessing>(loop));
163 break;
164 }
165 case Copy:
166 setProc(get_ink_proc<CopyInkProcessing>(loop));
167 break;
168 case LockAlpha:
169 setProc(get_ink_proc<LockAlphaInkProcessing>(loop));
170 break;
171 default:
172 setProc(get_ink_proc<TransparentInkProcessing>(loop));
173 break;
174 }
175 }
176 }
177
178};
179
180
181class ShadingInk : public PaintInk {
182public:
183 ShadingInk() : PaintInk(PaintInk::Simple) { }
184
185 Ink* clone() override { return new ShadingInk(*this); }
186
187 bool isPaint() const override { return true; }
188 bool isShading() const override { return true; }
189
190 void prepareInk(ToolLoop* loop) override {
191 if (loop->getShadingRemap()) {
192 if (loop->getBrush()->type() == doc::kImageBrushType) {
193 setProc(get_ink_proc<BrushShadingInkProcessing>(loop));
194 }
195 else {
196 setProc(get_ink_proc<ShadingInkProcessing>(loop));
197 }
198 }
199 else {
200 PaintInk::prepareInk(loop);
201 }
202 }
203
204};
205
206
207class GradientInk : public BaseInk {
208public:
209 Ink* clone() override { return new GradientInk(*this); }
210
211 bool isPaint() const override { return true; }
212 bool isEffect() const override { return true; }
213 bool withDitheringOptions() const override { return true; }
214
215 void prepareInk(ToolLoop* loop) override {
216 setProc(get_ink_proc<GradientInkProcessing>(loop));
217 }
218
219 void prepareForStrokes(ToolLoop* loop, Strokes& strokes) override {
220 proc()->prepareForStrokes(loop, strokes);
221 }
222
223};
224
225
226class ScrollInk : public Ink {
227public:
228 Ink* clone() override { return new ScrollInk(*this); }
229
230 bool isScrollMovement() const override { return true; }
231
232 void prepareInk(ToolLoop* loop) override {
233 // Do nothing
234 }
235
236 void inkHline(int x1, int y, int x2, ToolLoop* loop) override {
237 // Do nothing
238 }
239
240};
241
242
243class ZoomInk : public Ink {
244public:
245 Ink* clone() override { return new ZoomInk(*this); }
246
247 bool isZoom() const override { return true; }
248 void prepareInk(ToolLoop* loop) override { }
249 void inkHline(int x1, int y, int x2, ToolLoop* loop) override { }
250};
251
252
253class MoveInk : public Ink {
254 bool m_autoSelect;
255public:
256 MoveInk(bool autoSelect) : m_autoSelect(autoSelect) { }
257
258 Ink* clone() override { return new MoveInk(*this); }
259
260 bool isCelMovement() const override { return true; }
261 bool isAutoSelectLayer() const override { return m_autoSelect; }
262 void prepareInk(ToolLoop* loop) override { }
263 void inkHline(int x1, int y, int x2, ToolLoop* loop) override { }
264};
265
266
267class SelectLayerInk : public Ink {
268public:
269 Ink* clone() override { return new SelectLayerInk(*this); }
270
271 bool isCelMovement() const override { return true; }
272 void prepareInk(ToolLoop* loop) override { }
273 void inkHline(int x1, int y, int x2, ToolLoop* loop) override { }
274};
275
276
277class SliceInk : public BaseInk {
278 bool m_createSlice;
279 gfx::Rect m_maxBounds;
280
281public:
282 SliceInk() : m_createSlice(false) { }
283
284 Ink* clone() override { return new SliceInk(*this); }
285
286 bool isSlice() const override { return true; }
287 bool needsCelCoordinates() const override {
288 return (m_createSlice ? false: true);
289 }
290
291 void prepareInk(ToolLoop* loop) override {
292 setProc(get_ink_proc<XorInkProcessing>(loop));
293 }
294
295 void inkHline(int x1, int y, int x2, ToolLoop* loop) override {
296 if (m_createSlice)
297 m_maxBounds |= gfx::Rect(x1, y, x2-x1+1, 1);
298 else
299 BaseInk::inkHline(x1, y, x2, loop);
300 }
301
302 void setFinalStep(ToolLoop* loop, bool state) override {
303 m_createSlice = state;
304 if (state) {
305 m_maxBounds = gfx::Rect(0, 0, 0, 0);
306 }
307 else {
308 loop->onSliceRect(m_maxBounds);
309 }
310 }
311};
312
313
314class EraserInk : public BaseInk {
315public:
316 enum Type { Eraser, ReplaceFgWithBg, ReplaceBgWithFg };
317
318private:
319 Type m_type;
320
321public:
322 EraserInk(Type type) : m_type(type) { }
323
324 Ink* clone() override { return new EraserInk(*this); }
325
326 bool isPaint() const override { return true; }
327 bool isEffect() const override { return true; }
328 bool isEraser() const override { return true; }
329
330 void prepareInk(ToolLoop* loop) override {
331 switch (m_type) {
332 case Eraser: {
333 if (loop->getBrush()->type() == doc::kImageBrushType) {
334 setProc(get_ink_proc<BrushEraserInkProcessing>(loop));
335 }
336 else if (loop->getDstImage()->pixelFormat() == IMAGE_TILEMAP) {
337 color_t clearColor = doc::notile;
338 loop->setPrimaryColor(clearColor);
339 loop->setSecondaryColor(clearColor);
340 setProc(new CopyInkProcessing<TilemapTraits>(loop));
341 }
342 else {
343 // TODO app_get_color_to_clear_layer should receive the context as parameter
344 color_t clearColor = app_get_color_to_clear_layer(loop->getLayer());
345 loop->setPrimaryColor(clearColor);
346 loop->setSecondaryColor(clearColor);
347
348 if (loop->getOpacity() == 255) {
349 setProc(get_ink_proc<CopyInkProcessing>(loop));
350 }
351 else {
352 // For opaque layers
353 if (loop->getLayer()->isBackground()) {
354 setProc(get_ink_proc<TransparentInkProcessing>(loop));
355 }
356 // For transparent layers
357 else {
358 if (loop->sprite()->pixelFormat() == IMAGE_INDEXED)
359 loop->setPrimaryColor(loop->sprite()->transparentColor());
360
361 setProc(get_ink_proc<MergeInkProcessing>(loop));
362 }
363 }
364 }
365 break;
366 }
367 case ReplaceFgWithBg:
368 loop->setPrimaryColor(loop->getFgColor());
369 loop->setSecondaryColor(loop->getBgColor());
370 setProc(get_ink_proc2<ReplaceInkProcessing>(loop));
371 break;
372
373 case ReplaceBgWithFg:
374 loop->setPrimaryColor(loop->getBgColor());
375 loop->setSecondaryColor(loop->getFgColor());
376 setProc(get_ink_proc2<ReplaceInkProcessing>(loop));
377 break;
378 }
379 }
380
381};
382
383
384class BlurInk : public BaseInk {
385public:
386 Ink* clone() override { return new BlurInk(*this); }
387
388 bool isPaint() const override { return true; }
389 bool isEffect() const override { return true; }
390 bool needsSpecialSourceArea() const override { return true; }
391
392 void prepareInk(ToolLoop* loop) override {
393 setProc(get_ink_proc<BlurInkProcessing>(loop));
394 }
395
396 void createSpecialSourceArea(const gfx::Region& dirtyArea, gfx::Region& sourceArea) const override {
397 // We need one pixel more for each side, to use a 3x3 convolution matrix.
398 for (const auto& rc : dirtyArea) {
399 sourceArea.createUnion(sourceArea,
400 gfx::Region(gfx::Rect(rc).enlarge(1)));
401 }
402 }
403};
404
405
406class JumbleInk : public BaseInk {
407public:
408 Ink* clone() override { return new JumbleInk(*this); }
409
410 bool isPaint() const override { return true; }
411 bool isEffect() const override { return true; }
412 bool needsSpecialSourceArea() const override { return true; }
413
414 void prepareInk(ToolLoop* loop) override {
415 setProc(get_ink_proc<JumbleInkProcessing>(loop));
416 }
417
418 void createSpecialSourceArea(const gfx::Region& dirtyArea, gfx::Region& sourceArea) const override {
419 // We need one pixel more for each side.
420 for (const auto& rc : dirtyArea) {
421 sourceArea.createUnion(sourceArea,
422 gfx::Region(gfx::Rect(rc).enlarge(1)));
423 }
424 }
425};
426
427
428// Ink used for selection tools (like Rectangle Marquee, Lasso, Magic Wand, etc.)
429class SelectionInk : public BaseInk {
430 bool m_modify_selection;
431 Mask m_mask;
432 Mask m_intersectMask;
433 Rect m_maxBounds;
434
435public:
436 SelectionInk()
437 : m_modify_selection(false) { }
438
439 Ink* clone() override { return new SelectionInk(*this); }
440
441 void prepareInk(ToolLoop* loop) override {
442 setProc(get_ink_proc<XorInkProcessing>(loop));
443 }
444
445 bool isSelection() const override { return true; }
446 bool needsCelCoordinates() const override {
447 return (m_modify_selection ? false: true);
448 }
449
450 void inkHline(int x1, int y, int x2, ToolLoop* loop) override {
451 gfx::Rect rc(x1, y, x2-x1+1, 1);
452
453 // For tile point shape, the point shape is done in "tiles"
454 // coordinates, but we want the selection in canvas/pixels
455 // coordinates.
456 if (loop->getPointShape()->isTile()) {
457 const Grid& grid = loop->getGrid();
458 rc = grid.tileToCanvas(rc);
459 if (!m_modify_selection) {
460 // For feedback purposes, the coordinates must be relative to
461 // the getDstImage() and not in absolute sprite canvas
462 // coordinates.
463 rc.offset(-grid.origin());
464 }
465 }
466
467 if (m_modify_selection) {
468 int modifiers = int(loop->getModifiers());
469
470 if ((modifiers & (int(ToolLoopModifiers::kReplaceSelection) |
471 int(ToolLoopModifiers::kAddSelection))) != 0) {
472 m_mask.add(rc);
473 }
474 else if ((modifiers & int(ToolLoopModifiers::kSubtractSelection)) != 0) {
475 m_mask.subtract(rc);
476 }
477 else if ((modifiers & int(ToolLoopModifiers::kIntersectSelection)) != 0) {
478 m_intersectMask.add(rc);
479 }
480
481 m_maxBounds |= rc;
482 }
483 else {
484 rc &= loop->getDstImage()->bounds();
485 for (int v=rc.y; v<rc.y2(); ++v)
486 BaseInk::inkHline(rc.x, v, rc.x2()-1, loop);
487 }
488 }
489
490 void setFinalStep(ToolLoop* loop, bool state) override {
491 m_modify_selection = state;
492 int modifiers = int(loop->getModifiers());
493
494 if (state) {
495 m_maxBounds = loop->getMask()->bounds();
496
497 m_mask.copyFrom(loop->getMask());
498 m_mask.freeze();
499 m_mask.reserve(loop->sprite()->bounds());
500
501 if ((modifiers & int(ToolLoopModifiers::kIntersectSelection)) != 0) {
502 m_intersectMask.clear();
503 m_intersectMask.reserve(loop->sprite()->bounds());
504 }
505 }
506 else {
507 if ((modifiers & int(ToolLoopModifiers::kIntersectSelection)) != 0) {
508 m_mask.intersect(m_intersectMask);
509 m_intersectMask.clear();
510 }
511
512 // We can intersect the used bounds in inkHline() calls to
513 // reduce the shrink computation.
514 m_mask.intersect(m_maxBounds);
515
516 m_mask.unfreeze();
517
518 loop->setMask(&m_mask);
519 double cornerThick = (loop->isTilemapMode()) ?
520 CORNER_THICK_FOR_TILEMAP_MODE :
521 CORNER_THICK_FOR_PIXELS_MODE;
522 loop->getDocument()->setTransformation(
523 Transformation(RectF(m_mask.bounds()), cornerThick));
524
525 m_mask.clear();
526 }
527 }
528
529};
530
531
532} // namespace tools
533} // namespace app
534