Compare commits

...

6 Commits

Author SHA1 Message Date
David Capello cc15775e14 Cel ID is available again in onBefore/AfterRemoveCel() notifications
We have added a CelData reference counter (CelData::m_refs) to known
how many cels are referencing/linked with the same CelData in the
sprite. It makes the Cel::links() member function impl a lot simpler:
without the need to access the parent layer to iterate all cels to
count all links.
2025-09-29 19:07:52 -03:00
David Capello abd4caeac8 Avoid doc objects serialization in undo using a "suspended ID" (#4860)
We can just store the ID of each object into an alternative "suspended
ID" to keep the object in memory outside the current doc objects
collection, and easily bring it back when we undo an operation that
needs the object alive.

Anyway this is tricky and we have to fix some situations. E.g. when
removing a Cel, the Cel::id() might be required when we trigger some
notification, but we cannot remove the Cel first from the sprite as we
need to count the number of links (we need the related sprite/layer
for this).

We have to fix these issues yet (e.g. having a links counter in the
CelData).

This patch introduces more memory usage for doc::Image, as we are just
keeping images in memory, they are uncompressed in the undo history,
but before we were compressing/decompressing with write/read_image()
functions. We can fix this later compressing the data of suspended
images in a background thread or something similar.
2025-09-29 19:07:52 -03:00
David Capello 11df0bc877 Merge branch 'main' into beta
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
2025-09-29 19:04:14 -03:00
Joshua Lopez 41e5097c33 Fix mouse gestures not working after switching tabs (fix aseprite#4388)
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
2025-09-29 18:06:45 -03:00
Christian Kaiser cf290b7679 Check mask validity before pasting (fix #5361)
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
2025-09-29 11:32:39 -03:00
David Capello d7b2faca6d Fix crash painting deleted cels while applying filter (fix #4991)
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
This could happen in the Editor::onPaint() or Timeline::onPaint().
2025-09-24 16:36:34 -03:00
39 changed files with 320 additions and 226 deletions

2
laf

@ -1 +1 @@
Subproject commit de781a5066732d700ac4520ee5fc9034e92875ad
Subproject commit 39706c11063fb53cf4c8e865102c6f71e2606906

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020 Igara Studio S.A.
// Copyright (C) 2020-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -15,11 +15,7 @@
#include "app/doc_event.h"
#include "base/serialization.h"
#include "doc/cel.h"
#include "doc/cel_data_io.h"
#include "doc/cel_io.h"
#include "doc/image_io.h"
#include "doc/layer.h"
#include "doc/subobjects_io.h"
namespace app { namespace cmd {
@ -27,7 +23,7 @@ using namespace base::serialization;
using namespace base::serialization::little_endian;
using namespace doc;
AddCel::AddCel(Layer* layer, Cel* cel) : WithLayer(layer), WithCel(cel), m_size(0)
AddCel::AddCel(Layer* layer, Cel* cel) : WithLayer(layer), WithCel(cel)
{
}
@ -48,41 +44,20 @@ void AddCel::onUndo()
ASSERT(layer);
ASSERT(cel);
// Save the CelData only if the cel isn't linked
bool has_data = (cel->links() == 0);
write8(m_stream, has_data ? 1 : 0);
if (has_data) {
write_image(m_stream, cel->image());
write_celdata(m_stream, cel->data());
}
write_cel(m_stream, cel);
m_size = size_t(m_stream.tellp());
// We must suspend the cel after removing the cel, we cannot trigger
// onBeforeRemoveCel() with a cel without ID.
removeCel(layer, cel);
m_suspendedCel.suspend(cel);
}
void AddCel::onRedo()
{
Layer* layer = this->layer();
Cel* cel = m_suspendedCel.restore();
ASSERT(layer);
SubObjectsFromSprite io(layer->sprite());
bool has_data = (read8(m_stream) != 0);
if (has_data) {
ImageRef image(read_image(m_stream));
io.addImageRef(image);
CelDataRef celdata(read_celdata(m_stream, &io));
io.addCelDataRef(celdata);
}
Cel* cel = read_cel(m_stream, &io);
ASSERT(cel);
addCel(layer, cel);
m_stream.str(std::string());
m_stream.clear();
m_size = 0;
}
void AddCel::addCel(Layer* layer, Cel* cel)
@ -111,8 +86,6 @@ void AddCel::removeCel(Layer* layer, Cel* cel)
layer->incrementVersion();
doc->notify_observers<DocEvent&>(&DocObserver::onAfterRemoveCel, ev);
delete cel;
}
}} // namespace app::cmd

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2025 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -11,11 +12,10 @@
#include "app/cmd.h"
#include "app/cmd/with_cel.h"
#include "app/cmd/with_layer.h"
#include <sstream>
#include "app/cmd/with_suspended.h"
#include "doc/cel.h"
namespace doc {
class Cel;
class Layer;
} // namespace doc
@ -32,14 +32,13 @@ protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
size_t onMemSize() const override { return sizeof(*this) + m_size; }
size_t onMemSize() const override { return sizeof(*this) + m_suspendedCel.size(); }
private:
void addCel(Layer* layer, Cel* cel);
void removeCel(Layer* layer, Cel* cel);
size_t m_size;
std::stringstream m_stream;
WithSuspended<doc::Cel*> m_suspendedCel;
};
}} // namespace app::cmd

