Skip to content
Permalink
Browse files

Optimize ToolLoop with modified regions of pixels

Here we avoid copying and clearing pixels that will not be used
in the whole tool loop process.
Changes:
* Add several member functions in ToolLoop to validate/invalidate regions
  of source/destination images so we know what regions are safe to use
  by inks and can be shown in the editor
* Add new DocumentObserver::onExposeSpritePixels() member to validate
  pixels that will be displayed in the editor
* Add Ink::needs/createSpecialSourceArea() member functions to validate
  extra areas for inks like blur or jumble
* Add undoers::ModifiedRegion to save the undo information about the
  modified region
* Add ShowHideDrawingCursor class
* Change "blur" tool policy from overlap to accumulate

(This is a real fix for issue #239)
  • Loading branch information
dacap committed Dec 8, 2014
1 parent 07c7756 commit afbd3b2d96845942726c4bc246529e5a3d3fad3c
Showing with 1,057 additions and 599 deletions.
  1. +7 −7 data/gui.xml
  2. +1 −0 src/app/CMakeLists.txt
  3. +2 −1 src/app/commands/cmd_export_sprite_sheet.cpp
  4. +1 −2 src/app/commands/cmd_preview.cpp
  5. +8 −0 src/app/document.cpp
  6. +1 −0 src/app/document.h
  7. +6 −8 src/app/document_api.cpp
  8. +8 −3 src/app/settings/ui_settings_impl.cpp
  9. +2 −2 src/app/thumbnail_generator.cpp
  10. +10 −0 src/app/tools/ink.h
  11. +1 −1 src/app/tools/ink_processing.h
  12. +20 −1 src/app/tools/inks.h
  13. +4 −0 src/app/tools/intertwine.h
  14. +5 −6 src/app/tools/intertwiners.h
  15. +2 −1 src/app/tools/point_shapes.h
  16. +5 −5 src/app/tools/tool_box.cpp
  17. +24 −1 src/app/tools/tool_loop.h
  18. +49 −40 src/app/tools/tool_loop_manager.cpp
  19. +2 −5 src/app/tools/tool_loop_manager.h
  20. +29 −4 src/app/tools/trace_policy.h
  21. +19 −14 src/app/ui/editor/drawing_state.cpp
  22. +4 −1 src/app/ui/editor/drawing_state.h
  23. +88 −71 src/app/ui/editor/editor.cpp
  24. +4 −0 src/app/ui/editor/editor.h
  25. +7 −0 src/app/ui/editor/editor_state.h
  26. +3 −0 src/app/ui/editor/moving_pixels_state.cpp
  27. +13 −7 src/app/ui/editor/pixels_movement.cpp
  28. +51 −0 src/app/ui/editor/scoped_cursor.h
  29. +1 −0 src/app/ui/editor/select_box_state.cpp
  30. +7 −2 src/app/ui/editor/standby_state.cpp
  31. +44 −6 src/app/ui/editor/tool_loop_impl.cpp
  32. +91 −0 src/app/undoers/modified_region.cpp
  33. +62 −0 src/app/undoers/modified_region.h
  34. +216 −98 src/app/util/expand_cel_canvas.cpp
  35. +28 −16 src/app/util/expand_cel_canvas.h
  36. +8 −15 src/app/util/render.cpp
  37. +5 −4 src/app/util/render.h
  38. +15 −0 src/app/zoom.h
  39. +2 −2 src/doc/algorithm/rotsprite.cpp
  40. +1 −1 src/doc/brush.h
  41. +62 −51 src/doc/dirty.cpp
  42. +7 −7 src/doc/dirty.h
  43. +13 −12 src/doc/dirty_io.cpp
  44. +1 −0 src/doc/document_observer.h
  45. +2 −2 src/doc/image.h
  46. +101 −198 src/doc/image_impl.h
  47. +11 −3 src/doc/primitives.cpp
  48. +2 −0 src/doc/primitives.h
  49. +2 −2 src/doc/quantization.cpp
@@ -696,7 +696,7 @@
controller="freehand"
pointshape="pixel"
intertwine="as_lines"
tracepolicy="accumulative">
tracepolicy="accumulate">
<tooltip>*
Left-button: Replace/add to current selection.&#10;*
Right-button: Remove from current selection.
@@ -723,7 +723,7 @@
ink="selection"
controller="one_point"
pointshape="floodfill"
tracepolicy="accumulative">
tracepolicy="accumulate">
<tooltip>*
Left-button: Replace/add to current selection.&#10;*
Right-button: Remove from current selection.
@@ -738,7 +738,7 @@
controller="freehand"
pointshape="brush"
intertwine="as_lines"
tracepolicy="accumulative"
tracepolicy="accumulate"
/>
<tool id="spray"
text="Spray Tool"
@@ -757,7 +757,7 @@
controller="freehand"
pointshape="brush"
intertwine="as_lines"
tracepolicy="accumulative"
tracepolicy="accumulate"
default_brush_size="8">
<tooltip>*
Left-button: Erase with the background color in `Background' layer&#10;
@@ -810,7 +810,7 @@
ink="paint"
controller="one_point"
pointshape="floodfill"
tracepolicy="accumulative"
tracepolicy="accumulate"
/>
</group>

@@ -880,7 +880,7 @@
controller="freehand"
pointshape="brush"
intertwine="as_lines"
tracepolicy="accumulative"
tracepolicy="accumulate"
/>
<tool id="polygon"
text="Polygon Tool"
@@ -900,7 +900,7 @@
controller="freehand"
pointshape="brush"
intertwine="as_lines"
tracepolicy="overlap"
tracepolicy="accumulate"
default_brush_size="16"
/>
<tool id="jumble"
@@ -249,6 +249,7 @@ add_library(app-lib
undoers/dirty_area.cpp
undoers/flip_image.cpp
undoers/image_area.cpp
undoers/modified_region.cpp
undoers/move_layer.cpp
undoers/open_group.cpp
undoers/remap_palette.cpp
@@ -337,7 +337,8 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
// destination clipping bounds in Sprite::render() function.
tempImage->clear(0);
sprite->render(tempImage, 0, 0, frame);
resultImage->copy(tempImage, column*sprite->width(), row*sprite->height());
resultImage->copy(tempImage, column*sprite->width(), row*sprite->height(),
0, 0, tempImage->width(), tempImage->height());

if (++column >= columns) {
column = 0;
@@ -192,8 +192,7 @@ class PreviewWindow : public Window {

ImageBufferPtr buf = Editor::getRenderImageBuffer();
m_render.reset(
renderEngine.renderSprite(
0, 0, m_sprite->width(), m_sprite->height(),
renderEngine.renderSprite(m_sprite->bounds(),
m_editor->frame(), Zoom(1, 1), false, false, buf));
}

@@ -108,6 +108,14 @@ void Document::notifySpritePixelsModified(Sprite* sprite, const gfx::Region& reg
notifyObservers<doc::DocumentEvent&>(&doc::DocumentObserver::onSpritePixelsModified, ev);
}

void Document::notifyExposeSpritePixels(Sprite* sprite, const gfx::Region& region)
{
doc::DocumentEvent ev(this);
ev.sprite(sprite);
ev.region(region);
notifyObservers<doc::DocumentEvent&>(&doc::DocumentObserver::onExposeSpritePixels, ev);
}

void Document::notifyLayerMergedDown(Layer* srcLayer, Layer* targetLayer)
{
doc::DocumentEvent ev(this);
@@ -91,6 +91,7 @@ namespace app {

void notifyGeneralUpdate();
void notifySpritePixelsModified(Sprite* sprite, const gfx::Region& region);
void notifyExposeSpritePixels(Sprite* sprite, const gfx::Region& region);
void notifyLayerMergedDown(Layer* srcLayer, Layer* targetLayer);
void notifyCelMoved(Layer* fromLayer, FrameNumber fromFrame, Layer* toLayer, FrameNumber toFrame);
void notifyCelCopied(Layer* fromLayer, FrameNumber fromFrame, Layer* toLayer, FrameNumber toFrame);
@@ -1093,11 +1093,10 @@ void DocumentApi::flattenLayers(Sprite* sprite)

// We have to save the current state of `cel_image' in the undo.
if (undoEnabled()) {
Dirty* dirty = new Dirty(cel_image, image, image->bounds());
dirty->saveImagePixels(cel_image);
Dirty dirty(cel_image, image, image->bounds());
dirty.saveImagePixels(cel_image);
m_undoers->pushUndoer(new undoers::DirtyArea(
getObjects(), cel_image, dirty));
delete dirty;
getObjects(), cel_image, &dirty));
}
}
else {
@@ -1296,10 +1295,9 @@ void DocumentApi::flipImageWithMask(Layer* layer, Image* image, const Mask* mask

// Insert the undo operation.
if (undoEnabled()) {
base::UniquePtr<Dirty> dirty((new Dirty(image, flippedImage, image->bounds())));
dirty->saveImagePixels(image);

m_undoers->pushUndoer(new undoers::DirtyArea(getObjects(), image, dirty));
Dirty dirty(image, flippedImage, image->bounds());
dirty.saveImagePixels(image);
m_undoers->pushUndoer(new undoers::DirtyArea(getObjects(), image, &dirty));
}

// Copy the flipped image into the image specified as argument.
@@ -1026,18 +1026,23 @@ class UIToolSettingsImpl

tools::ToolBox* toolBox = App::instance()->getToolBox();
for (int i=0; i<2; ++i) {
if (m_tool->getTracePolicy(i) != tools::TracePolicy::Accumulate &&
m_tool->getTracePolicy(i) != tools::TracePolicy::AccumulateUpdateLast) {
continue;
}

switch (algorithm) {
case kDefaultFreehandAlgorithm:
m_tool->setIntertwine(i, toolBox->getIntertwinerById(tools::WellKnownIntertwiners::AsLines));
m_tool->setTracePolicy(i, tools::TracePolicyAccumulate);
m_tool->setTracePolicy(i, tools::TracePolicy::Accumulate);
break;
case kPixelPerfectFreehandAlgorithm:
m_tool->setIntertwine(i, toolBox->getIntertwinerById(tools::WellKnownIntertwiners::AsPixelPerfect));
m_tool->setTracePolicy(i, tools::TracePolicyLast);
m_tool->setTracePolicy(i, tools::TracePolicy::AccumulateUpdateLast);
break;
case kDotsFreehandAlgorithm:
m_tool->setIntertwine(i, toolBox->getIntertwinerById(tools::WellKnownIntertwiners::None));
m_tool->setTracePolicy(i, tools::TracePolicyAccumulate);
m_tool->setTracePolicy(i, tools::TracePolicy::Accumulate);
break;
}
}
@@ -85,8 +85,8 @@ class ThumbnailGenerator::Worker {

doc::ImageBufferPtr thumbnail_buffer(new doc::ImageBuffer);
base::UniquePtr<Image> image(renderEngine.renderSprite(
0, 0, sprite->width(), sprite->height(),
FrameNumber(0), Zoom(1, 1), true, false,
sprite->bounds(), FrameNumber(0),
Zoom(1, 1), true, false,
thumbnail_buffer));

// Calculate the thumbnail size
@@ -20,6 +20,10 @@
#define APP_TOOLS_INK_H_INCLUDED
#pragma once

namespace gfx {
class Region;
}

namespace app {
namespace tools {

@@ -64,6 +68,12 @@ namespace app {
// Returns true if this ink is used to mark slices
virtual bool isSlice() const { return false; }

// Returns true if this ink needs a special source area. For
// example, blur tool needs one extra pixel to all sides of the
// modified area, so it can use a 3x3 convolution matrix.
virtual bool needsSpecialSourceArea() const { return false; }
virtual void createSpecialSourceArea(const gfx::Region& dirtyArea, gfx::Region& sourceArea) const { }

// It is called when the tool-loop start (generally when the user
// presses a mouse button over a sprite editor)
virtual void prepareInk(ToolLoop* loop) { }
@@ -587,7 +587,7 @@ class JumbleInkProcessing : public DoubleInkProcessing<JumbleInkProcessing<Image
Point m_speed;
int m_opacity;
TiledMode m_tiledMode;
Image* m_srcImage;
const Image* m_srcImage;
int m_srcImageWidth;
int m_srcImageHeight;
color_t m_color;
@@ -1,5 +1,5 @@
/* Aseprite
* Copyright (C) 2001-2013 David Capello
* Copyright (C) 2001-2014 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,6 +27,7 @@
#include "app/tools/pick_ink.h"
#include "app/undoers/set_mask.h"
#include "doc/mask.h"
#include "gfx/region.h"

namespace app {
namespace tools {
@@ -220,6 +221,7 @@ class BlurInk : public Ink {
public:
bool isPaint() const { return true; }
bool isEffect() const { return true; }
bool needsSpecialSourceArea() const { return true; }

void prepareInk(ToolLoop* loop)
{
@@ -230,6 +232,14 @@ class BlurInk : public Ink {
{
(*m_proc)(x1, y, x2, loop);
}

void createSpecialSourceArea(const gfx::Region& dirtyArea, gfx::Region& sourceArea) const {
// We need one pixel more for each side, to use a 3x3 convolution matrix.
for (const auto& rc : dirtyArea) {
sourceArea.createUnion(sourceArea,
gfx::Region(gfx::Rect(rc).enlarge(1)));
}
}
};


@@ -239,6 +249,7 @@ class JumbleInk : public Ink {
public:
bool isPaint() const { return true; }
bool isEffect() const { return true; }
bool needsSpecialSourceArea() const { return true; }

void prepareInk(ToolLoop* loop)
{
@@ -249,6 +260,14 @@ class JumbleInk : public Ink {
{
(*m_proc)(x1, y, x2, loop);
}

void createSpecialSourceArea(const gfx::Region& dirtyArea, gfx::Region& sourceArea) const {
// We need one pixel more for each side.
for (const auto& rc : dirtyArea) {
sourceArea.createUnion(sourceArea,
gfx::Region(gfx::Rect(rc).enlarge(1)));
}
}
};


@@ -28,6 +28,10 @@ namespace app {
namespace tools {
class ToolLoop;

// Converts a sequence of points in several call to
// Intertwine::doPointshapePoint(). Basically each implementation
// says which pixels should be drawn between a sequence of
// user-defined points.
class Intertwine {
public:
typedef std::vector<gfx::Point> Points;
@@ -254,12 +254,11 @@ class IntertwineAsPixelPerfect : public Intertwine {
PPData data(m_pts, loop);

for (size_t c=0; c+1<points.size(); ++c) {
int x1 = points[c].x;
int y1 = points[c].y;
int x2 = points[c+1].x;
int y2 = points[c+1].y;

algo_line(x1, y1, x2, y2,
algo_line(
points[c].x,
points[c].y,
points[c+1].x,
points[c+1].y,
(void*)&data,
(AlgoPixel)&IntertwineAsPixelPerfect::pixelPerfectLine);
}
@@ -75,7 +75,8 @@ class FloodFillPointShape : public PointShape {

void transformPoint(ToolLoop* loop, int x, int y)
{
doc::algorithm::floodfill(loop->getSrcImage(), x, y,
doc::algorithm::floodfill(
const_cast<Image*>(loop->getSrcImage()), x, y,
paintBounds(loop, x, y),
loop->getTolerance(),
loop->getContiguous(),
@@ -275,14 +275,14 @@ void ToolBox::loadToolProperties(TiXmlElement* xmlTool, Tool* tool, int button,
throw base::Exception("Invalid intertwiner '%s' specified in '%s' tool.\n", intertwine, tool_id);

// Trace policy
TracePolicy tracepolicy_value = TracePolicyLast;
TracePolicy tracepolicy_value = TracePolicy::Last;
if (tracepolicy) {
if (strcmp(tracepolicy, "accumulative") == 0)
tracepolicy_value = TracePolicyAccumulate;
if (strcmp(tracepolicy, "accumulate") == 0)
tracepolicy_value = TracePolicy::Accumulate;
else if (strcmp(tracepolicy, "last") == 0)
tracepolicy_value = TracePolicyLast;
tracepolicy_value = TracePolicy::Last;
else if (strcmp(tracepolicy, "overlap") == 0)
tracepolicy_value = TracePolicyOverlap;
tracepolicy_value = TracePolicy::Overlap;
else
throw base::Exception("Invalid trace-policy '%s' specified in '%s' tool.\n", tracepolicy, tool_id);
}
@@ -88,11 +88,34 @@ namespace app {
virtual FrameNumber getFrame() = 0;

// Should return an image where we can read pixels (readonly image)
virtual Image* getSrcImage() = 0;
virtual const Image* getSrcImage() = 0;

// Should return an image where we can write pixels
virtual Image* getDstImage() = 0;

// Makes the specified region valid in the source
// image. Basically the implementation should copy from the
// original cel the given region to the source image. The source
// image is used by inks to create blur effects or similar.
virtual void validateSrcImage(const gfx::Region& rgn) = 0;

// Makes the specified destination image region valid to be
// painted. The destination image is used by inks to compose the
// brush, so we've to make sure that the destination image
// matches the original cel when we make that composition.
virtual void validateDstImage(const gfx::Region& rgn) = 0;

// Invalidates the whole destination image. It's used for tools
// like line or rectangle which don't accumulate the effect so
// they need to start with a fresh destination image on each
// loop step/cycle.
virtual void invalidateDstImage() = 0;
virtual void invalidateDstImage(const gfx::Region& rgn) = 0;

// Copies the given region from the destination to the source
// image, used by "overlap" tools like jumble or spray.
virtual void copyValidDstToSrcImage(const gfx::Region& rgn) = 0;

// Returns the RGB map used to convert RGB values to palette index.
virtual RgbMap* getRgbMap() = 0;

0 comments on commit afbd3b2

Please sign in to comment.
You can’t perform that action at this time.