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