View File

@ -13,8 +13,6 @@
#include "app/doc.h"
#include "app/doc_event.h"
#include "doc/layer.h"
#include "doc/layer_io.h"
#include "doc/subobjects_io.h"
namespace app { namespace cmd {
@ -24,7 +22,6 @@ AddLayer::AddLayer(Layer* group, Layer* newLayer, Layer* afterThis)
: m_group(group)
, m_newLayer(newLayer)
, m_afterThis(afterThis)
, m_size(0)
{
}
@ -41,25 +38,19 @@ void AddLayer::onUndo()
{
Layer* group = m_group.layer();
Layer* layer = m_newLayer.layer();
write_layer(m_stream, layer);
m_size = size_t(m_stream.tellp());
ASSERT(layer);
removeLayer(group, layer);
m_suspendedLayer.suspend(layer);
}
void AddLayer::onRedo()
{
Layer* group = m_group.layer();
SubObjectsFromSprite io(group->sprite());
Layer* newLayer = read_layer(m_stream, &io);
Layer* newLayer = m_suspendedLayer.restore();
Layer* afterThis = m_afterThis.layer();
addLayer(group, newLayer, afterThis);
m_stream.str(std::string());
m_stream.clear();
m_size = 0;
}
void AddLayer::addLayer(Layer* group, Layer* newLayer, Layer* afterThis)
@ -88,8 +79,6 @@ void AddLayer::removeLayer(Layer* group, Layer* layer)
group->sprite()->incrementVersion();
doc->notify_observers<DocEvent&>(&DocObserver::onAfterRemoveLayer, ev);
delete layer;
}
}} // namespace app::cmd

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (c) 2025 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -10,12 +11,8 @@
#include "app/cmd.h"
#include "app/cmd/with_layer.h"
#include <sstream>
namespace doc {
class Layer;
}
#include "app/cmd/with_suspended.h"
#include "doc/layer.h"
namespace app { namespace cmd {
using namespace doc;
@ -28,7 +25,7 @@ protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
size_t onMemSize() const override { return sizeof(*this) + m_size; }
size_t onMemSize() const override { return sizeof(*this) + m_suspendedLayer.size(); }
private:
void addLayer(Layer* group, Layer* newLayer, Layer* afterThis);
@ -37,8 +34,7 @@ private:
WithLayer m_group;
WithLayer m_newLayer;
WithLayer m_afterThis;
size_t m_size;
std::stringstream m_stream;
WithSuspended<doc::Layer*> m_suspendedLayer;
};
}} // namespace app::cmd

View File

