mirror of
https://github.com/aseprite/aseprite.git
synced 2025-04-16 23:42:57 +00:00
Add gfx::PackingRects class for packing multiple rectangles in a texture
This commit is contained in:
parent
9df732e27b
commit
c0d35b16e0
@ -1,8 +1,9 @@
|
||||
# ASEPRITE
|
||||
# Aseprite
|
||||
# Copyright (C) 2001-2014 David Capello
|
||||
|
||||
add_library(gfx-lib
|
||||
hsv.cpp
|
||||
packing_rects.cpp
|
||||
region.cpp
|
||||
rgb.cpp
|
||||
transformation.cpp)
|
||||
|
98
src/gfx/packing_rects.cpp
Normal file
98
src/gfx/packing_rects.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
// Aseprite Gfx Library
|
||||
// Copyright (C) 2001-2014 David Capello
|
||||
//
|
||||
// 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 "gfx/packing_rects.h"
|
||||
|
||||
#include "gfx/region.h"
|
||||
#include "gfx/size.h"
|
||||
|
||||
namespace gfx {
|
||||
|
||||
void PackingRects::add(const Size& sz)
|
||||
{
|
||||
m_rects.push_back(Rect(sz));
|
||||
}
|
||||
|
||||
void PackingRects::add(const Rect& rc)
|
||||
{
|
||||
m_rects.push_back(rc);
|
||||
}
|
||||
|
||||
Size PackingRects::bestFit()
|
||||
{
|
||||
Size size(0, 0);
|
||||
|
||||
// Calculate the amount of pixels that we need, the texture cannot
|
||||
// be smaller than that.
|
||||
int neededArea = 0;
|
||||
for (const auto& rc : m_rects) {
|
||||
neededArea += rc.w * rc.h;
|
||||
}
|
||||
|
||||
int w = 1;
|
||||
int h = 1;
|
||||
int z = 0;
|
||||
bool fit = false;
|
||||
while (true) {
|
||||
if (w*h >= neededArea) {
|
||||
fit = pack(Size(w, h));
|
||||
if (fit) {
|
||||
size = Size(w, h);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((++z) & 1)
|
||||
w *= 2;
|
||||
else
|
||||
h *= 2;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static bool by_area(const Rect* a, const Rect* b) {
|
||||
return a->w*a->h > b->w*b->h;
|
||||
}
|
||||
|
||||
bool PackingRects::pack(const Size& size)
|
||||
{
|
||||
m_bounds = Rect(size);
|
||||
|
||||
// We cannot sort m_rects because we want to
|
||||
std::vector<Rect*> rectPtrs(m_rects.size());
|
||||
int i = 0;
|
||||
for (auto& rc : m_rects)
|
||||
rectPtrs[i++] = &rc;
|
||||
std::sort(rectPtrs.begin(), rectPtrs.end(), by_area);
|
||||
|
||||
gfx::Region rgn(m_bounds);
|
||||
for (auto rcPtr : rectPtrs) {
|
||||
gfx::Rect& rc = *rcPtr;
|
||||
|
||||
for (int v=0; v<=m_bounds.h-rc.h; ++v) {
|
||||
for (int u=0; u<=m_bounds.w-rc.w; ++u) {
|
||||
gfx::Rect possible(u, v, rc.w, rc.h);
|
||||
Region::Overlap overlap = rgn.contains(possible);
|
||||
if (overlap == Region::In) {
|
||||
rc = possible;
|
||||
rgn.createSubtraction(rgn, gfx::Region(rc));
|
||||
goto next_rc;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false; // There is not enough room for "rc"
|
||||
next_rc:;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace gfx
|
53
src/gfx/packing_rects.h
Normal file
53
src/gfx/packing_rects.h
Normal file
@ -0,0 +1,53 @@
|
||||
// Aseprite Gfx Library
|
||||
// Copyright (C) 2001-2014 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef GFX_TEXTURE_SIZE_H_INCLUDED
|
||||
#define GFX_TEXTURE_SIZE_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "gfx/fwd.h"
|
||||
#include "gfx/rect.h"
|
||||
#include <vector>
|
||||
|
||||
namespace gfx {
|
||||
|
||||
// TODO add support for rotations
|
||||
class PackingRects {
|
||||
public:
|
||||
typedef std::vector<Rect> Rects;
|
||||
typedef Rects::const_iterator const_iterator;
|
||||
|
||||
// Iterate over all given rectangles (in the same order they where
|
||||
// given in addSize() calls).
|
||||
const_iterator begin() const { return m_rects.begin(); }
|
||||
const_iterator end() const { return m_rects.end(); }
|
||||
|
||||
size_t size() const { return m_rects.size(); }
|
||||
const Rect& operator[](int i) const { return m_rects[i]; }
|
||||
|
||||
// Adds a new rectangle.
|
||||
void add(const Size& sz);
|
||||
void add(const Rect& rc);
|
||||
|
||||
// Returns the best size for the texture.
|
||||
Size bestFit();
|
||||
|
||||
// Rearrange all given rectangles to best fit a texture size.
|
||||
// Returns true if all rectangles were correctly arranged or false
|
||||
// if there is not enough space.
|
||||
bool pack(const Size& size);
|
||||
|
||||
// Returns the bounds of the packed area.
|
||||
const Rect& bounds() const { return m_bounds; }
|
||||
|
||||
private:
|
||||
Rect m_bounds;
|
||||
Rects m_rects;
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
|
||||
#endif
|
96
src/gfx/packing_rects_tests.cpp
Normal file
96
src/gfx/packing_rects_tests.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
// Aseprite Gfx Library
|
||||
// Copyright (C) 2001-2014 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "gfx/packing_rects.h"
|
||||
#include "gfx/rect_io.h"
|
||||
#include "gfx/size.h"
|
||||
|
||||
using namespace gfx;
|
||||
|
||||
TEST(PackingRects, Simple)
|
||||
{
|
||||
PackingRects pr;
|
||||
pr.add(Size(256, 128));
|
||||
EXPECT_FALSE(pr.pack(Size(256, 120)));
|
||||
EXPECT_TRUE(pr.pack(Size(256, 128)));
|
||||
|
||||
EXPECT_EQ(Rect(0, 0, 256, 128), pr[0]);
|
||||
EXPECT_EQ(Rect(0, 0, 256, 128), pr.bounds());
|
||||
}
|
||||
|
||||
TEST(PackingRects, SimpleTwoRects)
|
||||
{
|
||||
PackingRects pr;
|
||||
pr.add(Size(256, 128));
|
||||
pr.add(Size(256, 120));
|
||||
EXPECT_TRUE(pr.pack(Size(256, 256)));
|
||||
|
||||
EXPECT_EQ(Rect(0, 0, 256, 256), pr.bounds());
|
||||
EXPECT_EQ(Rect(0, 0, 256, 128), pr[0]);
|
||||
EXPECT_EQ(Rect(0, 128, 256, 120), pr[1]);
|
||||
}
|
||||
|
||||
TEST(PackingRects, BestFit)
|
||||
{
|
||||
PackingRects pr;
|
||||
pr.add(Size(10, 12));
|
||||
pr.bestFit();
|
||||
EXPECT_EQ(Rect(0, 0, 16, 16), pr.bounds());
|
||||
}
|
||||
|
||||
TEST(PackingRects, BestFitTwoRects)
|
||||
{
|
||||
PackingRects pr;
|
||||
pr.add(Size(256, 128));
|
||||
pr.add(Size(256, 127));
|
||||
pr.bestFit();
|
||||
|
||||
EXPECT_EQ(Rect(0, 0, 256, 256), pr.bounds());
|
||||
EXPECT_EQ(Rect(0, 0, 256, 128), pr[0]);
|
||||
EXPECT_EQ(Rect(0, 128, 256, 127), pr[1]);
|
||||
}
|
||||
|
||||
TEST(PackingRects, BestFit6Frames100x100)
|
||||
{
|
||||
PackingRects pr;
|
||||
pr.add(Size(100, 100));
|
||||
pr.add(Size(100, 100));
|
||||
pr.add(Size(100, 100));
|
||||
pr.add(Size(100, 100));
|
||||
pr.add(Size(100, 100));
|
||||
pr.add(Size(100, 100));
|
||||
pr.bestFit();
|
||||
|
||||
EXPECT_EQ(Rect(0, 0, 512, 256), pr.bounds());
|
||||
EXPECT_EQ(Rect(0, 0, 100, 100), pr[0]);
|
||||
EXPECT_EQ(Rect(100, 0, 100, 100), pr[1]);
|
||||
EXPECT_EQ(Rect(200, 0, 100, 100), pr[2]);
|
||||
EXPECT_EQ(Rect(300, 0, 100, 100), pr[3]);
|
||||
EXPECT_EQ(Rect(400, 0, 100, 100), pr[4]);
|
||||
EXPECT_EQ(Rect(0, 100, 100, 100), pr[5]);
|
||||
}
|
||||
|
||||
TEST(PackingRects, KeepSameRectsOrder)
|
||||
{
|
||||
PackingRects pr;
|
||||
pr.add(Size(10, 10));
|
||||
pr.add(Size(20, 20));
|
||||
pr.add(Size(30, 30));
|
||||
pr.bestFit();
|
||||
|
||||
EXPECT_EQ(Rect(0, 0, 64, 32), pr.bounds());
|
||||
EXPECT_EQ(Rect(50, 0, 10, 10), pr[0]);
|
||||
EXPECT_EQ(Rect(30, 0, 20, 20), pr[1]);
|
||||
EXPECT_EQ(Rect(0, 0, 30, 30), pr[2]);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user