Respect z-index layer ordering when there are empty cels

An empty cel must be counted as a layer for the z-index ordering, so
the z-index refers to number of layers to move back/front, but number
of non-empty cels in the specific frame.

A this fix, a new issue appears: #3820
This commit is contained in:
David Capello 2023-04-17 19:24:20 -03:00
parent 2962bb3676
commit 172e1a615b
6 changed files with 81 additions and 34 deletions

View File

@ -229,7 +229,8 @@ void ShaderRenderer::renderPlan(SkCanvas* canvas,
const doc::frame_t frame,
const gfx::ClipF& area)
{
for (const Cel* cel : plan.cels()) {
for (const auto& item : plan.items()) {
const Cel* cel = item.cel;
const Layer* layer = cel->layer();
switch (layer->type()) {

View File

@ -26,9 +26,11 @@ 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.
// m_items array using z-indexes.
ASSERT(m_processZIndex == true);
++m_order;
// We can't read this layer
if (!layer->isVisible())
return;
@ -38,7 +40,7 @@ void RenderPlan::addLayer(const Layer* layer,
case ObjectType::LayerImage:
case ObjectType::LayerTilemap: {
if (Cel* cel = layer->cel(frame)) {
m_cels.push_back(cel);
m_items.emplace_back(m_order, cel);
}
break;
}
@ -57,10 +59,10 @@ 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
// If all cels has a z-index = 0, we can just use the m_items as it is
bool noZIndex = true;
for (int i=0; i<int(m_cels.size()); ++i) {
const int z = m_cels[i]->zIndex();
for (int i=0; i<int(m_items.size()); ++i) {
const int z = m_items[i].cel->zIndex();
if (z != 0) {
noZIndex = false;
break;
@ -69,24 +71,15 @@ void RenderPlan::processZIndexes() const
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){
// Order cels using its "order" number in m_items array + cel z-index offset
for (Item& item : m_items)
item.order = item.order + item.cel->zIndex();
std::sort(m_items.begin(), m_items.end(),
[](const Item& a, const Item& b){
return
(a.first < b.first) ||
(a.first == b.first && (a.second->zIndex() < b.second->zIndex()));
(a.order < b.order) ||
(a.order == b.order && (a.cel->zIndex() < b.cel->zIndex()));
});
for (int i=0; i<n; ++i) {
m_cels[i] = indexedCels[i].second;
}
}
} // namespace doc

View File

@ -19,12 +19,20 @@ namespace doc {
// layer/layers.
class RenderPlan {
public:
struct Item {
int order;
Cel* cel;
Item(int order = 0, Cel* cel = nullptr)
: order(order), cel(cel) { }
};
using Items = std::vector<Item>;
RenderPlan();
const CelList& cels() const {
const Items& items() const {
if (m_processZIndex)
processZIndexes();
return m_cels;
return m_items;
}
void addLayer(const Layer* layer,
@ -33,7 +41,8 @@ namespace doc {
private:
void processZIndexes() const;
mutable CelList m_cels;
int m_order = 0;
mutable Items m_items;
mutable bool m_processZIndex = true;
};

View File

@ -29,11 +29,11 @@ using namespace doc;
{ \
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); \
const auto& items = plan.items(); \
EXPECT_EQ(a, items[0].cel) << HELPER_LOG(items[0].cel, a); \
EXPECT_EQ(b, items[1].cel) << HELPER_LOG(items[1].cel, b); \
EXPECT_EQ(c, items[2].cel) << HELPER_LOG(items[2].cel, c); \
EXPECT_EQ(d, items[3].cel) << HELPER_LOG(items[3].cel, d); \
}
TEST(RenderPlan, ZIndex)
@ -91,6 +91,49 @@ TEST(RenderPlan, ZIndex)
EXPECT_PLAN(b, d, c, a);
}
TEST(RenderPlan, ZIndexBugWithEmptyCels)
{
#undef EXPECT_PLAN
#define EXPECT_PLAN(a, b, c) \
{ \
RenderPlan plan; \
plan.addLayer(spr->root(), 0); \
const auto& items = plan.items(); \
EXPECT_EQ(a, items[0].cel) << HELPER_LOG(items[0].cel, a); \
EXPECT_EQ(b, items[1].cel) << HELPER_LOG(items[1].cel, b); \
EXPECT_EQ(c, items[2].cel) << HELPER_LOG(items[2].cel, c); \
}
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, *d;
lay1->addCel(b = new Cel(0, ImageRef(Image::create(spec))));
// lay2 has an empty cel
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);
d->setZIndex(-1); EXPECT_PLAN(a, b, d); // -1 is not enough to pass through lay2
d->setZIndex(-2); EXPECT_PLAN(a, d, b);
d->setZIndex(-3); EXPECT_PLAN(d, a, b);
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);

View File

@ -632,9 +632,9 @@ void Sprite::pickCels(const gfx::PointF& pos,
{
// 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 auto& planItems = plan.items();
for (auto it=planItems.rbegin(), end=planItems.rend(); it!=end; ++it) {
Cel* cel = it->cel;
const Image* image = cel->image();
if (!image)
continue;

View File

@ -1006,7 +1006,8 @@ void Render::renderPlan(
const BlendMode blendMode,
bool isSelected)
{
for (const Cel* cel : plan.cels()) {
for (const auto& item : plan.items()) {
const Cel* cel = item.cel;
const Layer* layer = cel->layer();
ASSERT(layer->isVisible()); // Hidden layers shouldn't be in the plan