Add z-index property to cels (fix aseprite/Attachment-System#88)

* Now a Cel has a z-index property to change the order of layers per frame
* A new doc::RenderPlan class can calculate the order of cels to be rendered
* z-index is saved as a int16_t in the .aseprite files
* This new field can be set/get from Lua with Cel.zIndex
This commit is contained in:
David Capello 2023-04-10 17:44:22 -03:00
parent e38398d7aa
commit 24846eae10
35 changed files with 713 additions and 245 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -325,6 +325,7 @@
<part id="timeline_drop_frame_deco" x="252" y="120" w1="2" w2="1" w3="2" h1="3" h2="1" h3="3" />
<part id="timeline_loop_range" x="240" y="132" w1="4" w2="4" w3="4" h1="3" h2="6" h3="3" />
<part id="timeline_focused" x="228" y="12" w1="2" w2="8" w3="2" h1="2" h2="8" h3="2" />
<part id="timeline_zindex" x="272" y="122" w="6" h="6" />
<part id="flag_normal" x="0" y="240" w="16" h="10" />
<part id="flag_highlight" x="16" y="240" w="16" h="10" />
<part id="drop_pixels_ok" x="176" y="176" w="7" h="8" />
@ -446,6 +447,8 @@
<part id="cursor_skew_ne" x="288" y="160" w="16" h="16" focusx="8" focusy="8" />
<part id="one_win_icon" x="96" y="256" w="8" h="7" />
<part id="multi_win_icon" x="104" y="256" w="8" h="7" />
<part id="spin_up" x="128" y="256" w="5" h="3" />
<part id="spin_down" x="128" y="259" w="5" h="3" />
</parts>
<styles>
<style id="box" />
@ -936,6 +939,9 @@
<style id="timeline_both_links">
<background part="timeline_both_links_active" />
</style>
<style id="timeline_zindex">
<icon part="timeline_zindex" align="right bottom" x="0" y="-1" />
</style>
<style id="timeline_gear" extends="timeline_box">
<icon part="timeline_gear" />
<icon part="timeline_gear_active" state="focus" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -321,6 +321,7 @@
<part id="timeline_drop_frame_deco" x="252" y="120" w1="2" w2="1" w3="2" h1="3" h2="1" h3="3" />
<part id="timeline_loop_range" x="240" y="132" w1="4" w2="4" w3="4" h1="3" h2="6" h3="3" />
<part id="timeline_focused" x="228" y="12" w1="2" w2="8" w3="2" h1="2" h2="8" h3="2" />
<part id="timeline_zindex" x="272" y="122" w="6" h="6" />
<part id="flag_normal" x="0" y="240" w="16" h="10" />
<part id="flag_highlight" x="16" y="240" w="16" h="10" />
<part id="drop_pixels_ok" x="176" y="176" w="7" h="8" />
@ -442,6 +443,8 @@
<part id="cursor_skew_ne" x="288" y="160" w="16" h="16" focusx="8" focusy="8" />
<part id="one_win_icon" x="96" y="256" w="8" h="7" />
<part id="multi_win_icon" x="104" y="256" w="8" h="7" />
<part id="spin_up" x="128" y="256" w="5" h="3" />
<part id="spin_down" x="128" y="259" w="5" h="3" />
</parts>
<styles>
<style id="box" />
@ -926,6 +929,9 @@
<style id="timeline_both_links">
<background part="timeline_both_links_active" />
</style>
<style id="timeline_zindex">
<icon part="timeline_zindex" align="right bottom" x="0" y="-1" />
</style>
<style id="timeline_gear" extends="timeline_box">
<icon part="timeline_gear" />
<icon part="timeline_gear_active" state="focus" />

View File

@ -656,6 +656,7 @@ END
[cel_properties]
title = Cel Properties
opacity = Opacity:
zindex = Z-Index:
user_data_tooltip = User Data
[color_curve_point]

View File

@ -1,12 +1,20 @@
<!-- Aseprite -->
<!-- Copyright (C) 2020 by Igara Studio S.A. -->
<!-- Copyright (C) 2020-2023 by Igara Studio S.A. -->
<!-- Copyright (C) 2001-2016 by David Capello -->
<gui>
<window id="cel_properties" text="@.title">
<grid id="properties_grid" columns="3">
<grid id="properties_grid" columns="4">
<label text="@.opacity" />
<slider min="0" max="255" id="opacity" cell_align="horizontal" width="128" />
<slider min="0" max="255" id="opacity" cell_align="horizontal" width="128" cell_hspan="2" />
<button id="user_data" icon="icon_user_data" tooltip="@.user_data_tooltip" />
<label text="@.zindex" />
<expr id="zindex" cell_align="horizontal" width="128" />
<buttonset id="zindex_spin" columns="1">
<item icon="spin_up" />
<item icon="spin_down" />
</buttonset>
<boxfiller />
</grid>
</window>
</gui>

View File

@ -210,7 +210,11 @@ This chunk determine where to put a cel in the specified layer/frame.
1 - Linked Cel
2 - Compressed Image
3 - Compressed Tilemap
BYTE[7] For future (set to zero)
SHORT Z-Index
0 = default layer ordering
+N = show this cel N layers later
-N = show this cel N layers back
BYTE[5] For future (set to zero)
+ For cel type = 0 (Raw Image Data)
WORD Width in pixels
WORD Height in pixels

View File

@ -506,6 +506,7 @@ add_library(app-lib
cmd/set_cel_frame.cpp
cmd/set_cel_opacity.cpp
cmd/set_cel_position.cpp
cmd/set_cel_zindex.cpp
cmd/set_frame_duration.cpp
cmd/set_grid_bounds.cpp
cmd/set_last_point.cpp

View File

@ -0,0 +1,52 @@
// Aseprite
// Copyright (C) 2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/cmd/set_cel_zindex.h"
#include "app/doc.h"
#include "app/doc_event.h"
#include "doc/cel.h"
namespace app {
namespace cmd {
using namespace doc;
SetCelZIndex::SetCelZIndex(Cel* cel, int zindex)
: WithCel(cel)
, m_oldZIndex(cel->zIndex())
, m_newZIndex(zindex)
{
}
void SetCelZIndex::onExecute()
{
cel()->setZIndex(m_newZIndex);
cel()->incrementVersion();
}
void SetCelZIndex::onUndo()
{
cel()->setZIndex(m_oldZIndex);
cel()->incrementVersion();
}
void SetCelZIndex::onFireNotifications()
{
Cel* cel = this->cel();
Doc* doc = static_cast<Doc*>(cel->document());
DocEvent ev(doc);
ev.sprite(cel->sprite());
ev.cel(cel);
doc->notify_observers<DocEvent&>(&DocObserver::onCelZIndexChange, ev);
}
} // namespace cmd
} // namespace app

View File

@ -0,0 +1,39 @@
// Aseprite
// Copyright (C) 2023 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_CMD_SET_CEL_ZINDEX_H_INCLUDED
#define APP_CMD_SET_CEL_ZINDEX_H_INCLUDED
#pragma once
#include "app/cmd.h"
#include "app/cmd/with_cel.h"
namespace app {
namespace cmd {
using namespace doc;
class SetCelZIndex : public Cmd
, public WithCel {
public:
SetCelZIndex(Cel* cel, int zindex);
protected:
void onExecute() override;
void onUndo() override;
void onFireNotifications() override;
size_t onMemSize() const override {
return sizeof(*this);
}
private:
int m_oldZIndex;
int m_newZIndex;
};
} // namespace cmd
} // namespace app
#endif

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -19,6 +19,7 @@
#include "doc/image.h"
#include "doc/layer_tilemap.h"
#include "doc/primitives.h"
#include "doc/render_plan.h"
#include "doc/sprite.h"
#include "doc/tileset.h"
#include "gfx/point.h"
@ -127,9 +128,11 @@ void ColorPicker::pickColor(const Site& site,
// Pick from the composed image
case FromComposition: {
doc::RenderPlan plan;
plan.addLayer(sprite->root(), site.frame());
doc::CelList cels;
sprite->pickCels(pos.x, pos.y, site.frame(), kOpacityThreshold,
sprite->allVisibleLayers(), cels);
sprite->pickCels(pos, kOpacityThreshold, plan, cels);
if (!cels.empty())
m_layer = cels.front()->layer();
@ -191,9 +194,12 @@ void ColorPicker::pickColor(const Site& site,
}
case FromFirstReferenceLayer: {
doc::RenderPlan plan;
for (doc::Layer* refLayer : sprite->allVisibleReferenceLayers())
plan.addLayer(refLayer, site.frame());
doc::CelList cels;
sprite->pickCels(pos.x, pos.y, site.frame(), kOpacityThreshold,
sprite->allVisibleReferenceLayers(), cels);
sprite->pickCels(pos, kOpacityThreshold, plan, cels);
for (const Cel* cel : cels) {
doc::color_t imageColor;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2020-2023 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -11,6 +11,7 @@
#include "app/app.h"
#include "app/cmd/set_cel_opacity.h"
#include "app/cmd/set_cel_zindex.h"
#include "app/cmd/set_user_data.h"
#include "app/commands/command.h"
#include "app/console.h"
@ -33,6 +34,9 @@
#include "cel_properties.xml.h"
#include <algorithm>
#include <limits>
namespace app {
using namespace ui;
@ -49,11 +53,20 @@ public:
, m_userDataView(Preferences::instance().cels.userDataVisibility)
{
opacity()->Change.connect([this]{ onStartTimer(); });
zindex()->Change.connect([this]{ onStartTimer(); });
userData()->Click.connect([this]{ onToggleUserData(); });
m_timer.Tick.connect([this]{ onCommitChange(); });
m_userDataView.UserDataChange.connect([this]{ onStartTimer(); });
// TODO add to Expr widget spin flag to include these widgets in
// the same Expr
zindexSpin()->ItemChange.connect([this]{
int dz = (zindexSpin()->selectedItem() == 0 ? +1: -1);
zindex()->setTextf("%d", zindex()->textInt() + dz);
onStartTimer();
});
remapWindow();
centerWindow();
load_window_pos(this, "CelProperties");
@ -99,6 +112,10 @@ private:
return opacity()->getValue();
}
int zindexValue() const {
return zindex()->textInt();
}
int countCels(int* backgroundCount = nullptr) const {
if (backgroundCount)
*backgroundCount = 0;
@ -133,7 +150,8 @@ private:
switch (msg->type()) {
case kKeyDownMessage:
if (opacity()->hasFocus()) {
if (opacity()->hasFocus() ||
zindex()->hasFocus()) {
KeyScancode scancode = static_cast<KeyMessage*>(msg)->scancode();
if (scancode == kKeyEnter ||
scancode == kKeyEsc) {
@ -175,11 +193,16 @@ private:
m_timer.stop();
const int newOpacity = opacityValue();
// Clamp z-index to the limits of the .aseprite specs
const int newZIndex = std::clamp<int>(zindexValue(),
std::numeric_limits<int16_t>::min(),
std::numeric_limits<int16_t>::max());
const UserData newUserData= m_userDataView.userData();
const int count = countCels();
if ((count > 1) ||
(count == 1 && m_cel && (newOpacity != m_cel->opacity() ||
newZIndex != m_cel->zIndex() ||
newUserData != m_cel->data()->userData()))) {
try {
ContextWriter writer(UIContext::instance());
@ -194,6 +217,9 @@ private:
}
Sprite* sprite = m_document->sprite();
bool redrawTimeline = false;
// For each unique cel (don't repeat on links)
for (Cel* cel : sprite->uniqueCels(range.selectedFrames())) {
if (range.contains(cel->layer())) {
if (!cel->layer()->isBackground() && newOpacity != cel->opacity()) {
@ -206,12 +232,25 @@ private:
// Redraw timeline because the cel's user data/color
// might have changed.
if (newUserData.color() != cel->data()->userData().color()) {
App::instance()->timeline()->invalidate();
redrawTimeline = true;
}
}
}
}
// For all cels (repeat links)
for (Cel* cel : sprite->cels(range.selectedFrames())) {
if (range.contains(cel->layer())) {
if (newZIndex != cel->zIndex()) {
tx(new cmd::SetCelZIndex(cel, newZIndex));
redrawTimeline = true;
}
}
}
if (redrawTimeline)
App::instance()->timeline()->invalidate();
tx.commit();
}
catch (const std::exception& e) {
@ -253,6 +292,11 @@ private:
updateFromCel();
}
void onCelZIndexChange(DocEvent& ev) override {
if (m_cel == ev.cel())
updateFromCel();
}
void onUserDataChange(DocEvent& ev) override {
if (m_cel && m_cel->data() == ev.withUserData())
updateFromCel();
@ -272,6 +316,7 @@ private:
if (count > 0) {
if (m_cel) {
opacity()->setValue(m_cel->opacity());
zindex()->setTextf("%d", m_cel->zIndex());
color_t c = m_cel->data()->userData().color();
m_userDataView.color()->setColor(Color::fromRgb(rgba_getr(c), rgba_getg(c), rgba_getb(c), rgba_geta(c)));
m_userDataView.entry()->setText(m_cel->data()->userData().text());

View File

@ -1525,7 +1525,8 @@ void DocExporter::createDataFile(const Samples& samples,
layer->getCels(cels);
bool someCelWithData = false;
for (const Cel* cel : cels) {
if (!cel->data()->userData().isEmpty()) {
if (cel->zIndex() != 0 ||
!cel->data()->userData().isEmpty()) {
someCelWithData = true;
break;
}
@ -1536,15 +1537,21 @@ void DocExporter::createDataFile(const Samples& samples,
os << ", \"cels\": [";
for (const Cel* cel : cels) {
if (!cel->data()->userData().isEmpty()) {
if (cel->zIndex() != 0 ||
!cel->data()->userData().isEmpty()) {
if (firstCel)
firstCel = false;
else
os << ", ";
os << "{ \"frame\": " << cel->frame()
<< cel->data()->userData()
<< " }";
os << "{ \"frame\": " << cel->frame();
if (cel->zIndex() != 0) {
os << ", \"zIndex\": " << cel->zIndex();
}
if (!cel->data()->userData().isEmpty()) {
os << cel->data()->userData();
}
os << " }";
}
}
os << "]";

View File

@ -65,6 +65,7 @@ namespace app {
virtual void onCelFrameChanged(DocEvent& ev) { }
virtual void onCelPositionChanged(DocEvent& ev) { }
virtual void onCelOpacityChange(DocEvent& ev) { }
virtual void onCelZIndexChange(DocEvent& ev) { }
virtual void onUserDataChange(DocEvent& ev) { }

View File

@ -986,7 +986,8 @@ static void ase_file_write_cel_chunk(FILE* f, dio::AsepriteFrameHeader* frame_he
fputw(cel->y(), f);
fputc(cel->opacity(), f);
fputw(cel_type, f);
ase_file_write_padding(f, 7);
fputw(cel->zIndex(), f);
ase_file_write_padding(f, 5);
switch (cel_type) {

View File

@ -14,6 +14,7 @@
#include "app/color_utils.h"
#include "app/util/shader_helpers.h"
#include "doc/render_plan.h"
#include "os/skia/skia_surface.h"
#include "include/core/SkCanvas.h"
@ -215,21 +216,21 @@ void ShaderRenderer::renderSprite(os::Surface* dstSurface,
area.dst.y - area.src.y);
canvas->scale(m_proj.scaleX(), m_proj.scaleY());
drawLayerGroup(canvas, sprite, sprite->root(), frame, area);
RenderPlan plan;
plan.addLayer(sprite->root(), frame);
renderPlan(canvas, sprite, plan, frame, area);
}
canvas->restore();
}
void ShaderRenderer::drawLayerGroup(SkCanvas* canvas,
const doc::Sprite* sprite,
const doc::LayerGroup* group,
const doc::frame_t frame,
const gfx::ClipF& area)
void ShaderRenderer::renderPlan(SkCanvas* canvas,
const doc::Sprite* sprite,
const doc::RenderPlan& plan,
const doc::frame_t frame,
const gfx::ClipF& area)
{
for (auto layer : group->layers()) {
// Ignore hidden layers
if (!layer->isVisible())
continue;
for (const Cel* cel : plan.cels()) {
const Layer* layer = cel->layer();
switch (layer->type()) {
@ -346,10 +347,7 @@ void ShaderRenderer::drawLayerGroup(SkCanvas* canvas,
}
case doc::ObjectType::LayerGroup:
// TODO draw a group in a sub-surface and then compose that surface
drawLayerGroup(canvas, sprite,
static_cast<const doc::LayerGroup*>(layer),
frame, area);
ASSERT(false);
break;
}

View File

@ -18,6 +18,10 @@
class SkCanvas;
class SkRuntimeEffect;
namespace doc {
class RenderPlan;
}
namespace app {
// Use SkSL to compose images with Skia shaders on the CPU (with the
@ -72,11 +76,11 @@ namespace app {
const doc::BlendMode blendMode) override;
private:
void drawLayerGroup(SkCanvas* canvas,
const doc::Sprite* sprite,
const doc::LayerGroup* group,
const doc::frame_t frame,
const gfx::ClipF& area);
void renderPlan(SkCanvas* canvas,
const doc::Sprite* sprite,
const doc::RenderPlan& plan,
const doc::frame_t frame,
const gfx::ClipF& area);
void drawImage(SkCanvas* canvas,
const doc::Image* srcImage,
const int x,

View File

@ -12,6 +12,7 @@
#include "app/cmd/replace_image.h"
#include "app/cmd/set_cel_opacity.h"
#include "app/cmd/set_cel_position.h"
#include "app/cmd/set_cel_zindex.h"
#include "app/doc_api.h"
#include "app/script/docobj.h"
#include "app/script/engine.h"
@ -94,6 +95,13 @@ int Cel_get_opacity(lua_State* L)
return 1;
}
int Cel_get_zIndex(lua_State* L)
{
const auto cel = get_docobj<Cel>(L, 1);
lua_pushinteger(L, cel->zIndex());
return 1;
}
int Cel_set_frame(lua_State* L)
{
const auto cel = get_docobj<Cel>(L, 1);
@ -144,6 +152,15 @@ int Cel_set_opacity(lua_State* L)
return 0;
}
int Cel_set_zIndex(lua_State* L)
{
auto cel = get_docobj<Cel>(L, 1);
Tx tx;
tx(new cmd::SetCelZIndex(cel, lua_tointeger(L, 2)));
tx.commit();
return 0;
}
const luaL_Reg Cel_methods[] = {
{ "__eq", Cel_eq },
{ nullptr, nullptr }
@ -158,6 +175,7 @@ const Property Cel_properties[] = {
{ "bounds", Cel_get_bounds, nullptr },
{ "position", Cel_get_position, Cel_set_position },
{ "opacity", Cel_get_opacity, Cel_set_opacity },
{ "zIndex", Cel_get_zIndex, Cel_set_zIndex },
{ "color", UserData_get_color<Cel>, UserData_set_color<Cel> },
{ "data", UserData_get_text<Cel>, UserData_set_text<Cel> },
{ "properties", UserData_get_properties<Cel>, UserData_set_properties<Cel> },

View File

@ -2398,6 +2398,11 @@ void Timeline::drawCel(ui::Graphics* g, layer_t layerIndex, frame_t frame, Cel*
// Draw decorators to link the activeCel with its links.
if (data && data->activeIt != data->end)
drawCelLinkDecorators(g, full_bounds, cel, frame, is_loosely_active, is_hover, data);
// Draw 'z' if this cel has a custom z-index (non-zero)
if (cel && cel->zIndex() != 0) {
drawPart(g, bounds, nullptr, styles.timelineZindex(), is_loosely_active, is_hover);
}
}
void Timeline::updateCelOverlayBounds(const Hit& hit)

View File

@ -212,10 +212,11 @@ Cel* create_cel_copy(CmdSequence* cmds,
}
// New cel
std::unique_ptr<Cel> dstCel(
new Cel(dstFrame, ImageRef(Image::create(dstPixelFormat, dstSize.w, dstSize.h))));
auto dstCel = std::make_unique<Cel>(
dstFrame, ImageRef(Image::create(dstPixelFormat, dstSize.w, dstSize.h)));
dstCel->setOpacity(srcCel->opacity());
dstCel->setZIndex(srcCel->zIndex());
dstCel->data()->setUserData(srcCel->data()->userData());
// Special case were we copy from a tilemap...

View File

@ -831,7 +831,8 @@ doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
int y = ((int16_t)read16());
int opacity = read8();
int cel_type = read16();
readPadding(7);
int zIndex = ((int16_t)read16());
readPadding(5);
doc::Layer* layer = nullptr;
if (layer_index >= 0 && layer_index < doc::layer_t(m_allLayers.size()))
@ -868,6 +869,7 @@ doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
cel = std::make_unique<doc::Cel>(frame, image);
cel->setPosition(x, y);
cel->setOpacity(opacity);
cel->setZIndex(zIndex);
}
break;
}
@ -889,6 +891,7 @@ doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
cel->setPosition(x, y);
cel->setOpacity(opacity);
}
cel->setZIndex(zIndex);
}
else {
// Linked cel doesn't found
@ -909,6 +912,7 @@ doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
cel = std::make_unique<doc::Cel>(frame, image);
cel->setPosition(x, y);
cel->setOpacity(opacity);
cel->setZIndex(zIndex);
}
break;
}
@ -954,6 +958,7 @@ doc::Cel* AsepriteDecoder::readCelChunk(doc::Sprite* sprite,
cel = std::make_unique<doc::Cel>(frame, image);
cel->setPosition(x, y);
cel->setOpacity(opacity);
cel->setZIndex(zIndex);
}
break;
}

View File

@ -57,6 +57,7 @@ add_library(doc-lib
playback.cpp
primitives.cpp
remap.cpp
render_plan.cpp
rgbmap_rgb5a3.cpp
selected_frames.cpp
selected_layers.cpp

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2019-2020 Igara Studio S.A.
// Copyright (c) 2019-2023 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -46,6 +46,7 @@ Cel* Cel::MakeCopy(const frame_t newFrame,
cel->setPosition(other->position());
cel->setOpacity(other->opacity());
cel->setZIndex(other->zIndex());
return cel;
}
@ -53,7 +54,9 @@ Cel* Cel::MakeCopy(const frame_t newFrame,
Cel* Cel::MakeLink(const frame_t newFrame,
const Cel* other)
{
return new Cel(newFrame, other->dataRef());
Cel* cel = new Cel(newFrame, other->dataRef());
cel->setZIndex(other->zIndex());
return cel;
}
void Cel::setFrame(frame_t frame)
@ -93,6 +96,11 @@ void Cel::setOpacity(int opacity)
m_data->setOpacity(opacity);
}
void Cel::setZIndex(int zindex)
{
m_zIndex = zindex;
}
Document* Cel::document() const
{
ASSERT(m_layer);

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2019-2020 Igara Studio S.A.
// Copyright (c) 2019-2023 Igara Studio S.A.
// Copyright (c) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -39,6 +39,7 @@ namespace doc {
const gfx::Rect& bounds() const { return m_data->bounds(); }
const gfx::RectF& boundsF() const { return m_data->boundsF(); }
int opacity() const { return m_data->opacity(); }
int zIndex() const { return m_zIndex; }
gfx::Rect imageBounds() const { return m_data->imageBounds(); }
@ -62,6 +63,7 @@ namespace doc {
void setBounds(const gfx::Rect& bounds);
void setBoundsF(const gfx::RectF& bounds);
void setOpacity(int opacity);
void setZIndex(int zindex);
virtual int getMemSize() const override {
return sizeof(Cel) + m_data->getMemSize();
@ -76,6 +78,7 @@ namespace doc {
LayerImage* m_layer;
frame_t m_frame; // Frame position
CelDataRef m_data;
int m_zIndex = 0;
Cel();
DISABLE_COPYING(Cel);

View File

@ -1,4 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2023 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -27,6 +28,7 @@ void write_cel(std::ostream& os, const Cel* cel)
write32(os, cel->id());
write16(os, cel->frame());
write32(os, cel->dataRef()->id());
write16(os, uint16_t(int16_t(cel->zIndex())));
}
Cel* read_cel(std::istream& is, SubObjectsIO* subObjects, bool setId)
@ -34,11 +36,16 @@ Cel* read_cel(std::istream& is, SubObjectsIO* subObjects, bool setId)
ObjectId id = read32(is);
frame_t frame(read16(is));
ObjectId celDataId = read32(is);
int zIndex = int(int16_t(read16(is)));
if (is.eof())
zIndex = 0;
CelDataRef celData(subObjects->getCelDataRef(celDataId));
if (!celData)
return nullptr;
std::unique_ptr<Cel> cel(new Cel(frame, celData));
auto cel = std::make_unique<Cel>(frame, celData);
cel->setZIndex(zIndex);
if (setId)
cel->setId(id);
return cel.release();

92
src/doc/render_plan.cpp Normal file
View File

@ -0,0 +1,92 @@
// Aseprite Document Library
// Copyright (c) 2023 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "doc/render_plan.h"
#include "doc/cel.h"
#include "doc/layer.h"
#include <algorithm>
#include <cmath>
namespace doc {
RenderPlan::RenderPlan()
{
}
void RenderPlan::addLayer(const Layer* layer,
const frame_t frame)
{
// We cannot add new layers after using processZIndexes()/modified
// m_cels array using z-indexes.
ASSERT(m_processZIndex == true);
// We can't read this layer
if (!layer->isVisible())
return;
switch (layer->type()) {
case ObjectType::LayerImage:
case ObjectType::LayerTilemap: {
if (Cel* cel = layer->cel(frame)) {
m_cels.push_back(cel);
}
break;
}
case ObjectType::LayerGroup: {
for (const auto child : static_cast<const LayerGroup*>(layer)->layers()) {
addLayer(child, frame);
}
break;
}
}
}
void RenderPlan::processZIndexes() const
{
m_processZIndex = false;
// If all cels has a z-index = 0, we can just use the m_cels as it is
bool noZIndex = true;
for (int i=0; i<int(m_cels.size()); ++i) {
const int z = m_cels[i]->zIndex();
if (z != 0) {
noZIndex = false;
break;
}
}
if (noZIndex)
return;
// Order cels using its real index in the m_cels array + its z-index value
std::vector<std::pair<int, Cel*>> indexedCels;
const int n = m_cels.size();
indexedCels.reserve(n);
for (int i=0; i<n; ++i) {
Cel* cel = m_cels[i];
int z = cel->zIndex();
indexedCels.push_back(std::make_pair(i+z, cel));
}
std::sort(indexedCels.begin(), indexedCels.end(),
[](const auto& a, const auto& b){
return
(a.first < b.first) ||
(a.first == b.first && (a.second->zIndex() < b.second->zIndex()));
});
for (int i=0; i<n; ++i) {
m_cels[i] = indexedCels[i].second;
}
}
} // namespace doc

42
src/doc/render_plan.h Normal file
View File

@ -0,0 +1,42 @@
// Aseprite Document Library
// Copyright (c) 2023 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_RENDER_PLAN_H_INCLUDED
#define DOC_RENDER_PLAN_H_INCLUDED
#pragma once
#include "doc/cel_list.h"
#include "doc/frame.h"
namespace doc {
class Layer;
// Creates a list of cels to be rendered in the correct order
// (depending on layer ordering + z-index) to render the given root
// layer/layers.
class RenderPlan {
public:
RenderPlan();
const CelList& cels() const {
if (m_processZIndex)
processZIndexes();
return m_cels;
}
void addLayer(const Layer* layer,
const frame_t frame);
private:
void processZIndexes() const;
mutable CelList m_cels;
mutable bool m_processZIndex = true;
};
} // namespace doc
#endif

View File

@ -0,0 +1,98 @@
// Aseprite Document Library
// Copyright (c) 2023 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gtest/gtest.h>
#include "doc/render_plan.h"
#include "doc/cel.h"
#include "doc/document.h"
#include "doc/image.h"
#include "doc/layer.h"
#include "doc/sprite.h"
#include <memory>
using namespace doc;
#define HELPER_LOG(a, b) \
a->layer()->name() << " instead of " << b->layer()->name()
#define EXPECT_PLAN(a, b, c, d) \
{ \
RenderPlan plan; \
plan.addLayer(spr->root(), 0); \
const auto& cels = plan.cels(); \
EXPECT_EQ(a, cels[0]) << HELPER_LOG(cels[0], a); \
EXPECT_EQ(b, cels[1]) << HELPER_LOG(cels[1], b); \
EXPECT_EQ(c, cels[2]) << HELPER_LOG(cels[2], c); \
EXPECT_EQ(d, cels[3]) << HELPER_LOG(cels[3], d); \
}
TEST(RenderPlan, ZIndex)
{
auto doc = std::make_shared<Document>();
ImageSpec spec(ColorMode::INDEXED, 2, 2);
Sprite* spr;
doc->sprites().add(spr = Sprite::MakeStdSprite(spec));
LayerImage
*lay0 = static_cast<LayerImage*>(spr->root()->firstLayer()),
*lay1 = new LayerImage(spr),
*lay2 = new LayerImage(spr),
*lay3 = new LayerImage(spr);
lay0->setName("a");
lay1->setName("b");
lay2->setName("c");
lay3->setName("d");
Cel* a = lay0->cel(0),
*b, *c, *d;
lay1->addCel(b = new Cel(0, ImageRef(Image::create(spec))));
lay2->addCel(c = new Cel(0, ImageRef(Image::create(spec))));
lay3->addCel(d = new Cel(0, ImageRef(Image::create(spec))));
spr->root()->insertLayer(lay1, lay0);
spr->root()->insertLayer(lay2, lay1);
spr->root()->insertLayer(lay3, lay2);
a->setZIndex(0); EXPECT_PLAN(a, b, c, d);
a->setZIndex(1); EXPECT_PLAN(b, a, c, d);
a->setZIndex(2); EXPECT_PLAN(b, c, a, d);
a->setZIndex(3); EXPECT_PLAN(b, c, d, a);
a->setZIndex(4); EXPECT_PLAN(b, c, d, a);
a->setZIndex(1000); EXPECT_PLAN(b, c, d, a);
a->setZIndex(0); EXPECT_PLAN(a, b, c, d); // Back no normal
b->setZIndex(-1); EXPECT_PLAN(b, a, c, d);
b->setZIndex(-2); EXPECT_PLAN(b, a, c, d);
b->setZIndex(-3); EXPECT_PLAN(b, a, c, d);
b->setZIndex(-1000); EXPECT_PLAN(b, a, c, d);
b->setZIndex(0); EXPECT_PLAN(a, b, c, d); // Back no normal
a->setZIndex(-1);
b->setZIndex(-1);
c->setZIndex(-1);
d->setZIndex(-1);
EXPECT_PLAN(a, b, c, d);
a->setZIndex(2);
b->setZIndex(-1);
c->setZIndex(0);
d->setZIndex(-1);
EXPECT_PLAN(b, d, c, a);
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -22,6 +22,7 @@
#include "doc/palette.h"
#include "doc/primitives.h"
#include "doc/remap.h"
#include "doc/render_plan.h"
#include "doc/rgbmap_rgb5a3.h"
#include "doc/tag.h"
#include "doc/tilesets.h"
@ -624,22 +625,16 @@ void Sprite::remapTilemaps(const Tileset* tileset,
//////////////////////////////////////////////////////////////////////
// Drawing
void Sprite::pickCels(const double x,
const double y,
const frame_t frame,
void Sprite::pickCels(const gfx::PointF& pos,
const int opacityThreshold,
const LayerList& layers,
const RenderPlan& plan,
CelList& cels) const
{
gfx::PointF pos(x, y);
for (int i=(int)layers.size()-1; i>=0; --i) {
const Layer* layer = layers[i];
Cel* cel = layer->cel(frame);
if (!cel)
continue;
// Iterate cels in reversed order (from the front-most to the
// bottom-most) so we pick first visible cel in the given position.
const CelList& planCels = plan.cels();
for (auto it=planCels.rbegin(), end=planCels.rend(); it!=end; ++it) {
Cel* cel = *it;
const Image* image = cel->image();
if (!image)
continue;
@ -752,6 +747,11 @@ CelsRange Sprite::cels(frame_t frame) const
return CelsRange(this, selFrames);
}
CelsRange Sprite::cels(const SelectedFrames& selFrames) const
{
return CelsRange(this, selFrames, CelsRange::ALL);
}
CelsRange Sprite::uniqueCels() const
{
SelectedFrames selFrames;

View File

@ -36,7 +36,6 @@
#define DOC_SPRITE_MAX_HEIGHT 65535
namespace doc {
class CelsRange;
class Document;
class Image;
@ -46,6 +45,7 @@ namespace doc {
class Mask;
class Palette;
class Remap;
class RenderPlan;
class RgbMap;
class RgbMapRGB5A3;
class SelectedFrames;
@ -197,11 +197,9 @@ namespace doc {
void remapImages(const Remap& remap);
void remapTilemaps(const Tileset* tileset,
const Remap& remap);
void pickCels(const double x,
const double y,
const frame_t frame,
void pickCels(const gfx::PointF& pos,
const int opacityThreshold,
const LayerList& layers,
const RenderPlan& plan,
CelList& cels) const;
////////////////////////////////////////
@ -214,6 +212,7 @@ namespace doc {
CelsRange cels() const;
CelsRange cels(frame_t frame) const;
CelsRange cels(const SelectedFrames& selFrames) const;
CelsRange uniqueCels() const;
CelsRange uniqueCels(const SelectedFrames& selFrames) const;

View File

@ -1,5 +1,5 @@
# Aseprite Render Library
# Copyright (C) 2019 Igara Studio S.A.
# Copyright (C) 2019-2023 Igara Studio S.A.
# Copyright (C) 2001-2018 David Capello
add_library(render-lib

View File

@ -17,6 +17,7 @@
#include "doc/image_impl.h"
#include "doc/layer_tilemap.h"
#include "doc/playback.h"
#include "doc/render_plan.h"
#include "doc/tileset.h"
#include "doc/tilesets.h"
#include "gfx/clip.h"
@ -679,8 +680,11 @@ void Render::renderLayer(
return;
m_globalOpacity = 255;
renderLayer(
layer, dstImage, area,
doc::RenderPlan plan;
plan.addLayer(layer, frame);
renderPlan(
plan, dstImage, area,
frame, compositeImage,
true, true, blendMode, false);
}
@ -772,18 +776,21 @@ void Render::renderSprite(
}
void Render::renderSpriteLayers(Image* dstImage,
const gfx::ClipF& area,
frame_t frame,
CompositeImageFunc compositeImage)
const gfx::ClipF& area,
frame_t frame,
CompositeImageFunc compositeImage)
{
doc::RenderPlan plan;
plan.addLayer(m_sprite->root(), frame);
// Draw the background layer.
m_globalOpacity = 255;
renderLayer(m_sprite->root(), dstImage,
area, frame, compositeImage,
true,
false,
BlendMode::UNSPECIFIED,
false);
renderPlan(plan, dstImage,
area, frame, compositeImage,
true,
false,
BlendMode::UNSPECIFIED,
false);
// Draw onion skin behind the sprite.
if (m_onionskin.position() == OnionskinPosition::BEHIND)
@ -791,11 +798,11 @@ void Render::renderSpriteLayers(Image* dstImage,
// Draw the transparent layers.
m_globalOpacity = 255;
renderLayer(m_sprite->root(), dstImage,
area, frame, compositeImage,
false,
true,
BlendMode::UNSPECIFIED, false);
renderPlan(plan, dstImage,
area, frame, compositeImage,
false,
true,
BlendMode::UNSPECIFIED, false);
}
void Render::renderBackground(Image* image,
@ -890,8 +897,10 @@ void Render::renderOnionskin(
else if (m_onionskin.type() == OnionskinType::RED_BLUE_TINT)
blendMode = (frameOut < frame ? BlendMode::RED_TINT: BlendMode::BLUE_TINT);
renderLayer(
onionLayer, dstImage,
doc::RenderPlan plan;
plan.addLayer(onionLayer, frameIn);
renderPlan(
plan, dstImage,
area, frameIn, compositeImage,
// Render background only for "in-front" onion skinning and
// when opacity is < 255
@ -986,8 +995,8 @@ void Render::renderImage(
m_newBlendMethod);
}
void Render::renderLayer(
const Layer* layer,
void Render::renderPlan(
RenderPlan& plan,
Image* image,
const gfx::Clip& area,
const frame_t frame,
@ -997,185 +1006,176 @@ void Render::renderLayer(
const BlendMode blendMode,
bool isSelected)
{
// we can't read from this layer
if (!layer->isVisible())
return;
for (const Cel* cel : plan.cels()) {
const Layer* layer = cel->layer();
if (m_selectedLayerForOpacity == layer)
isSelected = true;
ASSERT(layer->isVisible()); // Hidden layers shouldn't be in the plan
const Cel* cel = nullptr;
gfx::Rect extraArea;
bool drawExtra = false;
if (m_selectedLayerForOpacity == layer)
isSelected = true;
if (m_extraCel &&
m_extraImage &&
layer == m_currentLayer &&
((layer->isBackground() && render_background) ||
(!layer->isBackground() && render_transparent)) &&
// Don't use a tilemap extra cel (IMAGE_TILEMAP) in a
// non-tilemap layer (in the other hand tilemap layers allow
// extra cels of any kind). This fixes a crash on renderCel()
// when we were painting the Preview window using a tilemap
// extra image to patch a regular layer, when switching from a
// tilemap layer to a regular layer.
((layer->isTilemap()) ||
(!layer->isTilemap() && m_extraImage->pixelFormat() != IMAGE_TILEMAP))) {
if (frame == m_extraCel->frame() &&
frame == m_currentFrame) { // TODO this double check is not necessary
drawExtra = true;
}
else {
// Check if we can draw the extra cel when we render a linked
// frame.
cel = layer->cel(frame);
Cel* cel2 = layer->cel(m_extraCel->frame());
if (cel && cel2 &&
cel->data() == cel2->data()) {
gfx::Rect extraArea;
bool drawExtra = false;
if (m_extraCel &&
m_extraImage &&
layer == m_currentLayer &&
((layer->isBackground() && render_background) ||
(!layer->isBackground() && render_transparent)) &&
// Don't use a tilemap extra cel (IMAGE_TILEMAP) in a
// non-tilemap layer (in the other hand tilemap layers allow
// extra cels of any kind). This fixes a crash on renderCel()
// when we were painting the Preview window using a tilemap
// extra image to patch a regular layer, when switching from a
// tilemap layer to a regular layer.
((layer->isTilemap()) ||
(!layer->isTilemap() && m_extraImage->pixelFormat() != IMAGE_TILEMAP))) {
if (frame == m_extraCel->frame() &&
frame == m_currentFrame) { // TODO this double check is not necessary
drawExtra = true;
}
}
}
if (drawExtra) {
extraArea = m_extraCel->bounds();
extraArea = m_proj.apply(extraArea);
if (m_proj.scaleX() < 1.0) extraArea.w--;
if (m_proj.scaleY() < 1.0) extraArea.h--;
if (extraArea.w < 1) extraArea.w = 1;
if (extraArea.h < 1) extraArea.h = 1;
}
switch (layer->type()) {
case ObjectType::LayerImage:
case ObjectType::LayerTilemap: {
if ((!render_background && layer->isBackground()) ||
(!render_transparent && !layer->isBackground()))
break;
// Ignore reference layers
if (!(m_flags & Flags::ShowRefLayers) &&
layer->isReference())
break;
if (!cel)
else {
// Check if we can draw the extra cel when we render a linked
// frame.
cel = layer->cel(frame);
if (cel) {
Palette* pal = m_sprite->palette(frame);
const Image* celImage = nullptr;
gfx::RectF celBounds;
// Is the 'm_previewImage' set to be used with this layer?
if (m_previewImage &&
checkIfWeShouldUsePreview(cel)) {
celImage = m_previewImage;
celBounds = gfx::RectF(m_previewPos.x,
m_previewPos.y,
m_previewImage->width(),
m_previewImage->height());
}
// If not, we use the original cel-image from the images' stock
else {
celImage = cel->image();
if (cel->layer()->isReference())
celBounds = cel->boundsF();
else
celBounds = cel->bounds();
Cel* cel2 = layer->cel(m_extraCel->frame());
if (cel && cel2 &&
cel->data() == cel2->data()) {
drawExtra = true;
}
}
}
if (celImage) {
const LayerImage* imgLayer = static_cast<const LayerImage*>(layer);
BlendMode layerBlendMode =
(blendMode == BlendMode::UNSPECIFIED ?
imgLayer->blendMode():
blendMode);
if (drawExtra) {
extraArea = m_extraCel->bounds();
extraArea = m_proj.apply(extraArea);
if (m_proj.scaleX() < 1.0) extraArea.w--;
if (m_proj.scaleY() < 1.0) extraArea.h--;
if (extraArea.w < 1) extraArea.w = 1;
if (extraArea.h < 1) extraArea.h = 1;
}
ASSERT(cel->opacity() >= 0);
ASSERT(cel->opacity() <= 255);
ASSERT(imgLayer->opacity() >= 0);
ASSERT(imgLayer->opacity() <= 255);
switch (layer->type()) {
// Multiple three opacities: cel*layer*global (*nonactive-layer-opacity)
int t;
int opacity = cel->opacity();
opacity = MUL_UN8(opacity, imgLayer->opacity(), t);
opacity = MUL_UN8(opacity, m_globalOpacity, t);
if (!isSelected && m_nonactiveLayersOpacity != 255)
opacity = MUL_UN8(opacity, m_nonactiveLayersOpacity, t);
case ObjectType::LayerImage:
case ObjectType::LayerTilemap: {
if ((!render_background && layer->isBackground()) ||
(!render_transparent && !layer->isBackground()))
break;
// Generally this is just one pass, but if we are using
// OVER_COMPOSITE extra cel, this will be two passes.
for (int pass=0; pass<2; ++pass) {
// Draw parts outside the "m_extraCel" area
if (drawExtra && m_extraType == ExtraType::PATCH) {
gfx::Region originalAreas(area.srcBounds());
originalAreas.createSubtraction(
originalAreas, gfx::Region(extraArea));
// Ignore reference layers
if (!(m_flags & Flags::ShowRefLayers) &&
layer->isReference())
break;
for (auto rc : originalAreas) {
renderCel(
image, cel, celImage, layer, pal, celBounds,
gfx::Clip(area.dst.x+rc.x-area.src.x,
area.dst.y+rc.y-area.src.y, rc),
compositeImage, opacity, layerBlendMode);
}
}
// Draw the whole cel
else {
renderCel(
image, cel, celImage, layer, pal,
celBounds, area, compositeImage,
opacity, layerBlendMode);
}
if (!cel)
cel = layer->cel(frame);
if (m_extraType == ExtraType::OVER_COMPOSITE &&
layer == m_currentLayer &&
pass == 0) {
// Go for second pass with the extra blend mode...
layerBlendMode = m_extraBlendMode;
}
if (cel) {
Palette* pal = m_sprite->palette(frame);
const Image* celImage = nullptr;
gfx::RectF celBounds;
// Is the 'm_previewImage' set to be used with this layer?
if (m_previewImage &&
checkIfWeShouldUsePreview(cel)) {
celImage = m_previewImage;
celBounds = gfx::RectF(m_previewPos.x,
m_previewPos.y,
m_previewImage->width(),
m_previewImage->height());
}
// If not, we use the original cel-image from the images' stock
else {
celImage = cel->image();
if (cel->layer()->isReference())
celBounds = cel->boundsF();
else
break;
celBounds = cel->bounds();
}
if (celImage) {
const LayerImage* imgLayer = static_cast<const LayerImage*>(layer);
BlendMode layerBlendMode =
(blendMode == BlendMode::UNSPECIFIED ?
imgLayer->blendMode():
blendMode);
ASSERT(cel->opacity() >= 0);
ASSERT(cel->opacity() <= 255);
ASSERT(imgLayer->opacity() >= 0);
ASSERT(imgLayer->opacity() <= 255);
// Multiple three opacities: cel*layer*global (*nonactive-layer-opacity)
int t;
int opacity = cel->opacity();
opacity = MUL_UN8(opacity, imgLayer->opacity(), t);
opacity = MUL_UN8(opacity, m_globalOpacity, t);
if (!isSelected && m_nonactiveLayersOpacity != 255)
opacity = MUL_UN8(opacity, m_nonactiveLayersOpacity, t);
// Generally this is just one pass, but if we are using
// OVER_COMPOSITE extra cel, this will be two passes.
for (int pass=0; pass<2; ++pass) {
// Draw parts outside the "m_extraCel" area
if (drawExtra && m_extraType == ExtraType::PATCH) {
gfx::Region originalAreas(area.srcBounds());
originalAreas.createSubtraction(
originalAreas, gfx::Region(extraArea));
for (auto rc : originalAreas) {
renderCel(
image, cel, celImage, layer, pal, celBounds,
gfx::Clip(area.dst.x+rc.x-area.src.x,
area.dst.y+rc.y-area.src.y, rc),
compositeImage, opacity, layerBlendMode);
}
}
// Draw the whole cel
else {
renderCel(
image, cel, celImage, layer, pal,
celBounds, area, compositeImage,
opacity, layerBlendMode);
}
if (m_extraType == ExtraType::OVER_COMPOSITE &&
layer == m_currentLayer &&
pass == 0) {
// Go for second pass with the extra blend mode...
layerBlendMode = m_extraBlendMode;
}
else
break;
}
}
}
break;
}
break;
case ObjectType::LayerGroup:
ASSERT(false);
break;
}
case ObjectType::LayerGroup: {
for (const Layer* child : static_cast<const LayerGroup*>(layer)->layers()) {
renderLayer(
child, image,
area, frame,
compositeImage,
render_background,
render_transparent,
blendMode,
isSelected);
// Draw extras
if (drawExtra && m_extraType != ExtraType::NONE) {
if (m_extraCel->opacity() > 0) {
renderCel(
image,
m_extraCel,
m_sprite,
m_extraImage,
m_currentLayer, // Current layer (useful to use get the tileset if extra cel is a tilemap)
m_sprite->palette(frame),
m_extraCel->bounds(),
gfx::Clip(area.dst.x+extraArea.x-area.src.x,
area.dst.y+extraArea.y-area.src.y,
extraArea),
m_extraCel->opacity(),
m_extraBlendMode);
}
break;
}
}
// Draw extras
if (drawExtra && m_extraType != ExtraType::NONE) {
if (m_extraCel->opacity() > 0) {
renderCel(
image,
m_extraCel,
m_sprite,
m_extraImage,
m_currentLayer, // Current layer (useful to use get the tileset if extra cel is a tilemap)
m_sprite->palette(frame),
m_extraCel->bounds(),
gfx::Clip(area.dst.x+extraArea.x-area.src.x,
area.dst.y+extraArea.y-area.src.y,
extraArea),
m_extraCel->opacity(),
m_extraBlendMode);
}
}
}

View File

@ -28,6 +28,7 @@ namespace doc {
class Image;
class Layer;
class Palette;
class RenderPlan;
class Sprite;
class Tileset;
}
@ -158,8 +159,8 @@ namespace render {
const frame_t frame,
const CompositeImageFunc compositeImage);
void renderLayer(
const Layer* layer,
void renderPlan(
doc::RenderPlan& plan,
Image* image,
const gfx::Clip& area,
const frame_t frame,

View File

@ -1,4 +1,4 @@
// Aseprite Document Library
// Aseprite Render Library
// Copyright (c) 2019-2023 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//

View File

@ -1,3 +1,4 @@
-- Copyright (C) 2023 Igara Studio S.A.
-- Copyright (C) 2018 David Capello
--
-- This file is released under the terms of the MIT license.
@ -27,3 +28,11 @@ assert(c.data == "test")
c.position = Point(2, 4)
assert(c.position == Point(2, 4))
assert(c.bounds == Rectangle(2, 4, 32, 64))
assert(c.opacity == 255)
c.opacity = 128
assert(c.opacity == 128)
assert(c.zIndex == 0)
c.zIndex = -2
assert(c.zIndex == -2)