From 5b32c517c1f3f90cb0b5eb6c3a8e1f6c6ace0cbc Mon Sep 17 00:00:00 2001 From: Fabien Freling Date: Thu, 17 Feb 2022 23:15:45 +0100 Subject: [PATCH] add mask --- README.md | 70 +++++++++++++++++++++++++++++++++++++++++------- justfile | 2 +- src/a2.cpp | 7 +++++ src/a2.h | 6 ++++- src/frame.cpp | 4 +++ src/frame.h | 4 +++ src/main_cli.cpp | 22 +++++++++++++-- src/main_gui.cpp | 18 ++++++++++--- src/mask.cpp | 34 +++++++++++++++++++++++ src/mask.h | 20 ++++++++++++++ src/png.cpp | 4 +++ src/png.h | 4 +++ 12 files changed, 179 insertions(+), 16 deletions(-) create mode 100644 src/mask.cpp create mode 100644 src/mask.h diff --git a/README.md b/README.md index 83a03e7..62069d0 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ The subject is available here: [Test Algo](./test_algo.pdf) - [ ] wrap stb_rect_pack? - [X] change bbox api to origin + size - [X] dummy A2 +- [X] package ## Installation @@ -19,13 +20,71 @@ The subject is available here: [Test Algo](./test_algo.pdf) ### Question 1 +> As a preprocessing step of a second algorithm A2, we would like to combine all +> the regions corresponding to the bounding boxes into a new image FRegions of +> dimension D × D, where D given by A2 and D < min(M, N ) + This is a packing problem, as described on [Wikipedia][1]. Since this is NP-hard, we know the "exact" solution might be unreachable but we could find a solution that is good enough for our needs. +I looked up some solutions online and found a great article by David Colson: +"[Exploring rectangle packing algorithms][2]". It gives a lot a references and +compares different algorithms. + +I decided at first to implement his naive "row packer" to quickly have an +implementation and try it out. + ### Question 2 + +> A2 then takes as input F Regions and outputs new bounding boxes B′. We would +> like now to compute the location of each of these new bounding boxes in F +> reference + +To map the new bounding boxes into the original frame F, we need to have a +mapping between the bounding boxes of F and the ones we packed into FRegions. +During the packing, I saved the bounding boxes position in FRegions, so the +mapping was straightforward: + +1. detect in which bounding box in FRegions the new bounding box is contained +2. apply the transformation from FRegions to F + +Because we look into every bounding box to find the enclosing one, we have a +complexity of N^2. Depending of the numbers of boxes, it could be problematic. +We can make it faster by using a spatially sorted structure like a quad-tree, +but it seems a bit overkill in this case. + +If we didn't have knowledge of the bounding boxes position in FRegions, we could +recompute them by looking at pixel similarity between F and FRegions but it +would be costly, and a bit wasteful since we already computed the bounding +boxes. + ### Question 3 +> We would like now to be able to provide to the algorithm A2 either the +> region-based image or the initial image without transformation, which +> modifications to your code architecture do you suggest in order to handle +> this? + +To support either a packed image (the region-based one) or a sparse image (the +initial image limited to the bounding boxes), the easiest solution is to add +support for a binary mask. In the same way A2 is suppose to ignore zeros valud +in the region-based frame, it could be modified to ignore areas in an image +where the mask is set to zero. + +## GUI + +In order to easily debug and better visualize the problem, I chose to implement +a minimal GUI using [raylib][3]. + +You can add, move, and resize boxes. Processing steps are triggered with +buttons. + +## CLI + +A commandline sample is also available, in case the raylib library cannot be +built, or if we need to benchmark performance. + ## PNG support I chose to support PNG files through the stb files: @@ -33,11 +92,6 @@ I chose to support PNG files through the stb files: I could have implemented basic image support with the PNM format but I think it is nicer to support common image formats with a simple library. -stb also implements a 2D rectangle packer: -[stb_rect_pack.h](https://github.com/nothings/stb/blob/master/stb_rect_pack.h) -I added this file in the project in order to compare my implementation to -another one. - ## 3rd party libraries I use 2 external libraries for better visualization: @@ -51,8 +105,6 @@ To avoid name collision, I created my own namespace `freling`. ## References -- [Exploring rectangle packing algorithms](https://www.david-colson.com/2020/03/10/exploring-rect-packing.html) -- [A Skyline-Based Heuristic for the 2D Rectangular Strip Packing Problem](https://www.researchgate.net/publication/221049934_A_Skyline-Based_Heuristic_for_the_2D_Rectangular_Strip_Packing_Problem) -- [New Improvements in Optimal Rectangle Packing](https://www.ijcai.org/Proceedings/09/Papers/092.pdf) - [1]: https://en.wikipedia.org/wiki/Packing_problems#Packing_of_rectangles +[2]: https://www.david-colson.com/2020/03/10/exploring-rect-packing.html +[3]: https://github.com/raysan5/raylib diff --git a/justfile b/justfile index 356bf44..be01ac2 100644 --- a/justfile +++ b/justfile @@ -43,5 +43,5 @@ massif: build-cli callgrind: build-cli valgrind --tool=callgrind {{exe_cli}} {{params}} -archive: generate-build +archive: #generate-build git archive --add-file={{build_sh}} --output={{name}}.zip --prefix={{name}}/ HEAD diff --git a/src/a2.cpp b/src/a2.cpp index 376e416..aca9ac3 100644 --- a/src/a2.cpp +++ b/src/a2.cpp @@ -1,7 +1,14 @@ #include "a2.h" +#include + namespace freling { +std::vector A2(const Frame& frame, const Mask& mask) { + assert((bool)"Not implemented\n" && false); + return {}; +} + std::vector A2(const std::vector& regions) { std::vector updated_boxes(regions.size()); for (int i = 0, size = regions.size(); i < size; ++i) { diff --git a/src/a2.h b/src/a2.h index 7bc6b9b..47b9545 100644 --- a/src/a2.h +++ b/src/a2.h @@ -3,10 +3,14 @@ #include #include "bounding_box.h" +#include "frame.h" +#include "mask.h" namespace freling { -// The normal signature for this function should be: +std::vector A2(const Frame& frame, const Mask& mask); + +// The normal signature for this function is the one above: // std::vector A2(const Frame& regions); // However, since we are mocking this algorithm we just need to fulfill this // constraint: "We assume for the sake of simplicity, that no bounding boxe diff --git a/src/frame.cpp b/src/frame.cpp index ec09e02..71b87ee 100644 --- a/src/frame.cpp +++ b/src/frame.cpp @@ -2,6 +2,8 @@ #include +namespace freling { + Frame::Frame(uint32_t width, uint32_t height) : width(width), height(height) { data = new Pixel[width * height]; } @@ -33,3 +35,5 @@ Frame::~Frame() { void Frame::fill(uint8_t p) { std::memset(data, p, width * height * sizeof(Pixel)); } + +} // namespace freling diff --git a/src/frame.h b/src/frame.h index d043b7d..623cdbb 100644 --- a/src/frame.h +++ b/src/frame.h @@ -2,6 +2,8 @@ #include +namespace freling { + struct Pixel { uint8_t r; uint8_t g; @@ -21,3 +23,5 @@ struct Frame { void fill(uint8_t p); }; + +} // namespace freling diff --git a/src/main_cli.cpp b/src/main_cli.cpp index 258ef12..d54b80c 100644 --- a/src/main_cli.cpp +++ b/src/main_cli.cpp @@ -2,7 +2,10 @@ #include #include +#include "a2.h" #include "bounding_box.h" +#include "mapping.h" +#include "mask.h" #include "pack.h" #include "png.h" @@ -25,7 +28,7 @@ int main(int argc, const char* argv[]) { return 1; } - const std::optional in_frame = load_png(input); + const std::optional in_frame = freling::load_png(input); if (!in_frame) { std::cerr << "Cannot load file " << input << "\n"; return 1; @@ -42,9 +45,24 @@ int main(int argc, const char* argv[]) { bboxes.push_back(freling::BoundingBox({x, y, w, h})); i += 4; } + freling::Mask in_mask(in_frame->width, in_frame->height); std::vector packed_bboxes; - std::optional regions = pack(*in_frame, bboxes, packed_bboxes); + std::optional regions = + pack(*in_frame, bboxes, packed_bboxes); + + in_mask.fill(false); + for (const auto& b : bboxes) { + for (int j = b.y; j < b.y + b.height; ++j) { + for (int i = b.x; i < b.x + b.width; ++i) { + in_mask.set(i, j, true); + } + } + }; + + std::vector a2_bboxes = freling::A2(packed_bboxes); + std::vector a2_bboxes_origin = + freling::map_to_origin(bboxes, packed_bboxes, a2_bboxes); return 0; } diff --git a/src/main_gui.cpp b/src/main_gui.cpp index a315184..57fb29c 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp @@ -17,6 +17,7 @@ #include "bounding_box.h" #include "frame.h" #include "mapping.h" +#include "mask.h" #include "pack.h" #include "png.h" @@ -52,7 +53,7 @@ void draw_region(const freling::BoundingBox& box, DrawRectangleRec(rect, ColorAlpha(color, 0.8)); } -Image image_from_frame(const Frame& frame) { +Image image_from_frame(const freling::Frame& frame) { Image image = {0}; const int format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; @@ -84,7 +85,7 @@ int main(int argc, const char* argv[]) { return 1; } - const std::optional in_frame = load_png(input); + const std::optional in_frame = freling::load_png(input); if (!in_frame) { std::cerr << "Cannot load file " << input << "\n"; return 1; @@ -106,6 +107,7 @@ int main(int argc, const char* argv[]) { std::vector packed_bboxes; std::vector a2_bboxes; std::vector a2_bboxes_origin; + freling::Mask in_mask(in_frame->width, in_frame->height); Vector2 win_size = {800, 450}; const int padding = 5; @@ -205,9 +207,19 @@ int main(int argc, const char* argv[]) { b_size.y}, "Pack Rects")) { a2_processed = false; - std::optional f_packed = + std::optional f_packed = freling::pack(*in_frame, bboxes, packed_bboxes); + // Update mask + in_mask.fill(false); + for (const auto& b : bboxes) { + for (int j = b.y; j < b.y + b.height; ++j) { + for (int i = b.x; i < b.x + b.width; ++i) { + in_mask.set(i, j, true); + } + } + }; + if (packed) { UnloadTexture(pack_texture); UnloadImage(pack_img); diff --git a/src/mask.cpp b/src/mask.cpp new file mode 100644 index 0000000..b586c5e --- /dev/null +++ b/src/mask.cpp @@ -0,0 +1,34 @@ +#include "mask.h" + +#include +#include + +namespace freling { + +Mask::Mask(uint32_t width, uint32_t height) : width(width), height(height) { + data.resize(width * height, true); +} + +void Mask::fill(bool value) { + for (int i = 0, size = data.size(); i < size; ++i) { + data[i] = value; + } +} + +void Mask::set(int x, int y, bool value) { + assert(x < width); + assert(y < height); + + const int i = x + y * width; + data[i] = value; +} + +bool Mask::get(int x, int y) { + assert(x < width); + assert(y < height); + + const int i = x + y * width; + return data[i]; +} + +} // namespace freling diff --git a/src/mask.h b/src/mask.h new file mode 100644 index 0000000..2ec3634 --- /dev/null +++ b/src/mask.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace freling { + +struct Mask { + uint32_t width; + uint32_t height; + std::vector data; // std::vector is specialized for bool + + Mask(uint32_t width, uint32_t height); + + void fill(bool b); + void set(int x, int y, bool b); + bool get(int x, int y); +}; + +} // namespace freling diff --git a/src/png.cpp b/src/png.cpp index e87e154..42f14de 100644 --- a/src/png.cpp +++ b/src/png.cpp @@ -5,6 +5,8 @@ #include "stb_image.h" +namespace freling { + std::optional load_png(const std::filesystem::path& in) { int x = 0; int y = 0; @@ -22,3 +24,5 @@ std::optional load_png(const std::filesystem::path& in) { stbi_image_free(data); return f; } + +} // namespace freling diff --git a/src/png.h b/src/png.h index 3521d89..2d313d6 100644 --- a/src/png.h +++ b/src/png.h @@ -5,4 +5,8 @@ #include "frame.h" +namespace freling { + std::optional load_png(const std::filesystem::path& in); + +}