├── .gitignore ├── Rect.h ├── Rect.cpp ├── Readme.txt ├── ShelfBinPack.cpp ├── ShelfBinPack.h ├── SkylineBinPack.h ├── MaxRectsBinPack.h ├── SkylineBinPack.cpp ├── GuillotineBinPack.cpp ├── GuillotineBinPack.h ├── RectangleBinPack.pdf ├── ShelfNextFitBinPack.h ├── ShelfNextFitBinPack.cpp ├── old ├── RectangleBinPack.h └── RectangleBinPack.cpp ├── Makefile ├── meson.build ├── CMakeLists.txt ├── boost_binpacker.py ├── RectangleBinPack ├── RectangleBinPack.sln └── RectangleBinPack.vcxproj ├── MaxRectsBinPackTest ├── BinPack.cpp └── MaxRectsBinPackTest.vcxproj ├── Jamroot ├── test └── MaxRectsBinPackTest.cpp ├── boost_binpacker.cpp └── MaxRectsBinPack.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | *.so 3 | *.dylib 4 | -------------------------------------------------------------------------------- /Rect.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/Rect.h -------------------------------------------------------------------------------- /Rect.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/Rect.cpp -------------------------------------------------------------------------------- /Readme.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/Readme.txt -------------------------------------------------------------------------------- /ShelfBinPack.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/ShelfBinPack.cpp -------------------------------------------------------------------------------- /ShelfBinPack.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/ShelfBinPack.h -------------------------------------------------------------------------------- /SkylineBinPack.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/SkylineBinPack.h -------------------------------------------------------------------------------- /MaxRectsBinPack.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/MaxRectsBinPack.h -------------------------------------------------------------------------------- /SkylineBinPack.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/SkylineBinPack.cpp -------------------------------------------------------------------------------- /GuillotineBinPack.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/GuillotineBinPack.cpp -------------------------------------------------------------------------------- /GuillotineBinPack.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/GuillotineBinPack.h -------------------------------------------------------------------------------- /RectangleBinPack.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/RectangleBinPack.pdf -------------------------------------------------------------------------------- /ShelfNextFitBinPack.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/ShelfNextFitBinPack.h -------------------------------------------------------------------------------- /ShelfNextFitBinPack.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/ShelfNextFitBinPack.cpp -------------------------------------------------------------------------------- /old/RectangleBinPack.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/old/RectangleBinPack.h -------------------------------------------------------------------------------- /old/RectangleBinPack.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juj/RectangleBinPack/HEAD/old/RectangleBinPack.cpp -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | # copyright 2013, Stefan Beller 3 | # This file is also public domain. 4 | 5 | OBJS = MaxRectsBinPackTest/BinPack.o MaxRectsBinPack.o Rect.o 6 | CXX = g++ 7 | # If you want to compile it faster try these options (gcc only) 8 | # CPPFLAGS = -march=native -mtune=native -Ofast -g -flto -Wall -Wextra 9 | # LDFLAGS = -march=native -mtune=native -Ofast -g -flto -fwhole-program 10 | 11 | all: $(OBJS) 12 | $(CXX) $(CPPFLAGS) $(LDFLAGS) $(OBJS) -o BinPackTest 13 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('RectangleBinPack', 'cpp') 2 | 3 | RectangleBinPack = static_library( 4 | 'RectangleBinPack', 5 | 6 | sources : [ 7 | 'GuillotineBinPack.cpp', 8 | 'MaxRectsBinPack.cpp', 9 | 'Rect.cpp', 10 | 'ShelfBinPack.cpp', 11 | 'ShelfNextFitBinPack.cpp', 12 | 'ShelfNextFitBinPack.cpp', 13 | 'SkylineBinPack.cpp' 14 | ] 15 | ) 16 | 17 | RectangleBinPack_dep = declare_dependency( 18 | link_with : RectangleBinPack, 19 | include_directories : include_directories('.') 20 | ) 21 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(RectangleBinPack VERSION "1.0.0" LANGUAGES CXX) 4 | 5 | set(TEST_PROJECT_NAME MaxRectsBinPackTest) 6 | 7 | add_library(${PROJECT_NAME} 8 | GuillotineBinPack.cpp 9 | MaxRectsBinPack.cpp 10 | Rect.cpp 11 | ShelfBinPack.cpp 12 | ShelfNextFitBinPack.cpp 13 | ShelfNextFitBinPack.cpp 14 | SkylineBinPack.cpp 15 | ) 16 | 17 | add_library(RectangleBinPack::RectangleBinPack ALIAS ${PROJECT_NAME}) 18 | add_executable(${TEST_PROJECT_NAME} test/MaxRectsBinPackTest.cpp MaxRectsBinPack.cpp Rect.cpp) 19 | 20 | target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 21 | target_include_directories(${TEST_PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 22 | 23 | target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_11) 24 | target_compile_features(${TEST_PROJECT_NAME} PUBLIC cxx_std_11) 25 | -------------------------------------------------------------------------------- /boost_binpacker.py: -------------------------------------------------------------------------------- 1 | import boost_binpacker_ext 2 | 3 | max_rect_packer = boost_binpacker_ext.MaxRectsBinPack(100, 100) 4 | second_packer = boost_binpacker_ext.MaxRectsBinPack() 5 | second_packer.init(100, 200) 6 | 7 | heuristic = boost_binpacker_ext.MaxRectsFreeRectChoiceHeuristic.RectBestShortSideFit 8 | print max_rect_packer.insert(20, 20, heuristic).height # '20': fits 9 | print max_rect_packer.insert(200, 20, heuristic).height # '0': does not fit 10 | 11 | guillotine_packer = boost_binpacker_ext.GuillotineBinPack(100, 100) 12 | heuristic = boost_binpacker_ext.GuillotineFreeRectChoiceHeuristic.RectBestShortSideFit 13 | splitmethod = boost_binpacker_ext.GuillotineSplitHeuristic.SplitMinimizeArea 14 | print guillotine_packer.insert(20, 20, True, heuristic, splitmethod).height # '20': fits 15 | 16 | shelf_packer = boost_binpacker_ext.ShelfBinPack(100, 100, True) 17 | heuristic = boost_binpacker_ext.ShelfChoiceHeuristic.ShelfNextFit 18 | print shelf_packer.insert(20, 20, heuristic).height # '20': fits 19 | 20 | skyline_packer = boost_binpacker_ext.SkylineBinPack(100, 100, True) 21 | heuristic = boost_binpacker_ext.SkylineLevelChoiceHeuristic.LevelBottomLeft 22 | print skyline_packer.insert(20, 20, heuristic).height # '20': fits 23 | -------------------------------------------------------------------------------- /RectangleBinPack/RectangleBinPack.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RectangleBinPack", "RectangleBinPack.vcxproj", "{CB584136-5A78-42BC-A2EB-367B9E60290D}" 5 | EndProject 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MaxRectsBinPackTest", "..\MaxRectsBinPackTest\MaxRectsBinPackTest.vcxproj", "{D20BF580-101C-4705-93FC-8240A728203E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Win32 = Debug|Win32 11 | Release|Win32 = Release|Win32 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {CB584136-5A78-42BC-A2EB-367B9E60290D}.Debug|Win32.ActiveCfg = Debug|Win32 15 | {CB584136-5A78-42BC-A2EB-367B9E60290D}.Debug|Win32.Build.0 = Debug|Win32 16 | {CB584136-5A78-42BC-A2EB-367B9E60290D}.Release|Win32.ActiveCfg = Release|Win32 17 | {CB584136-5A78-42BC-A2EB-367B9E60290D}.Release|Win32.Build.0 = Release|Win32 18 | {D20BF580-101C-4705-93FC-8240A728203E}.Debug|Win32.ActiveCfg = Debug|Win32 19 | {D20BF580-101C-4705-93FC-8240A728203E}.Debug|Win32.Build.0 = Debug|Win32 20 | {D20BF580-101C-4705-93FC-8240A728203E}.Release|Win32.ActiveCfg = Release|Win32 21 | {D20BF580-101C-4705-93FC-8240A728203E}.Release|Win32.Build.0 = Release|Win32 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /MaxRectsBinPackTest/BinPack.cpp: -------------------------------------------------------------------------------- 1 | #include "../MaxRectsBinPack.h" 2 | #include 3 | 4 | 5 | int main(int argc, char **argv) 6 | { 7 | 8 | if (argc < 5 || argc % 2 != 1) 9 | { 10 | printf("Usage: MaxRectsBinPackTest binWidth binHeight w_0 h_0 w_1 h_1 w_2 h_2 ... w_n h_n\n"); 11 | printf(" where binWidth and binHeight define the size of the bin.\n"); 12 | printf(" w_i is the width of the i'th rectangle to pack, and h_i the height.\n"); 13 | printf("Example: MaxRectsBinPackTest 256 256 30 20 50 20 10 80 90 20\n"); 14 | return 0; 15 | } 16 | 17 | using namespace rbp; 18 | 19 | // Create a bin to pack to, use the bin size from command line. 20 | MaxRectsBinPack bin; 21 | int binWidth = atoi(argv[1]); 22 | int binHeight = atoi(argv[2]); 23 | printf("Initializing bin to size %dx%d.\n", binWidth, binHeight); 24 | bin.Init(binWidth, binHeight); 25 | 26 | // Pack each rectangle (w_i, h_i) the user inputted on the command line. 27 | for(int i = 3; i < argc; i += 2) 28 | { 29 | // Read next rectangle to pack. 30 | int rectWidth = atoi(argv[i]); 31 | int rectHeight = atoi(argv[i+1]); 32 | printf("Packing rectangle of size %dx%d: ", rectWidth, rectHeight); 33 | 34 | // Perform the packing. 35 | MaxRectsBinPack::FreeRectChoiceHeuristic heuristic = MaxRectsBinPack::RectBestShortSideFit; // This can be changed individually even for each rectangle packed. 36 | Rect packedRect = bin.Insert(rectWidth, rectHeight, heuristic); 37 | 38 | // Test success or failure. 39 | if (packedRect.height > 0) 40 | printf("Packed to (x,y)=(%d,%d), (w,h)=(%d,%d). Free space left: %.2f%%\n", packedRect.x, packedRect.y, packedRect.width, packedRect.height, 100.f - bin.Occupancy()*100.f); 41 | else 42 | printf("Failed! Could not find a proper position to pack this rectangle into. Skipping this one.\n"); 43 | } 44 | printf("Done. All rectangles packed.\n"); 45 | } 46 | -------------------------------------------------------------------------------- /Jamroot: -------------------------------------------------------------------------------- 1 | # Copyright David Abrahams 2006. Distributed under the Boost 2 | # Software License, Version 1.0. (See accompanying 3 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | 5 | import python ; 6 | 7 | if ! [ python.configured ] 8 | { 9 | ECHO "notice: no Python configured in user-config.jam" ; 10 | ECHO "notice: will use default configuration" ; 11 | using python ; 12 | } 13 | 14 | # Specify the path to the Boost project. If you move this project, 15 | # adjust this path to refer to the Boost root directory. 16 | use-project boost 17 | : /path/to/boost/installation ; 18 | 19 | # Set up the project-wide requirements that everything uses the 20 | # boost_python library from the project whose global ID is 21 | # /boost/python. 22 | project 23 | : requirements /boost/python//boost_python 24 | /boost//headers 25 | : usage-requirements /boost//headers 26 | ; 27 | 28 | # Declare the three extension modules. You can specify multiple 29 | # source files after the colon separated by spaces. 30 | python-extension boost_binpacker_ext : boost_binpacker.cpp MaxRectsBinPack.cpp GuillotineBinPack.cpp ShelfBinPack.cpp SkylineBinPack.cpp Rect.cpp ; 31 | 32 | # Put the extension and Boost.Python DLL in the current directory, so 33 | # that running script by hand works. 34 | install convenient_copy 35 | : boost_binpacker_ext 36 | : on SHARED_LIB PYTHON_EXTENSION 37 | . 38 | ; 39 | 40 | # A little "rule" (function) to clean up the syntax of declaring tests 41 | # of these extension modules. 42 | local rule run-test ( test-name : sources + ) 43 | { 44 | import testing ; 45 | testing.make-test run-pyd : $(sources) : : $(test-name) ; 46 | } 47 | 48 | # Declare test targets 49 | run-test boost_binpacker : boost_binpacker_ext boost_binpacker.py ; 50 | -------------------------------------------------------------------------------- /test/MaxRectsBinPackTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #if defined(__unix__) || defined(__APPLE__) || defined (__CYGWIN__) 5 | #include 6 | #include 7 | #endif 8 | 9 | #ifdef __APPLE__ 10 | #include 11 | #endif 12 | 13 | #ifdef WIN32 14 | #include 15 | #endif 16 | 17 | #include 18 | #include "MaxRectsBinPack.h" 19 | 20 | using namespace rbp; 21 | 22 | uint64_t tick() 23 | { 24 | #if defined(WIN32) 25 | LARGE_INTEGER ddwTimer; 26 | BOOL success = QueryPerformanceCounter(&ddwTimer); 27 | return ddwTimer.QuadPart; 28 | #elif defined(__APPLE__) 29 | return mach_absolute_time(); 30 | #elif defined(_POSIX_MONOTONIC_CLOCK) 31 | timespec t; 32 | clock_gettime(CLOCK_MONOTONIC, &t); 33 | return t.tv_sec * 1000 * 1000 * 1000 + t.tv_nsec; 34 | #elif defined(_POSIX_C_SOURCE) 35 | timeval t; 36 | gettimeofday(&t, NULL); 37 | return t.tv_sec * 1000 * 1000 + t.tv_usec; 38 | #else 39 | return clock(); 40 | #endif 41 | } 42 | 43 | uint64_t ticksPerSec() 44 | { 45 | #if defined(WIN32) 46 | uint64_t ddwTimerFrequency; 47 | QueryPerformanceFrequency(reinterpret_cast(&ddwTimerFrequency)); 48 | return ddwTimerFrequency; 49 | #elif defined(__APPLE__) 50 | mach_timebase_info_data_t timeBaseInfo; 51 | mach_timebase_info(&timeBaseInfo); 52 | return 1000000000ULL * (uint64_t)timeBaseInfo.denom / (uint64_t)timeBaseInfo.numer; 53 | #elif defined(_POSIX_MONOTONIC_CLOCK) 54 | return 1000 * 1000 * 1000; 55 | #elif defined(_POSIX_C_SOURCE) || defined(__APPLE__) 56 | return 1000 * 1000; 57 | #else 58 | return CLOCKS_PER_SEC; 59 | #endif 60 | } 61 | 62 | bool AreDisjoint(const Rect &a, const Rect &b) 63 | { 64 | return a.x >= b.x + b.width || a.x + a.width <= b.x || 65 | a.y >= b.y + b.height || a.y + a.height <= b.y; 66 | } 67 | 68 | bool AllRectsDisjoint(std::vector &packed) 69 | { 70 | for(size_t i = 0; i < packed.size(); ++i) 71 | for(size_t j = i+1; j < packed.size(); ++j) 72 | { 73 | if (!AreDisjoint(packed[i], packed[j])) 74 | return false; 75 | } 76 | return true; 77 | } 78 | 79 | int main() 80 | { 81 | MaxRectsBinPack pack(1024*8, 1024*8, true); 82 | 83 | std::vector packed; 84 | srand(12412); 85 | uint64_t t0 = tick(); 86 | for(int i = 1; i < 1024*1024; ++i) 87 | { 88 | int a = (rand() % 128) + 1; 89 | int b = (rand() % 128) + 1; 90 | Rect r = pack.Insert(a, b, MaxRectsBinPack::RectBestShortSideFit); 91 | if (!r.width) 92 | break; 93 | packed.push_back(r); 94 | } 95 | uint64_t t1 = tick(); 96 | printf("Packing %d rectangles took %f msecs. All rects disjoint: %s. Occupancy: %f\n", 97 | (int)packed.size(), (t1 - t0) * 1000.0 / ticksPerSec(), AllRectsDisjoint(packed) ? "yes" : "NO!", pack.Occupancy()); 98 | } 99 | -------------------------------------------------------------------------------- /RectangleBinPack/RectangleBinPack.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {CB584136-5A78-42BC-A2EB-367B9E60290D} 15 | RectangleBinPack 16 | 17 | 18 | 19 | StaticLibrary 20 | true 21 | MultiByte 22 | 23 | 24 | StaticLibrary 25 | false 26 | true 27 | MultiByte 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | Level3 43 | Disabled 44 | 45 | 46 | true 47 | 48 | 49 | 50 | 51 | Level3 52 | MaxSpeed 53 | true 54 | true 55 | 56 | 57 | true 58 | true 59 | true 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /MaxRectsBinPackTest/MaxRectsBinPackTest.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | {D20BF580-101C-4705-93FC-8240A728203E} 15 | Win32Proj 16 | MaxRectsBinPackTest 17 | 18 | 19 | 20 | Application 21 | true 22 | Unicode 23 | 24 | 25 | Application 26 | false 27 | true 28 | Unicode 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | true 42 | 43 | 44 | false 45 | 46 | 47 | 48 | 49 | 50 | Level3 51 | Disabled 52 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 53 | 54 | 55 | Console 56 | true 57 | 58 | 59 | 60 | 61 | Level3 62 | 63 | 64 | MaxSpeed 65 | true 66 | true 67 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 68 | 69 | 70 | Console 71 | true 72 | true 73 | true 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | {cb584136-5a78-42bc-a2eb-367b9e60290d} 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /boost_binpacker.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "./MaxRectsBinPack.h" 4 | #include "./GuillotineBinPack.h" 5 | #include "./ShelfBinPack.h" 6 | #include "./SkylineBinPack.h" 7 | #include 8 | 9 | using namespace boost::python; 10 | 11 | BOOST_PYTHON_MODULE(boost_binpacker_ext) 12 | { 13 | using namespace rbp; 14 | 15 | class_("Rect") 16 | .add_property("x", &Rect::x) 17 | .add_property("y", &Rect::y) 18 | .add_property("width", &Rect::width) 19 | .add_property("height", &Rect::height) 20 | ; 21 | 22 | // MaxRectBinpack 23 | enum_("MaxRectsFreeRectChoiceHeuristic") 24 | .value("RectBestShortSideFit", MaxRectsBinPack::RectBestShortSideFit) 25 | .value("RectBestLongSideFit", MaxRectsBinPack::RectBestLongSideFit) 26 | .value("RectBestAreaFit", MaxRectsBinPack::RectBestAreaFit) 27 | .value("RectBottomLeftRule", MaxRectsBinPack::RectBottomLeftRule) 28 | .value("RectContactPointRule", MaxRectsBinPack::RectContactPointRule) 29 | ; 30 | 31 | Rect (MaxRectsBinPack::*MaxRectsInsert)(int, int, MaxRectsBinPack::FreeRectChoiceHeuristic) = &MaxRectsBinPack::Insert; 32 | 33 | class_("MaxRectsBinPack", init()) 34 | .def(init<>()) 35 | .def("init", &MaxRectsBinPack::Init) 36 | .def("insert", MaxRectsInsert) 37 | .def("occupancy", &MaxRectsBinPack::Occupancy) 38 | ; 39 | 40 | // GuillotineBinPack 41 | enum_("GuillotineFreeRectChoiceHeuristic") 42 | .value("RectBestAreaFit", GuillotineBinPack::RectBestAreaFit) 43 | .value("RectBestShortSideFit", GuillotineBinPack::RectBestShortSideFit) 44 | .value("RectBestLongSideFit", GuillotineBinPack::RectBestLongSideFit) 45 | .value("RectWorstAreaFit", GuillotineBinPack::RectWorstAreaFit) 46 | .value("RectWorstShortSideFit", GuillotineBinPack::RectWorstShortSideFit) 47 | .value("RectWorstLongSideFit", GuillotineBinPack::RectWorstLongSideFit) 48 | ; 49 | enum_("GuillotineSplitHeuristic") 50 | .value("SplitShorterLeftoverAxis", GuillotineBinPack::SplitShorterLeftoverAxis) 51 | .value("SplitLongerLeftoverAxis", GuillotineBinPack::SplitLongerLeftoverAxis) 52 | .value("SplitMinimizeArea", GuillotineBinPack::SplitMinimizeArea) 53 | .value("SplitMaximizeArea", GuillotineBinPack::SplitMaximizeArea) 54 | .value("SplitShorterAxis", GuillotineBinPack::SplitShorterAxis) 55 | .value("SplitLongerAxis", GuillotineBinPack::SplitLongerAxis) 56 | ; 57 | 58 | Rect (GuillotineBinPack::*GuillotineInsert)(int, int, bool, GuillotineBinPack::FreeRectChoiceHeuristic, GuillotineBinPack::GuillotineSplitHeuristic) = &GuillotineBinPack::Insert; 59 | 60 | class_("GuillotineBinPack", init()) 61 | .def(init<>()) 62 | .def("init", &GuillotineBinPack::Init) 63 | .def("insert", GuillotineInsert) 64 | .def("occupancy", &GuillotineBinPack::Occupancy) 65 | ; 66 | 67 | // ShelfBinPack 68 | enum_("ShelfChoiceHeuristic") 69 | .value("ShelfNextFit", ShelfBinPack::ShelfNextFit) 70 | .value("ShelfFirstFit", ShelfBinPack::ShelfFirstFit) 71 | .value("ShelfBestAreaFit", ShelfBinPack::ShelfBestAreaFit) 72 | .value("ShelfWorstAreaFit", ShelfBinPack::ShelfWorstAreaFit) 73 | .value("ShelfBestHeightFit", ShelfBinPack::ShelfBestHeightFit) 74 | .value("ShelfBestWidthFit", ShelfBinPack::ShelfBestWidthFit) 75 | .value("ShelfWorstWidthFit", ShelfBinPack::ShelfWorstWidthFit) 76 | ; 77 | 78 | class_("ShelfBinPack", init()) 79 | .def(init<>()) 80 | .def("init", &ShelfBinPack::Init) 81 | .def("insert", &ShelfBinPack::Insert) 82 | .def("occupancy", &ShelfBinPack::Occupancy) 83 | ; 84 | 85 | // SkylineBinPack 86 | enum_("SkylineLevelChoiceHeuristic") 87 | .value("LevelBottomLeft", SkylineBinPack::LevelBottomLeft) 88 | .value("LevelMinWasteFit", SkylineBinPack::LevelMinWasteFit) 89 | ; 90 | 91 | Rect (SkylineBinPack::*SkylineInsert)(int, int, SkylineBinPack::LevelChoiceHeuristic) = &SkylineBinPack::Insert; 92 | 93 | class_("SkylineBinPack", init()) 94 | .def(init<>()) 95 | .def("init", &SkylineBinPack::Init) 96 | .def("insert", SkylineInsert) 97 | .def("occupancy", &SkylineBinPack::Occupancy) 98 | ; 99 | } 100 | -------------------------------------------------------------------------------- /MaxRectsBinPack.cpp: -------------------------------------------------------------------------------- 1 | /** @file MaxRectsBinPack.cpp 2 | @author Jukka Jylänki 3 | 4 | @brief Implements different bin packer algorithms that use the MAXRECTS data structure. 5 | 6 | This work is released to Public Domain, do whatever you want with it. 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "MaxRectsBinPack.h" 19 | 20 | namespace rbp { 21 | 22 | using namespace std; 23 | 24 | MaxRectsBinPack::MaxRectsBinPack() 25 | :binWidth(0), 26 | binHeight(0) 27 | { 28 | } 29 | 30 | MaxRectsBinPack::MaxRectsBinPack(int width, int height, bool allowFlip) 31 | { 32 | Init(width, height, allowFlip); 33 | } 34 | 35 | void MaxRectsBinPack::Init(int width, int height, bool allowFlip) 36 | { 37 | binAllowFlip = allowFlip; 38 | binWidth = width; 39 | binHeight = height; 40 | 41 | Rect n; 42 | n.x = 0; 43 | n.y = 0; 44 | n.width = width; 45 | n.height = height; 46 | 47 | usedRectangles.clear(); 48 | 49 | freeRectangles.clear(); 50 | freeRectangles.push_back(n); 51 | } 52 | 53 | Rect MaxRectsBinPack::Insert(int width, int height, FreeRectChoiceHeuristic method) 54 | { 55 | Rect newNode; 56 | // Unused in this function. We don't need to know the score after finding the position. 57 | int score1 = std::numeric_limits::max(); 58 | int score2 = std::numeric_limits::max(); 59 | switch(method) 60 | { 61 | case RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit(width, height, score1, score2); break; 62 | case RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft(width, height, score1, score2); break; 63 | case RectContactPointRule: newNode = FindPositionForNewNodeContactPoint(width, height, score1); break; 64 | case RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit(width, height, score2, score1); break; 65 | case RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit(width, height, score1, score2); break; 66 | } 67 | 68 | if (newNode.height == 0) 69 | return newNode; 70 | 71 | PlaceRect(newNode); 72 | 73 | return newNode; 74 | } 75 | 76 | void MaxRectsBinPack::Insert(std::vector &rects, std::vector &dst, FreeRectChoiceHeuristic method) 77 | { 78 | dst.clear(); 79 | 80 | while(rects.size() > 0) 81 | { 82 | int bestScore1 = std::numeric_limits::max(); 83 | int bestScore2 = std::numeric_limits::max(); 84 | int bestRectIndex = -1; 85 | Rect bestNode; 86 | 87 | for(size_t i = 0; i < rects.size(); ++i) 88 | { 89 | int score1; 90 | int score2; 91 | Rect newNode = ScoreRect(rects[i].width, rects[i].height, method, score1, score2); 92 | 93 | if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2)) 94 | { 95 | bestScore1 = score1; 96 | bestScore2 = score2; 97 | bestNode = newNode; 98 | bestRectIndex = i; 99 | } 100 | } 101 | 102 | if (bestRectIndex == -1) 103 | return; 104 | 105 | PlaceRect(bestNode); 106 | dst.push_back(bestNode); 107 | rects[bestRectIndex] = rects.back(); 108 | rects.pop_back(); 109 | } 110 | } 111 | 112 | void MaxRectsBinPack::PlaceRect(const Rect &node) 113 | { 114 | for(size_t i = 0; i < freeRectangles.size();) 115 | { 116 | if (SplitFreeNode(freeRectangles[i], node)) 117 | { 118 | freeRectangles[i] = freeRectangles.back(); 119 | freeRectangles.pop_back(); 120 | } 121 | else 122 | ++i; 123 | } 124 | 125 | PruneFreeList(); 126 | 127 | usedRectangles.push_back(node); 128 | } 129 | 130 | Rect MaxRectsBinPack::ScoreRect(int width, int height, FreeRectChoiceHeuristic method, int &score1, int &score2) const 131 | { 132 | Rect newNode; 133 | score1 = std::numeric_limits::max(); 134 | score2 = std::numeric_limits::max(); 135 | switch(method) 136 | { 137 | case RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit(width, height, score1, score2); break; 138 | case RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft(width, height, score1, score2); break; 139 | case RectContactPointRule: newNode = FindPositionForNewNodeContactPoint(width, height, score1); 140 | score1 = -score1; // Reverse since we are minimizing, but for contact point score bigger is better. 141 | break; 142 | case RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit(width, height, score2, score1); break; 143 | case RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit(width, height, score1, score2); break; 144 | } 145 | 146 | // Cannot fit the current rectangle. 147 | if (newNode.height == 0) 148 | { 149 | score1 = std::numeric_limits::max(); 150 | score2 = std::numeric_limits::max(); 151 | } 152 | 153 | return newNode; 154 | } 155 | 156 | /// Computes the ratio of used surface area. 157 | double MaxRectsBinPack::Occupancy() const 158 | { 159 | uint64_t usedSurfaceArea = 0; 160 | for(size_t i = 0; i < usedRectangles.size(); ++i) 161 | usedSurfaceArea += usedRectangles[i].width * usedRectangles[i].height; 162 | 163 | return (double)usedSurfaceArea / ((uint64_t)binWidth * binHeight); 164 | } 165 | 166 | Rect MaxRectsBinPack::FindPositionForNewNodeBottomLeft(int width, int height, int &bestY, int &bestX) const 167 | { 168 | Rect bestNode = {}; 169 | 170 | bestY = std::numeric_limits::max(); 171 | bestX = std::numeric_limits::max(); 172 | 173 | for(size_t i = 0; i < freeRectangles.size(); ++i) 174 | { 175 | // Try to place the rectangle in upright (non-flipped) orientation. 176 | if (freeRectangles[i].width >= width && freeRectangles[i].height >= height) 177 | { 178 | int topSideY = freeRectangles[i].y + height; 179 | if (topSideY < bestY || (topSideY == bestY && freeRectangles[i].x < bestX)) 180 | { 181 | bestNode.x = freeRectangles[i].x; 182 | bestNode.y = freeRectangles[i].y; 183 | bestNode.width = width; 184 | bestNode.height = height; 185 | bestY = topSideY; 186 | bestX = freeRectangles[i].x; 187 | } 188 | } 189 | if (binAllowFlip && freeRectangles[i].width >= height && freeRectangles[i].height >= width) 190 | { 191 | int topSideY = freeRectangles[i].y + width; 192 | if (topSideY < bestY || (topSideY == bestY && freeRectangles[i].x < bestX)) 193 | { 194 | bestNode.x = freeRectangles[i].x; 195 | bestNode.y = freeRectangles[i].y; 196 | bestNode.width = height; 197 | bestNode.height = width; 198 | bestY = topSideY; 199 | bestX = freeRectangles[i].x; 200 | } 201 | } 202 | } 203 | return bestNode; 204 | } 205 | 206 | Rect MaxRectsBinPack::FindPositionForNewNodeBestShortSideFit(int width, int height, 207 | int &bestShortSideFit, int &bestLongSideFit) const 208 | { 209 | Rect bestNode = {}; 210 | 211 | bestShortSideFit = std::numeric_limits::max(); 212 | bestLongSideFit = std::numeric_limits::max(); 213 | 214 | for(size_t i = 0; i < freeRectangles.size(); ++i) 215 | { 216 | // Try to place the rectangle in upright (non-flipped) orientation. 217 | if (freeRectangles[i].width >= width && freeRectangles[i].height >= height) 218 | { 219 | int leftoverHoriz = abs(freeRectangles[i].width - width); 220 | int leftoverVert = abs(freeRectangles[i].height - height); 221 | int shortSideFit = min(leftoverHoriz, leftoverVert); 222 | int longSideFit = max(leftoverHoriz, leftoverVert); 223 | 224 | if (shortSideFit < bestShortSideFit || (shortSideFit == bestShortSideFit && longSideFit < bestLongSideFit)) 225 | { 226 | bestNode.x = freeRectangles[i].x; 227 | bestNode.y = freeRectangles[i].y; 228 | bestNode.width = width; 229 | bestNode.height = height; 230 | bestShortSideFit = shortSideFit; 231 | bestLongSideFit = longSideFit; 232 | } 233 | } 234 | 235 | if (binAllowFlip && freeRectangles[i].width >= height && freeRectangles[i].height >= width) 236 | { 237 | int flippedLeftoverHoriz = abs(freeRectangles[i].width - height); 238 | int flippedLeftoverVert = abs(freeRectangles[i].height - width); 239 | int flippedShortSideFit = min(flippedLeftoverHoriz, flippedLeftoverVert); 240 | int flippedLongSideFit = max(flippedLeftoverHoriz, flippedLeftoverVert); 241 | 242 | if (flippedShortSideFit < bestShortSideFit || (flippedShortSideFit == bestShortSideFit && flippedLongSideFit < bestLongSideFit)) 243 | { 244 | bestNode.x = freeRectangles[i].x; 245 | bestNode.y = freeRectangles[i].y; 246 | bestNode.width = height; 247 | bestNode.height = width; 248 | bestShortSideFit = flippedShortSideFit; 249 | bestLongSideFit = flippedLongSideFit; 250 | } 251 | } 252 | } 253 | return bestNode; 254 | } 255 | 256 | Rect MaxRectsBinPack::FindPositionForNewNodeBestLongSideFit(int width, int height, 257 | int &bestShortSideFit, int &bestLongSideFit) const 258 | { 259 | Rect bestNode = {}; 260 | 261 | bestShortSideFit = std::numeric_limits::max(); 262 | bestLongSideFit = std::numeric_limits::max(); 263 | 264 | for(size_t i = 0; i < freeRectangles.size(); ++i) 265 | { 266 | // Try to place the rectangle in upright (non-flipped) orientation. 267 | if (freeRectangles[i].width >= width && freeRectangles[i].height >= height) 268 | { 269 | int leftoverHoriz = abs(freeRectangles[i].width - width); 270 | int leftoverVert = abs(freeRectangles[i].height - height); 271 | int shortSideFit = min(leftoverHoriz, leftoverVert); 272 | int longSideFit = max(leftoverHoriz, leftoverVert); 273 | 274 | if (longSideFit < bestLongSideFit || (longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit)) 275 | { 276 | bestNode.x = freeRectangles[i].x; 277 | bestNode.y = freeRectangles[i].y; 278 | bestNode.width = width; 279 | bestNode.height = height; 280 | bestShortSideFit = shortSideFit; 281 | bestLongSideFit = longSideFit; 282 | } 283 | } 284 | 285 | if (binAllowFlip && freeRectangles[i].width >= height && freeRectangles[i].height >= width) 286 | { 287 | int leftoverHoriz = abs(freeRectangles[i].width - height); 288 | int leftoverVert = abs(freeRectangles[i].height - width); 289 | int shortSideFit = min(leftoverHoriz, leftoverVert); 290 | int longSideFit = max(leftoverHoriz, leftoverVert); 291 | 292 | if (longSideFit < bestLongSideFit || (longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit)) 293 | { 294 | bestNode.x = freeRectangles[i].x; 295 | bestNode.y = freeRectangles[i].y; 296 | bestNode.width = height; 297 | bestNode.height = width; 298 | bestShortSideFit = shortSideFit; 299 | bestLongSideFit = longSideFit; 300 | } 301 | } 302 | } 303 | return bestNode; 304 | } 305 | 306 | Rect MaxRectsBinPack::FindPositionForNewNodeBestAreaFit(int width, int height, 307 | int &bestAreaFit, int &bestShortSideFit) const 308 | { 309 | Rect bestNode = {}; 310 | 311 | bestAreaFit = std::numeric_limits::max(); 312 | bestShortSideFit = std::numeric_limits::max(); 313 | 314 | for(size_t i = 0; i < freeRectangles.size(); ++i) 315 | { 316 | int areaFit = freeRectangles[i].width * freeRectangles[i].height - width * height; 317 | 318 | // Try to place the rectangle in upright (non-flipped) orientation. 319 | if (freeRectangles[i].width >= width && freeRectangles[i].height >= height) 320 | { 321 | int leftoverHoriz = abs(freeRectangles[i].width - width); 322 | int leftoverVert = abs(freeRectangles[i].height - height); 323 | int shortSideFit = min(leftoverHoriz, leftoverVert); 324 | 325 | if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit)) 326 | { 327 | bestNode.x = freeRectangles[i].x; 328 | bestNode.y = freeRectangles[i].y; 329 | bestNode.width = width; 330 | bestNode.height = height; 331 | bestShortSideFit = shortSideFit; 332 | bestAreaFit = areaFit; 333 | } 334 | } 335 | 336 | if (binAllowFlip && freeRectangles[i].width >= height && freeRectangles[i].height >= width) 337 | { 338 | int leftoverHoriz = abs(freeRectangles[i].width - height); 339 | int leftoverVert = abs(freeRectangles[i].height - width); 340 | int shortSideFit = min(leftoverHoriz, leftoverVert); 341 | 342 | if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit)) 343 | { 344 | bestNode.x = freeRectangles[i].x; 345 | bestNode.y = freeRectangles[i].y; 346 | bestNode.width = height; 347 | bestNode.height = width; 348 | bestShortSideFit = shortSideFit; 349 | bestAreaFit = areaFit; 350 | } 351 | } 352 | } 353 | return bestNode; 354 | } 355 | 356 | /// Returns 0 if the two intervals i1 and i2 are disjoint, or the length of their overlap otherwise. 357 | int CommonIntervalLength(int i1start, int i1end, int i2start, int i2end) 358 | { 359 | if (i1end < i2start || i2end < i1start) 360 | return 0; 361 | return min(i1end, i2end) - max(i1start, i2start); 362 | } 363 | 364 | int MaxRectsBinPack::ContactPointScoreNode(int x, int y, int width, int height) const 365 | { 366 | int score = 0; 367 | 368 | if (x == 0 || x + width == binWidth) 369 | score += height; 370 | if (y == 0 || y + height == binHeight) 371 | score += width; 372 | 373 | for(size_t i = 0; i < usedRectangles.size(); ++i) 374 | { 375 | if (usedRectangles[i].x == x + width || usedRectangles[i].x + usedRectangles[i].width == x) 376 | score += CommonIntervalLength(usedRectangles[i].y, usedRectangles[i].y + usedRectangles[i].height, y, y + height); 377 | if (usedRectangles[i].y == y + height || usedRectangles[i].y + usedRectangles[i].height == y) 378 | score += CommonIntervalLength(usedRectangles[i].x, usedRectangles[i].x + usedRectangles[i].width, x, x + width); 379 | } 380 | return score; 381 | } 382 | 383 | Rect MaxRectsBinPack::FindPositionForNewNodeContactPoint(int width, int height, int &bestContactScore) const 384 | { 385 | Rect bestNode = {}; 386 | 387 | bestContactScore = -1; 388 | 389 | for(size_t i = 0; i < freeRectangles.size(); ++i) 390 | { 391 | // Try to place the rectangle in upright (non-flipped) orientation. 392 | if (freeRectangles[i].width >= width && freeRectangles[i].height >= height) 393 | { 394 | int score = ContactPointScoreNode(freeRectangles[i].x, freeRectangles[i].y, width, height); 395 | if (score > bestContactScore) 396 | { 397 | bestNode.x = freeRectangles[i].x; 398 | bestNode.y = freeRectangles[i].y; 399 | bestNode.width = width; 400 | bestNode.height = height; 401 | bestContactScore = score; 402 | } 403 | } 404 | if (binAllowFlip && freeRectangles[i].width >= height && freeRectangles[i].height >= width) 405 | { 406 | int score = ContactPointScoreNode(freeRectangles[i].x, freeRectangles[i].y, height, width); 407 | if (score > bestContactScore) 408 | { 409 | bestNode.x = freeRectangles[i].x; 410 | bestNode.y = freeRectangles[i].y; 411 | bestNode.width = height; 412 | bestNode.height = width; 413 | bestContactScore = score; 414 | } 415 | } 416 | } 417 | return bestNode; 418 | } 419 | 420 | bool MaxRectsBinPack::SplitFreeNode(const Rect &freeNode, const Rect &usedNode) 421 | { 422 | // Test with SAT if the rectangles even intersect. 423 | if (usedNode.x >= freeNode.x + freeNode.width || usedNode.x + usedNode.width <= freeNode.x || 424 | usedNode.y >= freeNode.y + freeNode.height || usedNode.y + usedNode.height <= freeNode.y) 425 | return false; 426 | 427 | // We add up to four new free rectangles to the free rectangles list below. None of these 428 | // four newly added free rectangles can overlap any other three, so keep a mark of them 429 | // to avoid testing them against each other. 430 | newFreeRectanglesLastSize = newFreeRectangles.size(); 431 | 432 | if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x) 433 | { 434 | // New node at the top side of the used node. 435 | if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height) 436 | { 437 | Rect newNode = freeNode; 438 | newNode.height = usedNode.y - newNode.y; 439 | InsertNewFreeRectangle(newNode); 440 | } 441 | 442 | // New node at the bottom side of the used node. 443 | if (usedNode.y + usedNode.height < freeNode.y + freeNode.height) 444 | { 445 | Rect newNode = freeNode; 446 | newNode.y = usedNode.y + usedNode.height; 447 | newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height); 448 | InsertNewFreeRectangle(newNode); 449 | } 450 | } 451 | 452 | if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y) 453 | { 454 | // New node at the left side of the used node. 455 | if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width) 456 | { 457 | Rect newNode = freeNode; 458 | newNode.width = usedNode.x - newNode.x; 459 | InsertNewFreeRectangle(newNode); 460 | } 461 | 462 | // New node at the right side of the used node. 463 | if (usedNode.x + usedNode.width < freeNode.x + freeNode.width) 464 | { 465 | Rect newNode = freeNode; 466 | newNode.x = usedNode.x + usedNode.width; 467 | newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width); 468 | InsertNewFreeRectangle(newNode); 469 | } 470 | } 471 | 472 | return true; 473 | } 474 | 475 | void MaxRectsBinPack::InsertNewFreeRectangle(const Rect &newFreeRect) 476 | { 477 | assert(newFreeRect.width > 0); 478 | assert(newFreeRect.height > 0); 479 | 480 | for(size_t i = 0; i < newFreeRectanglesLastSize;) 481 | { 482 | // This new free rectangle is already accounted for? 483 | if (IsContainedIn(newFreeRect, newFreeRectangles[i])) 484 | return; 485 | 486 | // Does this new free rectangle obsolete a previous new free rectangle? 487 | if (IsContainedIn(newFreeRectangles[i], newFreeRect)) 488 | { 489 | // Remove i'th new free rectangle, but do so by retaining the order 490 | // of the older vs newest free rectangles that we may still be placing 491 | // in calling function SplitFreeNode(). 492 | newFreeRectangles[i] = newFreeRectangles[--newFreeRectanglesLastSize]; 493 | newFreeRectangles[newFreeRectanglesLastSize] = newFreeRectangles.back(); 494 | newFreeRectangles.pop_back(); 495 | } 496 | else 497 | ++i; 498 | } 499 | newFreeRectangles.push_back(newFreeRect); 500 | } 501 | 502 | void MaxRectsBinPack::PruneFreeList() 503 | { 504 | // Test all newly introduced free rectangles against old free rectangles. 505 | for(size_t i = 0; i < freeRectangles.size(); ++i) 506 | for(size_t j = 0; j < newFreeRectangles.size();) 507 | { 508 | if (IsContainedIn(newFreeRectangles[j], freeRectangles[i])) 509 | { 510 | newFreeRectangles[j] = newFreeRectangles.back(); 511 | newFreeRectangles.pop_back(); 512 | } 513 | else 514 | { 515 | // The old free rectangles can never be contained in any of the 516 | // new free rectangles (the new free rectangles keep shrinking 517 | // in size) 518 | assert(!IsContainedIn(freeRectangles[i], newFreeRectangles[j])); 519 | 520 | ++j; 521 | } 522 | } 523 | 524 | // Merge new and old free rectangles to the group of old free rectangles. 525 | freeRectangles.insert(freeRectangles.end(), newFreeRectangles.begin(), newFreeRectangles.end()); 526 | newFreeRectangles.clear(); 527 | 528 | #ifdef _DEBUG 529 | for(size_t i = 0; i < freeRectangles.size(); ++i) 530 | for(size_t j = i+1; j < freeRectangles.size(); ++j) 531 | { 532 | assert(!IsContainedIn(freeRectangles[i], freeRectangles[j])); 533 | assert(!IsContainedIn(freeRectangles[j], freeRectangles[i])); 534 | } 535 | #endif 536 | } 537 | 538 | } 539 | --------------------------------------------------------------------------------