aseprite/src/undo_transaction.cpp
David Capello 29898744fe Fix issue #160, crashes undoing critical actions like "remove layer" or "remove frame".
The OpenGroup & CloseGroup undoers now save the current SpritePosition
when the Undoer instance is created. Before we were using the current
Layer and Frame at the moment of the OpenGroup creation, it was generating
buggy undoers.
2012-08-23 21:44:41 -03:00

1187 lines
35 KiB
C++

/* ASEPRITE
* Copyright (C) 2001-2012 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 "undo_transaction.h"
#include "base/unique_ptr.h"
#include "document.h"
#include "document_event.h"
#include "document_observer.h"
#include "document_undo.h"
#include "raster/algorithm/flip_image.h"
#include "raster/blend.h"
#include "raster/cel.h"
#include "raster/dirty.h"
#include "raster/image.h"
#include "raster/layer.h"
#include "raster/mask.h"
#include "raster/palette.h"
#include "raster/quantization.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/add_layer.h"
#include "undoers/add_palette.h"
#include "undoers/close_group.h"
#include "undoers/dirty_area.h"
#include "undoers/flip_image.h"
#include "undoers/image_area.h"
#include "undoers/move_layer.h"
#include "undoers/open_group.h"
#include "undoers/remove_cel.h"
#include "undoers/remove_image.h"
#include "undoers/remove_layer.h"
#include "undoers/remove_palette.h"
#include "undoers/replace_image.h"
#include "undoers/set_cel_frame.h"
#include "undoers/set_cel_position.h"
#include "undoers/set_current_frame.h"
#include "undoers/set_current_layer.h"
#include "undoers/set_frame_duration.h"
#include "undoers/set_layer_flags.h"
#include "undoers/set_layer_name.h"
#include "undoers/set_mask.h"
#include "undoers/set_mask_position.h"
#include "undoers/set_sprite_pixel_format.h"
#include "undoers/set_sprite_size.h"
#include "undoers/set_stock_pixel_format.h"
#include "undoers/set_total_frames.h"
UndoTransaction::UndoTransaction(Document* document, const char* label, undo::Modification modification)
: m_label(label)
, m_modification(modification)
{
ASSERT(label != NULL);
m_document = document;
m_sprite = document->getSprite();
m_undo = document->getUndo();
m_closed = false;
m_committed = false;
m_enabledFlag = m_undo->isEnabled();
if (isEnabled())
m_undo->pushUndoer(new undoers::OpenGroup(getObjects(),
m_label,
m_modification,
m_sprite));
}
UndoTransaction::~UndoTransaction()
{
try {
if (isEnabled()) {
// If it isn't committed, we have to rollback all changes.
if (!m_committed && !m_closed)
rollback();
ASSERT(m_closed);
}
}
catch (...) {
// Just avoid throwing an exception in the dtor (just in case
// rollback() failed).
// TODO logging error
}
}
void UndoTransaction::closeUndoGroup()
{
ASSERT(!m_closed);
if (isEnabled()) {
// Close the undo information.
m_undo->pushUndoer(new undoers::CloseGroup(getObjects(),
m_label,
m_modification,
m_sprite));
m_closed = true;
}
}
void UndoTransaction::commit()
{
ASSERT(!m_closed);
ASSERT(!m_committed);
if (isEnabled()) {
closeUndoGroup();
m_committed = true;
}
}
void UndoTransaction::rollback()
{
ASSERT(!m_closed);
ASSERT(!m_committed);
if (isEnabled()) {
closeUndoGroup();
// Undo the group of operations.
m_undo->doUndo();
// Clear the redo (sorry to the user, here we lost the old redo
// information).
m_undo->clearRedo();
}
}
void UndoTransaction::pushUndoer(undo::Undoer* undoer)
{
m_undo->pushUndoer(undoer);
}
undo::ObjectsContainer* UndoTransaction::getObjects() const
{
return m_undo->getObjects();
}
void UndoTransaction::setNumberOfFrames(FrameNumber frames)
{
ASSERT(frames >= 1);
// Save in undo the current totalFrames property
if (isEnabled())
m_undo->pushUndoer(new undoers::SetTotalFrames(m_undo->getObjects(), m_sprite));
// Change the property
m_sprite->setTotalFrames(frames);
}
void UndoTransaction::setCurrentFrame(FrameNumber frame)
{
ASSERT(frame >= 0);
if (isEnabled())
m_undo->pushUndoer(new undoers::SetCurrentFrame(m_undo->getObjects(), m_sprite));
m_sprite->setCurrentFrame(frame);
}
/**
* Sets the current selected layer in the sprite.
*
* @param layer
* The layer to select. Can be NULL.
*/
void UndoTransaction::setCurrentLayer(Layer* layer)
{
if (isEnabled())
m_undo->pushUndoer(new undoers::SetCurrentLayer(
m_undo->getObjects(), m_sprite));
m_sprite->setCurrentLayer(layer);
}
void UndoTransaction::setSpriteSize(int w, int h)
{
ASSERT(w > 0);
ASSERT(h > 0);
if (isEnabled())
m_undo->pushUndoer(new undoers::SetSpriteSize(m_undo->getObjects(), m_sprite));
m_sprite->setSize(w, h);
DocumentEvent ev(m_document, m_sprite);
m_document->notifyObservers<DocumentEvent&>(&DocumentObserver::onSpriteSizeChanged, ev);
}
void UndoTransaction::cropSprite(const gfx::Rect& bounds, int bgcolor)
{
setSpriteSize(bounds.w, bounds.h);
displaceLayers(m_sprite->getFolder(), -bounds.x, -bounds.y);
Layer *background_layer = m_sprite->getBackgroundLayer();
if (background_layer)
cropLayer(background_layer, 0, 0, m_sprite->getWidth(), m_sprite->getHeight(), bgcolor);
if (!m_document->getMask()->isEmpty())
setMaskPosition(m_document->getMask()->getBounds().x-bounds.x,
m_document->getMask()->getBounds().y-bounds.y);
}
void UndoTransaction::trimSprite(int bgcolor)
{
FrameNumber old_frame = m_sprite->getCurrentFrame();
gfx::Rect bounds;
UniquePtr<Image> image_wrap(Image::create(m_sprite->getPixelFormat(),
m_sprite->getWidth(),
m_sprite->getHeight()));
Image* image = image_wrap.get();
for (FrameNumber frame(0); frame<m_sprite->getTotalFrames(); ++frame) {
image->clear(0);
m_sprite->setCurrentFrame(frame);
m_sprite->render(image, 0, 0);
// TODO configurable (what color pixel to use as "refpixel",
// here we are using the top-left pixel by default)
gfx::Rect frameBounds;
if (image_shrink_rect(image, frameBounds, image_getpixel(image, 0, 0)))
bounds = bounds.createUnion(frameBounds);
}
m_sprite->setCurrentFrame(old_frame);
if (!bounds.isEmpty())
cropSprite(bounds, bgcolor);
}
void UndoTransaction::setPixelFormat(PixelFormat newFormat, DitheringMethod dithering_method)
{
Image *old_image;
Image *new_image;
int c;
if (m_sprite->getPixelFormat() == newFormat)
return;
// Change pixel format of the stock of images.
if (isEnabled())
m_undo->pushUndoer(new undoers::SetStockPixelFormat(m_undo->getObjects(), m_sprite->getStock()));
m_sprite->getStock()->setPixelFormat(newFormat);
// Use the rgbmap for the specified sprite
const RgbMap* rgbmap = m_sprite->getRgbMap();
for (c=0; c<m_sprite->getStock()->size(); c++) {
old_image = m_sprite->getStock()->getImage(c);
if (!old_image)
continue;
new_image = quantization::convert_pixel_format(old_image, newFormat, dithering_method, rgbmap,
// TODO check this out
m_sprite->getCurrentPalette(),
m_sprite->getBackgroundLayer() != NULL);
this->replaceStockImage(c, new_image);
}
// Change sprite's pixel format.
if (isEnabled())
m_undo->pushUndoer(new undoers::SetSpritePixelFormat(m_undo->getObjects(), m_sprite));
m_sprite->setPixelFormat(newFormat);
// Regenerate extras
m_document->destroyExtraCel();
// When we are converting to grayscale color mode, we've to destroy
// all palettes and put only one grayscaled-palette at the first
// frame.
if (newFormat == IMAGE_GRAYSCALE) {
// Add undoers to revert all palette changes.
if (isEnabled()) {
PalettesList palettes = m_sprite->getPalettes();
for (PalettesList::iterator it = palettes.begin(); it != palettes.end(); ++it) {
Palette* palette = *it;
m_undo->pushUndoer(new undoers::RemovePalette(
m_undo->getObjects(), m_sprite, palette->getFrame()));
}
m_undo->pushUndoer(new undoers::AddPalette(
m_undo->getObjects(), m_sprite, FrameNumber(0)));
}
// It's a UniquePtr because setPalette'll create a copy of "graypal".
UniquePtr<Palette> graypal(Palette::createGrayscale());
m_sprite->resetPalettes();
m_sprite->setPalette(graypal, true);
}
}
/**
* Adds a new image in the stock.
*
* @return
* The image index in the stock.
*/
int UndoTransaction::addImageInStock(Image* image)
{
ASSERT(image);
// add the image in the stock
int image_index = m_sprite->getStock()->addImage(image);
if (isEnabled())
m_undo->pushUndoer(new undoers::AddImage(m_undo->getObjects(),
m_sprite->getStock(), image_index));
return image_index;
}
/**
* Removes and destroys the specified image in the stock.
*/
void UndoTransaction::removeImageFromStock(int image_index)
{
ASSERT(image_index >= 0);
Image* image = m_sprite->getStock()->getImage(image_index);
ASSERT(image);
if (isEnabled())
m_undo->pushUndoer(new undoers::RemoveImage(m_undo->getObjects(),
m_sprite->getStock(), image_index));
m_sprite->getStock()->removeImage(image);
image_free(image);
}
void UndoTransaction::replaceStockImage(int image_index, Image* new_image)
{
// get the current image in the 'image_index' position
Image* old_image = m_sprite->getStock()->getImage(image_index);
ASSERT(old_image);
// replace the image in the stock
if (isEnabled())
m_undo->pushUndoer(new undoers::ReplaceImage(m_undo->getObjects(),
m_sprite->getStock(), image_index));
m_sprite->getStock()->replaceImage(image_index, new_image);
// destroy the old image
image_free(old_image);
DocumentEvent ev(m_document, m_sprite, NULL, NULL, new_image, image_index);
m_document->notifyObservers<DocumentEvent&>(&DocumentObserver::onImageReplaced, ev);
}
/**
* Creates a new transparent layer.
*/
LayerImage* UndoTransaction::newLayer()
{
// new layer
LayerImage* layer = new LayerImage(m_sprite);
// add the layer in the sprite set
if (isEnabled())
m_undo->pushUndoer(new undoers::AddLayer(m_undo->getObjects(),
m_sprite->getFolder(), layer));
m_sprite->getFolder()->addLayer(layer);
// select the new layer
setCurrentLayer(layer);
DocumentEvent ev(m_document, m_sprite, layer);
m_document->notifyObservers<DocumentEvent&>(&DocumentObserver::onAddLayer, ev);
return layer;
}
/**
* Removes and destroys the specified layer.
*/
void UndoTransaction::removeLayer(Layer* layer)
{
ASSERT(layer);
DocumentEvent ev(m_document, m_sprite, layer);
m_document->notifyObservers<DocumentEvent&>(&DocumentObserver::onRemoveLayer, ev);
LayerFolder* parent = layer->get_parent();
// if the layer to be removed is the selected layer
if (layer == m_sprite->getCurrentLayer()) {
Layer* layer_select = NULL;
// select: previous layer, or next layer, or parent(if it is not the
// main layer of sprite set)
if (layer->get_prev())
layer_select = layer->get_prev();
else if (layer->get_next())
layer_select = layer->get_next();
else if (parent != m_sprite->getFolder())
layer_select = parent;
// select other layer
setCurrentLayer(layer_select);
}
// remove the layer
if (isEnabled())
m_undo->pushUndoer(new undoers::RemoveLayer(m_undo->getObjects(),
layer));
parent->removeLayer(layer);
// destroy the layer
delete layer;
}
void UndoTransaction::restackLayerAfter(Layer* layer, Layer* afterThis)
{
if (isEnabled())
m_undo->pushUndoer(new undoers::MoveLayer(m_undo->getObjects(), layer));
layer->get_parent()->stackLayer(layer, afterThis);
DocumentEvent ev(m_document, m_sprite, layer);
m_document->notifyObservers<DocumentEvent&>(&DocumentObserver::onLayerRestacked, ev);
}
void UndoTransaction::cropLayer(Layer* layer, int x, int y, int w, int h, int bgcolor)
{
if (!layer->is_image())
return;
if (!layer->is_background())
bgcolor = 0;
CelIterator it = ((LayerImage*)layer)->getCelBegin();
CelIterator end = ((LayerImage*)layer)->getCelEnd();
for (; it != end; ++it)
cropCel(*it, x, y, w, h, bgcolor);
}
/**
* Moves every frame in @a layer with the offset (@a dx, @a dy).
*/
void UndoTransaction::displaceLayers(Layer* layer, int dx, int dy)
{
switch (layer->getType()) {
case GFXOBJ_LAYER_IMAGE: {
CelIterator it = ((LayerImage*)layer)->getCelBegin();
CelIterator end = ((LayerImage*)layer)->getCelEnd();
for (; it != end; ++it) {
Cel* cel = *it;
setCelPosition(cel, cel->getX()+dx, cel->getY()+dy);
}
break;
}
case GFXOBJ_LAYER_FOLDER: {
LayerIterator it = ((LayerFolder*)layer)->get_layer_begin();
LayerIterator end = ((LayerFolder*)layer)->get_layer_end();
for (; it != end; ++it)
displaceLayers(*it, dx, dy);
break;
}
}
}
void UndoTransaction::backgroundFromLayer(LayerImage* layer, int bgcolor)
{
ASSERT(layer);
ASSERT(layer->is_image());
ASSERT(layer->is_readable());
ASSERT(layer->is_writable());
ASSERT(layer->getSprite() == m_sprite);
ASSERT(m_sprite->getBackgroundLayer() == NULL);
// create a temporary image to draw each frame of the new
// `Background' layer
UniquePtr<Image> bg_image_wrap(Image::create(m_sprite->getPixelFormat(),
m_sprite->getWidth(),
m_sprite->getHeight()));
Image* bg_image = bg_image_wrap.get();
CelIterator it = layer->getCelBegin();
CelIterator end = layer->getCelEnd();
for (; it != end; ++it) {
Cel* cel = *it;
ASSERT((cel->getImage() > 0) &&
(cel->getImage() < m_sprite->getStock()->size()));
// get the image from the sprite's stock of images
Image* cel_image = m_sprite->getStock()->getImage(cel->getImage());
ASSERT(cel_image);
image_clear(bg_image, bgcolor);
image_merge(bg_image, cel_image,
cel->getX(),
cel->getY(),
MID(0, cel->getOpacity(), 255),
layer->getBlendMode());
// now we have to copy the new image (bg_image) to the cel...
setCelPosition(cel, 0, 0);
// same size of cel-image and bg-image
if (bg_image->w == cel_image->w &&
bg_image->h == cel_image->h) {
if (isEnabled())
m_undo->pushUndoer(new undoers::ImageArea(m_undo->getObjects(),
cel_image, 0, 0, cel_image->w, cel_image->h));
image_copy(cel_image, bg_image, 0, 0);
}
else {
replaceStockImage(cel->getImage(), Image::createCopy(bg_image));
}
}
// Fill all empty cels with a flat-image filled with bgcolor
for (FrameNumber frame(0); frame<m_sprite->getTotalFrames(); ++frame) {
Cel* cel = layer->getCel(frame);
if (!cel) {
Image* cel_image = Image::create(m_sprite->getPixelFormat(), m_sprite->getWidth(), m_sprite->getHeight());
image_clear(cel_image, bgcolor);
// Add the new image in the stock
int image_index = addImageInStock(cel_image);
// Create the new cel and add it to the new background layer
cel = new Cel(frame, image_index);
addCel(layer, cel);
}
}
configureLayerAsBackground(layer);
}
void UndoTransaction::layerFromBackground()
{
ASSERT(m_sprite->getBackgroundLayer() != NULL);
ASSERT(m_sprite->getCurrentLayer() != NULL);
ASSERT(m_sprite->getCurrentLayer()->is_image());
ASSERT(m_sprite->getCurrentLayer()->is_readable());
ASSERT(m_sprite->getCurrentLayer()->is_writable());
ASSERT(m_sprite->getCurrentLayer()->is_background());
if (isEnabled()) {
m_undo->pushUndoer(new undoers::SetLayerFlags(m_undo->getObjects(), m_sprite->getCurrentLayer()));
m_undo->pushUndoer(new undoers::SetLayerName(m_undo->getObjects(), m_sprite->getCurrentLayer()));
}
m_sprite->getCurrentLayer()->set_background(false);
m_sprite->getCurrentLayer()->set_moveable(true);
m_sprite->getCurrentLayer()->setName("Layer 0");
}
void UndoTransaction::flattenLayers(int bgcolor)
{
Image* cel_image;
Cel* cel;
// create a temporary image
UniquePtr<Image> image_wrap(Image::create(m_sprite->getPixelFormat(),
m_sprite->getWidth(),
m_sprite->getHeight()));
Image* image = image_wrap.get();
/* get the background layer from the sprite */
LayerImage* background = m_sprite->getBackgroundLayer();
if (!background) {
/* if there aren't a background layer we must to create the background */
background = new LayerImage(m_sprite);
if (isEnabled())
m_undo->pushUndoer(new undoers::AddLayer(m_undo->getObjects(),
m_sprite->getFolder(), background));
m_sprite->getFolder()->addLayer(background);
if (isEnabled())
m_undo->pushUndoer(new undoers::MoveLayer(m_undo->getObjects(),
background));
background->configureAsBackground();
}
/* copy all frames to the background */
for (FrameNumber frame(0); frame<m_sprite->getTotalFrames(); ++frame) {
/* clear the image and render this frame */
image_clear(image, bgcolor);
layer_render(m_sprite->getFolder(), image, 0, 0, frame);
cel = background->getCel(frame);
if (cel) {
cel_image = m_sprite->getStock()->getImage(cel->getImage());
ASSERT(cel_image != NULL);
/* we have to save the current state of `cel_image' in the undo */
if (isEnabled()) {
Dirty* dirty = new Dirty(cel_image, image);
dirty->saveImagePixels(cel_image);
m_undo->pushUndoer(new undoers::DirtyArea(
m_undo->getObjects(), cel_image, dirty));
delete dirty;
}
}
else {
/* if there aren't a cel in this frame in the background, we
have to create a copy of the image for the new cel */
cel_image = Image::createCopy(image);
/* TODO error handling: if (!cel_image) { ... } */
/* here we create the new cel (with the new image `cel_image') */
cel = new Cel(frame, m_sprite->getStock()->addImage(cel_image));
/* TODO error handling: if (!cel) { ... } */
/* and finally we add the cel in the background */
background->addCel(cel);
}
image_copy(cel_image, image, 0, 0);
}
/* select the background */
if (m_sprite->getCurrentLayer() != background) {
if (isEnabled())
m_undo->pushUndoer(new undoers::SetCurrentLayer(
m_undo->getObjects(), m_sprite));
m_sprite->setCurrentLayer(background);
}
// Remove old layers.
LayerList layers = m_sprite->getFolder()->get_layers_list();
LayerIterator it = layers.begin();
LayerIterator end = layers.end();
for (; it != end; ++it) {
if (*it != background) {
Layer* old_layer = *it;
// Remove the layer
if (isEnabled())
m_undo->pushUndoer(new undoers::RemoveLayer(m_undo->getObjects(),
old_layer));
m_sprite->getFolder()->removeLayer(old_layer);
// Destroy the layer
delete old_layer;
}
}
}
void UndoTransaction::configureLayerAsBackground(LayerImage* layer)
{
if (isEnabled()) {
m_undo->pushUndoer(new undoers::SetLayerFlags(m_undo->getObjects(), layer));
m_undo->pushUndoer(new undoers::SetLayerName(m_undo->getObjects(), layer));
m_undo->pushUndoer(new undoers::MoveLayer(m_undo->getObjects(), layer));
}
layer->configureAsBackground();
}
void UndoTransaction::newFrame()
{
FrameNumber newFrame = m_sprite->getCurrentFrame().next();
// add a new cel to every layer
newFrameForLayer(m_sprite->getFolder(), newFrame);
// increment frames counter in the sprite
setNumberOfFrames(m_sprite->getTotalFrames() + FrameNumber(1));
// go to next frame (the new one)
setCurrentFrame(newFrame);
DocumentEvent ev(m_document, m_sprite, NULL, NULL, NULL, -1, newFrame);
m_document->notifyObservers<DocumentEvent&>(&DocumentObserver::onAddFrame, ev);
}
void UndoTransaction::newFrameForLayer(Layer* layer, FrameNumber frame)
{
ASSERT(layer);
ASSERT(frame >= 0);
switch (layer->getType()) {
case GFXOBJ_LAYER_IMAGE:
// displace all cels in '>=frame' to the next frame
for (FrameNumber c=m_sprite->getLastFrame(); c>=frame; --c) {
Cel* cel = static_cast<LayerImage*>(layer)->getCel(c);
if (cel)
setCelFramePosition(cel, cel->getFrame().next());
}
copyPreviousFrame(layer, frame);
break;
case GFXOBJ_LAYER_FOLDER: {
LayerIterator it = static_cast<LayerFolder*>(layer)->get_layer_begin();
LayerIterator end = static_cast<LayerFolder*>(layer)->get_layer_end();
for (; it != end; ++it)
newFrameForLayer(*it, frame);
break;
}
}
}
void UndoTransaction::removeFrame(FrameNumber frame)
{
ASSERT(frame >= 0);
DocumentEvent ev(m_document, m_sprite, NULL, NULL, NULL, -1, frame);
m_document->notifyObservers<DocumentEvent&>(&DocumentObserver::onRemoveFrame, ev);
// Remove cels from this frame (and displace one position backward
// all next frames)
removeFrameOfLayer(m_sprite->getFolder(), frame);
// New value for totalFrames propety
FrameNumber newTotalFrames = m_sprite->getTotalFrames() - FrameNumber(1);
// Move backward if we will be outside the range of frames
if (m_sprite->getCurrentFrame() >= newTotalFrames)
setCurrentFrame(newTotalFrames.previous());
// Decrement frames counter in the sprite
setNumberOfFrames(newTotalFrames);
}
void UndoTransaction::removeFrameOfLayer(Layer* layer, FrameNumber frame)
{
ASSERT(layer);
ASSERT(frame >= 0);
switch (layer->getType()) {
case GFXOBJ_LAYER_IMAGE:
if (Cel* cel = static_cast<LayerImage*>(layer)->getCel(frame))
removeCel(static_cast<LayerImage*>(layer), cel);
for (++frame; frame<m_sprite->getTotalFrames(); ++frame)
if (Cel* cel = static_cast<LayerImage*>(layer)->getCel(frame))
setCelFramePosition(cel, cel->getFrame().previous());
break;
case GFXOBJ_LAYER_FOLDER: {
LayerIterator it = static_cast<LayerFolder*>(layer)->get_layer_begin();
LayerIterator end = static_cast<LayerFolder*>(layer)->get_layer_end();
for (; it != end; ++it)
removeFrameOfLayer(*it, frame);
break;
}
}
}
/**
* Copies the previous cel of @a frame to @frame.
*/
void UndoTransaction::copyPreviousFrame(Layer* layer, FrameNumber frame)
{
ASSERT(layer);
ASSERT(frame > 0);
// create a copy of the previous cel
Cel* src_cel = static_cast<LayerImage*>(layer)->getCel(frame.previous());
Image* src_image = src_cel ? m_sprite->getStock()->getImage(src_cel->getImage()):
NULL;
// nothing to copy, it will be a transparent cel
if (!src_image)
return;
// copy the image
Image* dst_image = Image::createCopy(src_image);
int image_index = addImageInStock(dst_image);
// create the new cel
Cel* dst_cel = new Cel(frame, image_index);
if (src_cel) { // copy the data from the previous cel
dst_cel->setPosition(src_cel->getX(), src_cel->getY());
dst_cel->setOpacity(src_cel->getOpacity());
}
// add the cel in the layer
addCel(static_cast<LayerImage*>(layer), dst_cel);
}
void UndoTransaction::addCel(LayerImage* layer, Cel* cel)
{
ASSERT(layer);
ASSERT(cel);
if (isEnabled())
m_undo->pushUndoer(new undoers::AddCel(m_undo->getObjects(),
layer, cel));
layer->addCel(cel);
DocumentEvent ev(m_document, m_sprite, layer, cel);
m_document->notifyObservers<DocumentEvent&>(&DocumentObserver::onAddCel, ev);
}
void UndoTransaction::removeCel(LayerImage* layer, Cel* cel)
{
ASSERT(layer);
ASSERT(cel);
DocumentEvent ev(m_document, m_sprite, layer, cel);
m_document->notifyObservers<DocumentEvent&>(&DocumentObserver::onRemoveCel, ev);
// find if the image that use the cel to remove, is used by
// another cels
bool used = false;
for (FrameNumber frame(0); frame<m_sprite->getTotalFrames(); ++frame) {
Cel* it = layer->getCel(frame);
if (it && it != cel && it->getImage() == cel->getImage()) {
used = true;
break;
}
}
// if the image is only used by this cel,
// we can remove the image from the stock
if (!used)
removeImageFromStock(cel->getImage());
if (isEnabled())
m_undo->pushUndoer(new undoers::RemoveCel(m_undo->getObjects(),
layer, cel));
// remove the cel from the layer
layer->removeCel(cel);
// and here we destroy the cel
delete cel;
}
void UndoTransaction::setCelFramePosition(Cel* cel, FrameNumber frame)
{
ASSERT(cel);
ASSERT(frame >= 0);
if (isEnabled())
m_undo->pushUndoer(new undoers::SetCelFrame(m_undo->getObjects(), cel));
cel->setFrame(frame);
DocumentEvent ev(m_document, m_sprite, NULL, cel);
m_document->notifyObservers<DocumentEvent&>(&DocumentObserver::onCelFrameChanged, ev);
}
void UndoTransaction::setCelPosition(Cel* cel, int x, int y)
{
ASSERT(cel);
if (isEnabled())
m_undo->pushUndoer(new undoers::SetCelPosition(m_undo->getObjects(), cel));
cel->setPosition(x, y);
DocumentEvent ev(m_document, m_sprite, NULL, cel);
m_document->notifyObservers<DocumentEvent&>(&DocumentObserver::onCelPositionChanged, ev);
}
void UndoTransaction::setFrameDuration(FrameNumber frame, int msecs)
{
if (isEnabled())
m_undo->pushUndoer(new undoers::SetFrameDuration(
m_undo->getObjects(), m_sprite, frame));
m_sprite->setFrameDuration(frame, msecs);
DocumentEvent ev(m_document, m_sprite, NULL, NULL, NULL, -1, frame);
m_document->notifyObservers<DocumentEvent&>(&DocumentObserver::onFrameDurationChanged, ev);
}
void UndoTransaction::setConstantFrameRate(int msecs)
{
if (isEnabled()) {
for (FrameNumber fr(0); fr<m_sprite->getTotalFrames(); ++fr)
m_undo->pushUndoer(new undoers::SetFrameDuration(
m_undo->getObjects(), m_sprite, fr));
}
m_sprite->setDurationForAllFrames(msecs);
}
void UndoTransaction::moveFrameBefore(FrameNumber frame, FrameNumber beforeFrame)
{
if (frame != beforeFrame &&
frame >= 0 &&
frame <= m_sprite->getLastFrame() &&
beforeFrame >= 0 &&
beforeFrame <= m_sprite->getLastFrame()) {
// Change the frame-lengths...
int frlen_aux = m_sprite->getFrameDuration(frame);
// Moving the frame to the future.
if (frame < beforeFrame) {
for (FrameNumber c=frame; c<beforeFrame.previous(); ++c)
setFrameDuration(c, m_sprite->getFrameDuration(c.next()));
setFrameDuration(beforeFrame.previous(), frlen_aux);
}
// Moving the frame to the past.
else if (beforeFrame < frame) {
for (FrameNumber c=frame; c>beforeFrame; --c)
setFrameDuration(c, m_sprite->getFrameDuration(c.previous()));
setFrameDuration(beforeFrame, frlen_aux);
}
// change the cels of position...
moveFrameBeforeLayer(m_sprite->getFolder(), frame, beforeFrame);
}
}
void UndoTransaction::moveFrameBeforeLayer(Layer* layer, FrameNumber frame, FrameNumber beforeFrame)
{
ASSERT(layer);
switch (layer->getType()) {
case GFXOBJ_LAYER_IMAGE: {
CelIterator it = ((LayerImage*)layer)->getCelBegin();
CelIterator end = ((LayerImage*)layer)->getCelEnd();
for (; it != end; ++it) {
Cel* cel = *it;
FrameNumber newFrame = cel->getFrame();
// moving the frame to the future
if (frame < beforeFrame) {
if (cel->getFrame() == frame) {
newFrame = beforeFrame.previous();
}
else if (cel->getFrame() > frame &&
cel->getFrame() < beforeFrame) {
--newFrame;
}
}
// moving the frame to the past
else if (beforeFrame < frame) {
if (cel->getFrame() == frame) {
newFrame = beforeFrame;
}
else if (cel->getFrame() >= beforeFrame &&
cel->getFrame() < frame) {
++newFrame;
}
}
if (cel->getFrame() != newFrame)
setCelFramePosition(cel, newFrame);
}
break;
}
case GFXOBJ_LAYER_FOLDER: {
LayerIterator it = static_cast<LayerFolder*>(layer)->get_layer_begin();
LayerIterator end = static_cast<LayerFolder*>(layer)->get_layer_end();
for (; it != end; ++it)
moveFrameBeforeLayer(*it, frame, beforeFrame);
break;
}
}
}
Cel* UndoTransaction::getCurrentCel()
{
if (m_sprite->getCurrentLayer() && m_sprite->getCurrentLayer()->is_image())
return static_cast<LayerImage*>(m_sprite->getCurrentLayer())->getCel(m_sprite->getCurrentFrame());
else
return NULL;
}
void UndoTransaction::cropCel(Cel* cel, int x, int y, int w, int h, int bgcolor)
{
Image* cel_image = m_sprite->getStock()->getImage(cel->getImage());
ASSERT(cel_image);
// create the new image through a crop
Image* new_image = image_crop(cel_image, x-cel->getX(), y-cel->getY(), w, h, bgcolor);
// replace the image in the stock that is pointed by the cel
replaceStockImage(cel->getImage(), new_image);
// update the cel's position
setCelPosition(cel, x, y);
}
Image* UndoTransaction::getCelImage(Cel* cel)
{
if (cel && cel->getImage() >= 0 && cel->getImage() < m_sprite->getStock()->size())
return m_sprite->getStock()->getImage(cel->getImage());
else
return NULL;
}
// clears the mask region in the current sprite with the specified background color
void UndoTransaction::clearMask(int bgcolor)
{
Cel* cel = getCurrentCel();
if (!cel)
return;
Image* image = getCelImage(cel);
if (!image)
return;
Mask* mask = m_document->getMask();
// If the mask is empty or is not visible then we have to clear the
// entire image in the cel.
if (!m_document->isMaskVisible()) {
// If the layer is the background then we clear the image.
if (m_sprite->getCurrentLayer()->is_background()) {
if (isEnabled())
m_undo->pushUndoer(new undoers::ImageArea(m_undo->getObjects(),
image, 0, 0, image->w, image->h));
// clear all
image_clear(image, bgcolor);
}
// If the layer is transparent we can remove the cel (and its
// associated image).
else {
removeCel(static_cast<LayerImage*>(m_sprite->getCurrentLayer()), cel);
}
}
else {
int offset_x = mask->getBounds().x-cel->getX();
int offset_y = mask->getBounds().y-cel->getY();
int u, v, putx, puty;
int x1 = MAX(0, offset_x);
int y1 = MAX(0, offset_y);
int x2 = MIN(image->w-1, offset_x+mask->getBounds().w-1);
int y2 = MIN(image->h-1, offset_y+mask->getBounds().h-1);
// do nothing
if (x1 > x2 || y1 > y2)
return;
if (isEnabled())
m_undo->pushUndoer(new undoers::ImageArea(m_undo->getObjects(),
image, x1, y1, x2-x1+1, y2-y1+1));
// clear the masked zones
for (v=0; v<mask->getBounds().h; v++) {
div_t d = div(0, 8);
uint8_t* address = ((uint8_t**)mask->getBitmap()->line)[v]+d.quot;
for (u=0; u<mask->getBounds().w; u++) {
if ((*address & (1<<d.rem))) {
putx = u + offset_x;
puty = v + offset_y;
image_putpixel(image, putx, puty, bgcolor);
}
_image_bitmap_next_bit(d, address);
}
}
}
}
void UndoTransaction::flipImage(Image* image,
const gfx::Rect& bounds,
raster::algorithm::FlipType flipType)
{
// Insert the undo operation.
if (isEnabled()) {
m_undo->pushUndoer
(new undoers::FlipImage
(m_undo->getObjects(), image, bounds, flipType));
}
// Flip the portion of the bitmap.
raster::algorithm::flip_image(image, bounds, flipType);
}
void UndoTransaction::flipImageWithMask(Image* image, const Mask* mask, raster::algorithm::FlipType flipType, int bgcolor)
{
UniquePtr<Image> flippedImage((Image::createCopy(image)));
// Flip the portion of the bitmap.
raster::algorithm::flip_image_with_mask(flippedImage, mask, flipType, bgcolor);
// Insert the undo operation.
if (isEnabled()) {
UniquePtr<Dirty> dirty((new Dirty(image, flippedImage)));
dirty->saveImagePixels(image);
m_undo->pushUndoer(new undoers::DirtyArea(m_undo->getObjects(), image, dirty));
}
// Copy the flipped image into the image specified as argument.
image_copy(image, flippedImage, 0, 0);
}
void UndoTransaction::pasteImage(const Image* src_image, int x, int y, int opacity)
{
const Layer* layer = m_sprite->getCurrentLayer();
ASSERT(layer);
ASSERT(layer->is_image());
ASSERT(layer->is_readable());
ASSERT(layer->is_writable());
Cel* cel = ((LayerImage*)layer)->getCel(m_sprite->getCurrentFrame());
ASSERT(cel);
Image* cel_image = m_sprite->getStock()->getImage(cel->getImage());
Image* cel_image2 = Image::createCopy(cel_image);
image_merge(cel_image2, src_image, x-cel->getX(), y-cel->getY(), opacity, BLEND_MODE_NORMAL);
replaceStockImage(cel->getImage(), cel_image2); // TODO fix this, improve, avoid replacing the whole image
}
void UndoTransaction::copyToCurrentMask(Mask* mask)
{
ASSERT(m_document->getMask());
ASSERT(mask);
if (isEnabled())
m_undo->pushUndoer(new undoers::SetMask(m_undo->getObjects(),
m_document));
m_document->getMask()->copyFrom(mask);
}
void UndoTransaction::setMaskPosition(int x, int y)
{
ASSERT(m_document->getMask());
if (isEnabled())
m_undo->pushUndoer(new undoers::SetMaskPosition(m_undo->getObjects(), m_document));
m_document->getMask()->setOrigin(x, y);
m_document->resetTransformation();
}
void UndoTransaction::deselectMask()
{
if (isEnabled())
m_undo->pushUndoer(new undoers::SetMask(m_undo->getObjects(),
m_document));
m_document->setMaskVisible(false);
}