From 55623c2ae9415c601ac365e005570eb4f69f068f Mon Sep 17 00:00:00 2001 From: augustin64 Date: Wed, 2 Apr 2025 17:35:25 +0200 Subject: [PATCH 1/7] mask: Attempting massive templating --- src/seam-carving.cpp | 186 ++++++++++++++++++++++++++++++------------- 1 file changed, 130 insertions(+), 56 deletions(-) diff --git a/src/seam-carving.cpp b/src/seam-carving.cpp index 2a6a250..a8d7586 100644 --- a/src/seam-carving.cpp +++ b/src/seam-carving.cpp @@ -27,6 +27,13 @@ bool nearly_equal(float a, float b) { std::nextafter(a, std::numeric_limits::max()) >= b; } +std::pair operator+(std::pair& p1, std::pair& p2) { + return { + (p1.first==1) || (p2.first==1) ? 1 : std::max(p1.first, p2.first), + p1.second+p2.second + }; +} + void export_image(const char *filename, const void *data, int width, int height, int nbChannels) { if (!silent) @@ -40,7 +47,7 @@ void export_image(const char *filename, const void *data, int width, int height, } #define compute_energy_for_pixel(source, width, height, i, j, nbChannels, \ - nbColorChannels, energy) \ + nbColorChannels, dest) \ auto indexPixel = (nbChannels) * (width * (j) + (i)); \ auto indexPixel_up = \ ((j) - 1 > 0) ? (nbChannels) * (width * ((j) - 1) + (i)) : indexPixel; \ @@ -52,9 +59,9 @@ void export_image(const char *filename, const void *data, int width, int height, auto indexPixel_right = ((i) + 1 < width) \ ? (nbChannels) * (width * (j) + ((i) + 1)) \ : indexPixel; \ - energy[width * j + i] = 0; \ + dest = 0; \ for (auto ch = 0; ch < (nbColorChannels); ch++) { \ - energy[(width) * (j) + (i)] += \ + dest += \ (fabs((float)source[indexPixel_up + ch] - source[indexPixel + ch]) + \ fabs((float)source[indexPixel_down + ch] - source[indexPixel + ch]) + \ fabs((float)source[indexPixel_left + ch] - source[indexPixel + ch]) + \ @@ -74,19 +81,21 @@ std::vector energy_e1(std::vector source, int width, float max_energy = 0; for (auto i = 0; i < width; i++) { for (auto j = 0; j < height; j++) { - compute_energy_for_pixel(source, width, height, i, j, nbChannels, - nbColorChannels, energy); + compute_energy_for_pixel( + source, width, height, + i, j, nbChannels, nbColorChannels, energy[width*j+i] + ); } } return energy; } -std::vector optimal_seam(std::vector energy, int width, int height, +/** Given the energy value, returns the optimal seam */ +template std::vector optimal_seam(std::vector energy, int width, int height, bool vertical) { - /** Given the energy value, returns the optimal seam */ // dyn_energy is indexed by [dim_large*(i : dim_long) + (j : dim_large)] - std::vector dyn_energy(width * height); + std::vector dyn_energy(width * height); int dim_large = vertical ? width : height; int dim_long = vertical ? height : width; // Number of elements in the seam @@ -98,23 +107,24 @@ std::vector optimal_seam(std::vector energy, int width, int height, for (auto i = 1; i < dim_long; i++) { // Propagate dyn_energy for (auto j = 0; j < dim_large; j++) { - dyn_energy[dim_large * i + j] = __FLT_MAX__; + dyn_energy[dim_large * i + j] = std::numeric_limits::max(); int lower_bound = std::max(j - max_step, 0); int upper_bound = std::min(j + max_step, dim_large - 1); for (auto k = lower_bound; k <= upper_bound; k++) { // Compute energy based on predecessors dyn_energy[dim_large * i + j] = std::min( - dyn_energy[dim_large * i + j], dyn_energy[dim_large * (i - 1) + k]); + dyn_energy[dim_large * i + j], dyn_energy[dim_large * (i - 1) + k] + ); } - dyn_energy[dim_large * i + j] += energy[im_index(i, j)]; + dyn_energy[dim_large * i + j] = dyn_energy[dim_large * i + j]+energy[im_index(i, j)]; } } std::vector result(dim_long); // Find the seam end int min_idx = -1; - float min_val = __FLT_MAX__; + T min_val = std::numeric_limits::max(); for (auto j = 0; j < dim_large; j++) { if (min_val > dyn_energy[dim_large * (dim_long - 1) + j]) { min_idx = j; @@ -166,8 +176,8 @@ std::vector optimal_seam(std::vector energy, int width, int height, return result; } -/** Carves an image by one seam. Returns the optimal seam used */ +/** Carves an image by one seam. Returns the optimal seam used */ template void remove_seam(const std::vector source, std::vector &output, int width, int height, int nbChannels, bool vertical, @@ -191,14 +201,68 @@ void remove_seam(const std::vector source, std::vector &output, int width, } } +// It would be preferable to use templates only for the value assignation but this is in fact far less efficient +void recompute_energy_along_seam( + std::vector carved_img, std::vector &output_energy, std::vector opt_seam, + int width, int height, int nbChannels, int nbColorChannels, bool vertical + ) { + int dim_large = vertical ? width : height; + int dim_long = vertical ? height : width; + + int newWidth = vertical ? width-1 : width; + int newHeight = vertical ? height : height-1; + + for (auto j = 0; j < dim_long; j++) { + for (auto i = -1; i < 2; i++) { + int x = vertical ? (opt_seam[j] + i) : j; + int y = vertical ? j : (opt_seam[j] + i); + if ((0 < (opt_seam[j] + i)) && ((opt_seam[j] + i) < dim_large - 1)) { + compute_energy_for_pixel( + carved_img, newWidth, newHeight, + x, y, nbChannels, + nbColorChannels, output_energy[width*j+i] + ); + } + } + } +} + +void recompute_energy_along_seam( + std::vector carved_img, std::vector> &output_energy, std::vector opt_seam, + int width, int height, int nbChannels, int nbColorChannels, bool vertical + ) { + int dim_large = vertical ? width : height; + int dim_long = vertical ? height : width; + + int newWidth = vertical ? width-1 : width; + int newHeight = vertical ? height : height-1; + + for (auto j = 0; j < dim_long; j++) { + for (auto i = -1; i < 2; i++) { + int x = vertical ? (opt_seam[j] + i) : j; + int y = vertical ? j : (opt_seam[j] + i); + if ((0 < (opt_seam[j] + i)) && ((opt_seam[j] + i) < dim_large - 1)) { + compute_energy_for_pixel( + carved_img, newWidth, newHeight, + x, y, nbChannels, + nbColorChannels, output_energy[width*j+i].first + ); + } + } + } +} + + +/** Carves an image and its energy by one seam, and recomputes the energy. + Returns the optimal seam used */ +//template std::vector carving_step(const std::vector source_img, - std::vector source_energy, + std::vector> source_energy, std::vector &output_img, - std::vector &output_energy, int width, + std::vector> &output_energy, int width, int height, int nbChannels, int nbColorChannels, bool vertical) { - /** Carves an image and its energy by one seam, and recomputes the energy. - Returns the optimal seam used */ + std::vector opt_seam = optimal_seam(source_energy, width, height, vertical); remove_seam(source_img, output_img, width, height, nbChannels, vertical, @@ -207,40 +271,27 @@ std::vector carving_step(const std::vector source_img, remove_seam(source_energy, output_energy, width, height, 1, vertical, opt_seam); - // Recompute the energy along the seam - if (energy_recompute_all) { - std::vector energy = - energy_e1(output_img, vertical ? width - 1 : width, - vertical ? height : height - 1, nbChannels); - std::copy(energy.begin(), energy.end(), output_energy.begin()); - } else { + // Recompute the energy along the seam, we need a separate function for templating + recompute_energy_along_seam( + source_img, output_energy, opt_seam, + width, height, nbChannels, nbColorChannels, + vertical + ); - // ASSUME WE ARE DOING A VERTICAL SEAM - if (vertical) { - for (auto j = 0; j < height; j++) { - for (auto i = -1; i < 2; i++) { - if ((0 < (opt_seam[j] + i)) && ((opt_seam[j] + i) < width - 1)) { - compute_energy_for_pixel(output_img, (width - 1), height, - (opt_seam[j] + i), j, nbChannels, - nbColorChannels, output_energy); - } - } - } - } else { - for (auto i = 0; i < width; i++) { - for (auto j = -1; j < 2; j++) { - if ((0 < (opt_seam[i] + j)) && ((opt_seam[i] + j) < height - 1)) { - compute_energy_for_pixel(output_img, width, height - 1, i, - (opt_seam[i] + j), nbChannels, - nbColorChannels, output_energy); - } - } - } - } - } return opt_seam; } +std::vector> mask_energy(std::vector energy, + int width, int height, unsigned char* mask) { + std::vector> output(width*height); + + for (auto i=0; i < width*height; i++) { + output[i] = {mask[i], energy[i]}; + } + + return output; +} + void seam_carving(unsigned char *source, int width, int height, int nbChannels, const char *out_filename, int nbSeams, bool vertical, unsigned char* mask=nullptr) { int nbColorChannels = nbChannels > 3 ? 3 : nbChannels; @@ -255,6 +306,9 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels, // Contains at each step the carved image std::vector source_energy(width * height); // Contains at each step the carved energy + std::vector> masked_energy; + std::vector> output_masked_energy(width*height); + // Source energy with (-1, 0, 1) on first element according to mask value std::vector output_img(width * height * nbChannels); // Receives at each step the newly carved image std::vector output_energy(width * height); @@ -271,6 +325,8 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels, } source_energy = energy_e1(source_img, width, height, nbChannels); + if (mask) + masked_energy = mask_energy(source_energy, width, height, mask); if (test_energy) { ini_energy = energy_e1(source_img, width, height, nbChannels); @@ -303,14 +359,27 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels, } SimpleProgressBar::ProgressBar bar(nbSeams); + bar.print(); for (auto seam_index = 0; seam_index < nbSeams; seam_index++) { - std::vector opt_seam = carving_step( - source_img, source_energy, output_img, output_energy, curWidth, - curHeight, nbChannels, nbColorChannels, vertical); + std::vector opt_seam; + if (mask) { + opt_seam = carving_step( + source_img, masked_energy, output_img, output_masked_energy, + curWidth, curHeight, nbChannels, nbColorChannels, vertical + ); + //} else { + // opt_seam = carving_step( + // source_img, source_energy, output_img, output_energy, + // curWidth, curHeight, nbChannels, nbColorChannels, vertical + // ); + } std::copy(output_img.begin(), output_img.end(), source_img.begin()); std::copy(output_energy.begin(), output_energy.end(), source_energy.begin()); + if (mask) + std::copy(output_masked_energy.begin(), output_masked_energy.end(), + masked_energy.begin()); vertical ? curWidth-- : curHeight--; // We just reduced the dimension @@ -335,6 +404,7 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels, bar.increment(); bar.print(); } + std::cout << std::endl; // Add newline after ProgressBar if (test_energy) { export_image(out_filename, test_energy_output.data(), width, height, @@ -352,7 +422,7 @@ int main(int argc, char **argv) { std::string outputImage = "output.png"; int nbSeams = 1; bool vertical = false; - + app.add_option("-s,--source", sourceImage, "Source image") ->required() ->check(CLI::ExistingFile); @@ -384,7 +454,7 @@ int main(int argc, char **argv) { int maskWidth, maskHeight, maskChannels; mask = stbi_load(maskImage.c_str(), &maskWidth, &maskHeight, &maskChannels, 0); - + if (maskWidth != width || maskHeight != height) { std::cerr << maskImage << " and " << sourceImage << " differ in dimension. Please provide a valid mask." @@ -400,11 +470,15 @@ int main(int argc, char **argv) { r = mask[maskChannels*i]; g = mask[maskChannels*i]; b = mask[maskChannels*i]; - mask[2*i] = (r == 0) && (g == 255) && (b == 0); - mask[2*i+1] = (r == 255) && (g == 0) && (b == 0); + bool positive = (g > r && g > b); + bool negative = (r > g && r > b); + mask[i] = positive ? 1 : (negative ? -1 : 0); } - //* From now on, mask has the same dimensions as source and exactly 2 channels - //* The first channel is positive, the second one negative. + //* From now on, mask has the same dimensions as source and one single channel + //* The values are: + //* . ( 1) we want to keep the pixel + //* . (-1) we want to remove the pixel + //* . ( 0) nothing in particular } nbSeams = std::min(nbSeams, vertical ? width-1 : height-1); // We want to keep at least one row or column From affb89c76e42a3220d46cab875afbfa36dea72ab Mon Sep 17 00:00:00 2001 From: augustin64 Date: Wed, 2 Apr 2025 17:41:58 +0200 Subject: [PATCH 2/7] Add templates back. Remove --energy-recompute-all --- src/seam-carving.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/seam-carving.cpp b/src/seam-carving.cpp index a8d7586..aadebb1 100644 --- a/src/seam-carving.cpp +++ b/src/seam-carving.cpp @@ -15,7 +15,6 @@ // Global flags bool silent = false; bool test_energy = false; -bool energy_recompute_all = false; int max_step = 1; // Get index for any table indexed by [width*(i : height) + (j : width)], but a @@ -34,6 +33,10 @@ std::pair operator+(std::pair& p1, std::pair }; } +void operator+=(std::pair& p1, std::pair& p2) { + p1 = p1+p2; +} + void export_image(const char *filename, const void *data, int width, int height, int nbChannels) { if (!silent) @@ -117,7 +120,7 @@ template std::vector optimal_seam(std::vector energy, int w dyn_energy[dim_large * i + j], dyn_energy[dim_large * (i - 1) + k] ); } - dyn_energy[dim_large * i + j] = dyn_energy[dim_large * i + j]+energy[im_index(i, j)]; + dyn_energy[dim_large * i + j] += energy[im_index(i, j)]; } } @@ -255,11 +258,11 @@ void recompute_energy_along_seam( /** Carves an image and its energy by one seam, and recomputes the energy. Returns the optimal seam used */ -//template +template std::vector carving_step(const std::vector source_img, - std::vector> source_energy, + std::vector source_energy, std::vector &output_img, - std::vector> &output_energy, int width, + std::vector &output_energy, int width, int height, int nbChannels, int nbColorChannels, bool vertical) { @@ -368,11 +371,11 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels, source_img, masked_energy, output_img, output_masked_energy, curWidth, curHeight, nbChannels, nbColorChannels, vertical ); - //} else { - // opt_seam = carving_step( - // source_img, source_energy, output_img, output_energy, - // curWidth, curHeight, nbChannels, nbColorChannels, vertical - // ); + } else { + opt_seam = carving_step( + source_img, source_energy, output_img, output_energy, + curWidth, curHeight, nbChannels, nbColorChannels, vertical + ); } std::copy(output_img.begin(), output_img.end(), source_img.begin()); std::copy(output_energy.begin(), output_energy.end(), @@ -440,8 +443,6 @@ int main(int argc, char **argv) { app.add_flag("--silent", silent, "No verbose messages"); app.add_flag("--test-energy", test_energy, "Don't resize image, just try the specified energy function"); - app.add_flag("--energy-recompute-all", energy_recompute_all, - "recompute the whole energy at each step"); CLI11_PARSE(app, argc, argv); // Image loading From 5a9eb05b6a5cac017b41d1b7f18bb8e8dc9c2778 Mon Sep 17 00:00:00 2001 From: augustin64 Date: Wed, 2 Apr 2025 18:26:43 +0200 Subject: [PATCH 3/7] Use valid limits --- src/seam-carving.cpp | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/seam-carving.cpp b/src/seam-carving.cpp index aadebb1..e138076 100644 --- a/src/seam-carving.cpp +++ b/src/seam-carving.cpp @@ -37,6 +37,18 @@ void operator+=(std::pair& p1, std::pair& p2) { p1 = p1+p2; } +namespace limits { + struct max_energy { + template operator T() { + return std::numeric_limits::max(); + } + operator std::pair() { + return {2, std::numeric_limits::max()}; + } + }; +} + + void export_image(const char *filename, const void *data, int width, int height, int nbChannels) { if (!silent) @@ -110,7 +122,7 @@ template std::vector optimal_seam(std::vector energy, int w for (auto i = 1; i < dim_long; i++) { // Propagate dyn_energy for (auto j = 0; j < dim_large; j++) { - dyn_energy[dim_large * i + j] = std::numeric_limits::max(); + dyn_energy[dim_large * i + j] = limits::max_energy(); int lower_bound = std::max(j - max_step, 0); int upper_bound = std::min(j + max_step, dim_large - 1); @@ -127,7 +139,8 @@ template std::vector optimal_seam(std::vector energy, int w std::vector result(dim_long); // Find the seam end int min_idx = -1; - T min_val = std::numeric_limits::max(); + T min_val = limits::max_energy(); + for (auto j = 0; j < dim_large; j++) { if (min_val > dyn_energy[dim_large * (dim_long - 1) + j]) { min_idx = j; @@ -138,17 +151,17 @@ template std::vector optimal_seam(std::vector energy, int w //* Backtracking to find the path for (auto i = dim_long - 1; i > 0; i--) { -// We want to find either (bot_l, bot_c, bot_r) with dyn_energy[.] = min_val - -// energy[cur] + // We want to find either (bot_l, bot_c, bot_r) with dyn_energy[.] = min_val - + // energy[cur] -// Idea : float next_energy = min_val - energy[width*i + min_idx]; -//! With floats, we don't always have x + y - y == x, so we check is x+y == x+y -// This define is a bit ugly but 200x faster than using a lambda function -#define is_next_idx(idx) \ - (dyn_energy[(i - 1) * dim_large + idx] + energy[im_index(i, min_idx)] == \ - min_val) + // Idea : float next_energy = min_val - energy[width*i + min_idx]; + //! With floats, we don't always have x + y - y == x, so we check is x+y == x+y + // This define is a bit ugly but 200x faster than using a lambda function + #define is_next_idx(idx) \ + (dyn_energy[(i - 1) * dim_large + idx] + energy[im_index(i, min_idx)] == \ + min_val) - // This is not the nicest way to do thiss but we want to check in priority + // This is not the nicest way to do this but we want to check in priority // at the center to have straight seams bool found = false; if (is_next_idx(min_idx)) { From a7d2c74a8998252d79c76e0cbc61986b9ea91165 Mon Sep 17 00:00:00 2001 From: augustin64 Date: Thu, 3 Apr 2025 08:29:35 +0200 Subject: [PATCH 4/7] mask: use valid (>0) unsigned char --- src/seam-carving.cpp | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/seam-carving.cpp b/src/seam-carving.cpp index e138076..d840206 100644 --- a/src/seam-carving.cpp +++ b/src/seam-carving.cpp @@ -28,7 +28,9 @@ bool nearly_equal(float a, float b) { std::pair operator+(std::pair& p1, std::pair& p2) { return { - (p1.first==1) || (p2.first==1) ? 1 : std::max(p1.first, p2.first), + // If one of the two pixels is "protected" (ie 2), we want to prevent this line removing + // Else, we want to keep the information of "is there a pixel to remove in this seam" (ie 0) + (p1.first==2) || (p2.first==2) ? 2 : std::min(p1.first, p2.first), p1.second+p2.second }; } @@ -43,7 +45,7 @@ namespace limits { return std::numeric_limits::max(); } operator std::pair() { - return {2, std::numeric_limits::max()}; + return {3, std::numeric_limits::max()}; } }; } @@ -236,7 +238,7 @@ void recompute_energy_along_seam( compute_energy_for_pixel( carved_img, newWidth, newHeight, x, y, nbChannels, - nbColorChannels, output_energy[width*j+i] + nbColorChannels, output_energy[width*y+x] ); } } @@ -261,7 +263,7 @@ void recompute_energy_along_seam( compute_energy_for_pixel( carved_img, newWidth, newHeight, x, y, nbChannels, - nbColorChannels, output_energy[width*j+i].first + nbColorChannels, output_energy[width*y+x].first ); } } @@ -371,6 +373,14 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels, } if (nbChannels == 4) test_energy_output[nbChannels * k + 3] = source_img[nbChannels * k + 3]; + + if (mask) { + if (mask[k] == 2) // Green + test_energy_output[nbChannels*k + 1] = 125; + else if (mask[k] == 0) {// Red + test_energy_output[nbChannels * k] = 125; + } + } } } @@ -482,17 +492,18 @@ int main(int argc, char **argv) { unsigned char r, g, b; for (auto i=0; i < width*height; i++) { r = mask[maskChannels*i]; - g = mask[maskChannels*i]; - b = mask[maskChannels*i]; - bool positive = (g > r && g > b); - bool negative = (r > g && r > b); - mask[i] = positive ? 1 : (negative ? -1 : 0); + g = mask[maskChannels*i+1]; + b = mask[maskChannels*i+2]; + bool positive = (g > r && g > b && g > 100); // Mask images are not always the cleanest + bool negative = (r > g && r > b && r > 100); + + mask[i] = positive ? 2 : (negative ? 0 : 1); } //* From now on, mask has the same dimensions as source and one single channel //* The values are: - //* . ( 1) we want to keep the pixel - //* . (-1) we want to remove the pixel - //* . ( 0) nothing in particular + //* . (2) we want to keep the pixel + //* . (1) nothing in particular + //* . (0) we want to remove the pixel } nbSeams = std::min(nbSeams, vertical ? width-1 : height-1); // We want to keep at least one row or column From 7b07f66bfba5d33581b9cb00a1e3cfd966001a85 Mon Sep 17 00:00:00 2001 From: augustin64 Date: Thu, 3 Apr 2025 08:48:38 +0200 Subject: [PATCH 5/7] Add --until-mask-removal Are all seams contributing to mask ? --- src/seam-carving.cpp | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/seam-carving.cpp b/src/seam-carving.cpp index d840206..6891f07 100644 --- a/src/seam-carving.cpp +++ b/src/seam-carving.cpp @@ -12,9 +12,12 @@ #include #include +#define DEFAULT_SEAMS 1 + // Global flags bool silent = false; bool test_energy = false; +bool until_mask_removal = false; int max_step = 1; // Get index for any table indexed by [width*(i : height) + (j : width)], but a @@ -310,6 +313,18 @@ std::vector> mask_energy(std::vector energy, return output; } +bool does_seam_remove_mask(unsigned char* mask, int width, int height, int nbChannels, + std::vector opt_seam, bool vertical) +{ + int dim_large = vertical ? width : height; + int dim_long = vertical ? height : width; + + for (int i=0; i < dim_long; i++) { + if (mask[im_index(i, opt_seam[i])] == 0) return true; + } + return false; +} + void seam_carving(unsigned char *source, int width, int height, int nbChannels, const char *out_filename, int nbSeams, bool vertical, unsigned char* mask=nullptr) { int nbColorChannels = nbChannels > 3 ? 3 : nbChannels; @@ -384,16 +399,23 @@ void seam_carving(unsigned char *source, int width, int height, int nbChannels, } } - SimpleProgressBar::ProgressBar bar(nbSeams); + SimpleProgressBar::ProgressBar bar(until_mask_removal ? dim_large : nbSeams); bar.print(); - for (auto seam_index = 0; seam_index < nbSeams; seam_index++) { + auto seam_index = 0; + while (seam_index++ < nbSeams || until_mask_removal) { std::vector opt_seam; if (mask) { opt_seam = carving_step( source_img, masked_energy, output_img, output_masked_energy, curWidth, curHeight, nbChannels, nbColorChannels, vertical ); + + if ( + until_mask_removal && + !does_seam_remove_mask(mask, width, height, nbChannels, opt_seam, vertical) + ) break; + } else { opt_seam = carving_step( source_img, source_energy, output_img, output_energy, @@ -446,7 +468,7 @@ int main(int argc, char **argv) { std::string maskImage; std::string sourceImage; std::string outputImage = "output.png"; - int nbSeams = 1; + int nbSeams = DEFAULT_SEAMS; bool vertical = false; app.add_option("-s,--source", sourceImage, "Source image") @@ -466,6 +488,8 @@ int main(int argc, char **argv) { app.add_flag("--silent", silent, "No verbose messages"); app.add_flag("--test-energy", test_energy, "Don't resize image, just try the specified energy function"); + app.add_flag("-u,--until-mask-removal", until_mask_removal, + "Carve the image until there are no more red pixels in the mask"); CLI11_PARSE(app, argc, argv); // Image loading @@ -504,6 +528,14 @@ int main(int argc, char **argv) { //* . (2) we want to keep the pixel //* . (1) nothing in particular //* . (0) we want to remove the pixel + } + if (until_mask_removal && maskImage.empty()) { + std::cerr << "Flag --until-mask-removal but no mask provided." << std::endl; + until_mask_removal = false; + } + if (until_mask_removal && nbSeams != DEFAULT_SEAMS) { + std::cerr << "Flag --nb-seams specified but --until-mask-removal provided." << std::endl; + nbSeams = DEFAULT_SEAMS; } nbSeams = std::min(nbSeams, vertical ? width-1 : height-1); // We want to keep at least one row or column From 095b14d3a95f652948ef8f91e583d04c9a5cf694 Mon Sep 17 00:00:00 2001 From: augustin64 Date: Thu, 3 Apr 2025 16:25:27 +0200 Subject: [PATCH 6/7] Create utils.cpp --- CMakeLists.txt | 2 ++ src/seam-carving.cpp | 36 ++++-------------------------------- src/utils.cpp | 21 +++++++++++++++++++++ src/utils.hpp | 19 +++++++++++++++++++ 4 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 src/utils.cpp create mode 100644 src/utils.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a21dbf..483fb68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ project (seam-carving) # Add an executable with the above sources add_executable(seam-carving src/seam-carving.cpp) +add_library(utils src/utils.cpp) # Set the directories that should be included in the build command for this target # when running g++ these will be included as -I/directory/path/ @@ -16,3 +17,4 @@ target_include_directories(seam-carving PRIVATE ${PROJECT_SOURCE_DIR}/deps ) +target_link_libraries(seam-carving utils) \ No newline at end of file diff --git a/src/seam-carving.cpp b/src/seam-carving.cpp index 6891f07..f28498e 100644 --- a/src/seam-carving.cpp +++ b/src/seam-carving.cpp @@ -11,6 +11,7 @@ #define STB_IMAGE_WRITE_IMPLEMENTATION #include #include +#include "utils.hpp" #define DEFAULT_SEAMS 1 @@ -24,35 +25,6 @@ int max_step = 1; // : dim_long, b : dim_large #define im_index(a, b) (vertical ? (width * a + b) : (width * b + a)) -bool nearly_equal(float a, float b) { - return std::nextafter(a, std::numeric_limits::lowest()) <= b && - std::nextafter(a, std::numeric_limits::max()) >= b; -} - -std::pair operator+(std::pair& p1, std::pair& p2) { - return { - // If one of the two pixels is "protected" (ie 2), we want to prevent this line removing - // Else, we want to keep the information of "is there a pixel to remove in this seam" (ie 0) - (p1.first==2) || (p2.first==2) ? 2 : std::min(p1.first, p2.first), - p1.second+p2.second - }; -} - -void operator+=(std::pair& p1, std::pair& p2) { - p1 = p1+p2; -} - -namespace limits { - struct max_energy { - template operator T() { - return std::numeric_limits::max(); - } - operator std::pair() { - return {3, std::numeric_limits::max()}; - } - }; -} - void export_image(const char *filename, const void *data, int width, int height, int nbChannels) { @@ -133,9 +105,9 @@ template std::vector optimal_seam(std::vector energy, int w for (auto k = lower_bound; k <= upper_bound; k++) { // Compute energy based on predecessors - dyn_energy[dim_large * i + j] = std::min( - dyn_energy[dim_large * i + j], dyn_energy[dim_large * (i - 1) + k] - ); + if (dyn_energy[dim_large*(i-1)+k] < dyn_energy[dim_large*i+j]) { + dyn_energy[dim_large*i+j] = dyn_energy[dim_large*(i-1)+k]; + } } dyn_energy[dim_large * i + j] += energy[im_index(i, j)]; } diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..3344a94 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,21 @@ +#include +#include +#include + +bool nearly_equal(float a, float b) { + return std::nextafter(a, std::numeric_limits::lowest()) <= b && + std::nextafter(a, std::numeric_limits::max()) >= b; +} + +std::pair operator+(std::pair& p1, std::pair& p2) { + return { + // If one of the two pixels is "protected" (ie 2), we want to prevent this line removing + // Else, we want to keep the information of "is there a pixel to remove in this seam" (ie 0) + (p1.first==2) || (p2.first==2) ? 2 : std::min(p1.first, p2.first), + p1.second+p2.second + }; +} + +void operator+=(std::pair& p1, std::pair& p2) { + p1 = p1+p2; +} \ No newline at end of file diff --git a/src/utils.hpp b/src/utils.hpp new file mode 100644 index 0000000..1d53aa4 --- /dev/null +++ b/src/utils.hpp @@ -0,0 +1,19 @@ +#include + + +bool nearly_equal(float a, float b); + +std::pair operator+(std::pair& p1, std::pair& p2); + +void operator+=(std::pair& p1, std::pair& p2); + +namespace limits { + struct max_energy { + template operator T() { + return std::numeric_limits::max(); + } + operator std::pair() { + return {3, std::numeric_limits::max()}; + } + }; +} \ No newline at end of file From a8cfc6a26bfa89a6e3541d9cf0e1cdbf410fc201 Mon Sep 17 00:00:00 2001 From: augustin64 Date: Thu, 3 Apr 2025 16:34:12 +0200 Subject: [PATCH 7/7] Move image lib to utils.cpp --- CMakeLists.txt | 7 ++++++- src/image.h | 19 +++++++++++++++++++ src/seam-carving.cpp | 6 ++---- src/utils.cpp | 5 +++++ 4 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 src/image.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 483fb68..674f775 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,4 +17,9 @@ target_include_directories(seam-carving PRIVATE ${PROJECT_SOURCE_DIR}/deps ) -target_link_libraries(seam-carving utils) \ No newline at end of file +target_link_libraries(seam-carving utils) + +target_include_directories(utils + PRIVATE + ${PROJECT_SOURCE_DIR}/deps +) \ No newline at end of file diff --git a/src/image.h b/src/image.h new file mode 100644 index 0000000..d123c54 --- /dev/null +++ b/src/image.h @@ -0,0 +1,19 @@ +#ifndef STBIDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIDEF static +#else +#ifdef __cplusplus +#define STBIDEF extern "C" +#else +#define STBIDEF extern +#endif +#endif +#endif + +typedef unsigned char stbi_uc; + +STBIDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp); + +STBIDEF void stbi_image_free (void *retval_from_stbi_load); diff --git a/src/seam-carving.cpp b/src/seam-carving.cpp index f28498e..c70701b 100644 --- a/src/seam-carving.cpp +++ b/src/seam-carving.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -6,12 +7,9 @@ #include // Image filtering and I/O -#define STB_IMAGE_IMPLEMENTATION -#include -#define STB_IMAGE_WRITE_IMPLEMENTATION #include -#include #include "utils.hpp" +#include "image.h" #define DEFAULT_SEAMS 1 diff --git a/src/utils.cpp b/src/utils.cpp index 3344a94..64ac8c8 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,3 +1,8 @@ +#define STB_IMAGE_IMPLEMENTATION +#include +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include + #include #include #include