@ -11,30 +11,21 @@
#include "app/cmd/add_palette.h"
#include "doc/palette.h"
#include "doc/palette_io.h"
#include "doc/sprite.h"
namespace app { namespace cmd {
using namespace doc;
AddPalette::AddPalette(Sprite* sprite, Palette* pal)
: WithSprite(sprite)
, m_size(0)
, m_frame(pal->frame())
AddPalette::AddPalette(Sprite* sprite, Palette* pal) : WithSprite(sprite), m_palette(*pal)
{
write_palette(m_stream, pal);
m_size = size_t(m_stream.tellp());
}
void AddPalette::onExecute()
{
m_stream.seekp(0);
Sprite* sprite = this->sprite();
Palette* pal = read_palette(m_stream);
sprite->setPalette(pal, true);
sprite->setPalette(&m_palette, true);
sprite->incrementVersion();
}
@ -42,7 +33,7 @@ void AddPalette::onUndo()
{
Sprite* sprite = this->sprite();
sprite->deletePalette(m_frame);
sprite->deletePalette(m_palette.frame());
sprite->incrementVersion();
}

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2025 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -10,9 +11,7 @@
#include "app/cmd.h"
#include "app/cmd/with_sprite.h"
#include "doc/frame.h"
#include <sstream>
#include "doc/palette.h"
namespace doc {
class Palette;
@ -30,12 +29,10 @@ public:
protected:
void onExecute() override;
void onUndo() override;
size_t onMemSize() const override { return sizeof(*this) + m_size; }
size_t onMemSize() const override { return sizeof(*this) + m_palette.getMemSize(); }
private:
size_t m_size;
std::stringstream m_stream;
frame_t m_frame;
Palette m_palette;
};
}} // namespace app::cmd

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
@ -14,14 +14,13 @@
#include "app/doc.h"
#include "app/doc_event.h"
#include "doc/slice.h"
#include "doc/slice_io.h"
#include "doc/sprite.h"
namespace app { namespace cmd {
using namespace doc;
AddSlice::AddSlice(Sprite* sprite, Slice* slice) : WithSprite(sprite), WithSlice(slice), m_size(0)
AddSlice::AddSlice(Sprite* sprite, Slice* slice) : WithSprite(sprite), WithSlice(slice)
{
}
@ -37,22 +36,17 @@ void AddSlice::onUndo()
{
Sprite* sprite = this->sprite();
Slice* slice = this->slice();
write_slice(m_stream, slice);
m_size = size_t(m_stream.tellp());
removeSlice(sprite, slice);
m_suspendedSlice.suspend(slice);
}
void AddSlice::onRedo()
{
Sprite* sprite = this->sprite();
Slice* slice = read_slice(m_stream);
Slice* slice = m_suspendedSlice.restore();
addSlice(sprite, slice);
m_stream.str(std::string());
m_stream.clear();
m_size = 0;
}
void AddSlice::addSlice(Sprite* sprite, Slice* slice)
@ -77,7 +71,6 @@ void AddSlice::removeSlice(Sprite* sprite, Slice* slice)
sprite->slices().remove(slice);
sprite->incrementVersion();
delete slice;
}
}} // namespace app::cmd

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
@ -12,8 +12,8 @@
#include "app/cmd.h"
#include "app/cmd/with_slice.h"
#include "app/cmd/with_sprite.h"
#include <sstream>
#include "app/cmd/with_suspended.h"
#include "doc/slice.h"
namespace app { namespace cmd {
using namespace doc;
@ -28,14 +28,13 @@ protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
size_t onMemSize() const override { return sizeof(*this) + m_size; }
size_t onMemSize() const override { return sizeof(*this) + m_suspendedSlice.size(); }
private:
void addSlice(Sprite* sprite, Slice* slice);
void removeSlice(Sprite* sprite, Slice* slice);
size_t m_size;
std::stringstream m_stream;
WithSuspended<doc::Slice*> m_suspendedSlice;
};
}} // namespace app::cmd

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -15,13 +15,12 @@
#include "app/doc_event.h"
#include "doc/sprite.h"
#include "doc/tag.h"
#include "doc/tag_io.h"
namespace app { namespace cmd {
using namespace doc;
AddTag::AddTag(Sprite* sprite, Tag* tag) : WithSprite(sprite), WithTag(tag), m_size(0)
AddTag::AddTag(Sprite* sprite, Tag* tag) : WithSprite(sprite), WithTag(tag)
{
}
@ -45,8 +44,6 @@ void AddTag::onUndo()
{
Sprite* sprite = this->sprite();
Tag* tag = this->tag();
write_tag(m_stream, tag);
m_size = size_t(m_stream.tellp());
// Notify observers about the new frame.
{
@ -59,21 +56,18 @@ void AddTag::onUndo()
sprite->tags().remove(tag);
sprite->incrementVersion();
delete tag;
m_suspendedTag.suspend(tag);
}
void AddTag::onRedo()
{
Sprite* sprite = this->sprite();
Tag* tag = read_tag(m_stream);
Tag* tag = m_suspendedTag.restore();
sprite->tags().add(tag);
sprite->incrementVersion();
m_stream.str(std::string());
m_stream.clear();
m_size = 0;
// Notify observers about the new frame.
Doc* doc = static_cast<Doc*>(sprite->document());
DocEvent ev(doc);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -11,9 +11,9 @@
#include "app/cmd.h"
#include "app/cmd/with_sprite.h"
#include "app/cmd/with_suspended.h"
#include "app/cmd/with_tag.h"
#include <sstream>
#include "doc/tag.h"
namespace app { namespace cmd {
using namespace doc;
@ -28,11 +28,10 @@ protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
size_t onMemSize() const override { return sizeof(*this) + m_size; }
size_t onMemSize() const override { return sizeof(*this) + m_suspendedTag.size(); }
private:
size_t m_size;
std::stringstream m_stream;
WithSuspended<doc::Tag*> m_suspendedTag;
};
}} // namespace app::cmd

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -11,7 +11,6 @@
#include "app/cmd/add_tile.h"
#include "app/doc.h"
#include "doc/image_io.h"
#include "doc/sprite.h"
#include "doc/tileset.h"
#include "doc/tilesets.h"
@ -20,8 +19,6 @@ namespace app { namespace cmd {
AddTile::AddTile(doc::Tileset* tileset, const doc::ImageRef& image, const doc::UserData& userData)
: WithTileset(tileset)
, WithImage(image.get())
, m_size(0)
, m_tileIndex(doc::notile)
, m_imageRef(image)
, m_userData(userData)
@ -30,10 +27,8 @@ AddTile::AddTile(doc::Tileset* tileset, const doc::ImageRef& image, const doc::U
AddTile::AddTile(doc::Tileset* tileset, const doc::tile_index ti)
: WithTileset(tileset)
, WithImage(tileset->get(ti).get())
, m_size(0)
, m_tileIndex(ti)
, m_imageRef(nullptr)
, m_imageRef(tileset->get(ti))
, m_userData(tileset->getTileData(ti))
{
}
@ -44,14 +39,11 @@ void AddTile::onExecute()
ASSERT(tileset);
if (m_tileIndex != doc::notile) {
ASSERT(!m_imageRef);
tileset->sprite()->incrementVersion();
tileset->incrementVersion();
}
else {
ASSERT(m_imageRef);
addTile(tileset, m_imageRef, m_userData);
m_imageRef.reset();
}
}
@ -60,8 +52,9 @@ void AddTile::onUndo()
doc::Tileset* tileset = this->tileset();
ASSERT(tileset);
write_image(m_stream, image());
m_size = size_t(m_stream.tellp());
ASSERT(m_imageRef);
m_suspendedImage.suspend(m_imageRef);
m_imageRef.reset();
tileset->erase(m_tileIndex);
@ -74,15 +67,10 @@ void AddTile::onRedo()
doc::Tileset* tileset = this->tileset();
ASSERT(!m_imageRef);
m_imageRef.reset(read_image(m_stream));
m_imageRef = m_suspendedImage.restore();
ASSERT(m_imageRef);
addTile(tileset, m_imageRef, m_userData);
m_imageRef.reset();
m_stream.str(std::string());
m_stream.clear();
m_size = 0;
}
void AddTile::onFireNotifications()

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -9,14 +9,12 @@
#pragma once
#include "app/cmd.h"
#include "app/cmd/with_image.h"
#include "app/cmd/with_suspended.h"
#include "app/cmd/with_tileset.h"
#include "doc/image_ref.h"
#include "doc/tile.h"
#include "doc/user_data.h"
#include <sstream>
namespace doc {
class Tileset;
}
@ -24,12 +22,9 @@ class Tileset;
namespace app { namespace cmd {
class AddTile : public Cmd,
public WithTileset,
public WithImage {
public WithTileset {
public:
AddTile(doc::Tileset* tileset,
const doc::ImageRef& image,
const doc::UserData& userData = UserData());
AddTile(doc::Tileset* tileset, const doc::ImageRef& image, const doc::UserData& userData = {});
AddTile(doc::Tileset* tileset, const doc::tile_index ti);
doc::tile_index tileIndex() const { return m_tileIndex; }
@ -42,14 +37,13 @@ protected:
size_t onMemSize() const override
{
// TODO add m_userData size
return sizeof(*this) + m_size;
return sizeof(*this) + m_suspendedImage.size();
}
private:
void addTile(doc::Tileset* tileset, const doc::ImageRef& image, const doc::UserData& userData);
size_t m_size;
std::stringstream m_stream;
WithSuspended<doc::ImageRef> m_suspendedImage;
doc::tile_index m_tileIndex;
doc::ImageRef m_imageRef;
doc::UserData m_userData;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -11,9 +11,7 @@
#include "app/cmd/add_tileset.h"
#include "doc/sprite.h"
#include "doc/subobjects_io.h"
#include "doc/tileset.h"
#include "doc/tileset_io.h"
#include "doc/tilesets.h"
namespace app { namespace cmd {
@ -23,7 +21,6 @@ using namespace doc;
AddTileset::AddTileset(doc::Sprite* sprite, doc::Tileset* tileset)
: WithSprite(sprite)
, WithTileset(tileset)
, m_size(0)
, m_tilesetIndex(-1)
{
}
@ -31,7 +28,6 @@ AddTileset::AddTileset(doc::Sprite* sprite, doc::Tileset* tileset)
AddTileset::AddTileset(doc::Sprite* sprite, const doc::tileset_index tsi)
: WithSprite(sprite)
, WithTileset(sprite->tilesets()->get(tsi))
, m_size(0)
, m_tilesetIndex(tsi)
{
}
@ -46,28 +42,20 @@ void AddTileset::onExecute()
void AddTileset::onUndo()
{
doc::Tileset* tileset = this->tileset();
write_tileset(m_stream, tileset);
m_size = size_t(m_stream.tellp());
m_suspendedTileset.suspend(tileset);
doc::Sprite* sprite = this->sprite();
sprite->tilesets()->erase(m_tilesetIndex);
sprite->incrementVersion();
sprite->tilesets()->incrementVersion();
delete tileset;
}
void AddTileset::onRedo()
{
auto sprite = this->sprite();
doc::Tileset* tileset = read_tileset(m_stream, sprite);
doc::Tileset* tileset = m_suspendedTileset.restore();
addTileset(tileset);
m_stream.str(std::string());
m_stream.clear();
m_size = 0;
}
void AddTileset::addTileset(doc::Tileset* tileset)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -10,10 +10,10 @@
#include "app/cmd.h"
#include "app/cmd/with_sprite.h"
#include "app/cmd/with_suspended.h"
#include "app/cmd/with_tileset.h"
#include "doc/tile.h"
#include <sstream>
#include "doc/tileset.h"
namespace doc {
class Tileset;
@ -34,13 +34,12 @@ protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
size_t onMemSize() const override { return sizeof(*this) + m_size; }
size_t onMemSize() const override { return sizeof(*this) + m_suspendedTileset.size(); }
private:
void addTileset(doc::Tileset* tileset);
size_t m_size;
std::stringstream m_stream;
WithSuspended<doc::Tileset*> m_suspendedTileset;
doc::tileset_index m_tilesetIndex;
};

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2023 Igara Studio S.A.
// Copyright (C) 2023-2025 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@ -14,10 +14,8 @@
#include "doc/cel.h"
#include "doc/cels_range.h"
#include "doc/image.h"
#include "doc/image_io.h"
#include "doc/image_ref.h"
#include "doc/sprite.h"
#include "doc/subobjects_io.h"
#include "doc/tilesets.h"
namespace app { namespace cmd {

View File

@ -12,8 +12,6 @@
#include "app/cmd/with_sprite.h"
#include "doc/image_ref.h"
#include <sstream>
namespace app { namespace cmd {
using namespace doc;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2021 Igara Studio S.A.
// Copyright (C) 2021-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -14,7 +14,6 @@
#include "doc/layer_tilemap.h"
#include "doc/sprite.h"
#include "doc/tileset.h"
#include "doc/tileset_io.h"
#include "doc/tilesets.h"
namespace app { namespace cmd {
@ -40,16 +39,11 @@ void ReplaceTileset::onExecute()
m_newTileset = nullptr;
}
else {
restoreTileset = doc::read_tileset(m_stream, spr, true);
restoreTileset = m_suspendedTileset.restore();
}
m_stream.str(std::string());
m_stream.clear();
doc::write_tileset(m_stream, actualTileset);
m_size = size_t(m_stream.tellp());
replaceTileset(restoreTileset);
delete actualTileset;
m_suspendedTileset.suspend(actualTileset);
}
void ReplaceTileset::replaceTileset(Tileset* newTileset)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2021 Igara Studio S.A.
// Copyright (C) 2021-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -10,10 +10,9 @@
#include "app/cmd.h"
#include "app/cmd/with_sprite.h"
#include "app/cmd/with_suspended.h"
#include "doc/tileset.h"
#include <sstream>
namespace app { namespace cmd {
class ReplaceTileset : public Cmd,
@ -25,15 +24,14 @@ protected:
void onExecute() override;
void onUndo() override { onExecute(); }
void onRedo() override { onExecute(); }
size_t onMemSize() const override { return sizeof(*this) + m_size; }
size_t onMemSize() const override { return sizeof(*this) + m_suspendedTileset.size(); }
private:
void replaceTileset(doc::Tileset* newTileset);
doc::tileset_index m_tsi;
doc::Tileset* m_newTileset;
std::stringstream m_stream;
size_t m_size = 0;
WithSuspended<doc::Tileset*> m_suspendedTileset;
};
}} // namespace app::cmd

View File

@ -12,11 +12,9 @@
#include "doc/cel.h"
#include "doc/image.h"
#include "doc/image_io.h"
#include "doc/image_ref.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include "doc/subobjects_io.h"
namespace app { namespace cmd {

View File

@ -12,8 +12,6 @@
#include "app/cmd/with_cel.h"
#include "doc/cel_data.h"
#include <sstream>
namespace app { namespace cmd {
using namespace doc;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -13,7 +13,6 @@
#include "app/cmd/with_document.h"
#include <memory>
#include <sstream>
namespace doc {
class Mask;

View File

@ -0,0 +1,62 @@
// Aseprite
// Copyright (C) 2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_WITH_SUSPEND_H_INCLUDED
#define APP_CMD_WITH_SUSPEND_H_INCLUDED
#pragma once
#include "doc/object.h"
#include <type_traits>
namespace app { namespace cmd {
// Auxiliary class to keep a doc::Object in memory but without IDs,
// and to restore all its IDs when it's required.
template<typename T>
class WithSuspended {
public:
~WithSuspended()
{
// If the object is suspended it's not included in the Sprite
// hierarchy, so it will not be automatically deleted, thereby we
// have to delete the object to avoid a memory leak.
if constexpr (std::is_pointer_v<T>)
delete m_object;
}
T object() { return m_object; }
size_t size() const { return m_size; }
void suspend(T object)
{
ASSERT(!m_object);
m_object = object;
m_size = m_object->getMemSize();
m_object->suspendObject();
}
T restore()
{
ASSERT(m_object);
T object = m_object;
m_object->restoreObject();
m_object = nullptr;
m_size = 0;
return object;
}
private:
size_t m_size = 0;
T m_object = nullptr;
};
}} // namespace app::cmd
#endif

View File

@ -325,10 +325,15 @@ void FilterManagerImpl::applyToTarget()
void FilterManagerImpl::initTransaction()
{
ASSERT(!m_tx);
m_writer.reset(new ContextWriter(m_reader));
m_writer = std::make_unique<ContextWriter>(m_reader);
m_tx.reset(new Tx(*m_writer, m_filter->getName(), ModifyDocument));
}
void FilterManagerImpl::updateWriterThread()
{
document()->updateWriterThread();
}
bool FilterManagerImpl::isTransaction() const
{
return (m_tx != nullptr);

View File

@ -93,6 +93,7 @@ public:
void applyToTarget();
void initTransaction();
void updateWriterThread();
bool isTransaction() const;
void commitTransaction();

View File

@ -118,7 +118,13 @@ FilterWorker::~FilterWorker()
void FilterWorker::run()
{
// Initialize writting transaction
// Initialize writing transaction from the main thread. This is
// required to get the activeSite() from the UIContext from
// CmdTransaction::calcSpritePosition().
//
// The document will keep the UI thread associated as the "writer"
// thread, but that will be updated later in
// applyFilterInBackground() with the worker thread ID.
m_filterMgr->initTransaction();
std::thread thread;
@ -182,6 +188,11 @@ bool FilterWorker::isCancelled()
void FilterWorker::applyFilterInBackground()
{
try {
// This background thread is the new writer. This is required to
// avoid read-locking from the UI thread from Editor and Timeline
// onPaint() events.
m_filterMgr->updateWriterThread();
// Apply the filter
m_filterMgr->applyToTarget();

View File

@ -124,6 +124,11 @@ Doc::LockResult Doc::upgradeToWrite(int timeout)
return res;
}
void Doc::updateWriterThread()
{
m_rwLock.updateWriterThread();
}
void Doc::downgradeToRead(LockResult lockResult)
{
DOC_TRACE("DOC: downgradeToRead", this, (int)lockResult);

View File

@ -82,6 +82,7 @@ public:
LockResult readLock(int timeout);
LockResult writeLock(int timeout);
LockResult upgradeToWrite(int timeout);
void updateWriterThread();
void downgradeToRead(LockResult lockResult);
void unlock(LockResult lockResult);

View File

@ -2810,7 +2810,7 @@ void Editor::pasteImage(const Image* image, const Mask* mask, const gfx::Point*
ASSERT(image);
std::unique_ptr<Mask> temp_mask;
if (!mask) {
if (!mask || mask->bounds().isEmpty()) {
gfx::Rect visibleBounds = getVisibleSpriteBounds();
gfx::Rect imageBounds = image->bounds();

View File

@ -25,6 +25,7 @@
#include "app/ui/workspace.h"
#include "app/ui/workspace_tabs.h"
#include "doc/sprite.h"
#include "ui/manager.h"
#include "ui/system.h"
#include <algorithm>
@ -120,6 +121,10 @@ void UIContext::setActiveView(DocView* docView)
mainWin->getTimeline()->updateUsingEditor(editor);
mainWin->getPreviewEditor()->updateUsingEditor(editor);
// Update mouse widgets immediately after changing views rather
// than waiting for mouse movement.
mainWin->manager()->_updateMouseWidgets();
// Change the image-type of color bar.
ColorBar::instance()->setPixelFormat(app_get_current_pixel_format());

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2019-2024 Igara Studio S.A.
// Copyright (c) 2019-2025 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -27,6 +27,7 @@ Cel::Cel(frame_t frame, const ImageRef& image)
, m_frame(frame)
, m_data(new CelData(image))
{
++m_data->m_refs;
}
Cel::Cel(frame_t frame, const CelDataRef& celData)
@ -35,6 +36,13 @@ Cel::Cel(frame_t frame, const CelDataRef& celData)
, m_frame(frame)
, m_data(celData)
{
++m_data->m_refs;
}
Cel::~Cel()
{
if (m_data)
--m_data->m_refs;
}
// static
@ -65,7 +73,11 @@ void Cel::setFrame(frame_t frame)
void Cel::setDataRef(const CelDataRef& celData)
{
ASSERT(celData);
if (m_data)
--m_data->m_refs;
m_data = celData;
if (m_data)
++m_data->m_refs;
}
void Cel::setPosition(int x, int y)
@ -119,10 +131,10 @@ Sprite* Cel::sprite() const
Cel* Cel::link() const
{
ASSERT(m_data);
if (m_data.get() == NULL)
return NULL;
if (!m_data)
return nullptr;
if (!m_data.unique()) {
if (links() > 0) {
for (frame_t fr = 0; fr < m_frame; ++fr) {
Cel* possible = m_layer->cel(fr);
if (possible && possible->dataRef().get() == m_data.get())
@ -135,16 +147,32 @@ Cel* Cel::link() const
std::size_t Cel::links() const
{
std::size_t links = 0;
if (m_data)
return std::max<std::size_t>(0, m_data->refs() - 1);
return 0;
}
Sprite* sprite = this->sprite();
for (frame_t fr = 0; fr < sprite->totalFrames(); ++fr) {
Cel* cel = m_layer->cel(fr);
if (cel && cel != this && cel->dataRef().get() == m_data.get())
++links;
void Cel::suspendObject()
{
if (m_data) {
if (--m_data->m_refs == 0) {
// Suspend the CelData only if this was the latest cel referencing it.
m_data->suspendObject();
}
}
return links;
Object::suspendObject();
}
void Cel::restoreObject()
{
Object::restoreObject();
if (m_data) {
if (++m_data->m_refs == 1) {
m_data->restoreObject();
}
}
}
void Cel::setParentLayer(LayerImage* layer)

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2019-2024 Igara Studio S.A.
// Copyright (c) 2019-2025 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -28,6 +28,7 @@ class Cel : public Object {
public:
Cel(frame_t frame, const ImageRef& image);
Cel(frame_t frame, const CelDataRef& celData);
~Cel();
static Cel* MakeCopy(const frame_t newFrame, const Cel* other);
static Cel* MakeLink(const frame_t newFrame, const Cel* other);
@ -65,7 +66,9 @@ public:
void setOpacity(int opacity);
void setZIndex(int zindex);
virtual int getMemSize() const override { return sizeof(Cel) + m_data->getMemSize(); }
int getMemSize() const override { return sizeof(Cel) + m_data->getMemSize(); }
void suspendObject() override;
void restoreObject() override;
void setParentLayer(LayerImage* layer);
Grid grid() const;

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2019-2022 Igara Studio S.A.
// Copyright (c) 2019-2025 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -23,7 +23,6 @@ namespace doc {
CelData::CelData(const ImageRef& image)
: WithUserData(ObjectType::CelData)
, m_image(image)
, m_opacity(255)
, m_bounds(0, 0, image ? image->width() : 0, image ? image->height() : 0)
, m_boundsF(nullptr)
{
@ -57,6 +56,20 @@ void CelData::setPosition(const gfx::Point& pos)
m_boundsF->setOrigin(gfx::PointF(pos));
}
void CelData::suspendObject()
{
if (m_image)
m_image->suspendObject();
WithUserData::suspendObject();
}
void CelData::restoreObject()
{
WithUserData::restoreObject();
if (m_image)
m_image->restoreObject();
}
void CelData::adjustBounds(Layer* layer)
{
ASSERT(m_image);

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -18,10 +18,13 @@
namespace doc {
class Cel;
class Layer;
class Tileset;
class CelData : public WithUserData {
friend class Cel;
public:
CelData(const ImageRef& image);
CelData(const CelData& celData);
@ -30,7 +33,8 @@ public:
gfx::Point position() const { return m_bounds.origin(); }
const gfx::Rect& bounds() const { return m_bounds; }
int opacity() const { return m_opacity; }
Image* image() const { return const_cast<Image*>(m_image.get()); };
std::size_t refs() const { return m_refs; }
Image* image() const { return const_cast<Image*>(m_image.get()); }
ImageRef imageRef() const { return m_image; }
// Returns a rectangle with the bounds of the image (width/height
@ -79,17 +83,20 @@ public:
bool hasBoundsF() const { return m_boundsF != nullptr; }
virtual int getMemSize() const override
int getMemSize() const override
{
ASSERT(m_image);
return sizeof(CelData) + m_image->getMemSize();
}
void suspendObject() override;
void restoreObject() override;
void adjustBounds(Layer* layer);
private:
ImageRef m_image;
int m_opacity;
int m_opacity = 255;
std::size_t m_refs = 0;
gfx::Rect m_bounds;
// Special bounds for reference layers that can have subpixel

View File

@ -249,6 +249,28 @@ int LayerImage::getMemSize() const
return size;
}
void LayerImage::suspendObject()
{
CelIterator it = getCelBegin();
CelIterator end = getCelEnd();
for (; it != end; ++it) {
Cel* cel = *it;
cel->suspendObject();
}
Layer::suspendObject();
}
void LayerImage::restoreObject()
{
Layer::restoreObject();
CelIterator it = getCelBegin();
CelIterator end = getCelEnd();
for (; it != end; ++it) {
Cel* cel = *it;
cel->restoreObject();
}
}
void LayerImage::destroyAllCels()
{
CelIterator it = getCelBegin();
@ -430,6 +452,20 @@ int LayerGroup::getMemSize() const
return size;
}
void LayerGroup::suspendObject()
{
for (Layer* child : m_layers)
child->suspendObject();
Layer::suspendObject();
}
void LayerGroup::restoreObject()
{
Layer::restoreObject();
for (Layer* child : m_layers)
child->restoreObject();
}
Layer* LayerGroup::firstLayerInWholeHierarchy() const
{
Layer* layer = firstLayer();

View File

@ -60,7 +60,7 @@ protected:
public:
virtual ~Layer();
virtual int getMemSize() const override;
int getMemSize() const override;
const std::string& name() const { return m_name; }
void setName(const std::string& name) { m_name = name; }
@ -170,7 +170,9 @@ public:
explicit LayerImage(Sprite* sprite);
virtual ~LayerImage();
virtual int getMemSize() const override;
int getMemSize() const override;
void suspendObject() override;
void restoreObject() override;
void addCel(Cel* cel);
void removeCel(Cel* cel);
@ -207,7 +209,9 @@ public:
explicit LayerGroup(Sprite* sprite);
virtual ~LayerGroup();
virtual int getMemSize() const override;
int getMemSize() const override;
void suspendObject() override;
void restoreObject() override;
const LayerList& layers() const { return m_layers; }
int layersCount() const { return (int)m_layers.size(); }

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -23,13 +23,14 @@ static ObjectId newId = 0;
// TODO Profile this and see if an unordered_map is better
static std::map<ObjectId, Object*> objects;
Object::Object(ObjectType type) : m_type(type), m_id(0), m_version(0)
Object::Object(ObjectType type) : m_type(type)
{
}
Object::Object(const Object& other)
: m_type(other.m_type)
, m_id(0) // We don't copy the ID
, m_id(NullId) // We don't copy the ID
, m_suspendedId(NullId)
, m_version(0) // We don't copy the version
{
}
@ -37,7 +38,7 @@ Object::Object(const Object& other)
Object::~Object()
{
if (m_id)
setId(0);
setId(NullId);
}
int Object::getMemSize() const
@ -47,6 +48,9 @@ int Object::getMemSize() const
const ObjectId Object::id() const
{
// We cannot ask for the ID from a "suspended" object.
ASSERT(m_suspendedId == NullId);
// The first time the ID is request, we store the object in the
// "objects" hash table.
if (!m_id) {
@ -99,6 +103,20 @@ void Object::setVersion(ObjectVersion version)
m_version = version;
}
void Object::suspendObject()
{
ASSERT(m_suspendedId == NullId);
m_suspendedId = m_id;
setId(NullId);
}
void Object::restoreObject()
{
ASSERT(m_id == NullId);
setId(m_suspendedId);
m_suspendedId = NullId;
}
Object* get_object(ObjectId id)
{
const std::lock_guard lock(g_mutex);

View File

@ -1,4 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2025 Igara Studio S.A.
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
@ -9,6 +10,7 @@
#pragma once
#include "doc/object_id.h"
#include "doc/object_ids.h"
#include "doc/object_type.h"
#include "doc/object_version.h"
@ -33,14 +35,24 @@ public:
// object use.
virtual int getMemSize() const;
// Removes or restore the ID of this object (and all its children)
// to keep this object in a "suspended" state (e.g. in the undo
// history) or to recover the object from a suspended state.
virtual void suspendObject();
virtual void restoreObject();
private:
ObjectType m_type;
// Unique identifier for this object (it is assigned by
// Objects class).
mutable ObjectId m_id;
mutable ObjectId m_id = NullId;
ObjectVersion m_version;
// ID saved when the objects is "deleted" but stored in the undo
// history. It's a way to save the previous ID and restore it.
ObjectId m_suspendedId = NullId;
ObjectVersion m_version = 0;
// Disable copy assignment
Object& operator=(const Object&);

View File

@ -1,4 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2025 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -12,9 +13,9 @@
namespace doc {
typedef uint32_t ObjectId;
using ObjectId = uint32_t;
const ObjectId NullId = 0;
constexpr const ObjectId NullId = 0;
} // namespace doc