mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-16 05:42:32 +00:00
1396 lines
40 KiB
C++
1396 lines
40 KiB
C++
/* 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::DocumentEvent&>(&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::DocumentEvent&>(&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> image_wrap(Image::create(sprite->pixelFormat(),
|
|
sprite->width(),
|
|
sprite->height()));
|
|
Image* image = image_wrap.get();
|
|
|
|
for (FrameNumber frame(0); frame<sprite->totalFrames(); ++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; c<sprite->stock()->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<Palette> 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<LayerImage*>(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::DocumentEvent&>(&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::DocumentEvent&>(&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<LayerImage*>(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<LayerFolder*>(layer)->getLayerBegin();
|
|
LayerIterator end = static_cast<LayerFolder*>(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<LayerImage*>(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<LayerFolder*>(layer)->getLayerBegin();
|
|
LayerIterator end = static_cast<LayerFolder*>(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::DocumentEvent&>(&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<LayerImage*>(layer);
|
|
if (Cel* cel = imglayer->getCel(frame))
|
|
removeCel(imglayer, cel);
|
|
|
|
for (++frame; frame<sprite->totalFrames(); ++frame)
|
|
if (Cel* cel = imglayer->getCel(frame))
|
|
setCelFramePosition(imglayer, cel, cel->frame().previous());
|
|
break;
|
|
}
|
|
|
|
case OBJECT_LAYER_FOLDER: {
|
|
LayerIterator it = static_cast<LayerFolder*>(layer)->getLayerBegin();
|
|
LayerIterator end = static_cast<LayerFolder*>(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::DocumentEvent&>(&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::DocumentEvent&>(&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; c<beforeFrame.previous(); ++c)
|
|
setFrameDuration(sprite, c, sprite->getFrameDuration(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<LayerImage*>(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<LayerFolder*>(layer)->getLayerBegin();
|
|
LayerIterator end = static_cast<LayerFolder*>(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::DocumentEvent&>(&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::DocumentEvent&>(&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::DocumentEvent&>(&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::DocumentEvent&>(&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::DocumentEvent&>(&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::DocumentEvent&>(&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::DocumentEvent&>(&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::DocumentEvent&>(&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::DocumentEvent&>(&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<Image> 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); frame<sprite->totalFrames(); ++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> 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); frame<sprite->totalFrames(); ++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<LayerImage> 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> 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<LayerImage*>(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<BitmapTraits> maskBits(mask->bitmap());
|
|
LockImageBits<BitmapTraits>::const_iterator it = maskBits.begin();
|
|
|
|
// Clear the masked zones
|
|
for (v=0; v<mask->bounds().h; ++v) {
|
|
for (u=0; u<mask->bounds().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<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 (undoEnabled()) {
|
|
base::UniquePtr<Dirty> 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<app::ISettings*>(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<app::ISettings*>(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
|