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 | |
21 | namespace app { |
22 | namespace tools { |
23 | |
24 | class BaseInk : public Ink { |
25 | public: |
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 | |
54 | protected: |
55 | void setProc(BaseInkProcessing* proc) { |
56 | m_proc.reset(proc); |
57 | } |
58 | |
59 | BaseInkProcessing* proc() { |
60 | return m_proc.get(); |
61 | } |
62 | |
63 | private: |
64 | InkProcessingPtr m_proc; |
65 | }; |
66 | |
67 | // Ink used for tools which paint with primary/secondary |
68 | // (or foreground/background colors) |
69 | class PaintInk : public BaseInk { |
70 | public: |
71 | enum Type { Simple, WithFg, WithBg, AlphaCompositing, Copy, LockAlpha}; |
72 | |
73 | private: |
74 | Type m_type; |
75 | |
76 | public: |
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 | |
181 | class ShadingInk : public PaintInk { |
182 | public: |
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 | |
207 | class GradientInk : public BaseInk { |
208 | public: |
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 | |
226 | class ScrollInk : public Ink { |
227 | public: |
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 | |
243 | class ZoomInk : public Ink { |
244 | public: |
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 | |
253 | class MoveInk : public Ink { |
254 | bool m_autoSelect; |
255 | public: |
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 | |
267 | class SelectLayerInk : public Ink { |
268 | public: |
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 | |
277 | class SliceInk : public BaseInk { |
278 | bool m_createSlice; |
279 | gfx::Rect m_maxBounds; |
280 | |
281 | public: |
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 | |
314 | class EraserInk : public BaseInk { |
315 | public: |
316 | enum Type { Eraser, ReplaceFgWithBg, ReplaceBgWithFg }; |
317 | |
318 | private: |
319 | Type m_type; |
320 | |
321 | public: |
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 | |
384 | class BlurInk : public BaseInk { |
385 | public: |
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 | |
406 | class JumbleInk : public BaseInk { |
407 | public: |
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.) |
429 | class SelectionInk : public BaseInk { |
430 | bool m_modify_selection; |
431 | Mask m_mask; |
432 | Mask m_intersectMask; |
433 | Rect m_maxBounds; |
434 | |
435 | public: |
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 | |