mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-28 16:20:50 +00:00
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:
parent
07c7756638
commit
afbd3b2d96
14
data/gui.xml
14
data/gui.xml
@ -696,7 +696,7 @@
|
||||
controller="freehand"
|
||||
pointshape="pixel"
|
||||
intertwine="as_lines"
|
||||
tracepolicy="accumulative">
|
||||
tracepolicy="accumulate">
|
||||
<tooltip>*
|
||||
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">
|
||||
<tooltip>*
|
||||
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"
|
||||
/>
|
||||
<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
|
||||
@ -810,7 +810,7 @@
|
||||
ink="paint"
|
||||
controller="one_point"
|
||||
pointshape="floodfill"
|
||||
tracepolicy="accumulative"
|
||||
tracepolicy="accumulate"
|
||||
/>
|
||||
</group>
|
||||
|
||||
@ -880,7 +880,7 @@
|
||||
controller="freehand"
|
||||
pointshape="brush"
|
||||
intertwine="as_lines"
|
||||
tracepolicy="accumulative"
|
||||
tracepolicy="accumulate"
|
||||
/>
|
||||
<tool id="polygon"
|
||||
text="Polygon Tool"
|
||||
@ -900,7 +900,7 @@
|
||||
controller="freehand"
|
||||
pointshape="brush"
|
||||
intertwine="as_lines"
|
||||
tracepolicy="overlap"
|
||||
tracepolicy="accumulate"
|
||||
default_brush_size="16"
|
||||
/>
|
||||
<tool id="jumble"
|
||||
|
@ -249,6 +249,7 @@ add_library(app-lib
|
||||
undoers/dirty_area.cpp
|
||||
undoers/flip_image.cpp
|
||||
undoers/image_area.cpp
|
||||
undoers/modified_region.cpp
|
||||
undoers/move_layer.cpp
|
||||
undoers/open_group.cpp
|
||||
undoers/remap_palette.cpp
|
||||
|
@ -337,7 +337,8 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
|
||||
// destination clipping bounds in Sprite::render() function.
|
||||
tempImage->clear(0);
|
||||
sprite->render(tempImage, 0, 0, frame);
|
||||
resultImage->copy(tempImage, column*sprite->width(), row*sprite->height());
|
||||
resultImage->copy(tempImage, column*sprite->width(), row*sprite->height(),
|
||||
0, 0, tempImage->width(), tempImage->height());
|
||||
|
||||
if (++column >= columns) {
|
||||
column = 0;
|
||||
|
@ -192,8 +192,7 @@ 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));
|
||||
}
|
||||
|
||||
|
@ -108,6 +108,14 @@ void Document::notifySpritePixelsModified(Sprite* sprite, const gfx::Region& reg
|
||||
notifyObservers<doc::DocumentEvent&>(&doc::DocumentObserver::onSpritePixelsModified, ev);
|
||||
}
|
||||
|
||||
void Document::notifyExposeSpritePixels(Sprite* sprite, const gfx::Region& region)
|
||||
{
|
||||
doc::DocumentEvent ev(this);
|
||||
ev.sprite(sprite);
|
||||
ev.region(region);
|
||||
notifyObservers<doc::DocumentEvent&>(&doc::DocumentObserver::onExposeSpritePixels, ev);
|
||||
}
|
||||
|
||||
void Document::notifyLayerMergedDown(Layer* srcLayer, Layer* targetLayer)
|
||||
{
|
||||
doc::DocumentEvent ev(this);
|
||||
|
@ -91,6 +91,7 @@ namespace app {
|
||||
|
||||
void notifyGeneralUpdate();
|
||||
void notifySpritePixelsModified(Sprite* sprite, const gfx::Region& region);
|
||||
void notifyExposeSpritePixels(Sprite* sprite, const gfx::Region& region);
|
||||
void notifyLayerMergedDown(Layer* srcLayer, Layer* targetLayer);
|
||||
void notifyCelMoved(Layer* fromLayer, FrameNumber fromFrame, Layer* toLayer, FrameNumber toFrame);
|
||||
void notifyCelCopied(Layer* fromLayer, FrameNumber fromFrame, Layer* toLayer, FrameNumber toFrame);
|
||||
|
@ -1093,11 +1093,10 @@ void DocumentApi::flattenLayers(Sprite* sprite)
|
||||
|
||||
// We have to save the current state of `cel_image' in the undo.
|
||||
if (undoEnabled()) {
|
||||
Dirty* dirty = new Dirty(cel_image, image, image->bounds());
|
||||
dirty->saveImagePixels(cel_image);
|
||||
Dirty dirty(cel_image, image, image->bounds());
|
||||
dirty.saveImagePixels(cel_image);
|
||||
m_undoers->pushUndoer(new undoers::DirtyArea(
|
||||
getObjects(), cel_image, dirty));
|
||||
delete dirty;
|
||||
getObjects(), cel_image, &dirty));
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -1296,10 +1295,9 @@ void DocumentApi::flipImageWithMask(Layer* layer, Image* image, const Mask* mask
|
||||
|
||||
// Insert the undo operation.
|
||||
if (undoEnabled()) {
|
||||
base::UniquePtr<Dirty> dirty((new Dirty(image, flippedImage, image->bounds())));
|
||||
dirty->saveImagePixels(image);
|
||||
|
||||
m_undoers->pushUndoer(new undoers::DirtyArea(getObjects(), image, dirty));
|
||||
Dirty dirty(image, flippedImage, image->bounds());
|
||||
dirty.saveImagePixels(image);
|
||||
m_undoers->pushUndoer(new undoers::DirtyArea(getObjects(), image, &dirty));
|
||||
}
|
||||
|
||||
// Copy the flipped image into the image specified as argument.
|
||||
|
@ -1026,18 +1026,23 @@ 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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -20,6 +20,10 @@
|
||||
#define APP_TOOLS_INK_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
namespace gfx {
|
||||
class Region;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
namespace tools {
|
||||
|
||||
@ -64,6 +68,12 @@ namespace app {
|
||||
// Returns true if this ink is used to mark slices
|
||||
virtual bool isSlice() const { return false; }
|
||||
|
||||
// Returns true if this ink needs a special source area. For
|
||||
// example, blur tool needs one extra pixel to all sides of the
|
||||
// modified area, so it can use a 3x3 convolution matrix.
|
||||
virtual bool needsSpecialSourceArea() const { return false; }
|
||||
virtual void createSpecialSourceArea(const gfx::Region& dirtyArea, gfx::Region& sourceArea) const { }
|
||||
|
||||
// It is called when the tool-loop start (generally when the user
|
||||
// presses a mouse button over a sprite editor)
|
||||
virtual void prepareInk(ToolLoop* loop) { }
|
||||
|
@ -587,7 +587,7 @@ 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;
|
||||
|
@ -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)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -28,6 +28,10 @@ namespace app {
|
||||
namespace tools {
|
||||
class ToolLoop;
|
||||
|
||||
// Converts a sequence of points in several call to
|
||||
// Intertwine::doPointshapePoint(). Basically each implementation
|
||||
// says which pixels should be drawn between a sequence of
|
||||
// user-defined points.
|
||||
class Intertwine {
|
||||
public:
|
||||
typedef std::vector<gfx::Point> Points;
|
||||
|
@ -254,12 +254,11 @@ 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);
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -275,14 +275,14 @@ void ToolBox::loadToolProperties(TiXmlElement* xmlTool, Tool* tool, int button,
|
||||
throw base::Exception("Invalid intertwiner '%s' specified in '%s' tool.\n", intertwine, tool_id);
|
||||
|
||||
// Trace policy
|
||||
TracePolicy tracepolicy_value = TracePolicyLast;
|
||||
TracePolicy tracepolicy_value = TracePolicy::Last;
|
||||
if (tracepolicy) {
|
||||
if (strcmp(tracepolicy, "accumulative") == 0)
|
||||
tracepolicy_value = TracePolicyAccumulate;
|
||||
if (strcmp(tracepolicy, "accumulate") == 0)
|
||||
tracepolicy_value = TracePolicy::Accumulate;
|
||||
else if (strcmp(tracepolicy, "last") == 0)
|
||||
tracepolicy_value = TracePolicyLast;
|
||||
tracepolicy_value = TracePolicy::Last;
|
||||
else if (strcmp(tracepolicy, "overlap") == 0)
|
||||
tracepolicy_value = TracePolicyOverlap;
|
||||
tracepolicy_value = TracePolicy::Overlap;
|
||||
else
|
||||
throw base::Exception("Invalid trace-policy '%s' specified in '%s' tool.\n", tracepolicy, tool_id);
|
||||
}
|
||||
|
@ -88,11 +88,34 @@ namespace app {
|
||||
virtual FrameNumber getFrame() = 0;
|
||||
|
||||
// Should return an image where we can read pixels (readonly image)
|
||||
virtual Image* getSrcImage() = 0;
|
||||
virtual const Image* getSrcImage() = 0;
|
||||
|
||||
// Should return an image where we can write pixels
|
||||
virtual Image* getDstImage() = 0;
|
||||
|
||||
// Makes the specified region valid in the source
|
||||
// image. Basically the implementation should copy from the
|
||||
// original cel the given region to the source image. The source
|
||||
// image is used by inks to create blur effects or similar.
|
||||
virtual void validateSrcImage(const gfx::Region& rgn) = 0;
|
||||
|
||||
// Makes the specified destination image region valid to be
|
||||
// painted. The destination image is used by inks to compose the
|
||||
// brush, so we've to make sure that the destination image
|
||||
// matches the original cel when we make that composition.
|
||||
virtual void validateDstImage(const gfx::Region& rgn) = 0;
|
||||
|
||||
// Invalidates the whole destination image. It's used for tools
|
||||
// like line or rectangle which don't accumulate the effect so
|
||||
// they need to start with a fresh destination image on each
|
||||
// loop step/cycle.
|
||||
virtual void invalidateDstImage() = 0;
|
||||
virtual void invalidateDstImage(const gfx::Region& rgn) = 0;
|
||||
|
||||
// Copies the given region from the destination to the source
|
||||
// image, used by "overlap" tools like jumble or spray.
|
||||
virtual void copyValidDstToSrcImage(const gfx::Region& rgn) = 0;
|
||||
|
||||
// Returns the RGB map used to convert RGB values to palette index.
|
||||
virtual RgbMap* getRgbMap() = 0;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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; }
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
51
src/app/ui/editor/scoped_cursor.h
Normal file
51
src/app/ui/editor/scoped_cursor.h
Normal 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
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
91
src/app/undoers/modified_region.cpp
Normal file
91
src/app/undoers/modified_region.cpp
Normal 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
|
62
src/app/undoers/modified_region.h
Normal file
62
src/app/undoers/modified_region.h
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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) { }
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user