diff --git a/data/gui.xml b/data/gui.xml index 6763386ca..f88d258f3 100644 --- a/data/gui.xml +++ b/data/gui.xml @@ -696,7 +696,7 @@ controller="freehand" pointshape="pixel" intertwine="as_lines" - tracepolicy="accumulative"> + tracepolicy="accumulate"> * Left-button: Replace/add to current selection. * Right-button: Remove from current selection. @@ -723,7 +723,7 @@ ink="selection" controller="one_point" pointshape="floodfill" - tracepolicy="accumulative"> + tracepolicy="accumulate"> * Left-button: Replace/add to current selection. * Right-button: Remove from current selection. @@ -738,7 +738,7 @@ controller="freehand" pointshape="brush" intertwine="as_lines" - tracepolicy="accumulative" + tracepolicy="accumulate" /> * Left-button: Erase with the background color in `Background' layer @@ -810,7 +810,7 @@ ink="paint" controller="one_point" pointshape="floodfill" - tracepolicy="accumulative" + tracepolicy="accumulate" /> @@ -880,7 +880,7 @@ controller="freehand" pointshape="brush" intertwine="as_lines" - tracepolicy="accumulative" + tracepolicy="accumulate" /> 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; diff --git a/src/app/commands/cmd_preview.cpp b/src/app/commands/cmd_preview.cpp index 89f416d3d..7779e6140 100644 --- a/src/app/commands/cmd_preview.cpp +++ b/src/app/commands/cmd_preview.cpp @@ -192,8 +192,7 @@ protected: 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)); } diff --git a/src/app/document.cpp b/src/app/document.cpp index 6b9864cc5..c3b073f60 100644 --- a/src/app/document.cpp +++ b/src/app/document.cpp @@ -108,6 +108,14 @@ void Document::notifySpritePixelsModified(Sprite* sprite, const gfx::Region& reg notifyObservers(&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::DocumentObserver::onExposeSpritePixels, ev); +} + void Document::notifyLayerMergedDown(Layer* srcLayer, Layer* targetLayer) { doc::DocumentEvent ev(this); diff --git a/src/app/document.h b/src/app/document.h index 8e64abbdd..caf762734 100644 --- a/src/app/document.h +++ b/src/app/document.h @@ -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); diff --git a/src/app/document_api.cpp b/src/app/document_api.cpp index 6fe3f9606..62bd77aee 100644 --- a/src/app/document_api.cpp +++ b/src/app/document_api.cpp @@ -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((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. diff --git a/src/app/settings/ui_settings_impl.cpp b/src/app/settings/ui_settings_impl.cpp index 5de3bee7d..5991dace6 100644 --- a/src/app/settings/ui_settings_impl.cpp +++ b/src/app/settings/ui_settings_impl.cpp @@ -1026,18 +1026,23 @@ public: 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; } } diff --git a/src/app/thumbnail_generator.cpp b/src/app/thumbnail_generator.cpp index b4f1cd983..6972f056e 100644 --- a/src/app/thumbnail_generator.cpp +++ b/src/app/thumbnail_generator.cpp @@ -85,8 +85,8 @@ private: doc::ImageBufferPtr thumbnail_buffer(new doc::ImageBuffer); base::UniquePtr 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 diff --git a/src/app/tools/ink.h b/src/app/tools/ink.h index 604c10799..6e33846d3 100644 --- a/src/app/tools/ink.h +++ b/src/app/tools/ink.h @@ -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) { } diff --git a/src/app/tools/ink_processing.h b/src/app/tools/ink_processing.h index 903e94602..c26c4ba03 100644 --- a/src/app/tools/ink_processing.h +++ b/src/app/tools/ink_processing.h @@ -587,7 +587,7 @@ private: 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; diff --git a/src/app/tools/inks.h b/src/app/tools/inks.h index 596dc7024..fcbe97fdb 100644 --- a/src/app/tools/inks.h +++ b/src/app/tools/inks.h @@ -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 @@ public: { (*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 @@ public: { (*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))); + } + } }; diff --git a/src/app/tools/intertwine.h b/src/app/tools/intertwine.h index 5978ef1fc..49062bbf2 100644 --- a/src/app/tools/intertwine.h +++ b/src/app/tools/intertwine.h @@ -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 Points; diff --git a/src/app/tools/intertwiners.h b/src/app/tools/intertwiners.h index 212312855..8d2e312fa 100644 --- a/src/app/tools/intertwiners.h +++ b/src/app/tools/intertwiners.h @@ -254,12 +254,11 @@ public: PPData data(m_pts, loop); for (size_t c=0; c+1getSrcImage(), x, y, + doc::algorithm::floodfill( + const_cast(loop->getSrcImage()), x, y, paintBounds(loop, x, y), loop->getTolerance(), loop->getContiguous(), diff --git a/src/app/tools/tool_box.cpp b/src/app/tools/tool_box.cpp index cccf318b4..ee505283a 100644 --- a/src/app/tools/tool_box.cpp +++ b/src/app/tools/tool_box.cpp @@ -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); } diff --git a/src/app/tools/tool_loop.h b/src/app/tools/tool_loop.h index eee2d6c3b..58664d0c2 100644 --- a/src/app/tools/tool_loop.h +++ b/src/app/tools/tool_loop.h @@ -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; diff --git a/src/app/tools/tool_loop_manager.cpp b/src/app/tools/tool_loop_manager.cpp index 1e8a7b25e..309c63288 100644 --- a/src/app/tools/tool_loop_manager.cpp +++ b/src/app/tools/tool_loop_manager.cpp @@ -44,6 +44,7 @@ using namespace filters; ToolLoopManager::ToolLoopManager(ToolLoop* toolLoop) : m_toolLoop(toolLoop) + , m_dirtyArea(toolLoop->getDirtyArea()) { } @@ -61,10 +62,6 @@ void ToolLoopManager::prepareLoop(const Pointer& pointer) // Start with no points at all m_points.clear(); - // Prepare the image where we will draw on - copy_image(m_toolLoop->getDstImage(), - m_toolLoop->getSrcImage(), 0, 0); - // Prepare the ink m_toolLoop->getInk()->prepareInk(m_toolLoop); m_toolLoop->getIntertwine()->prepareIntertwine(); @@ -185,25 +182,34 @@ void ToolLoopManager::doLoopStep(bool last_step) for (size_t i=0; igetTracePolicy()) { + // Calculate the area to be updated in all document observers. + calculateDirtyArea(points_to_interwine); - case TracePolicyAccumulate: - // Do nothing. We accumulate traces in the destination image. - break; - - case TracePolicyLast: - // Copy source to destination (reset the previous trace). Useful - // for tools like Line and Ellipse tools (we kept the last trace only). - clear_image(m_toolLoop->getDstImage(), 0); - copy_image(m_toolLoop->getDstImage(), m_toolLoop->getSrcImage(), 0, 0); - break; - - case TracePolicyOverlap: - // Copy destination to source (yes, destination to source). In - // this way each new trace overlaps the previous one. - copy_image(m_toolLoop->getSrcImage(), m_toolLoop->getDstImage(), 0, 0); - break; + // Validate source image area. + if (m_toolLoop->getInk()->needsSpecialSourceArea()) { + gfx::Region srcArea; + m_toolLoop->getInk()->createSpecialSourceArea(m_dirtyArea, srcArea); + m_toolLoop->validateSrcImage(srcArea); } + else { + m_toolLoop->validateSrcImage(m_dirtyArea); + } + + // Invalidate destionation image areas. + if (m_toolLoop->getTracePolicy() == TracePolicy::Last) { + // Copy source to destination (reset the previous trace). Useful + // for tools like Line and Ellipse (we kept the last trace only). + m_toolLoop->invalidateDstImage(); + } + else if (m_toolLoop->getTracePolicy() == TracePolicy::AccumulateUpdateLast) { + // Revalidate only this last dirty area (e.g. pixel-perfect + // freehand algorithm needs this trace policy to redraw only the + // last dirty area, which can vary in one pixel from the previous + // tool loop cycle). + m_toolLoop->invalidateDstImage(m_dirtyArea); + } + + m_toolLoop->validateDstImage(m_dirtyArea); // Get the modified area in the sprite with this intertwined set of points if (!m_toolLoop->getFilled() || (!last_step && !m_toolLoop->getPreviewFilled())) @@ -211,17 +217,13 @@ void ToolLoopManager::doLoopStep(bool last_step) else m_toolLoop->getIntertwine()->fillPoints(m_toolLoop, points_to_interwine); - // Calculate the area to be updated in all document observers. - Region& dirty_area = m_toolLoop->getDirtyArea(); - calculateDirtyArea(points_to_interwine, dirty_area); - - if (m_toolLoop->getTracePolicy() == TracePolicyLast) { - Region prev_dirty_area = dirty_area; - dirty_area.createUnion(dirty_area, m_oldDirtyArea); - m_oldDirtyArea = prev_dirty_area; + if (m_toolLoop->getTracePolicy() == TracePolicy::Overlap) { + // Copy destination to source (yes, destination to source). In + // this way each new trace overlaps the previous one. + m_toolLoop->copyValidDstToSrcImage(m_dirtyArea); } - if (!dirty_area.isEmpty()) + if (!m_dirtyArea.isEmpty()) m_toolLoop->updateDirtyArea(); } @@ -235,9 +237,15 @@ void ToolLoopManager::snapToGrid(Point& point) m_toolLoop->getDocumentSettings()->snapToGrid(point); } -void ToolLoopManager::calculateDirtyArea(const Points& points, Region& dirty_area) +void ToolLoopManager::calculateDirtyArea(const Points& points) { - dirty_area.clear(); + // Save the current dirty area if it's needed + Region prevDirtyArea; + if (m_toolLoop->getTracePolicy() == TracePolicy::Last) + prevDirtyArea = m_dirtyArea; + + // Start with a fresh dirty area + m_dirtyArea.clear(); if (points.size() > 0) { Point minpt, maxpt; @@ -248,12 +256,18 @@ void ToolLoopManager::calculateDirtyArea(const Points& points, Region& dirty_are m_toolLoop->getPointShape()->getModifiedArea(m_toolLoop, minpt.x, minpt.y, r1); m_toolLoop->getPointShape()->getModifiedArea(m_toolLoop, maxpt.x, maxpt.y, r2); - dirty_area.createUnion(dirty_area, Region(r1.createUnion(r2))); + m_dirtyArea.createUnion(m_dirtyArea, Region(r1.createUnion(r2))); } // Apply offset mode Point offset(m_toolLoop->getOffset()); - dirty_area.offset(-offset); + m_dirtyArea.offset(-offset); + + // Merge new dirty area with the previous one (for tools like line + // or rectangle it's needed to redraw the previous position and + // the new one) + if (m_toolLoop->getTracePolicy() == TracePolicy::Last) + m_dirtyArea.createUnion(m_dirtyArea, prevDirtyArea); // Apply tiled mode TiledMode tiledMode = m_toolLoop->getDocumentSettings()->getTiledMode(); @@ -262,7 +276,7 @@ void ToolLoopManager::calculateDirtyArea(const Points& points, Region& dirty_are int h = m_toolLoop->sprite()->height(); Region sprite_area(Rect(0, 0, w, h)); Region outside; - outside.createSubtraction(dirty_area, sprite_area); + outside.createSubtraction(m_dirtyArea, sprite_area); switch (tiledMode) { case TILED_X_AXIS: @@ -282,7 +296,7 @@ void ToolLoopManager::calculateDirtyArea(const Points& points, Region& dirty_are Region in_sprite; in_sprite.createIntersection(outside, sprite_area); outside.createSubtraction(outside, in_sprite); - dirty_area.createUnion(dirty_area, in_sprite); + m_dirtyArea.createUnion(m_dirtyArea, in_sprite); outsideBounds = outside.bounds(); if (outsideBounds.isEmpty()) @@ -312,11 +326,6 @@ void ToolLoopManager::calculateMinMax(const Points& points, Point& minpt, Point& maxpt.x = MAX(maxpt.x, points[c].x); maxpt.y = MAX(maxpt.y, points[c].y); } - - if (m_toolLoop->zoom().scale() < 1.0) { - maxpt.x += m_toolLoop->zoom().remove(1); - maxpt.y += m_toolLoop->zoom().remove(1); - } } } // namespace tools diff --git a/src/app/tools/tool_loop_manager.h b/src/app/tools/tool_loop_manager.h index adc8e7519..e8da33ece 100644 --- a/src/app/tools/tool_loop_manager.h +++ b/src/app/tools/tool_loop_manager.h @@ -103,10 +103,7 @@ namespace app { void doLoopStep(bool last_step); void snapToGrid(gfx::Point& point); - void calculateDirtyArea( - const Points& points, - gfx::Region& dirty_area); - + void calculateDirtyArea(const Points& points); void calculateMinMax(const Points& points, gfx::Point& minpt, gfx::Point& maxpt); @@ -114,7 +111,7 @@ namespace app { ToolLoop* m_toolLoop; Points m_points; gfx::Point m_oldPoint; - gfx::Region m_oldDirtyArea; + gfx::Region& m_dirtyArea; }; } // namespace tools diff --git a/src/app/tools/trace_policy.h b/src/app/tools/trace_policy.h index ebd675f56..289107c83 100644 --- a/src/app/tools/trace_policy.h +++ b/src/app/tools/trace_policy.h @@ -23,10 +23,35 @@ namespace app { namespace tools { - enum TracePolicy { - TracePolicyAccumulate, - TracePolicyLast, - TracePolicyOverlap, + // The trace policy indicates how pixels are updated between the + // source image (ToolLoop::getSrcImage) and destionation image + // (ToolLoop::getDstImage) in the whole ToolLoopManager life-time. + // Basically it says if we should accumulate the intertwined + // drawed points, or kept the last ones, or overlap/composite + // them, etc. + enum class TracePolicy { + + // All pixels are accumulated in the destination image. Used by + // freehand like tools. + Accumulate, + + // It's like accumulate, but the last modified area in the + // destination is invalidated and redraw from the source image + + // tool trace. It's used by pixel-perfect freehand algorithm + // (because last modified pixels can differ). + AccumulateUpdateLast, + + // Only the last trace is used. It means that on each ToolLoop + // step, the destination image is completely invalidated and + // restored from the source image. Used by + // line/rectangle/ellipse-like tools. + Last, + + // Like accumulate, but the destination is copied to the source + // on each ToolLoop step, so the tool overlaps its own effect. + // Used by blur, jumble, or spray. + Overlap, + }; } // namespace tools diff --git a/src/app/ui/editor/drawing_state.cpp b/src/app/ui/editor/drawing_state.cpp index 34c3f4b60..5191eb1c8 100644 --- a/src/app/ui/editor/drawing_state.cpp +++ b/src/app/ui/editor/drawing_state.cpp @@ -31,6 +31,7 @@ #include "app/tools/tool_loop.h" #include "app/tools/tool_loop_manager.h" #include "app/ui/editor/editor.h" +#include "app/ui/editor/scoped_cursor.h" #include "app/ui/keyboard_shortcuts.h" #include "app/ui_context.h" #include "doc/blend.h" @@ -60,21 +61,11 @@ static tools::ToolLoopManager::Pointer pointer_from_msg(MouseMessage* msg) tools::ToolLoopManager::Pointer(msg->position().x, msg->position().y, button_from_msg(msg)); } -DrawingState::DrawingState(tools::ToolLoop* toolLoop, Editor* editor, MouseMessage* msg) +DrawingState::DrawingState(tools::ToolLoop* toolLoop) : m_toolLoop(toolLoop) , m_toolLoopManager(new tools::ToolLoopManager(toolLoop)) , m_mouseMoveReceived(false) { - // Hide the cursor (mainly to clean the pen preview) - editor->hideDrawingCursor(); - - m_toolLoopManager->prepareLoop(pointer_from_msg(msg)); - m_toolLoopManager->pressButton(pointer_from_msg(msg)); - - // Show drawing cursor again (without pen preview in this case) - editor->showDrawingCursor(); - - editor->captureMouse(); } DrawingState::~DrawingState() @@ -82,6 +73,16 @@ DrawingState::~DrawingState() destroyLoop(); } +void DrawingState::initToolLoop(Editor* editor, MouseMessage* msg) +{ + HideShowDrawingCursor hideShow(editor); + + m_toolLoopManager->prepareLoop(pointer_from_msg(msg)); + m_toolLoopManager->pressButton(pointer_from_msg(msg)); + + editor->captureMouse(); +} + bool DrawingState::onMouseDown(Editor* editor, MouseMessage* msg) { // Drawing loop @@ -139,7 +140,7 @@ bool DrawingState::onMouseMove(Editor* editor, MouseMessage* msg) m_mouseMoveReceived = true; // Hide the drawing cursor - editor->hideDrawingCursor(); + HideShowDrawingCursor hideShow(editor); // Infinite scroll gfx::Point mousePos = editor->autoScroll(msg, AutoScroll::MouseDir, true); @@ -153,8 +154,6 @@ bool DrawingState::onMouseMove(Editor* editor, MouseMessage* msg) ->movement(tools::ToolLoopManager::Pointer(mousePos.x, mousePos.y, button_from_msg(msg))); - // draw the cursor again - editor->showDrawingCursor(); return true; } @@ -203,6 +202,12 @@ bool DrawingState::onUpdateStatusBar(Editor* editor) return false; } +void DrawingState::onExposeSpritePixels(const gfx::Region& rgn) +{ + if (m_toolLoop) + m_toolLoop->validateDstImage(rgn); +} + void DrawingState::destroyLoop() { delete m_toolLoopManager; diff --git a/src/app/ui/editor/drawing_state.h b/src/app/ui/editor/drawing_state.h index 4e9ddaaaa..6de2e1615 100644 --- a/src/app/ui/editor/drawing_state.h +++ b/src/app/ui/editor/drawing_state.h @@ -30,7 +30,7 @@ namespace app { class DrawingState : public StandbyState { public: - DrawingState(tools::ToolLoop* loop, Editor* editor, ui::MouseMessage* msg); + DrawingState(tools::ToolLoop* loop); virtual ~DrawingState(); virtual bool onMouseDown(Editor* editor, ui::MouseMessage* msg) override; virtual bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override; @@ -39,11 +39,14 @@ namespace app { virtual bool onKeyDown(Editor* editor, ui::KeyMessage* msg) override; virtual bool onKeyUp(Editor* editor, ui::KeyMessage* msg) override; virtual bool onUpdateStatusBar(Editor* editor) override; + virtual void onExposeSpritePixels(const gfx::Region& rgn) override; // Drawing state doesn't require the brush-preview because we are // already drawing (viewing the real trace). virtual bool requireBrushPreview() override { return false; } + void initToolLoop(Editor* editor, ui::MouseMessage* msg); + private: void destroyLoop(); diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 482ad3673..f7d0cbe15 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -44,6 +44,7 @@ #include "app/ui/editor/editor_decorator.h" #include "app/ui/editor/moving_pixels_state.h" #include "app/ui/editor/pixels_movement.h" +#include "app/ui/editor/scoped_cursor.h" #include "app/ui/editor/standby_state.h" #include "app/ui/main_window.h" #include "app/ui/skin/skin_theme.h" @@ -57,6 +58,7 @@ #include "base/unique_ptr.h" #include "doc/conversion_she.h" #include "doc/doc.h" +#include "doc/document_event.h" #include "she/surface.h" #include "she/system.h" #include "ui/ui.h" @@ -177,11 +179,15 @@ Editor::Editor(Document* document, EditorFlags flags) ->getDocumentSettings(m_document) ->addObserver(this); + m_document->addObserver(this); + m_state->onAfterChangeState(this); } Editor::~Editor() { + m_document->removeObserver(this); + UIContext::instance()->settings() ->getDocumentSettings(m_document) ->removeObserver(this); @@ -201,7 +207,7 @@ WidgetType editor_type() void Editor::setStateInternal(const EditorStatePtr& newState) { - hideDrawingCursor(); + HideShowDrawingCursor hideShow(this); // Fire before change state event, set the state, and fire after // change state event. @@ -229,9 +235,6 @@ void Editor::setStateInternal(const EditorStatePtr& newState) // Change to the new state. m_state->onAfterChangeState(this); - // Redraw all the editors with the same document of this editor - update_screen_for_document(m_document); - // Notify observers m_observers.notifyStateChanged(this); @@ -337,55 +340,36 @@ void Editor::updateEditor() View::getView(this)->updateView(); } -void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& rc, int dx, int dy) +void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& spriteRectToDraw, int dx, int dy) { - // Output information - int source_x = m_zoom.apply(rc.x); - int source_y = m_zoom.apply(rc.y); - int dest_x = dx + m_offset_x + source_x; - int dest_y = dy + m_offset_y + source_y; - int width = m_zoom.apply(rc.w); - int height = m_zoom.apply(rc.h); + // Clip from sprite and apply zoom + gfx::Rect rc = m_sprite->bounds().createIntersect(spriteRectToDraw); + rc = m_zoom.apply(rc); + + int dest_x = dx + m_offset_x + rc.x; + int dest_y = dy + m_offset_y + rc.y; // Clip from graphics/screen const gfx::Rect& clip = g->getClipBounds(); if (dest_x < clip.x) { - source_x += clip.x - dest_x; - width -= clip.x - dest_x; + rc.x += clip.x - dest_x; + rc.w -= clip.x - dest_x; dest_x = clip.x; } if (dest_y < clip.y) { - source_y += clip.y - dest_y; - height -= clip.y - dest_y; + rc.y += clip.y - dest_y; + rc.h -= clip.y - dest_y; dest_y = clip.y; } - if (dest_x+width > clip.x+clip.w) { - width = clip.x+clip.w-dest_x; + if (dest_x+rc.w > clip.x+clip.w) { + rc.w = clip.x+clip.w-dest_x; } - if (dest_y+height > clip.y+clip.h) { - height = clip.y+clip.h-dest_y; - } - - // Clip from sprite - if (source_x < 0) { - width += source_x; - dest_x -= source_x; - source_x = 0; - } - if (source_y < 0) { - height += source_y; - dest_y -= source_y; - source_y = 0; - } - if (source_x+width > m_zoom.apply(m_sprite->width())) { - width = m_zoom.apply(m_sprite->width()) - source_x; - } - if (source_y+height > m_zoom.apply(m_sprite->height())) { - height = m_zoom.apply(m_sprite->height()) - source_y; + if (dest_y+rc.h > clip.y+clip.h) { + rc.h = clip.y+clip.h-dest_y; } // Draw the sprite - if ((width > 0) && (height > 0)) { + if ((rc.w > 0) && (rc.h > 0)) { RenderEngine renderEngine(m_document, m_sprite, m_layer, m_frame); // Generate the rendered image @@ -394,9 +378,22 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& rc, in base::UniquePtr rendered(NULL); try { + // Generate a "expose sprite pixels" notification. This is used by + // tool managers that need to validate this region (copy pixels from + // the original cel) before it can be used by the RenderEngine. + { + gfx::Rect expose = m_zoom.remove(rc); + // If the zoom level is less than 100%, we add extra pixels to + // the exposed area. Those pixels could be shown in the + // rendering process depending on each cel position. + // E.g. when we are drawing in a cel with position < (0,0) + if (m_zoom.scale() < 1.0) + expose.enlarge(1./m_zoom.scale()); + m_document->notifyExposeSpritePixels(m_sprite, gfx::Region(expose)); + } + rendered.reset(renderEngine.renderSprite( - source_x, source_y, width, height, - m_frame, m_zoom, true, + rc, m_frame, m_zoom, true, ((m_flags & kShowOnionskin) == kShowOnionskin), render_buffer)); } @@ -408,23 +405,30 @@ void Editor::drawOneSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& rc, in // Pre-render decorator. if ((m_flags & kShowDecorators) && m_decorator) { EditorPreRenderImpl preRender(this, rendered, - Point(-source_x, -source_y), m_zoom); + Point(-rc.x, -rc.y), m_zoom); m_decorator->preRenderDecorator(&preRender); } - she::Surface* tmp(she::instance()->createRgbaSurface(width, height)); + // Convert the render to a she::Surface + she::Surface* tmp(she::instance()->createRgbaSurface(rc.w, rc.h)); if (tmp->nativeHandle()) { convert_image_to_surface(rendered, m_sprite->getPalette(m_frame), - tmp, 0, 0, 0, 0, width, height); - g->blit(tmp, 0, 0, dest_x, dest_y, width, height); + tmp, 0, 0, 0, 0, rc.w, rc.h); + g->blit(tmp, 0, 0, dest_x, dest_y, rc.w, rc.h); } tmp->dispose(); } } } -void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& rc) +void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& _rc) { + gfx::Rect rc = _rc; + // For odd zoom scales minor than 100% we have to add an extra window + // just to make sure the whole rectangle is drawn. + if (m_zoom.scale() < 1.0) + rc.inflate(1./m_zoom.scale(), 1./m_zoom.scale()); + gfx::Rect client = getClientBounds(); gfx::Rect spriteRect( client.x + m_offset_x, @@ -481,35 +485,42 @@ void Editor::drawSpriteUnclippedRect(ui::Graphics* g, const gfx::Rect& rc) // Grids { // Clipping - IntersectClip clip(g, editorToScreen(rc).offset(-getBounds().getOrigin())); + gfx::Rect cliprc = editorToScreen(rc).offset(-getBounds().getOrigin()); + cliprc = cliprc.createIntersect(spriteRect); + if (!cliprc.isEmpty()) { + IntersectClip clip(g, cliprc); - // Draw the pixel grid - if ((m_zoom.scale() > 2.0) && docSettings->getPixelGridVisible()) { - int alpha = docSettings->getPixelGridOpacity(); + // Draw the pixel grid + if ((m_zoom.scale() > 2.0) && docSettings->getPixelGridVisible()) { + int alpha = docSettings->getPixelGridOpacity(); - if (docSettings->getPixelGridAutoOpacity()) { - alpha = int(alpha * (m_zoom.scale()-2.) / (16.-2.)); - alpha = MID(0, alpha, 255); + if (docSettings->getPixelGridAutoOpacity()) { + alpha = int(alpha * (m_zoom.scale()-2.) / (16.-2.)); + alpha = MID(0, alpha, 255); + } + + drawGrid(g, enclosingRect, Rect(0, 0, 1, 1), + docSettings->getPixelGridColor(), alpha); } - drawGrid(g, enclosingRect, Rect(0, 0, 1, 1), - docSettings->getPixelGridColor(), alpha); - } + // Draw the grid + if (docSettings->getGridVisible()) { + gfx::Rect gridrc = docSettings->getGridBounds(); + if (m_zoom.apply(gridrc.w) > 2 && + m_zoom.apply(gridrc.h) > 2) { + int alpha = docSettings->getGridOpacity(); - // Draw the grid - if (docSettings->getGridVisible()) { - int alpha = docSettings->getGridOpacity(); + if (docSettings->getGridAutoOpacity()) { + double len = (m_zoom.apply(gridrc.w) + m_zoom.apply(gridrc.h)) / 2.; + alpha = int(alpha * len / 32.); + alpha = MID(0, alpha, 255); + } - if (docSettings->getGridAutoOpacity()) { - gfx::Rect rc = docSettings->getGridBounds(); - double len = (m_zoom.apply(rc.w) + m_zoom.apply(rc.h)) / 2.; - - alpha = int(alpha * len / 32.); - alpha = MID(0, alpha, 255); + if (alpha > 8) + drawGrid(g, enclosingRect, docSettings->getGridBounds(), + docSettings->getGridColor(), alpha); + } } - - drawGrid(g, enclosingRect, docSettings->getGridBounds(), - docSettings->getGridColor(), alpha); } } @@ -1317,6 +1328,12 @@ void Editor::onFgColorChange() } } +void Editor::onExposeSpritePixels(doc::DocumentEvent& ev) +{ + if (m_state && ev.sprite() == m_sprite) + m_state->onExposeSpritePixels(ev.region()); +} + void Editor::editor_setcursor() { bool used = false; diff --git a/src/app/ui/editor/editor.h b/src/app/ui/editor/editor.h index 19a5b20b7..b14d9037a 100644 --- a/src/app/ui/editor/editor.h +++ b/src/app/ui/editor/editor.h @@ -29,6 +29,7 @@ #include "app/ui/editor/editor_states_history.h" #include "app/zoom.h" #include "base/connection.h" +#include "doc/document_observer.h" #include "doc/frame_number.h" #include "doc/image_buffer.h" #include "gfx/fwd.h" @@ -66,6 +67,7 @@ namespace app { }; class Editor : public ui::Widget, + public doc::DocumentObserver, public DocumentSettingsObserver { public: typedef void (*PixelDelegate)(ui::Graphics*, const gfx::Point&, gfx::Color); @@ -216,6 +218,8 @@ namespace app { void onCurrentToolChange(); void onFgColorChange(); + void onExposeSpritePixels(doc::DocumentEvent& ev); + void onSetTiledMode(filters::TiledMode mode); void onSetGridVisible(bool state); void onSetGridBounds(const gfx::Rect& rect); diff --git a/src/app/ui/editor/editor_state.h b/src/app/ui/editor/editor_state.h index d7c4a7aaa..b01480f76 100644 --- a/src/app/ui/editor/editor_state.h +++ b/src/app/ui/editor/editor_state.h @@ -23,6 +23,10 @@ #include "base/disable_copying.h" #include "base/shared_ptr.h" +namespace gfx { + class Region; +} + namespace ui { class MouseMessage; class KeyMessage; @@ -104,6 +108,9 @@ namespace app { // Called when a key is released. virtual bool onUpdateStatusBar(Editor* editor) { return false; } + // When a part of the sprite will be exposed. + virtual void onExposeSpritePixels(const gfx::Region& rgn) { } + // Returns true if the this state requires the brush-preview as // drawing cursor. virtual bool requireBrushPreview() { return false; } diff --git a/src/app/ui/editor/moving_pixels_state.cpp b/src/app/ui/editor/moving_pixels_state.cpp index b658099c2..8d024f131 100644 --- a/src/app/ui/editor/moving_pixels_state.cpp +++ b/src/app/ui/editor/moving_pixels_state.cpp @@ -148,6 +148,9 @@ EditorState::BeforeChangeAction MovingPixelsState::onBeforeChangeState(Editor* e editor->releaseMouse(); + // Redraw the document without the transformation handles. + editor->document()->notifyGeneralUpdate(); + return DiscardState; } else { diff --git a/src/app/ui/editor/pixels_movement.cpp b/src/app/ui/editor/pixels_movement.cpp index 8ec4b836e..310554cc9 100644 --- a/src/app/ui/editor/pixels_movement.cpp +++ b/src/app/ui/editor/pixels_movement.cpp @@ -447,15 +447,21 @@ void PixelsMovement::stampImage() { // Expand the canvas to paste the image in the fully visible // portion of sprite. - ExpandCelCanvas expandCelCanvas(writer.context(), TILED_NONE, - m_undoTransaction); + ExpandCelCanvas expand(writer.context(), + TILED_NONE, m_undoTransaction, + ExpandCelCanvas::None); - composite_image(expandCelCanvas.getDestCanvas(), image, - -expandCelCanvas.getCel()->x(), - -expandCelCanvas.getCel()->y(), - cel->opacity(), BLEND_MODE_NORMAL); + // TODO can we reduce this region? + gfx::Region modifiedRegion(expand.getDestCanvas()->bounds()); + expand.validateDestCanvas(modifiedRegion); - expandCelCanvas.commit(); + composite_image( + expand.getDestCanvas(), image, + -expand.getCel()->x(), + -expand.getCel()->y(), + cel->opacity(), BLEND_MODE_NORMAL); + + expand.commit(); } // TODO // m_undoTransaction.commit(); diff --git a/src/app/ui/editor/scoped_cursor.h b/src/app/ui/editor/scoped_cursor.h new file mode 100644 index 000000000..1cde192d8 --- /dev/null +++ b/src/app/ui/editor/scoped_cursor.h @@ -0,0 +1,51 @@ +/* Aseprite + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef APP_UI_EDITOR_SCOPED_CURSOR_H_INCLUDED +#define APP_UI_EDITOR_SCOPED_CURSOR_H_INCLUDED +#pragma once + +namespace app { + + class ShowHideDrawingCursor { + public: + ShowHideDrawingCursor(Editor* editor) : m_editor(editor) { + m_editor->showDrawingCursor(); + } + ~ShowHideDrawingCursor() { + m_editor->hideDrawingCursor(); + } + private: + Editor* m_editor; + }; + + class HideShowDrawingCursor { + public: + HideShowDrawingCursor(Editor* editor) : m_editor(editor) { + m_editor->hideDrawingCursor(); + } + ~HideShowDrawingCursor() { + m_editor->showDrawingCursor(); + } + private: + Editor* m_editor; + }; + +} // namespace app + +#endif diff --git a/src/app/ui/editor/select_box_state.cpp b/src/app/ui/editor/select_box_state.cpp index a97a6dbaf..4b0ae0222 100644 --- a/src/app/ui/editor/select_box_state.cpp +++ b/src/app/ui/editor/select_box_state.cpp @@ -63,6 +63,7 @@ void SelectBoxState::setBoxBounds(const gfx::Rect& box) void SelectBoxState::onAfterChangeState(Editor* editor) { editor->setDecorator(this); + editor->invalidate(); } void SelectBoxState::onBeforePopState(Editor* editor) diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp index 34fd5a61c..667ae86be 100644 --- a/src/app/ui/editor/standby_state.cpp +++ b/src/app/ui/editor/standby_state.cpp @@ -269,8 +269,13 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg) // Start the Tool-Loop if (layer) { tools::ToolLoop* toolLoop = create_tool_loop(editor, context); - if (toolLoop) - editor->setState(EditorStatePtr(new DrawingState(toolLoop, editor, msg))); + if (toolLoop) { + EditorStatePtr newState(new DrawingState(toolLoop)); + editor->setState(newState); + + static_cast(newState.get()) + ->initToolLoop(editor, msg); + } return true; } diff --git a/src/app/ui/editor/tool_loop_impl.cpp b/src/app/ui/editor/tool_loop_impl.cpp index 4f86c0662..cdd825399 100644 --- a/src/app/ui/editor/tool_loop_impl.cpp +++ b/src/app/ui/editor/tool_loop_impl.cpp @@ -28,8 +28,10 @@ #include "app/context.h" #include "app/context_access.h" #include "app/document_undo.h" +#include "app/modules/gui.h" #include "app/settings/document_settings.h" #include "app/settings/settings.h" +#include "app/tools/controller.h" #include "app/tools/ink.h" #include "app/tools/shade_table.h" #include "app/tools/shading_options.h" @@ -86,7 +88,6 @@ class ToolLoopImpl : public tools::ToolLoop, UndoTransaction m_undoTransaction; ExpandCelCanvas m_expandCelCanvas; gfx::Region m_dirtyArea; - gfx::Rect m_dirtyBounds; tools::ShadeTable8* m_shadeTable; public: @@ -121,7 +122,18 @@ public: getInk()->isSlice() || getInk()->isZoom()) ? undo::DoesntModifyDocument: undo::ModifyDocument)) - , m_expandCelCanvas(m_context, m_docSettings->getTiledMode(), m_undoTransaction) + , m_expandCelCanvas(m_context, + m_docSettings->getTiledMode(), + m_undoTransaction, + ExpandCelCanvas::Flags( + ExpandCelCanvas::NeedsSource | + // If the tool is freehand-like, we can use the modified + // region directly as undo information to save the modified + // pixels (it's faster than creating a Dirty object). + // See ExpandCelCanvas::commit() for details about this flag. + (getController()->isFreehand() ? + ExpandCelCanvas::UseModifiedRegionAsUndoInfo: + ExpandCelCanvas::None))) , m_shadeTable(NULL) { // Settings @@ -188,26 +200,34 @@ public: ~ToolLoopImpl() { + bool redraw = false; + if (!m_canceled) { // Paint ink if (getInk()->isPaint()) { - m_expandCelCanvas.commit(m_dirtyBounds); + m_expandCelCanvas.commit(); } // Selection ink else if (getInk()->isSelection()) { m_document->generateMaskBoundaries(); + redraw = true; } m_undoTransaction.commit(); } + else + redraw = true; - // If the trace was not canceled or it is not a 'paint' ink... + // If the trace was canceled or it is not a 'paint' ink... if (m_canceled || !getInk()->isPaint()) { m_expandCelCanvas.rollback(); } delete m_brush; delete m_shadeTable; + + if (redraw) + update_screen_for_document(m_document); } // IToolLoop interface @@ -217,8 +237,25 @@ public: Sprite* sprite() override { return m_sprite; } Layer* getLayer() override { return m_layer; } FrameNumber getFrame() override { return m_frame; } - Image* getSrcImage() override { return m_expandCelCanvas.getSourceCanvas(); } + + const Image* getSrcImage() override { return m_expandCelCanvas.getSourceCanvas(); } Image* getDstImage() override { return m_expandCelCanvas.getDestCanvas(); } + void validateSrcImage(const gfx::Region& rgn) override { + return m_expandCelCanvas.validateSourceCanvas(rgn); + } + void validateDstImage(const gfx::Region& rgn) override { + return m_expandCelCanvas.validateDestCanvas(rgn); + } + void invalidateDstImage() override { + return m_expandCelCanvas.invalidateDestCanvas(); + } + void invalidateDstImage(const gfx::Region& rgn) override { + return m_expandCelCanvas.invalidateDestCanvas(rgn); + } + void copyValidDstToSrcImage(const gfx::Region& rgn) override { + return m_expandCelCanvas.copyValidDestToSourceCanvas(rgn); + } + RgbMap* getRgbMap() override { return m_sprite->getRgbMap(m_frame); } bool useMask() override { return m_useMask; } Mask* getMask() override { return m_mask; } @@ -264,8 +301,9 @@ public: void updateDirtyArea() override { - m_dirtyBounds = m_dirtyBounds.createUnion(m_dirtyArea.bounds()); + m_editor->hideDrawingCursor(); m_document->notifySpritePixelsModified(m_sprite, m_dirtyArea); + m_editor->showDrawingCursor(); } void updateStatusBar(const char* text) override diff --git a/src/app/undoers/modified_region.cpp b/src/app/undoers/modified_region.cpp new file mode 100644 index 000000000..33e658b9f --- /dev/null +++ b/src/app/undoers/modified_region.cpp @@ -0,0 +1,91 @@ +/* Aseprite + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "app/undoers/modified_region.h" + +#include "base/serialization.h" +#include "base/unique_ptr.h" +#include "doc/image.h" +#include "gfx/region.h" +#include "undo/objects_container.h" +#include "undo/undoers_collector.h" + +namespace app { +namespace undoers { + +using namespace undo; +using namespace base::serialization::little_endian; + +ModifiedRegion::ModifiedRegion(ObjectsContainer* objects, Image* image, Region& rgn) + : m_imageId(objects->addObject(image)) +{ + // Save region structure + write32(m_stream, rgn.size()); + for (const auto& rc : rgn) { + write32(m_stream, rc.x); + write32(m_stream, rc.y); + write32(m_stream, rc.w); + write32(m_stream, rc.h); + } + + // Save region pixels + for (const auto& rc : rgn) + for (int y=0; ygetPixelAddress(rc.x, rc.y+y), + image->getRowStrideSize(rc.w)); +} + +void ModifiedRegion::dispose() +{ + delete this; +} + +void ModifiedRegion::revert(ObjectsContainer* objects, UndoersCollector* redoers) +{ + Image* image = objects->getObjectT(m_imageId); + + gfx::Region rgn; + int nrects = read32(m_stream); + for (int n=0; npushUndoer(new ModifiedRegion(objects, image, rgn)); + + // Restore region pixels + for (const auto& rc : rgn) + for (int y=0; ygetPixelAddress(rc.x, rc.y+y), + image->getRowStrideSize(rc.w)); +} + +} // namespace undoers +} // namespace app diff --git a/src/app/undoers/modified_region.h b/src/app/undoers/modified_region.h new file mode 100644 index 000000000..5bd360940 --- /dev/null +++ b/src/app/undoers/modified_region.h @@ -0,0 +1,62 @@ +/* Aseprite + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef APP_UNDOERS_MODIFIED_REGION_H_INCLUDED +#define APP_UNDOERS_MODIFIED_REGION_H_INCLUDED +#pragma once + +#include "app/undoers/undoer_base.h" +#include "undo/object_id.h" + +#include + +namespace doc { + class Image; +} + +namespace gfx { + class Region; +} + +namespace app { + namespace undoers { + using namespace doc; + using namespace gfx; + using namespace undo; + + class ModifiedRegion : public UndoerBase { + public: + ModifiedRegion(ObjectsContainer* objects, Image* image, Region& rgn); + + void dispose() override; + size_t getMemSize() const override { return sizeof(*this) + getStreamSize(); } + void revert(ObjectsContainer* objects, UndoersCollector* redoers) override; + + private: + size_t getStreamSize() const { + return const_cast(&m_stream)->tellp(); + } + + undo::ObjectId m_imageId; + std::stringstream m_stream; + }; + + } // namespace undoers +} // namespace app + +#endif // UNDOERS_MODIFIED_REGION_H_INCLUDED diff --git a/src/app/util/expand_cel_canvas.cpp b/src/app/util/expand_cel_canvas.cpp index 0bc2eea78..30ae99d28 100644 --- a/src/app/util/expand_cel_canvas.cpp +++ b/src/app/util/expand_cel_canvas.cpp @@ -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 @@ -30,11 +30,13 @@ #include "app/undoers/add_cel.h" #include "app/undoers/add_image.h" #include "app/undoers/dirty_area.h" +#include "app/undoers/modified_region.h" #include "app/undoers/replace_image.h" #include "app/undoers/set_cel_position.h" #include "base/unique_ptr.h" #include "doc/cel.h" #include "doc/dirty.h" +#include "doc/image.h" #include "doc/layer.h" #include "doc/primitives.h" #include "doc/sprite.h" @@ -65,10 +67,13 @@ static void create_buffers() namespace app { -ExpandCelCanvas::ExpandCelCanvas(Context* context, TiledMode tiledMode, UndoTransaction& undo) +ExpandCelCanvas::ExpandCelCanvas(Context* context, TiledMode tiledMode, UndoTransaction& undo, Flags flags) : m_cel(NULL) , m_celImage(NULL) , m_celCreated(false) + , m_flags(flags) + , m_srcImage(NULL) + , m_dstImage(NULL) , m_closed(false) , m_committed(false) , m_undo(undo) @@ -88,60 +93,36 @@ ExpandCelCanvas::ExpandCelCanvas(Context* context, TiledMode tiledMode, UndoTran // If there is no Cel if (m_cel == NULL) { - // Create the image - m_celImage = Image::create(m_sprite->pixelFormat(), - m_sprite->width(), - m_sprite->height()); - - color_t bg = m_sprite->transparentColor(); - m_celImage->setMaskColor(bg); - clear_image(m_celImage, bg); - // Create the cel + m_celCreated = true; m_cel = new Cel(location.frame(), 0); static_cast(m_layer)->addCel(m_cel); - - m_celCreated = true; } - m_originalCelX = m_cel->x(); - m_originalCelY = m_cel->y(); + m_origCelPos = m_cel->position(); // Region to draw gfx::Rect celBounds( m_cel->x(), m_cel->y(), - m_celImage->width(), - m_celImage->height()); + m_celImage ? m_celImage->width(): m_sprite->width(), + m_celImage ? m_celImage->height(): m_sprite->height()); gfx::Rect spriteBounds(0, 0, m_sprite->width(), m_sprite->height()); - gfx::Rect bounds; - if (tiledMode == TILED_NONE) { // Non-tiled - bounds = celBounds.createUnion(spriteBounds); + m_bounds = celBounds.createUnion(spriteBounds); } - else { // Tiled - bounds = spriteBounds; + else { // Tiled + m_bounds = spriteBounds; } - // create two copies of the image region which we'll modify with the tool - m_srcImage = crop_image(m_celImage, - bounds.x - celBounds.x, - bounds.y - celBounds.y, - bounds.w, - bounds.h, - m_sprite->transparentColor(), - src_buffer); - - m_dstImage = Image::createCopy(m_srcImage, dst_buffer); - // We have to adjust the cel position to match the m_dstImage // position (the new m_dstImage will be used in RenderEngine to // draw this cel). - m_cel->setPosition(bounds.x, bounds.y); + m_cel->setPosition(m_bounds.x, m_bounds.y); } ExpandCelCanvas::~ExpandCelCanvas() @@ -157,89 +138,93 @@ ExpandCelCanvas::~ExpandCelCanvas() delete m_dstImage; } -void ExpandCelCanvas::commit(const gfx::Rect& bounds) +void ExpandCelCanvas::commit() { ASSERT(!m_closed); ASSERT(!m_committed); - // If the size of each image is the same, we can create an undo - // with only the differences between both images. - if (m_cel->x() == m_originalCelX && - m_cel->y() == m_originalCelY && - m_celImage->width() == m_dstImage->width() && - m_celImage->height() == m_dstImage->height()) { - // Was m_celImage created in the start of the tool-loop?. - if (m_celCreated) { - // We can keep the m_celImage + // Was the cel created in the start of the tool-loop? + if (m_celCreated) { + ASSERT(m_cel); + ASSERT(!m_celImage); - // We copy the destination image to the m_celImage - copy_image(m_celImage, m_dstImage, 0, 0); + // Validate the whole m_dstImage (invalid areas are cleared, as we + // don't have a m_celImage) + validateDestCanvas(gfx::Region(m_bounds)); - // Add the m_celImage in the images stock of the sprite. - m_cel->setImage(m_sprite->stock()->addImage(m_celImage)); + // Add a copy of m_dstImage in the sprite's image stock + m_cel->setImage(m_sprite->stock()->addImage( + Image::createCopy(m_dstImage))); - // Is the undo enabled?. - if (m_undo.isEnabled()) { - // We can temporary remove the cel. - static_cast(m_layer)->removeCel(m_cel); + // Is the undo enabled?. + if (m_undo.isEnabled()) { + // We can temporary remove the cel. + static_cast(m_layer)->removeCel(m_cel); - // We create the undo information (for the new m_celImage - // in the stock and the new cel in the layer)... - m_undo.pushUndoer(new undoers::AddImage(m_undo.getObjects(), - m_sprite->stock(), m_cel->imageIndex())); - m_undo.pushUndoer(new undoers::AddCel(m_undo.getObjects(), - m_layer, m_cel)); + // We create the undo information (for the new m_celImage + // in the stock and the new cel in the layer)... + m_undo.pushUndoer(new undoers::AddImage(m_undo.getObjects(), + m_sprite->stock(), m_cel->imageIndex())); + m_undo.pushUndoer(new undoers::AddCel(m_undo.getObjects(), + m_layer, m_cel)); - // And finally we add the cel again in the layer. - static_cast(m_layer)->addCel(m_cel); - } + // And finally we add the cel again in the layer. + static_cast(m_layer)->addCel(m_cel); } - // If the m_celImage was already created before the whole process... - else { + } + else if (m_celImage) { + // If the size of each image is the same, we can create an undo + // with only the differences between both images. + if (m_cel->position() == m_origCelPos && + m_bounds.getOrigin() == m_origCelPos && + m_celImage->width() == m_dstImage->width() && + m_celImage->height() == m_dstImage->height()) { // Add to the undo history the differences between m_celImage and m_dstImage if (m_undo.isEnabled()) { - gfx::Rect dirtyBounds; - if (bounds.isEmpty()) - dirtyBounds = m_celImage->bounds(); - else - dirtyBounds = m_celImage->bounds().createIntersect( - gfx::Rect(bounds).offset(-m_originalCelX, -m_originalCelY)); - - base::UniquePtr dirty(new Dirty(m_celImage, m_dstImage, dirtyBounds)); - - dirty->saveImagePixels(m_celImage); - if (dirty != NULL) - m_undo.pushUndoer(new undoers::DirtyArea(m_undo.getObjects(), m_celImage, dirty)); + if ((m_flags & UseModifiedRegionAsUndoInfo) == UseModifiedRegionAsUndoInfo) { + m_undo.pushUndoer(new undoers::ModifiedRegion( + m_undo.getObjects(), m_celImage, m_validDstRegion)); + } + else { + Dirty dirty(m_celImage, m_dstImage, m_validDstRegion); + dirty.saveImagePixels(m_celImage); + m_undo.pushUndoer(new undoers::DirtyArea( + m_undo.getObjects(), m_celImage, &dirty)); + } } // Copy the destination to the cel image. - copy_image(m_celImage, m_dstImage, 0, 0); + copyValidDestToOriginalCel(); } - } - // If the size of both images are different, we have to - // replace the entire image. - else { - if (m_undo.isEnabled()) { - if (m_cel->x() != m_originalCelX || - m_cel->y() != m_originalCelY) { - int newX = m_cel->x(); - int newY = m_cel->y(); - m_cel->setPosition(m_originalCelX, m_originalCelY); - m_undo.pushUndoer(new undoers::SetCelPosition(m_undo.getObjects(), m_cel)); - m_cel->setPosition(newX, newY); + // If the size of both images are different, we have to + // replace the entire image. + else { + if (m_undo.isEnabled()) { + if (m_cel->position() != m_origCelPos) { + gfx::Point newPos = m_cel->position(); + m_cel->setPosition(m_origCelPos); + m_undo.pushUndoer(new undoers::SetCelPosition(m_undo.getObjects(), m_cel)); + m_cel->setPosition(newPos); + } + + m_undo.pushUndoer(new undoers::ReplaceImage(m_undo.getObjects(), + m_sprite->stock(), m_cel->imageIndex())); } - m_undo.pushUndoer(new undoers::ReplaceImage(m_undo.getObjects(), - m_sprite->stock(), m_cel->imageIndex())); + // Validate the whole m_dstImage copying invalid areas from m_celImage + validateDestCanvas(gfx::Region(m_bounds)); + + // Replace the image in the stock. We need to create a copy of + // image because m_dstImage's ImageBuffer cannot be shared. + m_sprite->stock()->replaceImage(m_cel->imageIndex(), + Image::createCopy(m_dstImage)); + + // Destroy the old cel image. + delete m_celImage; } - - // Replace the image in the stock. We need to create a copy of - // image because m_dstImage's ImageBuffer cannot be shared. - m_sprite->stock()->replaceImage(m_cel->imageIndex(), - Image::createCopy(m_dstImage)); - - // Destroy the old cel image. - delete m_celImage; + } + else { + ASSERT(false); } m_committed = true; @@ -251,7 +236,7 @@ void ExpandCelCanvas::rollback() ASSERT(!m_committed); // Here we destroy the temporary 'cel' created and restore all as it was before - m_cel->setPosition(m_originalCelX, m_originalCelY); + m_cel->setPosition(m_origCelPos); if (m_celCreated) { static_cast(m_layer)->removeCel(m_cel); @@ -262,4 +247,137 @@ void ExpandCelCanvas::rollback() m_closed = true; } +Image* ExpandCelCanvas::getSourceCanvas() +{ + ASSERT((m_flags & NeedsSource) == NeedsSource); + + if (!m_srcImage) { + m_srcImage = Image::create(m_sprite->pixelFormat(), + m_bounds.w, m_bounds.h, src_buffer); + + m_srcImage->setMaskColor(m_sprite->transparentColor()); + } + return m_srcImage; +} + +Image* ExpandCelCanvas::getDestCanvas() +{ + if (!m_dstImage) { + m_dstImage = Image::create(m_sprite->pixelFormat(), + m_bounds.w, m_bounds.h, dst_buffer); + + m_dstImage->setMaskColor(m_sprite->transparentColor()); + } + return m_dstImage; +} + +void ExpandCelCanvas::validateSourceCanvas(const gfx::Region& rgn) +{ + getSourceCanvas(); + + gfx::Region rgnToValidate(rgn); + rgnToValidate.offset(-m_bounds.getOrigin()); + rgnToValidate.createSubtraction(rgnToValidate, m_validSrcRegion); + rgnToValidate.createIntersection(rgnToValidate, gfx::Region(m_srcImage->bounds())); + + if (m_celImage) { + gfx::Region rgnToClear; + rgnToClear.createSubtraction(rgnToValidate, + gfx::Region(m_celImage->bounds() + .offset(m_origCelPos) + .offset(-m_bounds.getOrigin()))); + for (const auto& rc : rgnToClear) + fill_rect(m_srcImage, rc, m_srcImage->maskColor()); + + for (const auto& rc : rgnToValidate) + m_srcImage->copy(m_celImage, rc.x, rc.y, + rc.x+m_bounds.x-m_origCelPos.x, + rc.y+m_bounds.y-m_origCelPos.y, rc.w, rc.h); + } + else { + for (const auto& rc : rgnToValidate) + fill_rect(m_srcImage, rc, m_srcImage->maskColor()); + } + + m_validSrcRegion.createUnion(m_validSrcRegion, rgnToValidate); +} + +void ExpandCelCanvas::validateDestCanvas(const gfx::Region& rgn) +{ + Image* src; + int src_x, src_y; + if ((m_flags & NeedsSource) == NeedsSource) { + validateSourceCanvas(rgn); + src = m_srcImage; + src_x = m_bounds.x; + src_y = m_bounds.y; + } + else { + src = m_celImage; + src_x = m_origCelPos.x; + src_y = m_origCelPos.y; + } + + getDestCanvas(); + + gfx::Region rgnToValidate(rgn); + rgnToValidate.offset(-m_bounds.getOrigin()); + rgnToValidate.createSubtraction(rgnToValidate, m_validDstRegion); + rgnToValidate.createIntersection(rgnToValidate, gfx::Region(m_dstImage->bounds())); + + if (src) { + gfx::Region rgnToClear; + rgnToClear.createSubtraction(rgnToValidate, + gfx::Region(src->bounds() + .offset(src_x, src_y) + .offset(-m_bounds.getOrigin()))); + for (const auto& rc : rgnToClear) + fill_rect(m_dstImage, rc, m_dstImage->maskColor()); + + for (const auto& rc : rgnToValidate) + m_dstImage->copy(src, rc.x, rc.y, + rc.x+m_bounds.x-src_x, + rc.y+m_bounds.y-src_y, rc.w, rc.h); + } + else { + for (const auto& rc : rgnToValidate) + fill_rect(m_dstImage, rc, m_dstImage->maskColor()); + } + + m_validDstRegion.createUnion(m_validDstRegion, rgnToValidate); +} + +void ExpandCelCanvas::invalidateDestCanvas() +{ + m_validDstRegion.clear(); +} + +void ExpandCelCanvas::invalidateDestCanvas(const gfx::Region& rgn) +{ + gfx::Region rgnToInvalidate(rgn); + rgnToInvalidate.offset(-m_bounds.getOrigin()); + m_validDstRegion.createSubtraction(m_validDstRegion, rgnToInvalidate); +} + +void ExpandCelCanvas::copyValidDestToSourceCanvas(const gfx::Region& rgn) +{ + gfx::Region rgn2(rgn); + rgn2.offset(-m_bounds.getOrigin()); + rgn2.createIntersection(rgn2, m_validSrcRegion); + rgn2.createIntersection(rgn2, m_validDstRegion); + for (const auto& rc : rgn2) + m_srcImage->copy(m_dstImage, rc.x, rc.y, rc.x, rc.y, rc.w, rc.h); +} + +void ExpandCelCanvas::copyValidDestToOriginalCel() +{ + // Copy valid destination region to the m_celImage + for (const auto& rc : m_validDstRegion) { + m_celImage->copy(m_dstImage, + rc.x-m_bounds.x+m_origCelPos.x, + rc.y-m_bounds.y+m_origCelPos.y, + rc.x, rc.y, rc.w, rc.h); + } +} + } // namespace app diff --git a/src/app/util/expand_cel_canvas.h b/src/app/util/expand_cel_canvas.h index 5785f9816..52686d87d 100644 --- a/src/app/util/expand_cel_canvas.h +++ b/src/app/util/expand_cel_canvas.h @@ -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 @@ -21,7 +21,10 @@ #pragma once #include "filters/tiled_mode.h" +#include "gfx/point.h" #include "gfx/rect.h" +#include "gfx/region.h" +#include "gfx/size.h" namespace doc { class Cel; @@ -46,46 +49,55 @@ namespace app { // state using "Undo" command. class ExpandCelCanvas { public: - ExpandCelCanvas(Context* context, TiledMode tiledMode, UndoTransaction& undo); + enum Flags { + None = 0, + NeedsSource = 1, + UseModifiedRegionAsUndoInfo = 2, + }; + + ExpandCelCanvas(Context* context, TiledMode tiledMode, UndoTransaction& undo, Flags flags); ~ExpandCelCanvas(); // Commit changes made in getDestCanvas() in the cel's image. Adds // information in the undo history so the user can undo the // modifications in the canvas. - void commit(const gfx::Rect& bounds = gfx::Rect()); + void commit(); // Restore the cel as its original state as when ExpandCelCanvas() // was created. void rollback(); - // You can read pixels from here - Image* getSourceCanvas() { // TODO this should be "const" - return m_srcImage; - } + Image* getSourceCanvas(); // You can read pixels from here + Image* getDestCanvas(); // You can write pixels right here - // You can write pixels right here - Image* getDestCanvas() { - return m_dstImage; - } + void validateSourceCanvas(const gfx::Region& rgn); + void validateDestCanvas(const gfx::Region& rgn); + void invalidateDestCanvas(); + void invalidateDestCanvas(const gfx::Region& rgn); + void copyValidDestToSourceCanvas(const gfx::Region& rgn); - const Cel* getCel() const { - return m_cel; - } + const Cel* getCel() const { return m_cel; } private: + void copyValidDestToOriginalCel(); + Document* m_document; Sprite* m_sprite; Layer* m_layer; Cel* m_cel; Image* m_celImage; bool m_celCreated; - int m_originalCelX; - int m_originalCelY; + gfx::Point m_origCelPos; + gfx::Point m_celPos; + Flags m_flags; + gfx::Rect m_bounds; Image* m_srcImage; Image* m_dstImage; bool m_closed; bool m_committed; UndoTransaction& m_undo; + gfx::Region m_validSrcRegion; + gfx::Region m_validDstRegion; }; } // namespace app diff --git a/src/app/util/render.cpp b/src/app/util/render.cpp index 152941183..20e9e2404 100644 --- a/src/app/util/render.cpp +++ b/src/app/util/render.cpp @@ -475,15 +475,8 @@ void RenderEngine::setPreviewImage(const Layer* layer, FrameNumber frame, Image* preview_image = image; } -/** - Draws the @a frame of animation of the specified @a sprite - in a new image and return it. - - Positions source_x, source_y, width and height must have the - zoom applied (zoom.apply(sorce_x), zoom.apply(source_y), zoom.apply(width), etc.) - */ -Image* RenderEngine::renderSprite(int source_x, int source_y, - int width, int height, +Image* RenderEngine::renderSprite( + const gfx::Rect& zoomedRect, FrameNumber frame, Zoom zoom, bool draw_tiled_bg, bool enable_onionskin, @@ -516,20 +509,21 @@ Image* RenderEngine::renderSprite(int source_x, int source_y, } // Create a temporary RGB bitmap to draw all to it - image = Image::create(IMAGE_RGB, width, height, buffer); + image = Image::create(IMAGE_RGB, zoomedRect.w, zoomedRect.h, buffer); if (!image) return NULL; // Draw checked background if (need_checked_bg && draw_tiled_bg) - renderCheckedBackground(image, source_x, source_y, zoom); + renderCheckedBackground(image, zoomedRect.x, zoomedRect.y, zoom); else clear_image(image, bg_color); // Draw the current frame. global_opacity = 255; renderLayer(m_sprite->folder(), image, - source_x, source_y, frame, zoom, zoomed_func, true, true, -1); + zoomedRect.x, zoomedRect.y, + frame, zoom, zoomed_func, true, true, -1); // Onion-skin feature: Draw previous/next frames with different // opacity (<255) (it is the onion-skinning) @@ -560,7 +554,7 @@ Image* RenderEngine::renderSprite(int source_x, int source_y, blend_mode = (f < frame ? BLEND_MODE_RED_TINT: BLEND_MODE_BLUE_TINT); renderLayer(m_sprite->folder(), image, - source_x, source_y, f, zoom, zoomed_func, + zoomedRect.x, zoomedRect.y, f, zoom, zoomed_func, true, true, blend_mode); } } @@ -571,8 +565,7 @@ Image* RenderEngine::renderSprite(int source_x, int source_y, // static void RenderEngine::renderCheckedBackground(Image* image, - int source_x, int source_y, - Zoom zoom) + int source_x, int source_y, Zoom zoom) { int x, y, u, v; int tile_w = 16; diff --git a/src/app/util/render.h b/src/app/util/render.h index c408b528a..ab2be6ba5 100644 --- a/src/app/util/render.h +++ b/src/app/util/render.h @@ -24,6 +24,7 @@ #include "app/zoom.h" #include "doc/frame_number.h" #include "doc/image_buffer.h" +#include "gfx/rect.h" namespace doc { class Image; @@ -68,10 +69,10 @@ namespace app { static void setPreviewImage(const Layer* layer, FrameNumber frame, Image* drawable); ////////////////////////////////////////////////////////////////////// - // Main function used by sprite-editors to render the sprite - - Image* renderSprite(int source_x, int source_y, - int width, int height, + // Main function used by sprite-editors to render the sprite. + // Draws the given sprite frame in a new image and return it. + // Note: zoomedRect must have the zoom applied (zoomedRect = zoom.apply(spriteRect)). + Image* renderSprite(const gfx::Rect& zoomedRect, FrameNumber frame, Zoom zoom, bool draw_tiled_bg, bool enable_onionskin, diff --git a/src/app/zoom.h b/src/app/zoom.h index 873efaee4..9ffbe2949 100644 --- a/src/app/zoom.h +++ b/src/app/zoom.h @@ -20,6 +20,8 @@ #define APP_ZOOM_H_INCLUDED #pragma once +#include "gfx/rect.h" + namespace app { class Zoom { @@ -36,6 +38,19 @@ namespace app { double apply(double x) const { return x * m_num / m_den; } double remove(double x) const { return x * m_den / m_num; } + gfx::Rect apply(const gfx::Rect& r) const { + return gfx::Rect( + apply(r.x), apply(r.y), + apply(r.x+r.w) - apply(r.x), + apply(r.y+r.h) - apply(r.y)); + } + gfx::Rect remove(const gfx::Rect& r) const { + return gfx::Rect( + remove(r.x), remove(r.y), + remove(r.x+r.w) - remove(r.x), + remove(r.y+r.h) - remove(r.y)); + } + void in(); void out(); diff --git a/src/doc/algorithm/rotsprite.cpp b/src/doc/algorithm/rotsprite.cpp index 8daeec4ea..e4c5dd78c 100644 --- a/src/doc/algorithm/rotsprite.cpp +++ b/src/doc/algorithm/rotsprite.cpp @@ -176,12 +176,12 @@ void rotsprite_image(Image* bmp, Image* spr, bmp_copy->clear(bmp->maskColor()); spr_copy->clear(maskColor); - spr_copy->copy(spr, 0, 0); + spr_copy->copy(spr, 0, 0, 0, 0, spr->width(), spr->height()); for (int i=0; i<3; ++i) { tmp_copy->clear(maskColor); image_scale2x(tmp_copy, spr_copy, spr->width()*(1<height()*(1<copy(tmp_copy, 0, 0); + spr_copy->copy(tmp_copy, 0, 0, 0, 0, tmp_copy->width(), tmp_copy->height()); } doc::algorithm::parallelogram(bmp_copy, spr_copy, diff --git a/src/doc/brush.h b/src/doc/brush.h index d35ccb5f9..50d0a970a 100644 --- a/src/doc/brush.h +++ b/src/doc/brush.h @@ -24,7 +24,7 @@ namespace doc { class Brush { public: static const int kMinBrushSize = 1; - static const int kMaxBrushSize = 32; + static const int kMaxBrushSize = 64; Brush(); Brush(BrushType type, int size, int angle); diff --git a/src/doc/dirty.cpp b/src/doc/dirty.cpp index 8a8c7f803..8de44d020 100644 --- a/src/doc/dirty.cpp +++ b/src/doc/dirty.cpp @@ -18,35 +18,6 @@ namespace doc { -Dirty::Dirty(PixelFormat format, int x1, int y1, int x2, int y2) - : m_format(format) - , m_x1(x1), m_y1(y1) - , m_x2(x2), m_y2(y2) -{ -} - -Dirty::Dirty(const Dirty& src) - : m_format(src.m_format) - , m_x1(src.m_x1), m_y1(src.m_y1) - , m_x2(src.m_x2), m_y2(src.m_y2) -{ - m_rows.resize(src.m_rows.size()); - - for (size_t v=0; vcols.resize(srcRow.cols.size()); - - for (size_t u=0; ucols.size(); ++u) { - Col* col = new Col(*srcRow.cols[u]); - row->cols[u] = col; - } - - m_rows[v] = row; - } -} - template inline bool shrink_row(const Image* image, const Image* image_diff, int& x1, int y, int& x2) { @@ -68,45 +39,85 @@ inline bool shrink_row(const Image* image, const Image* image_diff, int& x1, int return true; } -Dirty::Dirty(Image* image, Image* image_diff, const gfx::Rect& bounds) - : m_format(image->pixelFormat()) - , m_x1(bounds.x), m_y1(bounds.y) - , m_x2(bounds.x2()-1), m_y2(bounds.y2()-1) +Dirty::Dirty(PixelFormat format, const gfx::Rect& bounds) + : m_format(format) + , m_bounds(bounds) +{ +} + +Dirty::Dirty(const Dirty& src) + : m_format(src.m_format) + , m_bounds(src.m_bounds) +{ + m_rows.resize(src.m_rows.size()); + + for (size_t v=0; vcols.resize(srcRow.cols.size()); + + for (size_t u=0; ucols.size(); ++u) { + Col* col = new Col(*srcRow.cols[u]); + row->cols[u] = col; + } + + m_rows[v] = row; + } +} + +Dirty::Dirty(Image* image1, Image* image2, const gfx::Rect& bounds) + : m_format(image1->pixelFormat()) + , m_bounds(bounds) +{ + initialize(image1, image2, gfx::Region(bounds)); +} + +Dirty::Dirty(Image* image1, Image* image2, const gfx::Region& region) + : m_format(image1->pixelFormat()) + , m_bounds(region.bounds()) +{ + initialize(image1, image2, region); +} + +void Dirty::initialize(Image* image1, Image* image2, const gfx::Region& region) { int y, x1, x2; - for (y=m_y1; y<=m_y2; y++) { - x1 = m_x1; - x2 = m_x2; + for (const auto& rc : region) { + for (y=rc.y; ypixelFormat()) { - case IMAGE_RGB: - res = shrink_row(image, image_diff, x1, y, x2); - break; + bool res; + switch (m_format) { + case IMAGE_RGB: + res = shrink_row(image1, image2, x1, y, x2); + break; - case IMAGE_GRAYSCALE: - res = shrink_row(image, image_diff, x1, y, x2); - break; + case IMAGE_GRAYSCALE: + res = shrink_row(image1, image2, x1, y, x2); + break; - case IMAGE_INDEXED: - res = shrink_row(image, image_diff, x1, y, x2); - break; + case IMAGE_INDEXED: + res = shrink_row(image1, image2, x1, y, x2); + break; - default: - ASSERT(false && "Not implemented for bitmaps"); - return; + default: + ASSERT(false && "Not implemented for bitmaps"); + return; + } + if (!res) + continue; + + Col* col = new Col(x1, x2-x1+1); + col->data.resize(getLineSize(col->w)); + + Row* row = new Row(y); + row->cols.push_back(col); + + m_rows.push_back(row); } - if (!res) - continue; - - Col* col = new Col(x1, x2-x1+1); - col->data.resize(getLineSize(col->w)); - - Row* row = new Row(y); - row->cols.push_back(col); - - m_rows.push_back(row); } } diff --git a/src/doc/dirty.h b/src/doc/dirty.h index 67e974005..0e07f35e8 100644 --- a/src/doc/dirty.h +++ b/src/doc/dirty.h @@ -9,6 +9,7 @@ #pragma once #include "doc/image.h" +#include "gfx/region.h" #include @@ -44,18 +45,16 @@ namespace doc { }; public: - Dirty(PixelFormat format, int x1, int y1, int x2, int y2); + Dirty(PixelFormat format, const gfx::Rect& bounds); Dirty(const Dirty& src); Dirty(Image* image1, Image* image2, const gfx::Rect& bounds); + Dirty(Image* image1, Image* image2, const gfx::Region& region); ~Dirty(); int getMemSize() const; PixelFormat pixelFormat() const { return m_format; } - int x1() const { return m_x1; } - int y1() const { return m_y1; } - int x2() const { return m_x2; } - int y2() const { return m_y2; } + gfx::Rect bounds() const { return m_bounds; } int getRowsCount() const { return m_rows.size(); } const Row& getRow(int i) const { return *m_rows[i]; } @@ -70,6 +69,8 @@ public: Dirty* clone() const { return new Dirty(*this); } private: + void initialize(Image* image1, Image* image2, const gfx::Region& region); + // Disable copying through operator= Dirty& operator=(const Dirty&); @@ -79,8 +80,7 @@ public: // new Undo implementation is finished. PixelFormat m_format; - int m_x1, m_y1; - int m_x2, m_y2; + gfx::Rect m_bounds; RowsList m_rows; }; diff --git a/src/doc/dirty_io.cpp b/src/doc/dirty_io.cpp index 7b82d4f92..bb0c78cfc 100644 --- a/src/doc/dirty_io.cpp +++ b/src/doc/dirty_io.cpp @@ -38,10 +38,10 @@ using namespace base::serialization::little_endian; void write_dirty(std::ostream& os, Dirty* dirty) { write8(os, dirty->pixelFormat()); - write16(os, dirty->x1()); - write16(os, dirty->y1()); - write16(os, dirty->x2()); - write16(os, dirty->y2()); + write16(os, dirty->bounds().x); + write16(os, dirty->bounds().y); + write16(os, dirty->bounds().w); + write16(os, dirty->bounds().h); write16(os, dirty->getRowsCount()); for (int v=0; vgetRowsCount(); v++) { @@ -62,19 +62,20 @@ void write_dirty(std::ostream& os, Dirty* dirty) Dirty* read_dirty(std::istream& is) { - int u, v, x, y, w; int pixelFormat = read8(is); - int x1 = read16(is); - int y1 = read16(is); - int x2 = read16(is); - int y2 = read16(is); - base::UniquePtr dirty(new Dirty(static_cast(pixelFormat), x1, y1, x2, y2)); + int x = read16(is); + int y = read16(is); + int w = read16(is); + int h = read16(is); + base::UniquePtr dirty(new Dirty( + static_cast(pixelFormat), + gfx::Rect(x, y, w, h))); int noRows = read16(is); if (noRows > 0) { dirty->m_rows.resize(noRows); - for (v=0; vgetRowsCount(); v++) { + for (int v=0; vgetRowsCount(); v++) { y = read16(is); base::UniquePtr row(new Dirty::Row(y)); @@ -82,7 +83,7 @@ Dirty* read_dirty(std::istream& is) int noCols = read16(is); row->cols.resize(noCols); - for (u=0; u* src = (const ImageImpl*)_src; - ImageImpl* dst = this; address_t src_address; address_t dst_address; - int xbeg, xend, xsrc; - int ybeg, yend, ysrc, ydst; int bytes; - // Clipping - - xsrc = 0; - ysrc = 0; - - xbeg = x; - ybeg = y; - xend = x+src->width()-1; - yend = y+src->height()-1; - - if ((xend < 0) || (xbeg >= dst->width()) || - (yend < 0) || (ybeg >= dst->height())) + if (!clip_rects(src, dst_x, dst_y, src_x, src_y, w, h)) return; - if (xbeg < 0) { - xsrc -= xbeg; - xbeg = 0; - } - - if (ybeg < 0) { - ysrc -= ybeg; - ybeg = 0; - } - - if (xend >= dst->width()) - xend = dst->width()-1; - - if (yend >= dst->height()) - yend = dst->height()-1; - // Copy process + bytes = Traits::getRowStrideBytes(w); - bytes = Traits::getRowStrideBytes(xend - xbeg + 1); - - for (ydst=ybeg; ydst<=yend; ++ydst, ++ysrc) { - src_address = src->address(xsrc, ysrc); - dst_address = dst->address(xbeg, ydst); + for (int end_y=dst_y+h; dst_yaddress(src_x, src_y); + dst_address = address(dst_x, dst_y); memcpy(dst_address, src_address, bytes); } } - void merge(const Image* _src, int x, int y, int opacity, int blend_mode) override { + void merge(const Image* _src, int dst_x, int dst_y, int src_x, int src_y, int w, int h, int opacity, int blend_mode) override { BLEND_COLOR blender = Traits::get_blender(blend_mode); const ImageImpl* src = (const ImageImpl*)_src; ImageImpl* dst = this; address_t src_address; address_t dst_address; - int xbeg, xend, xsrc, xdst; - int ybeg, yend, ysrc, ydst; uint32_t mask_color = src->maskColor(); // nothing to do if (!opacity) return; - // clipping - - xsrc = 0; - ysrc = 0; - - xbeg = x; - ybeg = y; - xend = x+src->width()-1; - yend = y+src->height()-1; - - if ((xend < 0) || (xbeg >= dst->width()) || - (yend < 0) || (ybeg >= dst->height())) + if (!clip_rects(src, dst_x, dst_y, src_x, src_y, w, h)) return; - if (xbeg < 0) { - xsrc -= xbeg; - xbeg = 0; - } - - if (ybeg < 0) { - ysrc -= ybeg; - ybeg = 0; - } - - if (xend >= dst->width()) - xend = dst->width()-1; - - if (yend >= dst->height()) - yend = dst->height()-1; - // Merge process + int end_x = dst_x+w; + for (int end_y=dst_y+h; dst_yaddress(src_x, src_y); + dst_address = dst->address(dst_x, dst_y); - for (ydst=ybeg; ydst<=yend; ++ydst, ++ysrc) { - src_address = (address_t)src->address(xsrc, ysrc); - dst_address = (address_t)dst->address(xbeg, ydst); - - for (xdst=xbeg; xdst<=xend; ++xdst) { + for (int x=dst_x; x width()) { + w = width() - dst_x; + if (w < 0) + return false; + } + if (dst_y+h > height()) { + h = height() - dst_y; + if (h < 0) + return false; + } + + // Clip with source image + if (src_x < 0) { + dst_x -= src_x; + src_x = 0; + } + if (src_y < 0) { + dst_y -= src_y; + src_y = 0; + } + if (src_x+w > src->width()) { + w = src->width() - src_x; + if (w < 0) + return false; + } + if (src_y+h > src->height()) { + h = src->height() - src_y; + if (h < 0) + return false; + } + + // Empty cases + if ((src_x+w <= 0) || (src_x >= src->width()) || + (src_y+h <= 0) || (src_y >= src->height())) + return false; + + if ((dst_x+w <= 0) || (dst_x >= width()) || + (dst_y+h <= 0) || (dst_y >= height())) + return false; + + ASSERT(src->bounds().contains(gfx::Rect(src_x, src_y, w, h))); + ASSERT(bounds().contains(gfx::Rect(dst_x, dst_y, w, h))); + + return true; + } }; ////////////////////////////////////////////////////////////////////// @@ -283,52 +280,22 @@ namespace doc { } template<> - inline void ImageImpl::merge(const Image* src, int x, int y, int opacity, int blend_mode) { - Image* dst = this; - address_t src_address; - address_t dst_address; - int xbeg, xend, xsrc, xdst; - int ybeg, yend, ysrc, ydst; - - // clipping - - xsrc = 0; - ysrc = 0; - - xbeg = x; - ybeg = y; - xend = x+src->width()-1; - yend = y+src->height()-1; - - if ((xend < 0) || (xbeg >= dst->width()) || - (yend < 0) || (ybeg >= dst->height())) + inline void ImageImpl::merge(const Image* src, int dst_x, int dst_y, int src_x, int src_y, int w, int h, int opacity, int blend_mode) { + if (!clip_rects(src, dst_x, dst_y, src_x, src_y, w, h)) return; - if (xbeg < 0) { - xsrc -= xbeg; - xbeg = 0; - } + address_t src_address; + address_t dst_address; - if (ybeg < 0) { - ysrc -= ybeg; - ybeg = 0; - } + int end_x = dst_x+w; - if (xend >= dst->width()) - xend = dst->width()-1; - - if (yend >= dst->height()) - yend = dst->height()-1; - - // merge process - - // direct copy + // Direct copy if (blend_mode == BLEND_MODE_COPY) { - for (ydst=ybeg; ydst<=yend; ++ydst, ++ysrc) { - src_address = src->getPixelAddress(xsrc, ysrc); - dst_address = dst->getPixelAddress(xbeg, ydst); + for (int end_y=dst_y+h; dst_ygetPixelAddress(src_x, src_y); + dst_address = getPixelAddress(dst_x, dst_y); - for (xdst=xbeg; xdst<=xend; xdst++) { + for (int x=dst_x; xmaskColor(); - for (ydst=ybeg; ydst<=yend; ++ydst, ++ysrc) { - src_address = src->getPixelAddress(xsrc, ysrc); - dst_address = dst->getPixelAddress(xbeg, ydst); + for (int end_y=dst_y+h; dst_ygetPixelAddress(src_x, src_y); + dst_address = getPixelAddress(dst_x, dst_y); - for (xdst=xbeg; xdst<=xend; ++xdst) { + for (int x=dst_x; x - inline void ImageImpl::copy(const Image* src, int x, int y) { - Image* dst = this; - int xbeg, xend, xsrc, xdst; - int ybeg, yend, ysrc, ydst; - - // clipping - - xsrc = 0; - ysrc = 0; - - xbeg = x; - ybeg = y; - xend = x+src->width()-1; - yend = y+src->height()-1; - - if ((xend < 0) || (xbeg >= dst->width()) || - (yend < 0) || (ybeg >= dst->height())) + inline void ImageImpl::copy(const Image* src, int dst_x, int dst_y, int src_x, int src_y, int w, int h) { + if (!clip_rects(src, dst_x, dst_y, src_x, src_y, w, h)) return; - if (xbeg < 0) { - xsrc -= xbeg; - xbeg = 0; - } + // Copy process + ImageConstIterator src_it(src, gfx::Rect(src_x, src_y, w, h), src_x, src_y); + ImageIterator dst_it(this, gfx::Rect(dst_x, dst_y, w, h), dst_x, dst_y); - if (ybeg < 0) { - ysrc -= ybeg; - ybeg = 0; - } + int end_x = dst_x+w; - if (xend >= dst->width()) - xend = dst->width()-1; - - if (yend >= dst->height()) - yend = dst->height()-1; - - // copy process - - int w = xend - xbeg + 1; - int h = yend - ybeg + 1; - ImageConstIterator src_it(src, gfx::Rect(xsrc, ysrc, w, h), xsrc, ysrc); - ImageIterator dst_it(dst, gfx::Rect(xbeg, ybeg, w, h), xbeg, ybeg); - - for (ydst=ybeg; ydst<=yend; ++ydst, ++ysrc) { - for (xdst=xbeg; xdst<=xend; ++xdst) { + for (int end_y=dst_y+h; dst_y - inline void ImageImpl::merge(const Image* src, int x, int y, int opacity, int blend_mode) { - Image* dst = this; - int xbeg, xend, xsrc, xdst; - int ybeg, yend, ysrc, ydst; - - // clipping - - xsrc = 0; - ysrc = 0; - - xbeg = x; - ybeg = y; - xend = x+src->width()-1; - yend = y+src->height()-1; - - if ((xend < 0) || (xbeg >= dst->width()) || - (yend < 0) || (ybeg >= dst->height())) + inline void ImageImpl::merge(const Image* src, int dst_x, int dst_y, int src_x, int src_y, int w, int h, int opacity, int blend_mode) { + if (!clip_rects(src, dst_x, dst_y, src_x, src_y, w, h)) return; - if (xbeg < 0) { - xsrc -= xbeg; - xbeg = 0; - } + // Merge process + ImageConstIterator src_it(src, gfx::Rect(src_x, src_y, w, h), src_x, src_y); + ImageIterator dst_it(this, gfx::Rect(dst_x, dst_y, w, h), dst_x, dst_y); - if (ybeg < 0) { - ysrc -= ybeg; - ybeg = 0; - } + int end_x = dst_x+w; - if (xend >= dst->width()) - xend = dst->width()-1; - - if (yend >= dst->height()) - yend = dst->height()-1; - - // merge process - - int w = xend - xbeg + 1; - int h = yend - ybeg + 1; - ImageConstIterator src_it(src, gfx::Rect(xsrc, ysrc, w, h), xsrc, ysrc); - ImageIterator dst_it(dst, gfx::Rect(xbeg, ybeg, w, h), xbeg, ybeg); - - for (ydst=ybeg; ydst<=yend; ++ydst, ++ysrc) { - for (xdst=xbeg; xdst<=xend; ++xdst) { + for (int end_y=dst_y+h; dst_ycopy(src, x, y); + dst->copy(src, x, y, 0, 0, src->width(), src->height()); } void composite_image(Image* dst, const Image* src, int x, int y, int opacity, int blend_mode) { - dst->merge(src, x, y, opacity, blend_mode); + dst->merge(src, x, y, 0, 0, src->width(), src->height(), opacity, blend_mode); } Image* crop_image(const Image* image, int x, int y, int w, int h, color_t bg, const ImageBufferPtr& buffer) @@ -84,7 +84,7 @@ Image* crop_image(const Image* image, int x, int y, int w, int h, color_t bg, co trim->setMaskColor(image->maskColor()); clear_image(trim, bg); - copy_image(trim, image, -x, -y); + trim->copy(image, 0, 0, x, y, w, h); return trim; } @@ -222,6 +222,14 @@ void fill_rect(Image* image, int x1, int y1, int x2, int y2, color_t color) image->fillRect(x1, y1, x2, y2, color); } +void fill_rect(Image* image, const gfx::Rect& rc, color_t c) +{ + gfx::Rect clip = rc.createIntersect(image->bounds()); + if (!clip.isEmpty()) + image->fillRect(clip.x, clip.y, + clip.x+clip.w-1, clip.y+clip.h-1, c); +} + void blend_rect(Image* image, int x1, int y1, int x2, int y2, color_t color, int opacity) { int t; diff --git a/src/doc/primitives.h b/src/doc/primitives.h index 723915a90..ba6481e5a 100644 --- a/src/doc/primitives.h +++ b/src/doc/primitives.h @@ -10,6 +10,7 @@ #include "doc/color.h" #include "doc/image_buffer.h" +#include "gfx/fwd.h" namespace doc { class Brush; @@ -32,6 +33,7 @@ namespace doc { void draw_vline(Image* image, int x, int y1, int y2, color_t c); void draw_rect(Image* image, int x1, int y1, int x2, int y2, color_t c); void fill_rect(Image* image, int x1, int y1, int x2, int y2, color_t c); + void fill_rect(Image* image, const gfx::Rect& rc, color_t c); void blend_rect(Image* image, int x1, int y1, int x2, int y2, color_t c, int opacity); void draw_line(Image* image, int x1, int y1, int x2, int y2, color_t c); void draw_ellipse(Image* image, int x1, int y1, int x2, int y2, color_t c); diff --git a/src/doc/quantization.cpp b/src/doc/quantization.cpp index be59627b0..ccdfac0a5 100644 --- a/src/doc/quantization.cpp +++ b/src/doc/quantization.cpp @@ -108,7 +108,7 @@ Image* convert_pixel_format( // RGB -> RGB case IMAGE_RGB: - new_image->copy(image, 0, 0); + new_image->copy(image, 0, 0, 0, 0, image->width(), image->height()); break; // RGB -> Grayscale @@ -189,7 +189,7 @@ Image* convert_pixel_format( // Grayscale -> Grayscale case IMAGE_GRAYSCALE: - new_image->copy(image, 0, 0); + new_image->copy(image, 0, 0, 0, 0, image->width(), image->height()); break; // Grayscale -> Indexed