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)
This commit is contained in:
David Capello 2014-12-08 14:57:56 -03:00
parent 07c7756638
commit afbd3b2d96
49 changed files with 1065 additions and 607 deletions

View File

@ -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"

View File

@ -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

View File

@ -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;

View File

@ -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));
}

View File

@ -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);

View File

@ -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);

View File

@ -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.

View File

@ -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;
}
}

View File

@ -85,8 +85,8 @@ private:
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

View File

@ -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) { }

View File

@ -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;

View File

@ -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)));
}
}
};

View File

@ -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;

View File

@ -254,12 +254,11 @@ public:
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);
}

View File

@ -75,7 +75,8 @@ public:
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(),

View File

@ -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);
}

View File

@ -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;

View File

@ -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; i<points_to_interwine.size(); ++i)
points_to_interwine[i] += offset;
switch (m_toolLoop->getTracePolicy()) {
// 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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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();

View File

@ -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<Image> 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;

View File

@ -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);

View File

@ -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; }

View File

@ -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 {

View File

@ -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();

View File

@ -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

View File

@ -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)

View File

@ -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<DrawingState*>(newState.get())
->initToolLoop(editor, msg);
}
return true;
}

View File

@ -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

View File

@ -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; y<rc.h; ++y)
m_stream.write(
(const char*)image->getPixelAddress(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<Image>(m_imageId);
gfx::Region rgn;
int nrects = read32(m_stream);
for (int n=0; n<nrects; ++n) {
gfx::Rect rc;
rc.x = read32(m_stream);
rc.y = read32(m_stream);
rc.w = read32(m_stream);
rc.h = read32(m_stream);
rgn.createUnion(rgn, gfx::Region(rc));
}
// Save the modified region in the "redoers" (the region now
// contains the pixels before the undo)
redoers->pushUndoer(new ModifiedRegion(objects, image, rgn));
// Restore region pixels
for (const auto& rc : rgn)
for (int y=0; y<rc.h; ++y)
m_stream.read(
(char*)image->getPixelAddress(rc.x, rc.y+y),
image->getRowStrideSize(rc.w));
}
} // namespace undoers
} // namespace app

View File

@ -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 <sstream>
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<std::stringstream*>(&m_stream)->tellp();
}
undo::ObjectId m_imageId;
std::stringstream m_stream;
};
} // namespace undoers
} // namespace app
#endif // UNDOERS_MODIFIED_REGION_H_INCLUDED

View File

