1 // Aseprite
2 // Copyright (C) 2001-2017  David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6 
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #include "app/ui/editor/tool_loop_impl.h"
12 
13 #include "app/app.h"
14 #include "app/cmd/add_slice.h"
15 #include "app/cmd/set_last_point.h"
16 #include "app/cmd/set_mask.h"
17 #include "app/color.h"
18 #include "app/color_utils.h"
19 #include "app/console.h"
20 #include "app/context.h"
21 #include "app/context_access.h"
22 #include "app/doc_undo.h"
23 #include "app/i18n/strings.h"
24 #include "app/modules/gui.h"
25 #include "app/modules/palettes.h"
26 #include "app/pref/preferences.h"
27 #include "app/tools/controller.h"
28 #include "app/tools/freehand_algorithm.h"
29 #include "app/tools/ink.h"
30 #include "app/tools/point_shape.h"
31 #include "app/tools/symmetries.h"
32 #include "app/tools/tool.h"
33 #include "app/tools/tool_box.h"
34 #include "app/tools/tool_loop.h"
35 #include "app/transaction.h"
36 #include "app/ui/color_bar.h"
37 #include "app/ui/context_bar.h"
38 #include "app/ui/editor/editor.h"
39 #include "app/ui/main_window.h"
40 #include "app/ui/status_bar.h"
41 #include "app/util/expand_cel_canvas.h"
42 #include "doc/brush.h"
43 #include "doc/cel.h"
44 #include "doc/image.h"
45 #include "doc/layer.h"
46 #include "doc/mask.h"
47 #include "doc/palette.h"
48 #include "doc/palette_picks.h"
49 #include "doc/remap.h"
50 #include "doc/slice.h"
51 #include "doc/sprite.h"
52 #include "fmt/format.h"
53 #include "render/dithering.h"
54 #include "render/render.h"
55 #include "ui/ui.h"
56 
57 namespace app {
58 
59 using namespace ui;
60 
61 //////////////////////////////////////////////////////////////////////
62 // Common properties between drawing/preview ToolLoop impl
63 
64 class ToolLoopBase : public tools::ToolLoop {
65 
66 protected:
67   Editor* m_editor;
68   tools::Tool* m_tool;
69   BrushRef m_brush;
70   gfx::Point m_oldPatternOrigin;
71   Doc* m_document;
72   Sprite* m_sprite;
73   Layer* m_layer;
74   frame_t m_frame;
75   RgbMap* m_rgbMap;
76   DocumentPreferences& m_docPref;
77   ToolPreferences& m_toolPref;
78   int m_opacity;
79   int m_tolerance;
80   bool m_contiguous;
81   gfx::Point m_celOrigin;
82   gfx::Point m_speed;
83   tools::ToolLoop::Button m_button;
84   base::UniquePtr<tools::Ink> m_ink;
85   tools::Controller* m_controller;
86   tools::PointShape* m_pointShape;
87   tools::Intertwine* m_intertwine;
88   tools::TracePolicy m_tracePolicy;
89   base::UniquePtr<tools::Symmetry> m_symmetry;
90   base::UniquePtr<doc::Remap> m_shadingRemap;
91   app::ColorTarget m_colorTarget;
92   doc::color_t m_fgColor;
93   doc::color_t m_bgColor;
94   doc::color_t m_primaryColor;
95   doc::color_t m_secondaryColor;
96   gfx::Region m_dirtyArea;
97 
98 public:
ToolLoopBase(Editor * editor,Layer * layer,tools::Tool * tool,tools::Ink * ink,tools::Controller * controller,Doc * document,tools::ToolLoop::Button button,const app::Color & fgColor,const app::Color & bgColor)99   ToolLoopBase(Editor* editor,
100                Layer* layer,
101                tools::Tool* tool,
102                tools::Ink* ink,
103                tools::Controller* controller,
104                Doc* document,
105                tools::ToolLoop::Button button,
106                const app::Color& fgColor,
107                const app::Color& bgColor)
108     : m_editor(editor)
109     , m_tool(tool)
110     , m_brush(App::instance()->contextBar()->activeBrush(m_tool, ink))
111     , m_oldPatternOrigin(m_brush->patternOrigin())
112     , m_document(document)
113     , m_sprite(editor->sprite())
114     , m_layer(layer)
115     , m_frame(editor->frame())
116     , m_rgbMap(nullptr)
117     , m_docPref(Preferences::instance().document(m_document))
118     , m_toolPref(Preferences::instance().tool(m_tool))
119     , m_opacity(m_toolPref.opacity())
120     , m_tolerance(m_toolPref.tolerance())
121     , m_contiguous(m_toolPref.contiguous())
122     , m_button(button)
123     , m_ink(ink->clone())
124     , m_controller(controller)
125     , m_pointShape(m_tool->getPointShape(m_button))
126     , m_intertwine(m_tool->getIntertwine(m_button))
127     , m_tracePolicy(m_tool->getTracePolicy(m_button))
128     , m_symmetry(nullptr)
129     , m_colorTarget(m_layer ? ColorTarget(m_layer):
130                               ColorTarget(ColorTarget::BackgroundLayer,
131                                           m_sprite->pixelFormat(),
132                                           m_sprite->transparentColor()))
133     , m_fgColor(color_utils::color_for_target_mask(fgColor, m_colorTarget))
134     , m_bgColor(color_utils::color_for_target_mask(bgColor, m_colorTarget))
135     , m_primaryColor(button == tools::ToolLoop::Left ? m_fgColor: m_bgColor)
136     , m_secondaryColor(button == tools::ToolLoop::Left ? m_bgColor: m_fgColor)
137   {
138     if (m_tracePolicy == tools::TracePolicy::Accumulate ||
139         m_tracePolicy == tools::TracePolicy::AccumulateUpdateLast) {
140       tools::ToolBox* toolbox = App::instance()->toolBox();
141 
142       switch (m_toolPref.freehandAlgorithm()) {
143         case tools::FreehandAlgorithm::DEFAULT:
144           m_intertwine = toolbox->getIntertwinerById(tools::WellKnownIntertwiners::AsLines);
145           m_tracePolicy = tools::TracePolicy::Accumulate;
146           break;
147         case tools::FreehandAlgorithm::PIXEL_PERFECT:
148           m_intertwine = toolbox->getIntertwinerById(tools::WellKnownIntertwiners::AsPixelPerfect);
149           m_tracePolicy = tools::TracePolicy::AccumulateUpdateLast;
150           break;
151         case tools::FreehandAlgorithm::DOTS:
152           m_intertwine = toolbox->getIntertwinerById(tools::WellKnownIntertwiners::None);
153           m_tracePolicy = tools::TracePolicy::Accumulate;
154           break;
155       }
156     }
157 
158     // Symmetry mode
159     if (Preferences::instance().symmetryMode.enabled()) {
160       switch (m_docPref.symmetry.mode()) {
161 
162         case app::gen::SymmetryMode::NONE:
163           ASSERT(m_symmetry == nullptr);
164           break;
165 
166         case app::gen::SymmetryMode::HORIZONTAL:
167           m_symmetry.reset(new app::tools::HorizontalSymmetry(m_docPref.symmetry.xAxis()));
168           break;
169 
170         case app::gen::SymmetryMode::VERTICAL:
171           m_symmetry.reset(new app::tools::VerticalSymmetry(m_docPref.symmetry.yAxis()));
172           break;
173 
174         case app::gen::SymmetryMode::BOTH:
175           m_symmetry.reset(
176             new app::tools::SymmetryCombo(
177               new app::tools::HorizontalSymmetry(m_docPref.symmetry.xAxis()),
178               new app::tools::VerticalSymmetry(m_docPref.symmetry.yAxis())));
179           break;
180       }
181     }
182 
183     // Ignore opacity for these inks
184     if (!tools::inkHasOpacity(m_toolPref.ink()) &&
185         m_brush->type() != kImageBrushType &&
186         !m_ink->isEffect()) {
187       m_opacity = 255;
188     }
189 
190     if (m_toolPref.ink() == tools::InkType::SHADING) {
191       m_shadingRemap.reset(
192         App::instance()->contextBar()->createShadeRemap(
193           button == tools::ToolLoop::Left));
194     }
195   }
196 
~ToolLoopBase()197   ~ToolLoopBase() {
198     m_brush->setPatternOrigin(m_oldPatternOrigin);
199   }
200 
201   // IToolLoop interface
getTool()202   tools::Tool* getTool() override { return m_tool; }
getBrush()203   Brush* getBrush() override { return m_brush.get(); }
getDocument()204   Doc* getDocument() override { return m_document; }
sprite()205   Sprite* sprite() override { return m_sprite; }
getLayer()206   Layer* getLayer() override { return m_layer; }
getFrame()207   frame_t getFrame() override { return m_frame; }
getRgbMap()208   RgbMap* getRgbMap() override {
209     if (!m_rgbMap) {
210       Sprite::RgbMapFor forLayer =
211         ((!m_layer ||
212           m_layer->isBackground() ||
213           m_sprite->pixelFormat() == IMAGE_RGB) ?
214          Sprite::RgbMapFor::OpaqueLayer:
215          Sprite::RgbMapFor::TransparentLayer);
216       m_rgbMap = m_sprite->rgbMap(m_frame, forLayer);
217     }
218     return m_rgbMap;
219   }
getMouseButton()220   ToolLoop::Button getMouseButton() override { return m_button; }
getFgColor()221   doc::color_t getFgColor() override { return m_fgColor; }
getBgColor()222   doc::color_t getBgColor() override { return m_bgColor; }
getPrimaryColor()223   doc::color_t getPrimaryColor() override { return m_primaryColor; }
setPrimaryColor(doc::color_t color)224   void setPrimaryColor(doc::color_t color) override { m_primaryColor = color; }
getSecondaryColor()225   doc::color_t getSecondaryColor() override { return m_secondaryColor; }
setSecondaryColor(doc::color_t color)226   void setSecondaryColor(doc::color_t color) override { m_secondaryColor = color; }
getOpacity()227   int getOpacity() override { return m_opacity; }
getTolerance()228   int getTolerance() override { return m_tolerance; }
getContiguous()229   bool getContiguous() override { return m_contiguous; }
getModifiers()230   tools::ToolLoopModifiers getModifiers() override { return m_editor->getToolLoopModifiers(); }
getTiledMode()231   filters::TiledMode getTiledMode() override { return m_docPref.tiled.mode(); }
getGridVisible()232   bool getGridVisible() override { return m_docPref.show.grid(); }
getSnapToGrid()233   bool getSnapToGrid() override { return m_docPref.grid.snap(); }
getStopAtGrid()234   bool getStopAtGrid() override {
235     switch (m_toolPref.floodfill.stopAtGrid()) {
236       case app::gen::StopAtGrid::NEVER:
237         return false;
238       case app::gen::StopAtGrid::IF_VISIBLE:
239         return m_docPref.show.grid();
240       case app::gen::StopAtGrid::ALWAYS:
241         return true;
242     }
243     return false;
244   }
getGridBounds()245   gfx::Rect getGridBounds() override { return m_docPref.grid.bounds(); }
getCelOrigin()246   gfx::Point getCelOrigin() override { return m_celOrigin; }
setSpeed(const gfx::Point & speed)247   void setSpeed(const gfx::Point& speed) override { m_speed = speed; }
getSpeed()248   gfx::Point getSpeed() override { return m_speed; }
getInk()249   tools::Ink* getInk() override { return m_ink; }
getController()250   tools::Controller* getController() override { return m_controller; }
getPointShape()251   tools::PointShape* getPointShape() override { return m_pointShape; }
getIntertwine()252   tools::Intertwine* getIntertwine() override { return m_intertwine; }
getTracePolicy()253   tools::TracePolicy getTracePolicy() override {
254     if (m_controller->handleTracePolicy())
255       return m_controller->getTracePolicy();
256     else
257       return m_tracePolicy;
258   }
getSymmetry()259   tools::Symmetry* getSymmetry() override { return m_symmetry.get(); }
getShadingRemap()260   doc::Remap* getShadingRemap() override { return m_shadingRemap; }
261 
getDirtyArea()262   gfx::Region& getDirtyArea() override {
263     return m_dirtyArea;
264   }
265 
updateDirtyArea()266   void updateDirtyArea() override {
267     // This is necessary here so the "on sprite crosshair" is hidden,
268     // we update screen pixels with the new sprite, and then we show
269     // the crosshair saving the updated pixels. It fixes problems with
270     // filled shape tools when we release the button, or paint-bucket
271     // when we press the button.
272     HideBrushPreview hide(m_editor->brushPreview());
273 
274     m_document->notifySpritePixelsModified(
275       m_sprite, m_dirtyArea, m_frame);
276   }
277 
updateStatusBar(const char * text)278   void updateStatusBar(const char* text) override {
279     StatusBar::instance()->setStatusText(0, text);
280   }
281 
statusBarPositionOffset()282   gfx::Point statusBarPositionOffset() override {
283     return -m_editor->mainTilePosition();
284   }
285 
getDitheringMatrix()286   render::DitheringMatrix getDitheringMatrix() override {
287     return App::instance()->contextBar()->ditheringMatrix();
288   }
289 
getDitheringAlgorithm()290   render::DitheringAlgorithmBase* getDitheringAlgorithm() override {
291     return App::instance()->contextBar()->ditheringAlgorithm();
292   }
293 
294 };
295 
296 //////////////////////////////////////////////////////////////////////
297 // For drawing
298 
299 class ToolLoopImpl : public ToolLoopBase {
300   Context* m_context;
301   bool m_filled;
302   bool m_previewFilled;
303   int m_sprayWidth;
304   int m_spraySpeed;
305   bool m_useMask;
306   Mask* m_mask;
307   gfx::Point m_maskOrigin;
308   bool m_canceled;
309   Transaction m_transaction;
310   ExpandCelCanvas* m_expandCelCanvas;
311   Image* m_floodfillSrcImage;
312   bool m_saveLastPoint;
313 
314 public:
ToolLoopImpl(Editor * editor,Layer * layer,Context * context,tools::Tool * tool,tools::Ink * ink,tools::Controller * controller,Doc * document,tools::ToolLoop::Button button,const app::Color & fgColor,const app::Color & bgColor,const bool saveLastPoint)315   ToolLoopImpl(Editor* editor,
316                Layer* layer,
317                Context* context,
318                tools::Tool* tool,
319                tools::Ink* ink,
320                tools::Controller* controller,
321                Doc* document,
322                tools::ToolLoop::Button button,
323                const app::Color& fgColor,
324                const app::Color& bgColor,
325                const bool saveLastPoint)
326     : ToolLoopBase(editor,
327                    layer,
328                    tool,
329                    ink,
330                    controller,
331                    document,
332                    button,
333                    fgColor,
334                    bgColor)
335     , m_context(context)
336     , m_canceled(false)
337     , m_transaction(m_context,
338                     m_tool->getText().c_str(),
339                     ((getInk()->isSelection() ||
340                       getInk()->isEyedropper() ||
341                       getInk()->isScrollMovement() ||
342                       getInk()->isSlice() ||
343                       getInk()->isZoom()) ? DoesntModifyDocument:
344                                             ModifyDocument))
345     , m_expandCelCanvas(nullptr)
346     , m_floodfillSrcImage(nullptr)
347     , m_saveLastPoint(saveLastPoint)
348   {
349     ASSERT(m_context->activeDocument() == m_editor->document());
350 
351     if (m_pointShape->isFloodFill()) {
352       // Prepare a special image for floodfill when it's configured to
353       // stop using all visible layers.
354       if (m_toolPref.floodfill.referTo() == gen::FillReferTo::ALL_LAYERS) {
355         m_floodfillSrcImage = Image::create(m_sprite->pixelFormat(),
356                                             m_sprite->width(),
357                                             m_sprite->height());
358 
359         m_floodfillSrcImage->clear(m_sprite->transparentColor());
360 
361         render::Render().renderSprite(
362           m_floodfillSrcImage,
363           m_sprite,
364           m_frame,
365           gfx::Clip(m_sprite->bounds()));
366       }
367       else {
368         Cel* cel = m_layer->cel(m_frame);
369         if (cel && (cel->x() != 0 || cel->y() != 0)) {
370           m_floodfillSrcImage = Image::create(m_sprite->pixelFormat(),
371                                               m_sprite->width(),
372                                               m_sprite->height());
373           m_floodfillSrcImage->clear(m_sprite->transparentColor());
374           copy_image(m_floodfillSrcImage, cel->image(), cel->x(), cel->y());
375         }
376       }
377     }
378 
379     m_expandCelCanvas = new ExpandCelCanvas(
380       editor->getSite(),
381       layer,
382       m_docPref.tiled.mode(),
383       m_transaction,
384       ExpandCelCanvas::Flags(
385         ExpandCelCanvas::NeedsSource |
386         // If the tool is freehand-like, we can use the modified
387         // region directly as undo information to save the modified
388         // pixels. See ExpandCelCanvas::commit() for details about this flag.
389         (getController()->isFreehand() ?
390          ExpandCelCanvas::UseModifiedRegionAsUndoInfo:
391          ExpandCelCanvas::None)));
392 
393     if (!m_floodfillSrcImage)
394       m_floodfillSrcImage = const_cast<Image*>(getSrcImage());
395 
396     // Settings
397     switch (tool->getFill(m_button)) {
398       case tools::FillNone:
399         m_filled = false;
400         break;
401       case tools::FillAlways:
402         m_filled = true;
403         break;
404       case tools::FillOptional:
405         m_filled = m_toolPref.filled();
406         break;
407     }
408 
409     m_previewFilled = m_toolPref.filledPreview();
410     m_sprayWidth = m_toolPref.spray.width();
411     m_spraySpeed = m_toolPref.spray.speed();
412 
413     if (m_ink->isSelection())
414       m_useMask = false;
415     else
416       m_useMask = m_document->isMaskVisible();
417 
418     // Start with an empty mask if the user is selecting with "default selection mode"
419     if (getInk()->isSelection() &&
420         (!m_document->isMaskVisible() ||
421          (int(getModifiers()) & int(tools::ToolLoopModifiers::kReplaceSelection)))) {
422       Mask emptyMask;
423       m_transaction.execute(new cmd::SetMask(m_document, &emptyMask));
424     }
425 
426     m_celOrigin = m_expandCelCanvas->getCel()->position();
427     m_mask = m_document->mask();
428     m_maskOrigin = (!m_mask->isEmpty() ? gfx::Point(m_mask->bounds().x-m_celOrigin.x,
429                                                     m_mask->bounds().y-m_celOrigin.y):
430                                          gfx::Point(0, 0));
431   }
432 
~ToolLoopImpl()433   ~ToolLoopImpl() {
434     if (m_floodfillSrcImage != getSrcImage())
435       delete m_floodfillSrcImage;
436     delete m_expandCelCanvas;
437   }
438 
439   // IToolLoop interface
commitOrRollback()440   void commitOrRollback() override {
441     bool redraw = false;
442 
443     if (!m_canceled) {
444       // Freehand changes the last point
445       if (m_saveLastPoint) {
446         m_transaction.execute(
447           new cmd::SetLastPoint(
448             m_document,
449             getController()->getLastPoint()));
450       }
451 
452       // Paint ink
453       if (getInk()->isPaint()) {
454         try {
455           ContextReader reader(m_context, 500);
456           ContextWriter writer(reader, 500);
457           m_expandCelCanvas->commit();
458         }
459         catch (const LockedDocException& ex) {
460           Console::showException(ex);
461         }
462       }
463       // Selection ink
464       else if (getInk()->isSelection()) {
465         m_document->generateMaskBoundaries();
466         redraw = true;
467 
468         // Show selection edges
469         if (Preferences::instance().selection.autoShowSelectionEdges())
470           m_docPref.show.selectionEdges(true);
471       }
472       // Slice ink
473       else if (getInk()->isSlice()) {
474         redraw = true;
475       }
476 
477       m_transaction.commit();
478     }
479     else {
480       redraw = true;
481     }
482 
483     // If the trace was canceled or it is not a 'paint' ink...
484     if (m_canceled || !getInk()->isPaint()) {
485       try {
486         ContextReader reader(m_context, 500);
487         ContextWriter writer(reader, 500);
488         m_expandCelCanvas->rollback();
489       }
490       catch (const LockedDocException& ex) {
491         Console::showException(ex);
492       }
493     }
494 
495     if (redraw)
496       update_screen_for_document(m_document);
497   }
498 
getSrcImage()499   const Image* getSrcImage() override { return m_expandCelCanvas->getSourceCanvas(); }
getFloodFillSrcImage()500   const Image* getFloodFillSrcImage() override { return m_floodfillSrcImage; }
getDstImage()501   Image* getDstImage() override { return m_expandCelCanvas->getDestCanvas(); }
validateSrcImage(const gfx::Region & rgn)502   void validateSrcImage(const gfx::Region& rgn) override {
503     m_expandCelCanvas->validateSourceCanvas(rgn);
504   }
validateDstImage(const gfx::Region & rgn)505   void validateDstImage(const gfx::Region& rgn) override {
506     m_expandCelCanvas->validateDestCanvas(rgn);
507   }
invalidateDstImage()508   void invalidateDstImage() override {
509     m_expandCelCanvas->invalidateDestCanvas();
510   }
invalidateDstImage(const gfx::Region & rgn)511   void invalidateDstImage(const gfx::Region& rgn) override {
512     m_expandCelCanvas->invalidateDestCanvas(rgn);
513   }
copyValidDstToSrcImage(const gfx::Region & rgn)514   void copyValidDstToSrcImage(const gfx::Region& rgn) override {
515     m_expandCelCanvas->copyValidDestToSourceCanvas(rgn);
516   }
517 
useMask()518   bool useMask() override { return m_useMask; }
getMask()519   Mask* getMask() override { return m_mask; }
setMask(Mask * newMask)520   void setMask(Mask* newMask) override {
521     m_transaction.execute(new cmd::SetMask(m_document, newMask));
522   }
addSlice(Slice * newSlice)523   void addSlice(Slice* newSlice) override {
524     auto color = Preferences::instance().slices.defaultColor();
525     newSlice->userData().setColor(
526       doc::rgba(color.getRed(),
527                 color.getGreen(),
528                 color.getBlue(),
529                 color.getAlpha()));
530 
531     m_transaction.execute(new cmd::AddSlice(m_sprite, newSlice));
532   }
getMaskOrigin()533   gfx::Point getMaskOrigin() override { return m_maskOrigin; }
getFilled()534   bool getFilled() override { return m_filled; }
getPreviewFilled()535   bool getPreviewFilled() override { return m_previewFilled; }
getSprayWidth()536   int getSprayWidth() override { return m_sprayWidth; }
getSpraySpeed()537   int getSpraySpeed() override { return m_spraySpeed; }
538 
cancel()539   void cancel() override { m_canceled = true; }
isCanceled()540   bool isCanceled() override { return m_canceled; }
541 
542 };
543 
create_tool_loop(Editor * editor,Context * context,const tools::Pointer::Button button,const bool convertLineToFreehand)544 tools::ToolLoop* create_tool_loop(
545   Editor* editor,
546   Context* context,
547   const tools::Pointer::Button button,
548   const bool convertLineToFreehand)
549 {
550   tools::Tool* tool = editor->getCurrentEditorTool();
551   tools::Ink* ink = editor->getCurrentEditorInk();
552   if (!tool || !ink)
553     return nullptr;
554 
555   Layer* layer;
556 
557   // For selection tools, we can use any layer (even without layers at
558   // all), so we specify a nullptr here as the active layer. This is
559   // used as a special case by the render::Render class to show the
560   // preview image/selection stroke as a xor'd overlay in the render
561   // result.
562   //
563   // Anyway this cannot be used in 'magic wand' tool (isSelection +
564   // isFloodFill) because we need the original layer source
565   // image/pixels to stop the flood-fill algorithm.
566   if (ink->isSelection() &&
567       !tool->getPointShape(button != tools::Pointer::Left ? 1: 0)->isFloodFill()) {
568     layer = nullptr;
569   }
570   else {
571     layer = editor->layer();
572     if (!layer) {
573       StatusBar::instance()->showTip(
574         1000, "There is no active layer");
575       return nullptr;
576     }
577     else if (!layer->isVisibleHierarchy()) {
578       StatusBar::instance()->showTip(
579         1000, "Layer '%s' is hidden", layer->name().c_str());
580       return nullptr;
581     }
582     // If the active layer is read-only.
583     else if (!layer->isEditableHierarchy()) {
584       StatusBar::instance()->showTip(
585         1000, "Layer '%s' is locked", layer->name().c_str());
586       return nullptr;
587     }
588     // If the active layer is reference.
589     else if (layer->isReference()) {
590       StatusBar::instance()->showTip(
591         1000, "Layer '%s' is reference, cannot be modified", layer->name().c_str());
592       return nullptr;
593     }
594   }
595 
596   // Get fg/bg colors
597   ColorBar* colorbar = ColorBar::instance();
598   app::Color fg = colorbar->getFgColor();
599   app::Color bg = colorbar->getBgColor();
600 
601   if (!fg.isValid() || !bg.isValid()) {
602     Alert::show(Strings::alerts_invalid_fg_or_bg_colors());
603     return NULL;
604   }
605 
606   // Create the new tool loop
607   try {
608     tools::ToolLoop::Button toolLoopButton =
609       (button == tools::Pointer::Left ? tools::ToolLoop::Left:
610                                         tools::ToolLoop::Right);
611 
612     tools::Controller* controller =
613       (convertLineToFreehand ?
614        App::instance()->toolBox()->getControllerById(
615          tools::WellKnownControllers::LineFreehand):
616        tool->getController(toolLoopButton));
617 
618     const bool saveLastPoint =
619       (ink->isPaint() &&
620        (controller->isFreehand() ||
621         convertLineToFreehand));
622 
623     return new ToolLoopImpl(
624       editor, layer, context,
625       tool,
626       ink,
627       controller,
628       editor->document(),
629       toolLoopButton,
630       fg, bg,
631       saveLastPoint);
632   }
633   catch (const std::exception& ex) {
634     Console::showException(ex);
635     return NULL;
636   }
637 }
638 
639 //////////////////////////////////////////////////////////////////////
640 // For preview
641 
642 class PreviewToolLoopImpl : public ToolLoopBase {
643   Image* m_image;
644 
645 public:
PreviewToolLoopImpl(Editor * editor,tools::Tool * tool,tools::Ink * ink,Doc * document,const app::Color & fgColor,const app::Color & bgColor,Image * image,const gfx::Point & celOrigin)646   PreviewToolLoopImpl(
647     Editor* editor,
648     tools::Tool* tool,
649     tools::Ink* ink,
650     Doc* document,
651     const app::Color& fgColor,
652     const app::Color& bgColor,
653     Image* image,
654     const gfx::Point& celOrigin)
655     : ToolLoopBase(editor,
656                    editor->layer(),
657                    tool,
658                    ink,
659                    tool->getController(tools::ToolLoop::Left),
660                    document,
661                    tools::ToolLoop::Left,
662                    fgColor,
663                    bgColor)
664     , m_image(image)
665   {
666     m_celOrigin = celOrigin;
667 
668     // Avoid preview for spray and flood fill like tools
669     if (m_pointShape->isSpray()) {
670       m_pointShape = App::instance()->toolBox()->getPointShapeById(
671         tools::WellKnownPointShapes::Brush);
672     }
673     else if (m_pointShape->isFloodFill()) {
674       m_pointShape = App::instance()->toolBox()->getPointShapeById(
675         tools::WellKnownPointShapes::Pixel);
676     }
677   }
678 
679   // IToolLoop interface
commitOrRollback()680   void commitOrRollback() override {
681     // Do nothing
682   }
getSrcImage()683   const Image* getSrcImage() override { return m_image; }
getFloodFillSrcImage()684   const Image* getFloodFillSrcImage() override { return m_image; }
getDstImage()685   Image* getDstImage() override { return m_image; }
validateSrcImage(const gfx::Region & rgn)686   void validateSrcImage(const gfx::Region& rgn) override { }
validateDstImage(const gfx::Region & rgn)687   void validateDstImage(const gfx::Region& rgn) override { }
invalidateDstImage()688   void invalidateDstImage() override { }
invalidateDstImage(const gfx::Region & rgn)689   void invalidateDstImage(const gfx::Region& rgn) override { }
copyValidDstToSrcImage(const gfx::Region & rgn)690   void copyValidDstToSrcImage(const gfx::Region& rgn) override { }
691 
useMask()692   bool useMask() override { return false; }
getMask()693   Mask* getMask() override { return nullptr; }
setMask(Mask * newMask)694   void setMask(Mask* newMask) override { }
addSlice(Slice * newSlice)695   void addSlice(Slice* newSlice) override { }
getMaskOrigin()696   gfx::Point getMaskOrigin() override { return gfx::Point(0, 0); }
getFilled()697   bool getFilled() override { return false; }
getPreviewFilled()698   bool getPreviewFilled() override { return false; }
getSprayWidth()699   int getSprayWidth() override { return 0; }
getSpraySpeed()700   int getSpraySpeed() override { return 0; }
701 
cancel()702   void cancel() override { }
isCanceled()703   bool isCanceled() override { return true; }
704 
705 };
706 
create_tool_loop_preview(Editor * editor,Image * image,const gfx::Point & celOrigin)707 tools::ToolLoop* create_tool_loop_preview(
708   Editor* editor, Image* image,
709   const gfx::Point& celOrigin)
710 {
711   tools::Tool* tool = editor->getCurrentEditorTool();
712   tools::Ink* ink = editor->getCurrentEditorInk();
713   if (!tool || !ink)
714     return NULL;
715 
716   Layer* layer = editor->layer();
717   if (!layer ||
718       !layer->isVisibleHierarchy() ||
719       !layer->isEditableHierarchy() ||
720       layer->isReference()) {
721     return nullptr;
722   }
723 
724   // Get fg/bg colors
725   ColorBar* colorbar = ColorBar::instance();
726   app::Color fg = colorbar->getFgColor();
727   app::Color bg = colorbar->getBgColor();
728   if (!fg.isValid() || !bg.isValid())
729     return nullptr;
730 
731   // Create the new tool loop
732   try {
733     return new PreviewToolLoopImpl(
734       editor,
735       tool,
736       ink,
737       editor->document(),
738       fg, bg, image, celOrigin);
739   }
740   catch (const std::exception&) {
741     return nullptr;
742   }
743 }
744 
745 //////////////////////////////////////////////////////////////////////
746 
747 } // namespace app
748