diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 152e137ec..dbe1ce0f2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -293,6 +293,7 @@ add_library(aseprite-library util/celmove.cpp util/clipboard.cpp util/col_file.cpp + util/expand_cel_canvas.cpp util/filetoks.cpp util/misc.cpp util/msk_file.cpp diff --git a/src/util/expand_cel_canvas.cpp b/src/util/expand_cel_canvas.cpp new file mode 100644 index 000000000..d27541dac --- /dev/null +++ b/src/util/expand_cel_canvas.cpp @@ -0,0 +1,221 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 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 + */ + +#include "config.h" + +#include "util/expand_cel_canvas.h" + +#include "base/unique_ptr.h" +#include "document.h" +#include "raster/cel.h" +#include "raster/dirty.h" +#include "raster/layer.h" +#include "raster/sprite.h" +#include "raster/stock.h" +#include "undo/undo_history.h" +#include "undoers/add_cel.h" +#include "undoers/add_image.h" +#include "undoers/close_group.h" +#include "undoers/dirty_area.h" +#include "undoers/open_group.h" +#include "undoers/replace_image.h" +#include "undoers/set_cel_position.h" + +ExpandCelCanvas::ExpandCelCanvas(Document* document, Sprite* sprite, Layer* layer, TiledMode tiledMode) + : m_document(document) + , m_sprite(sprite) + , m_layer(layer) + , m_cel(NULL) + , m_celImage(NULL) + , m_celCreated(false) + , m_closed(false) + , m_committed(false) +{ + if (m_layer->is_image()) { + m_cel = static_cast(layer)->getCel(m_sprite->getCurrentFrame()); + if (m_cel) + m_celImage = m_sprite->getStock()->getImage(m_cel->getImage()); + } + + // If there is no Cel + if (m_cel == NULL) { + // Create the image + m_celImage = image_new(m_sprite->getImgType(), m_sprite->getWidth(), m_sprite->getHeight()); + image_clear(m_celImage, + m_celImage->mask_color); + + // create the cel + m_cel = new Cel(m_sprite->getCurrentFrame(), 0); + static_cast(m_layer)->addCel(m_cel); + + m_celCreated = true; + } + + m_originalCelX = m_cel->getX(); + m_originalCelY = m_cel->getY(); + + // region to draw + int x1, y1, x2, y2; + + if (tiledMode == TILED_NONE) { // non-tiled + x1 = MIN(m_cel->getX(), 0); + y1 = MIN(m_cel->getY(), 0); + x2 = MAX(m_cel->getX()+m_celImage->w, m_sprite->getWidth()); + y2 = MAX(m_cel->getY()+m_celImage->h, m_sprite->getHeight()); + } + else { // tiled + x1 = 0; + y1 = 0; + x2 = m_sprite->getWidth(); + y2 = m_sprite->getHeight(); + } + + // create two copies of the image region which we'll modify with the tool + m_srcImage = image_crop(m_celImage, + x1-m_cel->getX(), + y1-m_cel->getY(), x2-x1, y2-y1, + m_celImage->mask_color); + + m_dstImage = image_new_copy(m_srcImage); + + // 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(x1, y1); +} + +ExpandCelCanvas::~ExpandCelCanvas() +{ + try { + if (!m_committed && !m_closed) + rollback(); + } + catch (...) { + // Do nothing + } + delete m_srcImage; + delete m_dstImage; +} + +void ExpandCelCanvas::commit() +{ + ASSERT(!m_closed); + ASSERT(!m_committed); + + undo::UndoHistory* undo = m_document->getUndoHistory(); + + // If the size of each image is the same, we can create an undo + // with only the differences between both images. + if (m_cel->getX() == m_originalCelX && + m_cel->getY() == m_originalCelY && + m_celImage->w == m_dstImage->w && + m_celImage->h == m_dstImage->h) { + // Was m_celImage created in the start of the tool-loop?. + if (m_celCreated) { + // We can keep the m_celImage + + // We copy the destination image to the m_celImage + image_copy(m_celImage, m_dstImage, 0, 0); + + // Add the m_celImage in the images stock of the sprite. + m_cel->setImage(m_sprite->getStock()->addImage(m_celImage)); + + // Is the undo enabled?. + if (undo->isEnabled()) { + // We can temporary remove the cel. + static_cast(m_sprite->getCurrentLayer())->removeCel(m_cel); + + // We create the undo information (for the new m_celImage + // in the stock and the new cel in the layer)... + undo->pushUndoer(new undoers::OpenGroup()); + undo->pushUndoer(new undoers::AddImage(undo->getObjects(), + m_sprite->getStock(), m_cel->getImage())); + undo->pushUndoer(new undoers::AddCel(undo->getObjects(), + m_sprite->getCurrentLayer(), m_cel)); + undo->pushUndoer(new undoers::CloseGroup()); + + // And finally we add the cel again in the layer. + static_cast(m_sprite->getCurrentLayer())->addCel(m_cel); + } + } + // If the m_celImage was already created before the whole process... + else { + // Add to the undo history the differences between m_celImage and m_dstImage + if (undo->isEnabled()) { + UniquePtr dirty(new Dirty(m_celImage, m_dstImage)); + + dirty->saveImagePixels(m_celImage); + if (dirty != NULL) + undo->pushUndoer(new undoers::DirtyArea(undo->getObjects(), m_celImage, dirty)); + } + + // Copy the destination to the cel image. + image_copy(m_celImage, m_dstImage, 0, 0); + } + } + // If the size of both images are different, we have to + // replace the entire image. + else { + if (undo->isEnabled()) { + undo->pushUndoer(new undoers::OpenGroup()); + + if (m_cel->getX() != m_originalCelX || + m_cel->getY() != m_originalCelY) { + int x = m_cel->getX(); + int y = m_cel->getY(); + m_cel->setPosition(m_originalCelX, m_originalCelY); + + undo->pushUndoer(new undoers::SetCelPosition(undo->getObjects(), m_cel)); + + m_cel->setPosition(x, y); + } + + undo->pushUndoer(new undoers::ReplaceImage(undo->getObjects(), + m_sprite->getStock(), m_cel->getImage())); + undo->pushUndoer(new undoers::CloseGroup()); + } + + // Replace the image in the stock. + m_sprite->getStock()->replaceImage(m_cel->getImage(), m_dstImage); + + // Destroy the old cel image. + image_free(m_celImage); + + // Now the m_dstImage is used, so we haven't to destroy it. + m_dstImage = NULL; + } + + m_committed = true; +} + +void ExpandCelCanvas::rollback() +{ + ASSERT(!m_closed); + ASSERT(!m_committed); + + // Here we destroy the temporary 'cel' created and restore all as it was before + m_cel->setPosition(m_originalCelX, m_originalCelY); + + if (m_celCreated) { + static_cast(m_layer)->removeCel(m_cel); + delete m_cel; + delete m_celImage; + } + + m_closed = true; +} diff --git a/src/util/expand_cel_canvas.h b/src/util/expand_cel_canvas.h new file mode 100644 index 000000000..9ecd0103a --- /dev/null +++ b/src/util/expand_cel_canvas.h @@ -0,0 +1,80 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 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 UTIL_EXPAND_CEL_CANVAS_H_INCLUDED +#define UTIL_EXPAND_CEL_CANVAS_H_INCLUDED + +#include "filters/tiled_mode.h" + +class Cel; +class Document; +class Image; +class Layer; +class Sprite; + +// This class can be used to expand the canvas of the current cel to +// the visible portion of sprite. If the user cancels the operation, +// you've a rollback() method to restore the cel to its original +// state. If all changes are committed, some undo information is +// stored in the document's UndoHistory to go back to the original +// state using "Undo" command. +class ExpandCelCanvas +{ +public: + ExpandCelCanvas(Document* document, Sprite* sprite, Layer* layer, TiledMode tiledMode); + ~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(); + + // 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; + } + + // You can write pixels right here + Image* getDestCanvas() { + return m_dstImage; + } + + const Cel* getCel() const { + return m_cel; + } + +private: + Document* m_document; + Sprite* m_sprite; + Layer* m_layer; + Cel* m_cel; + Image* m_celImage; + bool m_celCreated; + int m_originalCelX; + int m_originalCelY; + Image* m_srcImage; + Image* m_dstImage; + bool m_closed; + bool m_committed; +}; + +#endif diff --git a/src/widgets/editor/tool_loop_impl.cpp b/src/widgets/editor/tool_loop_impl.cpp index 6a973bc93..a6e3dac69 100644 --- a/src/widgets/editor/tool_loop_impl.cpp +++ b/src/widgets/editor/tool_loop_impl.cpp @@ -23,32 +23,22 @@ #include "app.h" #include "app/color.h" #include "app/color_utils.h" -#include "base/unique_ptr.h" #include "context.h" #include "gui/gui.h" #include "modules/editors.h" #include "raster/cel.h" -#include "raster/dirty.h" -#include "raster/image.h" #include "raster/layer.h" #include "raster/mask.h" #include "raster/pen.h" #include "raster/sprite.h" -#include "raster/stock.h" #include "settings/settings.h" #include "tools/ink.h" #include "tools/tool.h" #include "tools/tool_loop.h" #include "undo/undo_history.h" -#include "undoers/add_cel.h" -#include "undoers/add_image.h" -#include "undoers/close_group.h" -#include "undoers/dirty_area.h" -#include "undoers/open_group.h" -#include "undoers/replace_image.h" -#include "undoers/set_cel_position.h" -#include "widgets/editor/editor.h" +#include "util/expand_cel_canvas.h" #include "widgets/color_bar.h" +#include "widgets/editor/editor.h" #include "widgets/statebar.h" #include @@ -63,17 +53,11 @@ class ToolLoopImpl : public tools::ToolLoop Sprite* m_sprite; Layer* m_layer; Cel* m_cel; - Image* m_cel_image; - bool m_cel_created; - int m_old_cel_x; - int m_old_cel_y; bool m_filled; bool m_previewFilled; int m_sprayWidth; int m_spraySpeed; TiledMode m_tiled_mode; - Image* m_src_image; - Image* m_dst_image; bool m_useMask; Mask* m_mask; gfx::Point m_maskOrigin; @@ -85,6 +69,7 @@ class ToolLoopImpl : public tools::ToolLoop tools::ToolLoop::Button m_button; int m_primary_color; int m_secondary_color; + ExpandCelCanvas m_expandCelCanvas; public: ToolLoopImpl(Editor* editor, @@ -102,19 +87,16 @@ public: , m_document(document) , m_sprite(sprite) , m_layer(layer) - , m_cel(NULL) - , m_cel_image(NULL) - , m_cel_created(false) , m_canceled(false) + , m_tiled_mode(m_context->getSettings()->getTiledMode()) , m_button(button) , m_primary_color(color_utils::color_for_layer(primary_color, layer)) , m_secondary_color(color_utils::color_for_layer(secondary_color, layer)) + , m_expandCelCanvas(document, sprite, layer, m_tiled_mode) { // Settings ISettings* settings = m_context->getSettings(); - m_tiled_mode = settings->getTiledMode(); - switch (tool->getFill(m_button)) { case tools::FillNone: m_filled = false; @@ -139,54 +121,6 @@ public: pen_settings->getSize(), pen_settings->getAngle()); - // Get cel and image where we can draw - - if (m_layer->is_image()) { - m_cel = static_cast(sprite->getCurrentLayer())->getCel(sprite->getCurrentFrame()); - if (m_cel) - m_cel_image = sprite->getStock()->getImage(m_cel->getImage()); - } - - if (m_cel == NULL) { - // create the image - m_cel_image = image_new(sprite->getImgType(), sprite->getWidth(), sprite->getHeight()); - image_clear(m_cel_image, - m_cel_image->mask_color); - - // create the cel - m_cel = new Cel(sprite->getCurrentFrame(), 0); - static_cast(sprite->getCurrentLayer())->addCel(m_cel); - - m_cel_created = true; - } - - m_old_cel_x = m_cel->getX(); - m_old_cel_y = m_cel->getY(); - - // region to draw - int x1, y1, x2, y2; - - // non-tiled - if (m_tiled_mode == TILED_NONE) { - x1 = MIN(m_cel->getX(), 0); - y1 = MIN(m_cel->getY(), 0); - x2 = MAX(m_cel->getX()+m_cel_image->w, m_sprite->getWidth()); - y2 = MAX(m_cel->getY()+m_cel_image->h, m_sprite->getHeight()); - } - else { // tiled - x1 = 0; - y1 = 0; - x2 = m_sprite->getWidth(); - y2 = m_sprite->getHeight(); - } - - // create two copies of the image region which we'll modify with the tool - m_src_image = image_crop(m_cel_image, - x1-m_cel->getX(), - y1-m_cel->getY(), x2-x1, y2-y1, - m_cel_image->mask_color); - m_dst_image = image_new_copy(m_src_image); - m_useMask = m_document->isMaskVisible(); // Selection ink @@ -195,6 +129,9 @@ public: m_document->setMask(&emptyMask); } + int x1 = m_expandCelCanvas.getCel()->getX(); + int y1 = m_expandCelCanvas.getCel()->getY(); + m_mask = m_document->getMask(); m_maskOrigin = (!m_mask->is_empty() ? gfx::Point(m_mask->x-x1, m_mask->y-y1): gfx::Point(0, 0)); @@ -204,9 +141,6 @@ public: m_speed.x = 0; m_speed.y = 0; - // we have to modify the cel position because it's used in the - // `render_sprite' routine to draw the `dst_image' - m_cel->setPosition(x1, y1); m_offset.x = -x1; m_offset.y = -y1; @@ -227,112 +161,21 @@ public: ~ToolLoopImpl() { if (!m_canceled) { - undo::UndoHistory* undo = m_document->getUndoHistory(); - // Paint ink if (getInk()->isPaint()) { - // If the size of each image is the same, we can create an - // undo with only the differences between both images. - if (m_cel->getX() == m_old_cel_x && - m_cel->getY() == m_old_cel_y && - m_cel_image->w == m_dst_image->w && - m_cel_image->h == m_dst_image->h) { - // Was the 'cel_image' created in the start of the tool-loop?. - if (m_cel_created) { - // Then we can keep the 'cel_image'... - - // We copy the 'destination' image to the 'cel_image'. - image_copy(m_cel_image, m_dst_image, 0, 0); - - // Add the 'cel_image' in the images' stock of the sprite. - m_cel->setImage(m_sprite->getStock()->addImage(m_cel_image)); - - // Is the undo enabled?. - if (undo->isEnabled()) { - // We can temporary remove the cel. - static_cast(m_sprite->getCurrentLayer())->removeCel(m_cel); - - // We create the undo information (for the new cel_image - // in the stock and the new cel in the layer)... - undo->pushUndoer(new undoers::OpenGroup()); - undo->pushUndoer(new undoers::AddImage(undo->getObjects(), - m_sprite->getStock(), m_cel->getImage())); - undo->pushUndoer(new undoers::AddCel(undo->getObjects(), - m_sprite->getCurrentLayer(), m_cel)); - undo->pushUndoer(new undoers::CloseGroup()); - - // And finally we add the cel again in the layer. - static_cast(m_sprite->getCurrentLayer())->addCel(m_cel); - } - } - else { - // Undo the dirty region. - if (undo->isEnabled()) { - UniquePtr dirty(new Dirty(m_cel_image, m_dst_image)); - - dirty->saveImagePixels(m_cel_image); - if (dirty != NULL) - undo->pushUndoer(new undoers::DirtyArea(undo->getObjects(), - m_cel_image, dirty)); - } - - // Copy the 'dst_image' to the cel_image. - image_copy(m_cel_image, m_dst_image, 0, 0); - } - } - // If the size of both images are different, we have to - // replace the entire image. - else { - if (undo->isEnabled()) { - undo->pushUndoer(new undoers::OpenGroup()); - - if (m_cel->getX() != m_old_cel_x || - m_cel->getY() != m_old_cel_y) { - int x = m_cel->getX(); - int y = m_cel->getY(); - m_cel->setPosition(m_old_cel_x, m_old_cel_y); - - undo->pushUndoer(new undoers::SetCelPosition(undo->getObjects(), m_cel)); - - m_cel->setPosition(x, y); - } - - undo->pushUndoer(new undoers::ReplaceImage(undo->getObjects(), - m_sprite->getStock(), m_cel->getImage())); - undo->pushUndoer(new undoers::CloseGroup()); - } - - // Replace the image in the stock. - m_sprite->getStock()->replaceImage(m_cel->getImage(), m_dst_image); - - // Destroy the old cel image. - image_free(m_cel_image); - - // Now the `dst_image' is used, so we haven't to destroy it. - m_dst_image = NULL; - } + m_expandCelCanvas.commit(); } - // Selection ink - if (getInk()->isSelection()) + else if (getInk()->isSelection()) { m_document->generateMaskBoundaries(); + } } // If the trace was not canceled or it is not a 'paint' ink... if (m_canceled || !getInk()->isPaint()) { - // Here we destroy the temporary 'cel' created and restore all as it was before - - m_cel->setPosition(m_old_cel_x, m_old_cel_y); - - if (m_cel_created) { - static_cast(m_layer)->removeCel(m_cel); - delete m_cel; - delete m_cel_image; - } + m_expandCelCanvas.rollback(); } - delete m_src_image; - delete m_dst_image; delete m_pen; } @@ -343,8 +186,8 @@ public: Document* getDocument() { return m_document; } Sprite* getSprite() { return m_sprite; } Layer* getLayer() { return m_layer; } - Image* getSrcImage() { return m_src_image; } - Image* getDstImage() { return m_dst_image; } + Image* getSrcImage() { return m_expandCelCanvas.getSourceCanvas(); } + Image* getDstImage() { return m_expandCelCanvas.getDestCanvas(); } bool useMask() { return m_useMask; } Mask* getMask() { return m_mask; } gfx::Point getMaskOrigin() { return m_maskOrigin; }