diff --git a/src/seam-carving.cpp b/src/seam-carving.cpp index 6daf157..4fe1348 100644 --- a/src/seam-carving.cpp +++ b/src/seam-carving.cpp @@ -1,5 +1,5 @@ -#include #include +#include #include #include #include @@ -7,9 +7,9 @@ #include // Image filtering and I/O -#include -#include "utils.hpp" #include "image.h" +#include "utils.hpp" +#include #define DEFAULT_SEAMS 1 @@ -23,7 +23,6 @@ int max_step = 1; // : dim_long, b : dim_large #define im_index(a, b) (vertical ? (width * a + b) : (width * b + a)) - void export_image(const char *filename, const void *data, int width, int height, int nbChannels) { if (!silent) @@ -38,9 +37,10 @@ void export_image(const char *filename, const void *data, int width, int height, std::string function = "grad"; -#define compute_energy_for_pixel(source, width, height, i, j, nbChannels, \ - /* computes the energy at pixel i,j, i.e. energy[width*j+i]*/ \ - nbColorChannels, dest) \ +#define compute_energy_for_pixel( \ + source, width, height, i, j, \ + nbChannels, /* computes the energy at pixel i,j, i.e. energy[width*j+i]*/ \ + nbColorChannels, dest) \ auto indexPixel = (nbChannels) * (width * (j) + (i)); \ auto indexPixel_up = \ ((j) - 1 > 0) ? (nbChannels) * (width * ((j) - 1) + (i)) : indexPixel; \ @@ -52,53 +52,50 @@ std::string function = "grad"; auto indexPixel_right = ((i) + 1 < width) \ ? (nbChannels) * (width * (j) + ((i) + 1)) \ : indexPixel; \ - dest =0; \ + dest = 0; \ if (function == "gradnorm") { \ for (auto ch = 0; ch < (nbColorChannels); ch++) { \ - dest+= \ - (std::pow(fabs((float)source[indexPixel_up + ch] - \ - source[indexPixel + ch]), \ - 2) + \ + dest += (std::pow(fabs((float)source[indexPixel_up + ch] - \ + source[indexPixel + ch]), \ + 2) + \ \ - std::pow(fabs((float)source[indexPixel_down + ch] - \ - source[indexPixel + ch]), \ - 2) + \ + std::pow(fabs((float)source[indexPixel_down + ch] - \ + source[indexPixel + ch]), \ + 2) + \ \ - std::pow(fabs((float)source[indexPixel_left + ch] - \ - source[indexPixel + ch]), \ - 2) + \ - std::pow(fabs((float)source[indexPixel_right + ch] - \ - source[indexPixel + ch]), \ - 2)); \ + std::pow(fabs((float)source[indexPixel_left + ch] - \ + source[indexPixel + ch]), \ + 2) + \ + std::pow(fabs((float)source[indexPixel_right + ch] - \ + source[indexPixel + ch]), \ + 2)); \ } \ } else if (function == "grad") { \ for (auto ch = 0; ch < (nbColorChannels); ch++) { \ - dest += \ - ((fabs((float)source[indexPixel_up + ch] - \ - source[indexPixel + ch])) + \ + dest += ((fabs((float)source[indexPixel_up + ch] - \ + source[indexPixel + ch])) + \ \ - (fabs((float)source[indexPixel_down + ch] - \ - source[indexPixel + ch])) + \ + (fabs((float)source[indexPixel_down + ch] - \ + source[indexPixel + ch])) + \ \ - (fabs((float)source[indexPixel_left + ch] - \ - source[indexPixel + ch])) + \ - (fabs((float)source[indexPixel_right + ch] - \ - source[indexPixel + ch]))); \ + (fabs((float)source[indexPixel_left + ch] - \ + source[indexPixel + ch])) + \ + (fabs((float)source[indexPixel_right + ch] - \ + source[indexPixel + ch]))); \ } \ } else if (function == "gradnorminf") { \ for (auto ch = 0; ch < (nbColorChannels); ch++) { \ - dest+= \ - std::max(std::max((fabs((float)source[indexPixel_up + ch] - \ - source[indexPixel + ch])), \ + dest += std::max(std::max((fabs((float)source[indexPixel_up + ch] - \ + source[indexPixel + ch])), \ \ - (fabs((float)source[indexPixel_down + ch] - \ - source[indexPixel + ch]))), \ - std::max( \ + (fabs((float)source[indexPixel_down + ch] - \ + source[indexPixel + ch]))), \ + std::max( \ \ - (fabs((float)source[indexPixel_left + ch] - \ - source[indexPixel + ch])), \ - (fabs((float)source[indexPixel_right + ch] - \ - source[indexPixel + ch])))); \ + (fabs((float)source[indexPixel_left + ch] - \ + source[indexPixel + ch])), \ + (fabs((float)source[indexPixel_right + ch] - \ + source[indexPixel + ch])))); \ } \ } else { \ std::cerr << "function " << function << " not available" << std::endl; \ @@ -118,17 +115,16 @@ std::vector energy_e1(std::vector source, int width, 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[width*j+i] - ); + compute_energy_for_pixel(source, width, height, i, j, nbChannels, + nbColorChannels, energy[width * j + i]); } } return energy; } /** Given the energy value, returns the optimal seam */ -template std::vector optimal_seam(std::vector energy, int width, int height, +template +std::vector optimal_seam(std::vector energy, int width, int height, bool vertical) { // dyn_energy is indexed by [dim_large*(i : dim_long) + (j : dim_large)] @@ -150,9 +146,10 @@ template std::vector optimal_seam(std::vector energy, int w for (auto k = lower_bound; k <= upper_bound; k++) { // Compute energy based on predecessors - 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]; - } + 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)]; } @@ -173,15 +170,15 @@ 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 this but we want to check in priority // at the center to have straight seams @@ -239,59 +236,56 @@ 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 - ) { +// 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; + 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*y+x] - ); + compute_energy_for_pixel(carved_img, newWidth, newHeight, x, y, + nbChannels, nbColorChannels, + output_energy[width * y + x]); } } } } 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 - ) { + 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; + 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*y+x].first - ); + compute_energy_for_pixel(carved_img, newWidth, newHeight, x, y, + nbChannels, nbColorChannels, + output_energy[width * y + x].first); } } } } - /** Carves an image and its energy by one seam, and recomputes the energy. Returns the optimal seam used */ template @@ -309,45 +303,41 @@ std::vector carving_step(const std::vector source_img, opt_seam); remove_seam(source_energy, output_energy, width, height, 1, vertical, opt_seam); - // 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 - ); - + // 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); return opt_seam; } - - - std::vector> mask_energy(std::vector energy, - int width, int height, unsigned char* mask) { - std::vector> output(width*height); + int width, int height, + unsigned char *mask) { + std::vector> output(width * height); - for (auto i=0; i < width*height; i++) { + for (auto i = 0; i < width * height; i++) { output[i] = {mask[i], energy[i]}; } return output; } -bool does_seam_remove_mask(unsigned char* mask, int width, int height, int nbChannels, - std::vector opt_seam, bool vertical) -{ +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; + for (int i = 0; i < dim_long; i++) { + if (mask[im_index(i, opt_seam[i])] == 0) + return true; } return false; } -auto seam_carving(unsigned char *source, int width, int height, int nbChannels, - int nbSeams, bool vertical, unsigned char* mask=nullptr) { +auto seam_carving(unsigned char *source, int width, int height, int nbChannels, + int nbSeams, bool vertical, unsigned char *mask = nullptr) { int nbColorChannels = nbChannels > 3 ? 3 : nbChannels; int curWidth = width; @@ -362,7 +352,7 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels, std::vector source_energy(width * height); // Contains at each step the carved energy std::vector> masked_energy; - std::vector> output_masked_energy(width*height); + 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 @@ -413,8 +403,8 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels, 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 + 1] = 125; + else if (mask[k] == 0) { // Red test_energy_output[nbChannels * k] = 125; } } @@ -428,28 +418,26 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels, 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 - ); + 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; + 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, - curWidth, curHeight, nbChannels, nbColorChannels, vertical - ); + 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()); + masked_energy.begin()); vertical ? curWidth-- : curHeight--; // We just reduced the dimension @@ -512,8 +500,9 @@ int main(int argc, char **argv) { "Don't resize image, just try the specified energy function"); app.add_option("-f,--function", function, "The function to apply to compute the energy"); - app.add_flag("-u,--until-mask-removal", until_mask_removal, - "Carve the image until there are no more red pixels in the mask"); + 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 @@ -525,42 +514,44 @@ int main(int argc, char **argv) { if (!maskImage.empty()) { int maskWidth, maskHeight, maskChannels; mask = - stbi_load(maskImage.c_str(), &maskWidth, &maskHeight, &maskChannels, 0); + 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." - << std::endl; - exit(1); - } - if (maskChannels < 3) { - std::cerr << maskImage << " needs to be RGB." << std::endl; - exit(1); - } - unsigned char r, g, b; - for (auto i=0; i < width*height; i++) { - r = mask[maskChannels*i]; - 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); + if (maskWidth != width || maskHeight != height) { + std::cerr << maskImage << " and " << sourceImage + << " differ in dimension. Please provide a valid mask." + << std::endl; + exit(1); + } + if (maskChannels < 3) { + std::cerr << maskImage << " needs to be RGB." << std::endl; + exit(1); + } + unsigned char r, g, b; + for (auto i = 0; i < width * height; i++) { + r = mask[maskChannels * i]; + 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: - //* . (2) we want to keep the pixel - //* . (1) nothing in particular - //* . (0) we want to remove the pixel - } + mask[i] = positive ? 2 : (negative ? 0 : 1); + } + //* From now on, mask has the same dimensions as source and one single + //channel + //* The values are: + //* . (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; + std::cerr << "Flag --nb-seams specified but --until-mask-removal provided." + << std::endl; nbSeams = DEFAULT_SEAMS; - } nbSeams = std::min(