@ -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<LayerImage*>(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<LayerImage*>(m_layer)->removeCel(m_cel);
// Is the undo enabled?.
if (m_undo.isEnabled()) {
// We can temporary remove the cel.
static_cast<LayerImage*>(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<LayerImage*>(m_layer)->addCel(m_cel);
}
// And finally we add the cel again in the layer.
static_cast<LayerImage*>(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> 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<LayerImage*>(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

View File

@ -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

View File

@ -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;

View File

@ -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,

View File

@ -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();

View File

@ -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<<i), spr->height()*(1<<i));
spr_copy->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,

View File

@ -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);

View File

@ -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; v<m_rows.size(); ++v) {
const Row& srcRow = src.getRow(v);
Row* row = new Row(srcRow.y);
row->cols.resize(srcRow.cols.size());
for (size_t u=0; u<row->cols.size(); ++u) {
Col* col = new Col(*srcRow.cols[u]);
row->cols[u] = col;
}
m_rows[v] = row;
}
}
template<typename ImageTraits>
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; v<m_rows.size(); ++v) {
const Row& srcRow = src.getRow(v);
Row* row = new Row(srcRow.y);
row->cols.resize(srcRow.cols.size());
for (size_t u=0; u<row->cols.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; y<rc.y+rc.h; ++y) {
x1 = rc.x;
x2 = rc.x+rc.w-1;
bool res;
switch (image->pixelFormat()) {
case IMAGE_RGB:
res = shrink_row<RgbTraits>(image, image_diff, x1, y, x2);
break;
bool res;
switch (m_format) {
case IMAGE_RGB:
res = shrink_row<RgbTraits>(image1, image2, x1, y, x2);
break;
case IMAGE_GRAYSCALE:
res = shrink_row<GrayscaleTraits>(image, image_diff, x1, y, x2);
break;
case IMAGE_GRAYSCALE:
res = shrink_row<GrayscaleTraits>(image1, image2, x1, y, x2);
break;
case IMAGE_INDEXED:
res = shrink_row<IndexedTraits>(image, image_diff, x1, y, x2);
break;
case IMAGE_INDEXED:
res = shrink_row<IndexedTraits>(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);
}
}

View File

@ -9,6 +9,7 @@
#pragma once
#include "doc/image.h"
#include "gfx/region.h"
#include <vector>
@ -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;
};

View File

@ -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; v<dirty->getRowsCount(); 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> dirty(new Dirty(static_cast<PixelFormat>(pixelFormat), x1, y1, x2, y2));
int x = read16(is);
int y = read16(is);
int w = read16(is);
int h = read16(is);
base::UniquePtr<Dirty> dirty(new Dirty(
static_cast<PixelFormat>(pixelFormat),
gfx::Rect(x, y, w, h)));
int noRows = read16(is);
if (noRows > 0) {
dirty->m_rows.resize(noRows);
for (v=0; v<dirty->getRowsCount(); v++) {
for (int v=0; v<dirty->getRowsCount(); v++) {
y = read16(is);
base::UniquePtr<Dirty::Row> 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<noCols; u++) {
for (int u=0; u<noCols; u++) {
x = read16(is);
w = read16(is);

View File

@ -51,6 +51,7 @@ namespace doc {
virtual void onImagePixelsModified(DocumentEvent& ev) { }
virtual void onSpritePixelsModified(DocumentEvent& ev) { }
virtual void onExposeSpritePixels(DocumentEvent& ev) { }
// When the number of total frames available is modified.
virtual void onTotalFramesChanged(DocumentEvent& ev) { }

View File

@ -72,8 +72,8 @@ namespace doc {
virtual color_t getPixel(int x, int y) const = 0;
virtual void putPixel(int x, int y, color_t color) = 0;
virtual void clear(color_t color) = 0;
virtual void copy(const Image* src, int x, int y) = 0;
virtual void merge(const Image* src, int x, int y, int opacity, int blend_mode) = 0;
virtual void copy(const Image* src, int dst_x, int dst_y, int src_x, int src_y, int w, int h) = 0;
virtual 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) = 0;
virtual void drawHLine(int x1, int y, int x2, color_t color) = 0;
virtual void fillRect(int x1, int y1, int x2, int y2, color_t color) = 0;
virtual void blendRect(int x1, int y1, int x2, int y2, color_t color, int opacity) = 0;

View File

@ -103,108 +103,48 @@ namespace doc {
*it = color;
}
void copy(const Image* _src, int x, int y) override {
void copy(const Image* _src, int dst_x, int dst_y, int src_x, int src_y, int w, int h) override {
const ImageImpl<Traits>* src = (const ImageImpl<Traits>*)_src;
ImageImpl<Traits>* 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_y<end_y; ++dst_y, ++src_y) {
src_address = src->address(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<Traits>* src = (const ImageImpl<Traits>*)_src;
ImageImpl<Traits>* 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_y<end_y; ++dst_y, ++src_y) {
src_address = src->address(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<end_x; ++x) {
if (*src_address != mask_color)
*dst_address = (*blender)(*dst_address, *src_address, opacity);
@ -231,6 +171,63 @@ namespace doc {
void blendRect(int x1, int y1, int x2, int y2, color_t color, int opacity) override {
fillRect(x1, y1, x2, y2, color);
}
private:
bool clip_rects(const Image* src, int& dst_x, int& dst_y, int& src_x, int& src_y, int& w, int& h) const {
// Clip with destionation image
if (dst_x < 0) {
src_x -= dst_x;
dst_x = 0;
}
if (dst_y < 0) {
src_y -= dst_y;
dst_y = 0;
}
if (dst_x+w > 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<IndexedTraits>::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<IndexedTraits>::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_y<end_y; ++dst_y, ++src_y) {
src_address = src->getPixelAddress(src_x, src_y);
dst_address = getPixelAddress(dst_x, dst_y);
for (xdst=xbeg; xdst<=xend; xdst++) {
for (int x=dst_x; x<end_x; ++x) {
*dst_address = (*src_address);
++dst_address;
@ -336,15 +303,15 @@ namespace doc {
}
}
}
// with mask
// With mask
else {
int mask_color = src->maskColor();
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_y<end_y; ++dst_y, ++src_y) {
src_address = src->getPixelAddress(src_x, src_y);
dst_address = getPixelAddress(dst_x, dst_y);
for (xdst=xbeg; xdst<=xend; ++xdst) {
for (int x=dst_x; x<end_x; ++x) {
if (*src_address != mask_color)
*dst_address = (*src_address);
@ -356,50 +323,18 @@ namespace doc {
}
template<>
inline void ImageImpl<BitmapTraits>::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<BitmapTraits>::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<BitmapTraits> src_it(src, gfx::Rect(src_x, src_y, w, h), src_x, src_y);
ImageIterator<BitmapTraits> 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<BitmapTraits> src_it(src, gfx::Rect(xsrc, ysrc, w, h), xsrc, ysrc);
ImageIterator<BitmapTraits> 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<end_y; ++dst_y, ++src_y) {
for (int x=dst_x; x<end_x; ++x) {
*dst_it = *src_it;
++src_it;
++dst_it;
@ -408,50 +343,18 @@ namespace doc {
}
template<>
inline void ImageImpl<BitmapTraits>::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<BitmapTraits>::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<BitmapTraits> src_it(src, gfx::Rect(src_x, src_y, w, h), src_x, src_y);
ImageIterator<BitmapTraits> 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<BitmapTraits> src_it(src, gfx::Rect(xsrc, ysrc, w, h), xsrc, ysrc);
ImageIterator<BitmapTraits> 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<end_y; ++dst_y, ++src_y) {
for (int x=dst_x; x<end_x; ++x) {
if (*dst_it != 0)
*dst_it = *src_it;
++src_it;

View File

@ -67,12 +67,12 @@ void clear_image(Image* image, color_t color)
void copy_image(Image* dst, const Image* src, int x, int y)
{
dst->copy(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;

View File

@ -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);

View File

@ -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