Add gfx::PackingRects class for packing multiple rectangles in a texture

This commit is contained in:
David Capello 2014-11-07 10:32:14 -03:00
parent 9df732e27b
commit c0d35b16e0
4 changed files with 249 additions and 1 deletions

View File

@ -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
View 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
View 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

View 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();
}