mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-28 18:32:50 +00:00
29898744fe
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.
1187 lines
35 KiB
C++
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);
|
|
}
|