/* 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/document_api.h" #include "app/color_target.h" #include "app/color_utils.h" #include "app/document.h" #include "app/document_undo.h" #include "app/settings/settings.h" #include "app/undoers/add_cel.h" #include "app/undoers/add_frame.h" #include "app/undoers/add_image.h" #include "app/undoers/add_layer.h" #include "app/undoers/add_palette.h" #include "app/undoers/dirty_area.h" #include "app/undoers/flip_image.h" #include "app/undoers/image_area.h" #include "app/undoers/move_layer.h" #include "app/undoers/move_layer.h" #include "app/undoers/remove_cel.h" #include "app/undoers/remove_frame.h" #include "app/undoers/remove_image.h" #include "app/undoers/remove_layer.h" #include "app/undoers/remove_palette.h" #include "app/undoers/replace_image.h" #include "app/undoers/set_cel_frame.h" #include "app/undoers/set_cel_opacity.h" #include "app/undoers/set_cel_position.h" #include "app/undoers/set_frame_duration.h" #include "app/undoers/set_layer_flags.h" #include "app/undoers/set_layer_flags.h" #include "app/undoers/set_layer_name.h" #include "app/undoers/set_layer_name.h" #include "app/undoers/set_mask.h" #include "app/undoers/set_mask_position.h" #include "app/undoers/set_palette_colors.h" #include "app/undoers/set_sprite_pixel_format.h" #include "app/undoers/set_sprite_size.h" #include "app/undoers/set_sprite_transparent_color.h" #include "app/undoers/set_total_frames.h" #include "base/unique_ptr.h" #include "doc/context.h" #include "doc/document_event.h" #include "doc/document_observer.h" #include "raster/algorithm/flip_image.h" #include "raster/algorithm/shrink_bounds.h" #include "raster/blend.h" #include "raster/cel.h" #include "raster/dirty.h" #include "raster/image.h" #include "raster/image_bits.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" namespace app { DocumentApi::DocumentApi(Document* document, undo::UndoersCollector* undoers) : m_document(document) , m_undoers(undoers) { } undo::ObjectsContainer* DocumentApi::getObjects() const { return m_document->getUndo()->getObjects(); } void DocumentApi::setSpriteSize(Sprite* sprite, int w, int h) { ASSERT(w > 0); ASSERT(h > 0); if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetSpriteSize(getObjects(), sprite)); sprite->setSize(w, h); doc::DocumentEvent ev(m_document); ev.sprite(sprite); m_document->notifyObservers(&doc::DocumentObserver::onSpriteSizeChanged, ev); } void DocumentApi::setSpriteTransparentColor(Sprite* sprite, color_t maskColor) { if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetSpriteTransparentColor(getObjects(), sprite)); sprite->setTransparentColor(maskColor); doc::DocumentEvent ev(m_document); ev.sprite(sprite); m_document->notifyObservers(&doc::DocumentObserver::onSpriteTransparentColorChanged, ev); } void DocumentApi::cropSprite(Sprite* sprite, const gfx::Rect& bounds, color_t bgcolor) { setSpriteSize(sprite, bounds.w, bounds.h); displaceLayers(sprite->folder(), -bounds.x, -bounds.y); Layer *background_layer = sprite->backgroundLayer(); if (background_layer) cropLayer(background_layer, 0, 0, sprite->width(), sprite->height(), bgcolor); if (!m_document->mask()->isEmpty()) setMaskPosition(m_document->mask()->bounds().x-bounds.x, m_document->mask()->bounds().y-bounds.y); } void DocumentApi::trimSprite(Sprite* sprite, color_t bgcolor) { gfx::Rect bounds; base::UniquePtr image_wrap(Image::create(sprite->pixelFormat(), sprite->width(), sprite->height())); Image* image = image_wrap.get(); for (FrameNumber frame(0); frametotalFrames(); ++frame) { image->clear(0); sprite->render(image, 0, 0, frame); // TODO configurable (what color pixel to use as "refpixel", // here we are using the top-left pixel by default) gfx::Rect frameBounds; if (raster::algorithm::shrink_bounds(image, frameBounds, get_pixel(image, 0, 0))) bounds = bounds.createUnion(frameBounds); } if (!bounds.isEmpty()) cropSprite(sprite, bounds, bgcolor); } void DocumentApi::setPixelFormat(Sprite* sprite, PixelFormat newFormat, DitheringMethod dithering_method) { Image* old_image; Image* new_image; int c; if (sprite->pixelFormat() == newFormat) return; // TODO Review this, why we use the palette in frame 0? FrameNumber frame(0); // Use the rgbmap for the specified sprite const RgbMap* rgbmap = sprite->getRgbMap(frame); // Get the list of cels from the background layer (if it // exists). This list will be used to check if each image belong to // the background layer. CelList bgCels; if (sprite->backgroundLayer() != NULL) sprite->backgroundLayer()->getCels(bgCels); for (c=0; cstock()->size(); c++) { old_image = sprite->stock()->getImage(c); if (!old_image) continue; bool is_image_from_background = false; for (CelList::iterator it=bgCels.begin(), end=bgCels.end(); it != end; ++it) { if ((*it)->imageIndex() == c) { is_image_from_background = true; break; } } new_image = quantization::convert_pixel_format (old_image, NULL, newFormat, dithering_method, rgbmap, sprite->getPalette(frame), is_image_from_background); replaceStockImage(sprite, c, new_image); } // Set all cels opacity to 100% if we are converting to indexed. if (newFormat == IMAGE_INDEXED) { CelList cels; sprite->getCels(cels); for (CelIterator it = cels.begin(), end = cels.end(); it != end; ++it) { Cel* cel = *it; if (cel->opacity() < 255) setCelOpacity(sprite, *it, 255); } } // Change sprite's pixel format. if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetSpritePixelFormat(getObjects(), sprite)); 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 (undoEnabled()) { PalettesList palettes = sprite->getPalettes(); for (PalettesList::iterator it = palettes.begin(); it != palettes.end(); ++it) { Palette* palette = *it; m_undoers->pushUndoer(new undoers::RemovePalette( getObjects(), sprite, palette->frame())); } m_undoers->pushUndoer(new undoers::AddPalette( getObjects(), sprite, FrameNumber(0))); } // It's a base::UniquePtr because setPalette'll create a copy of "graypal". base::UniquePtr graypal(Palette::createGrayscale()); sprite->resetPalettes(); sprite->setPalette(graypal, true); } } void DocumentApi::addFrame(Sprite* sprite, FrameNumber newFrame) { copyFrame(sprite, newFrame.previous(), newFrame); } void DocumentApi::addEmptyFrame(Sprite* sprite, FrameNumber newFrame, color_t bgcolor) { // Add the frame in the sprite structure, it adjusts the total // number of frames in the sprite. if (undoEnabled()) m_undoers->pushUndoer(new undoers::AddFrame(getObjects(), m_document, sprite, newFrame)); sprite->addFrame(newFrame); // Move cels. displaceFrames(sprite->folder(), newFrame); // Add background cel Layer* bgLayer = sprite->backgroundLayer(); if (bgLayer) { LayerImage* imglayer = static_cast(bgLayer); Image* bgimage = Image::create(sprite->pixelFormat(), sprite->width(), sprite->height()); clear_image(bgimage, bgcolor); addImage(imglayer, newFrame, bgimage); } // Notify observers about the new frame. doc::DocumentEvent ev(m_document); ev.sprite(sprite); ev.frame(newFrame); m_document->notifyObservers(&doc::DocumentObserver::onAddFrame, ev); } void DocumentApi::addEmptyFramesTo(Sprite* sprite, FrameNumber newFrame, color_t bgcolor) { while (sprite->totalFrames() <= newFrame) addEmptyFrame(sprite, sprite->totalFrames(), bgcolor); } void DocumentApi::copyFrame(Sprite* sprite, FrameNumber fromFrame, FrameNumber newFrame) { // Add the frame in the sprite structure, it adjusts the total // number of frames in the sprite. if (undoEnabled()) m_undoers->pushUndoer(new undoers::AddFrame(getObjects(), m_document, sprite, newFrame)); sprite->addFrame(newFrame); // Move cels, and create copies of the cels in the given "newFrame". copyFrameForLayer(sprite->folder(), fromFrame, newFrame); // Notify observers about the new frame. doc::DocumentEvent ev(m_document); ev.sprite(sprite); ev.frame(newFrame); m_document->notifyObservers(&doc::DocumentObserver::onAddFrame, ev); } void DocumentApi::displaceFrames(Layer* layer, FrameNumber frame) { ASSERT(layer); ASSERT(frame >= 0); Sprite* sprite = layer->sprite(); switch (layer->type()) { case OBJECT_LAYER_IMAGE: { LayerImage* imglayer = static_cast(layer); // Displace all cels in '>=frame' to the next frame. for (FrameNumber c=sprite->lastFrame(); c>=frame; --c) { Cel* cel = imglayer->getCel(c); if (cel) setCelFramePosition(imglayer, cel, cel->frame().next()); } break; } case OBJECT_LAYER_FOLDER: { LayerIterator it = static_cast(layer)->getLayerBegin(); LayerIterator end = static_cast(layer)->getLayerEnd(); for (; it != end; ++it) displaceFrames(*it, frame); break; } } } void DocumentApi::copyFrameForLayer(Layer* layer, FrameNumber fromFrame, FrameNumber frame) { ASSERT(layer); ASSERT(frame >= 0); switch (layer->type()) { case OBJECT_LAYER_IMAGE: { LayerImage* imglayer = static_cast(layer); displaceFrames(imglayer, frame); if (fromFrame >= frame) fromFrame = fromFrame.next(); copyCel(imglayer, fromFrame, imglayer, frame, 0); break; } case OBJECT_LAYER_FOLDER: { LayerIterator it = static_cast(layer)->getLayerBegin(); LayerIterator end = static_cast(layer)->getLayerEnd(); for (; it != end; ++it) copyFrameForLayer(*it, fromFrame, frame); break; } } } void DocumentApi::removeFrame(Sprite* sprite, FrameNumber frame) { ASSERT(frame >= 0); // Remove cels from this frame (and displace one position backward // all next frames) removeFrameOfLayer(sprite->folder(), frame); // Add undoers to restore the removed frame from the sprite (to // restore the number and durations of frames). if (undoEnabled()) m_undoers->pushUndoer(new undoers::RemoveFrame(getObjects(), m_document, sprite, frame)); // Remove the frame from the sprite. This is the low level // operation, it modifies the number and duration of frames. sprite->removeFrame(frame); // Notify observers. doc::DocumentEvent ev(m_document); ev.sprite(sprite); ev.frame(frame); m_document->notifyObservers(&doc::DocumentObserver::onRemoveFrame, ev); } // Does the hard part of removing a frame: Removes all cels located in // the given frame, and moves all following cels one frame position back. void DocumentApi::removeFrameOfLayer(Layer* layer, FrameNumber frame) { ASSERT(layer); ASSERT(frame >= 0); Sprite* sprite = layer->sprite(); switch (layer->type()) { case OBJECT_LAYER_IMAGE: { LayerImage* imglayer = static_cast(layer); if (Cel* cel = imglayer->getCel(frame)) removeCel(imglayer, cel); for (++frame; frametotalFrames(); ++frame) if (Cel* cel = imglayer->getCel(frame)) setCelFramePosition(imglayer, cel, cel->frame().previous()); break; } case OBJECT_LAYER_FOLDER: { LayerIterator it = static_cast(layer)->getLayerBegin(); LayerIterator end = static_cast(layer)->getLayerEnd(); for (; it != end; ++it) removeFrameOfLayer(*it, frame); break; } } } void DocumentApi::setTotalFrames(Sprite* sprite, FrameNumber frames) { ASSERT(frames >= 1); // Add undoers. if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetTotalFrames(getObjects(), m_document, sprite)); // Do the action. sprite->setTotalFrames(frames); // Notify observers. doc::DocumentEvent ev(m_document); ev.sprite(sprite); ev.frame(frames); m_document->notifyObservers(&doc::DocumentObserver::onTotalFramesChanged, ev); } void DocumentApi::setFrameDuration(Sprite* sprite, FrameNumber frame, int msecs) { // Add undoers. if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetFrameDuration( getObjects(), sprite, frame)); // Do the action. sprite->setFrameDuration(frame, msecs); // Notify observers. doc::DocumentEvent ev(m_document); ev.sprite(sprite); ev.frame(frame); m_document->notifyObservers(&doc::DocumentObserver::onFrameDurationChanged, ev); } void DocumentApi::setFrameRangeDuration(Sprite* sprite, FrameNumber from, FrameNumber to, int msecs) { ASSERT(from >= FrameNumber(0)); ASSERT(from < to); ASSERT(to <= sprite->lastFrame()); // Add undoers. if (undoEnabled()) { for (FrameNumber fr(from); fr<=to; ++fr) m_undoers->pushUndoer(new undoers::SetFrameDuration( getObjects(), sprite, fr)); } // Do the action. sprite->setFrameRangeDuration(from, to, msecs); } void DocumentApi::moveFrame(Sprite* sprite, FrameNumber frame, FrameNumber beforeFrame) { if (frame != beforeFrame && frame >= 0 && frame <= sprite->lastFrame() && beforeFrame >= 0 && beforeFrame <= sprite->lastFrame().next()) { // Change the frame-lengths... int frlen_aux = sprite->getFrameDuration(frame); // Moving the frame to the future. if (frame < beforeFrame) { for (FrameNumber c=frame; cgetFrameDuration(c.next())); setFrameDuration(sprite, beforeFrame.previous(), frlen_aux); } // Moving the frame to the past. else if (beforeFrame < frame) { for (FrameNumber c=frame; c>beforeFrame; --c) setFrameDuration(sprite, c, sprite->getFrameDuration(c.previous())); setFrameDuration(sprite, beforeFrame, frlen_aux); } // change the cels of position... moveFrameLayer(sprite->folder(), frame, beforeFrame); } } void DocumentApi::moveFrameLayer(Layer* layer, FrameNumber frame, FrameNumber beforeFrame) { ASSERT(layer); switch (layer->type()) { case OBJECT_LAYER_IMAGE: { LayerImage* imglayer = static_cast(layer); CelList cels; imglayer->getCels(cels); CelIterator it = cels.begin(); CelIterator end = cels.end(); for (; it != end; ++it) { Cel* cel = *it; FrameNumber celFrame = cel->frame(); FrameNumber newFrame = celFrame; // fthe frame to the future if (frame < beforeFrame) { if (celFrame == frame) { newFrame = beforeFrame.previous(); } else if (celFrame > frame && celFrame < beforeFrame) { --newFrame; } } // moving the frame to the past else if (beforeFrame < frame) { if (celFrame == frame) { newFrame = beforeFrame; } else if (celFrame >= beforeFrame && celFrame < frame) { ++newFrame; } } if (celFrame != newFrame) setCelFramePosition(imglayer, cel, newFrame); } break; } case OBJECT_LAYER_FOLDER: { LayerIterator it = static_cast(layer)->getLayerBegin(); LayerIterator end = static_cast(layer)->getLayerEnd(); for (; it != end; ++it) moveFrameLayer(*it, frame, beforeFrame); break; } } } void DocumentApi::addCel(LayerImage* layer, Cel* cel) { ASSERT(layer); ASSERT(cel); if (undoEnabled()) m_undoers->pushUndoer(new undoers::AddCel(getObjects(), layer, cel)); layer->addCel(cel); doc::DocumentEvent ev(m_document); ev.sprite(layer->sprite()); ev.layer(layer); ev.cel(cel); m_document->notifyObservers(&doc::DocumentObserver::onAddCel, ev); } void DocumentApi::removeCel(LayerImage* layer, Cel* cel) { ASSERT(layer); ASSERT(cel); Sprite* sprite = layer->sprite(); doc::DocumentEvent ev(m_document); ev.sprite(sprite); ev.layer(layer); ev.cel(cel); m_document->notifyObservers(&doc::DocumentObserver::onRemoveCel, ev); // Find if the image that use this cel we are going to remove, is // used by other cels. size_t refs = sprite->getImageRefs(cel->imageIndex()); // If the image is only used by this cel, we can remove the image // from the stock. if (refs == 1) removeImageFromStock(sprite, cel->imageIndex()); if (undoEnabled()) m_undoers->pushUndoer(new undoers::RemoveCel(getObjects(), layer, cel)); // Remove the cel from the layer. layer->removeCel(cel); // and here we destroy the cel delete cel; } void DocumentApi::setCelFramePosition(LayerImage* layer, Cel* cel, FrameNumber frame) { ASSERT(cel); ASSERT(frame >= 0); if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetCelFrame(getObjects(), layer, cel)); layer->moveCel(cel, frame); doc::DocumentEvent ev(m_document); ev.sprite(layer->sprite()); ev.layer(layer); ev.cel(cel); ev.frame(frame); m_document->notifyObservers(&doc::DocumentObserver::onCelFrameChanged, ev); } void DocumentApi::setCelPosition(Sprite* sprite, Cel* cel, int x, int y) { ASSERT(cel); if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetCelPosition(getObjects(), cel)); cel->setPosition(x, y); doc::DocumentEvent ev(m_document); ev.sprite(sprite); ev.cel(cel); m_document->notifyObservers(&doc::DocumentObserver::onCelPositionChanged, ev); } void DocumentApi::setCelOpacity(Sprite* sprite, Cel* cel, int newOpacity) { ASSERT(cel); ASSERT(sprite->supportAlpha()); if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetCelOpacity(getObjects(), cel)); cel->setOpacity(newOpacity); doc::DocumentEvent ev(m_document); ev.sprite(sprite); ev.cel(cel); m_document->notifyObservers(&doc::DocumentObserver::onCelOpacityChanged, ev); } void DocumentApi::cropCel(Sprite* sprite, Cel* cel, int x, int y, int w, int h, color_t bgcolor) { Image* cel_image = cel->image(); ASSERT(cel_image); // create the new image through a crop Image* new_image = crop_image(cel_image, x-cel->x(), y-cel->y(), w, h, bgcolor); // replace the image in the stock that is pointed by the cel replaceStockImage(sprite, cel->imageIndex(), new_image); // update the cel's position setCelPosition(sprite, cel, x, y); } void DocumentApi::clearCel(LayerImage* layer, FrameNumber frame, color_t bgcolor) { Cel* cel = layer->getCel(frame); Image* image = (cel ? cel->image(): NULL); if (!image) return; if (layer->isBackground()) { ASSERT(image); if (image) clearImage(image, bgcolor); } else { removeCel(layer, cel); } } void DocumentApi::moveCel( LayerImage* srcLayer, FrameNumber srcFrame, LayerImage* dstLayer, FrameNumber dstFrame, color_t bgcolor) { ASSERT(srcLayer != NULL); ASSERT(dstLayer != NULL); Sprite* srcSprite = srcLayer->sprite(); Sprite* dstSprite = dstLayer->sprite(); ASSERT(srcSprite != NULL); ASSERT(dstSprite != NULL); ASSERT(srcFrame >= 0 && srcFrame < srcSprite->totalFrames()); ASSERT(dstFrame >= 0); clearCel(dstLayer, dstFrame, bgcolor); addEmptyFramesTo(dstSprite, dstFrame, bgcolor); Cel* srcCel = srcLayer->getCel(srcFrame); Cel* dstCel = dstLayer->getCel(dstFrame); Image* srcImage = (srcCel ? srcCel->image(): NULL); Image* dstImage = (dstCel ? dstCel->image(): NULL); if (srcCel) { if (srcLayer == dstLayer) { if (dstLayer->isBackground()) { ASSERT(dstImage); if (dstImage) composite_image(dstImage, srcImage, srcCel->x(), srcCel->y(), 255, BLEND_MODE_NORMAL); clearImage(srcImage, bgcolor); } // Move the cel in the same layer. else { setCelFramePosition(srcLayer, srcCel, dstFrame); } } // Move the cel between different layers. else { if (!dstCel) { dstCel = new Cel(*srcCel); dstCel->setFrame(dstFrame); dstImage = crop_image(srcImage, -srcCel->x(), -srcCel->y(), dstSprite->width(), // TODO dstSprite or srcSprite dstSprite->height(), 0); dstCel->setImage(addImageInStock(dstSprite, dstImage)); } if (dstLayer->isBackground()) { composite_image(dstImage, srcImage, srcCel->x(), srcCel->y(), 255, BLEND_MODE_NORMAL); } else { addCel(dstLayer, dstCel); } if (srcLayer->isBackground()) clearImage(srcImage, bgcolor); else removeCel(srcLayer, srcCel); } } m_document->notifyCelMoved(srcLayer, srcFrame, dstLayer, dstFrame); } void DocumentApi::copyCel( LayerImage* srcLayer, FrameNumber srcFrame, LayerImage* dstLayer, FrameNumber dstFrame, color_t bgcolor) { ASSERT(srcLayer != NULL); ASSERT(dstLayer != NULL); Sprite* srcSprite = srcLayer->sprite(); Sprite* dstSprite = dstLayer->sprite(); ASSERT(srcSprite != NULL); ASSERT(dstSprite != NULL); ASSERT(srcFrame >= 0 && srcFrame < srcSprite->totalFrames()); ASSERT(dstFrame >= 0); clearCel(dstLayer, dstFrame, bgcolor); addEmptyFramesTo(dstSprite, dstFrame, bgcolor); Cel* srcCel = srcLayer->getCel(srcFrame); Cel* dstCel = dstLayer->getCel(dstFrame); Image* srcImage = (srcCel ? srcCel->image(): NULL); Image* dstImage = (dstCel ? dstCel->image(): NULL); if (dstLayer->isBackground()) { if (srcCel) { ASSERT(dstImage); if (dstImage) composite_image(dstImage, srcImage, srcCel->x(), srcCel->y(), 255, BLEND_MODE_NORMAL); } } else { if (dstCel) removeCel(dstLayer, dstCel); if (srcCel) { // Add the image in the stock dstImage = Image::createCopy(srcImage); int imageIndex = addImageInStock(dstSprite, dstImage); dstCel = new Cel(*srcCel); dstCel->setFrame(dstFrame); dstCel->setImage(imageIndex); addCel(dstLayer, dstCel); } } m_document->notifyCelCopied(srcLayer, srcFrame, dstLayer, dstFrame); } LayerImage* DocumentApi::newLayer(Sprite* sprite) { LayerImage* layer = new LayerImage(sprite); addLayer(sprite->folder(), layer, sprite->folder()->getLastLayer()); return layer; } LayerFolder* DocumentApi::newLayerFolder(Sprite* sprite) { LayerFolder* layer = new LayerFolder(sprite); addLayer(sprite->folder(), layer, sprite->folder()->getLastLayer()); return layer; } void DocumentApi::addLayer(LayerFolder* folder, Layer* newLayer, Layer* afterThis) { // Add undoers. if (undoEnabled()) m_undoers->pushUndoer(new undoers::AddLayer(getObjects(), m_document, newLayer)); // Do the action. folder->addLayer(newLayer); folder->stackLayer(newLayer, afterThis); // Notify observers. doc::DocumentEvent ev(m_document); ev.sprite(folder->sprite()); ev.layer(newLayer); m_document->notifyObservers(&doc::DocumentObserver::onAddLayer, ev); } void DocumentApi::removeLayer(Layer* layer) { ASSERT(layer != NULL); // Notify observers that a layer will be removed (e.g. an Editor can // select another layer if the removed layer is the active one). doc::DocumentEvent ev(m_document); ev.sprite(layer->sprite()); ev.layer(layer); m_document->notifyObservers(&doc::DocumentObserver::onBeforeRemoveLayer, ev); // Add undoers. if (undoEnabled()) m_undoers->pushUndoer(new undoers::RemoveLayer(getObjects(), m_document, layer)); // Do the action. layer->parent()->removeLayer(layer); m_document->notifyObservers(&doc::DocumentObserver::onAfterRemoveLayer, ev); delete layer; } void DocumentApi::configureLayerAsBackground(LayerImage* layer) { // Add undoers. if (undoEnabled()) { m_undoers->pushUndoer(new undoers::SetLayerFlags(getObjects(), layer)); m_undoers->pushUndoer(new undoers::SetLayerName(getObjects(), layer)); m_undoers->pushUndoer(new undoers::MoveLayer(getObjects(), layer)); } // Do the action. layer->configureAsBackground(); } void DocumentApi::restackLayerAfter(Layer* layer, Layer* afterThis) { if (undoEnabled()) m_undoers->pushUndoer(new undoers::MoveLayer(getObjects(), layer)); layer->parent()->stackLayer(layer, afterThis); doc::DocumentEvent ev(m_document); ev.sprite(layer->sprite()); ev.layer(layer); m_document->notifyObservers(&doc::DocumentObserver::onLayerRestacked, ev); } void DocumentApi::restackLayerBefore(Layer* layer, Layer* beforeThis) { LayerIndex beforeThisIdx = layer->sprite()->layerToIndex(beforeThis); LayerIndex afterThisIdx = beforeThisIdx.previous(); restackLayerAfter(layer, layer->sprite()->indexToLayer(afterThisIdx)); } void DocumentApi::cropLayer(Layer* layer, int x, int y, int w, int h, color_t bgcolor) { if (!layer->isImage()) return; if (!layer->isBackground()) bgcolor = 0; // TODO use proper mask color Sprite* sprite = layer->sprite(); CelIterator it = ((LayerImage*)layer)->getCelBegin(); CelIterator end = ((LayerImage*)layer)->getCelEnd(); for (; it != end; ++it) cropCel(sprite, *it, x, y, w, h, bgcolor); } // Moves every frame in @a layer with the offset (@a dx, @a dy). void DocumentApi::displaceLayers(Layer* layer, int dx, int dy) { switch (layer->type()) { case OBJECT_LAYER_IMAGE: { CelIterator it = ((LayerImage*)layer)->getCelBegin(); CelIterator end = ((LayerImage*)layer)->getCelEnd(); for (; it != end; ++it) { Cel* cel = *it; setCelPosition(layer->sprite(), cel, cel->x()+dx, cel->y()+dy); } break; } case OBJECT_LAYER_FOLDER: { LayerIterator it = ((LayerFolder*)layer)->getLayerBegin(); LayerIterator end = ((LayerFolder*)layer)->getLayerEnd(); for (; it != end; ++it) displaceLayers(*it, dx, dy); break; } } } void DocumentApi::backgroundFromLayer(LayerImage* layer, color_t bgcolor) { ASSERT(layer); ASSERT(layer->isImage()); ASSERT(layer->isReadable()); ASSERT(layer->isWritable()); ASSERT(layer->sprite() != NULL); ASSERT(layer->sprite()->backgroundLayer() == NULL); Sprite* sprite = layer->sprite(); // create a temporary image to draw each frame of the new // `Background' layer base::UniquePtr bg_image_wrap(Image::create(sprite->pixelFormat(), sprite->width(), sprite->height())); Image* bg_image = bg_image_wrap.get(); CelIterator it = layer->getCelBegin(); CelIterator end = layer->getCelEnd(); for (; it != end; ++it) { Cel* cel = *it; // get the image from the sprite's stock of images Image* cel_image = cel->image(); ASSERT(cel_image); clear_image(bg_image, bgcolor); composite_image(bg_image, cel_image, cel->x(), cel->y(), MID(0, cel->opacity(), 255), layer->getBlendMode()); // now we have to copy the new image (bg_image) to the cel... setCelPosition(sprite, cel, 0, 0); // same size of cel-image and bg-image if (bg_image->width() == cel_image->width() && bg_image->height() == cel_image->height()) { if (undoEnabled()) m_undoers->pushUndoer(new undoers::ImageArea(getObjects(), cel_image, 0, 0, cel_image->width(), cel_image->height())); copy_image(cel_image, bg_image, 0, 0); } else { replaceStockImage(sprite, cel->imageIndex(), Image::createCopy(bg_image)); } } // Fill all empty cels with a flat-image filled with bgcolor for (FrameNumber frame(0); frametotalFrames(); ++frame) { Cel* cel = layer->getCel(frame); if (!cel) { Image* cel_image = Image::create(sprite->pixelFormat(), sprite->width(), sprite->height()); clear_image(cel_image, bgcolor); // Add the new image in the stock int image_index = addImageInStock(sprite, 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 DocumentApi::layerFromBackground(Layer* layer) { ASSERT(layer != NULL); ASSERT(layer->isImage()); ASSERT(layer->isReadable()); ASSERT(layer->isWritable()); ASSERT(layer->isBackground()); ASSERT(layer->sprite() != NULL); ASSERT(layer->sprite()->backgroundLayer() != NULL); if (undoEnabled()) { m_undoers->pushUndoer(new undoers::SetLayerFlags(getObjects(), layer)); m_undoers->pushUndoer(new undoers::SetLayerName(getObjects(), layer)); } layer->setBackground(false); layer->setMoveable(true); layer->setName("Layer 0"); } void DocumentApi::flattenLayers(Sprite* sprite, color_t bgcolor) { Image* cel_image; Cel* cel; // Create a temporary image. base::UniquePtr image_wrap(Image::create(sprite->pixelFormat(), sprite->width(), sprite->height())); Image* image = image_wrap.get(); // Get the background layer from the sprite. LayerImage* background = sprite->backgroundLayer(); if (!background) { // If there aren't a background layer we must to create the background. background = new LayerImage(sprite); addLayer(sprite->folder(), background, NULL); configureLayerAsBackground(background); } // Copy all frames to the background. for (FrameNumber frame(0); frametotalFrames(); ++frame) { // Clear the image and render this frame. clear_image(image, bgcolor); layer_render(sprite->folder(), image, 0, 0, frame); cel = background->getCel(frame); if (cel) { cel_image = cel->image(); ASSERT(cel_image != NULL); // 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); m_undoers->pushUndoer(new undoers::DirtyArea( 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 createCopy throws // Here we create the new cel (with the new image `cel_image'). cel = new Cel(frame, sprite->stock()->addImage(cel_image)); // TODO error handling: if new Cel throws // And finally we add the cel in the background. background->addCel(cel); } copy_image(cel_image, image, 0, 0); } // Delete old layers. LayerList layers = sprite->folder()->getLayersList(); LayerIterator it = layers.begin(); LayerIterator end = layers.end(); for (; it != end; ++it) if (*it != background) removeLayer(*it); } void DocumentApi::duplicateLayerAfter(Layer* sourceLayer, Layer* afterLayer) { base::UniquePtr newLayerPtr(new LayerImage(sourceLayer->sprite())); m_document->copyLayerContent(sourceLayer, m_document, newLayerPtr); newLayerPtr->setName(newLayerPtr->name() + " Copy"); addLayer(sourceLayer->parent(), newLayerPtr, afterLayer); // Release the pointer as it is owned by the sprite now. newLayerPtr.release(); } void DocumentApi::duplicateLayerBefore(Layer* sourceLayer, Layer* beforeLayer) { LayerIndex beforeThisIdx = sourceLayer->sprite()->layerToIndex(beforeLayer); LayerIndex afterThisIdx = beforeThisIdx.previous(); duplicateLayerAfter(sourceLayer, sourceLayer->sprite()->indexToLayer(afterThisIdx)); } // Adds a new image in the stock. Returns the image index in the // stock. int DocumentApi::addImageInStock(Sprite* sprite, Image* image) { ASSERT(image != NULL); // Do the action. int imageIndex = sprite->stock()->addImage(image); // Add undoers. if (undoEnabled()) m_undoers->pushUndoer(new undoers::AddImage(getObjects(), sprite->stock(), imageIndex)); return imageIndex; } Cel* DocumentApi::addImage(LayerImage* layer, FrameNumber frameNumber, Image* image) { int imageIndex = addImageInStock(layer->sprite(), image); base::UniquePtr cel(new Cel(frameNumber, imageIndex)); addCel(layer, cel); cel.release(); return cel; } // Removes and destroys the specified image in the stock. void DocumentApi::removeImageFromStock(Sprite* sprite, int imageIndex) { ASSERT(imageIndex >= 0); Image* image = sprite->stock()->getImage(imageIndex); ASSERT(image); if (undoEnabled()) m_undoers->pushUndoer(new undoers::RemoveImage(getObjects(), sprite->stock(), imageIndex)); sprite->stock()->removeImage(image); delete image; } void DocumentApi::replaceStockImage(Sprite* sprite, int imageIndex, Image* newImage) { // Get the current image in the 'image_index' position. Image* oldImage = sprite->stock()->getImage(imageIndex); ASSERT(oldImage); // Replace the image in the stock. if (undoEnabled()) m_undoers->pushUndoer(new undoers::ReplaceImage(getObjects(), sprite->stock(), imageIndex)); sprite->stock()->replaceImage(imageIndex, newImage); delete oldImage; } void DocumentApi::clearImage(Image* image, color_t bgcolor) { if (undoEnabled()) m_undoers->pushUndoer(new undoers::ImageArea(getObjects(), image, 0, 0, image->width(), image->height())); // clear all clear_image(image, bgcolor); } // Clears the mask region in the current sprite with the specified background color. void DocumentApi::clearMask(Layer* layer, Cel* cel, color_t bgcolor) { ASSERT(cel); ASSERT(layer == cel->layer()); Image* image = (cel ? cel->image(): NULL); if (!image) return; Mask* mask = m_document->mask(); // 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 (layer->isBackground()) { clearImage(image, bgcolor); } // If the layer is transparent we can remove the cel (and its // associated image). else { ASSERT(layer->isImage()); removeCel(static_cast(layer), cel); } } else { int offset_x = mask->bounds().x-cel->x(); int offset_y = mask->bounds().y-cel->y(); int u, v, putx, puty; int x1 = MAX(0, offset_x); int y1 = MAX(0, offset_y); int x2 = MIN(image->width()-1, offset_x+mask->bounds().w-1); int y2 = MIN(image->height()-1, offset_y+mask->bounds().h-1); // Do nothing if (x1 > x2 || y1 > y2) return; if (undoEnabled()) m_undoers->pushUndoer(new undoers::ImageArea(getObjects(), image, x1, y1, x2-x1+1, y2-y1+1)); const LockImageBits maskBits(mask->bitmap()); LockImageBits::const_iterator it = maskBits.begin(); // Clear the masked zones for (v=0; vbounds().h; ++v) { for (u=0; ubounds().w; ++u, ++it) { ASSERT(it != maskBits.end()); if (*it) { putx = u + offset_x; puty = v + offset_y; put_pixel(image, putx, puty, bgcolor); } } } ASSERT(it == maskBits.end()); } } void DocumentApi::flipImage(Image* image, const gfx::Rect& bounds, raster::algorithm::FlipType flipType) { // Insert the undo operation. if (undoEnabled()) { m_undoers->pushUndoer (new undoers::FlipImage (getObjects(), image, bounds, flipType)); } // Flip the portion of the bitmap. raster::algorithm::flip_image(image, bounds, flipType); } void DocumentApi::flipImageWithMask(Image* image, const Mask* mask, raster::algorithm::FlipType flipType, color_t bgcolor) { base::UniquePtr 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 (undoEnabled()) { base::UniquePtr dirty((new 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. copy_image(image, flippedImage, 0, 0); } void DocumentApi::pasteImage(Sprite* sprite, Cel* cel, const Image* src_image, int x, int y, int opacity) { ASSERT(cel != NULL); Image* cel_image = cel->image(); Image* cel_image2 = Image::createCopy(cel_image); composite_image(cel_image2, src_image, x-cel->x(), y-cel->y(), opacity, BLEND_MODE_NORMAL); replaceStockImage(sprite, cel->imageIndex(), cel_image2); // TODO fix this, improve, avoid replacing the whole image } void DocumentApi::copyToCurrentMask(Mask* mask) { ASSERT(m_document->mask()); ASSERT(mask); if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetMask(getObjects(), m_document)); m_document->mask()->copyFrom(mask); } void DocumentApi::setMaskPosition(int x, int y) { ASSERT(m_document->mask()); if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetMaskPosition(getObjects(), m_document)); m_document->mask()->setOrigin(x, y); m_document->resetTransformation(); } void DocumentApi::deselectMask() { if (undoEnabled()) m_undoers->pushUndoer(new undoers::SetMask(getObjects(), m_document)); m_document->setMaskVisible(false); } void DocumentApi::setPalette(Sprite* sprite, FrameNumber frame, Palette* newPalette) { Palette* currentSpritePalette = sprite->getPalette(frame); // Sprite current pal int from, to; // Check differences between current sprite palette and current system palette from = to = -1; currentSpritePalette->countDiff(newPalette, &from, &to); if (from >= 0 && to >= from) { // Add undo information to save the range of pal entries that will be modified. if (undoEnabled()) { m_undoers->pushUndoer (new undoers::SetPaletteColors(getObjects(), sprite, currentSpritePalette, frame, from, to)); } // Change the sprite palette sprite->setPalette(newPalette, false); } } bool DocumentApi::undoEnabled() { return m_undoers != NULL && m_document->getUndo()->isEnabled(); } raster::color_t DocumentApi::bgColor() { app::ISettings* appSettings = dynamic_cast(m_document->context()->settings()); return color_utils::color_for_target( appSettings ? appSettings->getBgColor(): Color::fromMask(), ColorTarget(ColorTarget::BackgroundLayer, m_document->sprite()->pixelFormat(), m_document->sprite()->transparentColor())); } raster::color_t DocumentApi::bgColor(Layer* layer) { app::ISettings* appSettings = dynamic_cast(m_document->context()->settings()); if (layer->isBackground()) return color_utils::color_for_layer( appSettings ? appSettings->getBgColor(): Color::fromMask(), layer); else return layer->sprite()->transparentColor(); } } // namespace app