This commit is contained in:
Fabien Freling 2022-02-17 23:15:45 +01:00
parent 4447cd67d1
commit 5b32c517c1
12 changed files with 179 additions and 16 deletions

View file

@ -12,6 +12,7 @@ The subject is available here: [Test Algo](./test_algo.pdf)
- [ ] wrap stb_rect_pack? - [ ] wrap stb_rect_pack?
- [X] change bbox api to origin + size - [X] change bbox api to origin + size
- [X] dummy A2 - [X] dummy A2
- [X] package
## Installation ## Installation
@ -19,13 +20,71 @@ The subject is available here: [Test Algo](./test_algo.pdf)
### Question 1 ### 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]. 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 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. 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 ### 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 ### 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 ## PNG support
I chose to support PNG files through the stb files: 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 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. 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 ## 3rd party libraries
I use 2 external libraries for better visualization: I use 2 external libraries for better visualization:
@ -51,8 +105,6 @@ To avoid name collision, I created my own namespace `freling`.
## References ## 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 [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

View file

@ -43,5 +43,5 @@ massif: build-cli
callgrind: build-cli callgrind: build-cli
valgrind --tool=callgrind {{exe_cli}} {{params}} valgrind --tool=callgrind {{exe_cli}} {{params}}
archive: generate-build archive: #generate-build
git archive --add-file={{build_sh}} --output={{name}}.zip --prefix={{name}}/ HEAD git archive --add-file={{build_sh}} --output={{name}}.zip --prefix={{name}}/ HEAD

View file

