mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-27 06:35:16 +00:00
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:
parent
e38398d7aa
commit
24846eae10
Binary file not shown.
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@ -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 |
@ -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" />
|
||||
|
@ -656,6 +656,7 @@ END
|
||||
[cel_properties]
|
||||
title = Cel Properties
|
||||
opacity = Opacity:
|
||||
zindex = Z-Index:
|
||||
user_data_tooltip = User Data
|
||||
|
||||
[color_curve_point]
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
52
src/app/cmd/set_cel_zindex.cpp
Normal file
52
src/app/cmd/set_cel_zindex.cpp
Normal 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
|
39
src/app/cmd/set_cel_zindex.h
Normal file
39
src/app/cmd/set_cel_zindex.h
Normal 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
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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 << "]";
|
||||
|
@ -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) { }
|
||||
|
||||
|
@ -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) {
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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> },
|
||||
|
@ -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)
|
||||
|
@ -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...
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
92
src/doc/render_plan.cpp
Normal 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
42
src/doc/render_plan.h
Normal 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
|
98
src/doc/render_plan_tests.cpp
Normal file
98
src/doc/render_plan_tests.cpp
Normal 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();
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Aseprite Document Library
|
||||
// Aseprite Render Library
|
||||
// Copyright (c) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2018 David Capello
|
||||
//
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user