@ -1,7 +1,14 @@
#include "a2.h" #include "a2.h"
#include <cassert>
namespace freling { namespace freling {
std::vector<BoundingBox> A2(const Frame& frame, const Mask& mask) {
assert((bool)"Not implemented\n" && false);
return {};
}
std::vector<BoundingBox> A2(const std::vector<BoundingBox>& regions) { std::vector<BoundingBox> A2(const std::vector<BoundingBox>& regions) {
std::vector<BoundingBox> updated_boxes(regions.size()); std::vector<BoundingBox> updated_boxes(regions.size());
for (int i = 0, size = regions.size(); i < size; ++i) { for (int i = 0, size = regions.size(); i < size; ++i) {

View file

@ -3,10 +3,14 @@
#include <vector> #include <vector>
#include "bounding_box.h" #include "bounding_box.h"
#include "frame.h"
#include "mask.h"
namespace freling { namespace freling {
// The normal signature for this function should be: std::vector<BoundingBox> A2(const Frame& frame, const Mask& mask);
// The normal signature for this function is the one above:
// std::vector<BoundingBox> A2(const Frame& regions); // std::vector<BoundingBox> A2(const Frame& regions);
// However, since we are mocking this algorithm we just need to fulfill this // 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 // constraint: "We assume for the sake of simplicity, that no bounding boxe

View file

@ -2,6 +2,8 @@
#include <cstring> #include <cstring>
namespace freling {
Frame::Frame(uint32_t width, uint32_t height) : width(width), height(height) { Frame::Frame(uint32_t width, uint32_t height) : width(width), height(height) {
data = new Pixel[width * height]; data = new Pixel[width * height];
} }
@ -33,3 +35,5 @@ Frame::~Frame() {
void Frame::fill(uint8_t p) { void Frame::fill(uint8_t p) {
std::memset(data, p, width * height * sizeof(Pixel)); std::memset(data, p, width * height * sizeof(Pixel));
} }
} // namespace freling

View file

@ -2,6 +2,8 @@
#include <cstdint> #include <cstdint>
namespace freling {
struct Pixel { struct Pixel {
uint8_t r; uint8_t r;
uint8_t g; uint8_t g;
@ -21,3 +23,5 @@ struct Frame {
void fill(uint8_t p); void fill(uint8_t p);
}; };
} // namespace freling

View file

@ -2,7 +2,10 @@
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include "a2.h"
#include "bounding_box.h" #include "bounding_box.h"
#include "mapping.h"
#include "mask.h"
#include "pack.h" #include "pack.h"
#include "png.h" #include "png.h"
@ -25,7 +28,7 @@ int main(int argc, const char* argv[]) {
return 1; return 1;
} }
const std::optional<Frame> in_frame = load_png(input); const std::optional<freling::Frame> in_frame = freling::load_png(input);
if (!in_frame) { if (!in_frame) {
std::cerr << "Cannot load file " << input << "\n"; std::cerr << "Cannot load file " << input << "\n";
return 1; return 1;
@ -42,9 +45,24 @@ int main(int argc, const char* argv[]) {
bboxes.push_back(freling::BoundingBox({x, y, w, h})); bboxes.push_back(freling::BoundingBox({x, y, w, h}));
i += 4; i += 4;
} }
freling::Mask in_mask(in_frame->width, in_frame->height);
std::vector<freling::BoundingBox> packed_bboxes; std::vector<freling::BoundingBox> packed_bboxes;
std::optional<Frame> regions = pack(*in_frame, bboxes, packed_bboxes); std::optional<freling::Frame> 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<freling::BoundingBox> a2_bboxes = freling::A2(packed_bboxes);
std::vector<freling::BoundingBox> a2_bboxes_origin =
freling::map_to_origin(bboxes, packed_bboxes, a2_bboxes);
return 0; return 0;
} }

View file

@ -17,6 +17,7 @@
#include "bounding_box.h" #include "bounding_box.h"
#include "frame.h" #include "frame.h"
#include "mapping.h" #include "mapping.h"
#include "mask.h"
#include "pack.h" #include "pack.h"
#include "png.h" #include "png.h"
@ -52,7 +53,7 @@ void draw_region(const freling::BoundingBox& box,
DrawRectangleRec(rect, ColorAlpha(color, 0.8)); DrawRectangleRec(rect, ColorAlpha(color, 0.8));
} }
Image image_from_frame(const Frame& frame) { Image image_from_frame(const freling::Frame& frame) {
Image image = {0}; Image image = {0};
const int format = PIXELFORMAT_UNCOMPRESSED_R8G8B8; const int format = PIXELFORMAT_UNCOMPRESSED_R8G8B8;
@ -84,7 +85,7 @@ int main(int argc, const char* argv[]) {
return 1; return 1;
} }
const std::optional<Frame> in_frame = load_png(input); const std::optional<freling::Frame> in_frame = freling::load_png(input);
if (!in_frame) { if (!in_frame) {
std::cerr << "Cannot load file " << input << "\n"; std::cerr << "Cannot load file " << input << "\n";
return 1; return 1;
@ -106,6 +107,7 @@ int main(int argc, const char* argv[]) {
std::vector<freling::BoundingBox> packed_bboxes; std::vector<freling::BoundingBox> packed_bboxes;
std::vector<freling::BoundingBox> a2_bboxes; std::vector<freling::BoundingBox> a2_bboxes;
std::vector<freling::BoundingBox> a2_bboxes_origin; std::vector<freling::BoundingBox> a2_bboxes_origin;
freling::Mask in_mask(in_frame->width, in_frame->height);
Vector2 win_size = {800, 450}; Vector2 win_size = {800, 450};
const int padding = 5; const int padding = 5;
@ -205,9 +207,19 @@ int main(int argc, const char* argv[]) {
b_size.y}, b_size.y},
"Pack Rects")) { "Pack Rects")) {
a2_processed = false; a2_processed = false;
std::optional<Frame> f_packed = std::optional<freling::Frame> f_packed =
freling::pack(*in_frame, bboxes, packed_bboxes); 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) { if (packed) {
UnloadTexture(pack_texture); UnloadTexture(pack_texture);
UnloadImage(pack_img); UnloadImage(pack_img);

34
src/mask.cpp Normal file
View file

@ -0,0 +1,34 @@
#include "mask.h"
#include <cassert>
#include <cstring>
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

20
src/mask.h Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include <cstdint>
#include <vector>
namespace freling {
struct Mask {
uint32_t width;
uint32_t height;
std::vector<bool> 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

View file

@ -5,6 +5,8 @@
#include "stb_image.h" #include "stb_image.h"
namespace freling {
std::optional<Frame> load_png(const std::filesystem::path& in) { std::optional<Frame> load_png(const std::filesystem::path& in) {
int x = 0; int x = 0;
int y = 0; int y = 0;
@ -22,3 +24,5 @@ std::optional<Frame> load_png(const std::filesystem::path& in) {
stbi_image_free(data); stbi_image_free(data);
return f; return f;
} }
} // namespace freling

View file

@ -5,4 +5,8 @@
#include "frame.h" #include "frame.h"
namespace freling {
std::optional<Frame> load_png(const std::filesystem::path& in); std::optional<Frame> load_png(const std::filesystem::path& in);
}