Compare commits

..

No commits in common. "8fdcd1d966b9ab43a10a604552f3ad11f7526bad" and "a3019975c7f2b25613e9af794c86b7e2af60e6d1" have entirely different histories.

5 changed files with 204 additions and 218 deletions

View File

@ -2,6 +2,7 @@
## References: ## References:
- [Seam carving for content-aware image resizing](https://perso.crans.org/frenoy/matlab2012/seamcarving.pdf) - [Seam carving for content-aware image resizing](https://perso.crans.org/frenoy/matlab2012/seamcarving.pdf)
- [By-example Synthesis of Architectural Textures](https://inria.hal.science/inria-00547754v1/file/paper.pdf)
## Build ## Build
@ -16,21 +17,5 @@ cd ..
## Usage ## Usage
```bash ```bash
build/seam-carving ... build/seam-carving --source imgs/birds.jpg --output out.jpg -n 300
``` ```
Mandatory arguments:
- `-s FILE`: source image
- `-o FILE`: output image
Options:
- `-m FILE`: the mask. RGB image, of the same size than the source, red pixel are those to be removed, the green ones must be kept.
- `-n NUMBER`: number of seams to compute or remove (default 0)
- `--show-seams`: do not carve the image, only outputs the energy and the relevant seams
- `-f FUNCTION NAME`: the energy function to use, see the [beginning of the source file to see the available functions](src/seam-carving.cpp)
- `-u`: when a mask is provided and no seam number is provided, remove seams until the zone to remove has disappeared.
## Generation of the images of the report
A `Makefile` is provided at the root of the repo, it can be used to generate the images that are shown in the report.

View File

@ -1,21 +1,19 @@
#ifndef STBIDEF #ifndef STBIDEF
#ifdef STB_IMAGE_WRITE_STATIC #ifdef STB_IMAGE_WRITE_STATIC
#define STBIDEF static #define STBIDEF static
#else #else
#ifdef __cplusplus #ifdef __cplusplus
#define STBIDEF extern "C" #define STBIDEF extern "C"
#else #else
#define STBIDEF extern #define STBIDEF extern
#endif #endif
#endif #endif
#endif #endif
typedef unsigned char stbi_uc; typedef unsigned char stbi_uc;
STBIDEF int stbi_write_png(char const *filename, int w, int h, int comp, STBIDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
const void *data, int stride_in_bytes);
STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp);
int req_comp);
STBIDEF void stbi_image_free(void *retval_from_stbi_load); STBIDEF void stbi_image_free (void *retval_from_stbi_load);

View File

@ -20,113 +20,6 @@ bool until_mask_removal = false;
int max_step = 1; int max_step = 1;
std::string function = "grad"; std::string function = "grad";
/* The next macro is ugly, but speeds up the computation. It computes the
* energy at one pixel of the image, depending on the energy function used */
#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; \
auto indexPixel_down = ((j) + 1 < height) \
? (nbChannels) * (width * ((j) + 1) + (i)) \
: indexPixel; \
auto indexPixel_left = \
((i) - 1 > 0) ? (nbChannels) * (width * (j) + ((i) - 1)) : indexPixel; \
auto indexPixel_right = ((i) + 1 < width) \
? (nbChannels) * (width * (j) + ((i) + 1)) \
: indexPixel; \
dest = 0; \
if (function == "grad") { \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
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])) + \
(fabs((float)source[indexPixel_right + ch] - \
source[indexPixel + ch]))); \
} \
} else if (function == "gradnorm") { \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
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_left + ch] - \
source[indexPixel + ch]), \
2) + \
std::pow(fabs((float)source[indexPixel_right + ch] - \
source[indexPixel + ch]), \
2)); \
} \
} else if (function == "gradhoriz") { \
/* take the gradient along the horizontal only*/ \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest += ((fabs((float)source[indexPixel_left + ch] - \
source[indexPixel + ch])) + \
(fabs((float)source[indexPixel_right + ch] - \
source[indexPixel + ch]))); \
} \
} else if (function == "gradvertic") { \
/* take the gradient along the vertical only*/ \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest += ((fabs((float)source[indexPixel_up + ch] - \
source[indexPixel + ch])) + \
(fabs((float)source[indexPixel_down + 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])), \
\
(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])))); \
} \
} else if (function == "sumexpgradcomp") { \
\
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest += (std::exp(fabs((float)source[indexPixel_up + ch] - \
source[indexPixel + ch]) / \
255) + \
\
std::exp(fabs((float)source[indexPixel_down + ch] - \
source[indexPixel + ch]) / \
255) + \
\
std::exp(fabs((float)source[indexPixel_left + ch] - \
source[indexPixel + ch]) / \
255) + \
std::exp(fabs((float)source[indexPixel_right + ch] - \
source[indexPixel + ch]) / \
255)); \
} \
} else { \
std::cerr << "no implementation found for function \"" << function \
<< "\"" << std::endl; \
exit(1); \
} \
}
// Le alpha n'est pas pris en compte dans l'énergie
// Here, we use this /ugly/ macro to avoid defining a function that would be way
// slower...
void export_image(const char *filename, const void *data, int width, int height, void export_image(const char *filename, const void *data, int width, int height,
int nbChannels) { int nbChannels) {
@ -140,9 +33,117 @@ void export_image(const char *filename, const void *data, int width, int height,
} }
} }
#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; \
auto indexPixel_down = ((j) + 1 < height) \
? (nbChannels) * (width * ((j) + 1) + (i)) \
: indexPixel; \
auto indexPixel_left = \
((i) - 1 > 0) ? (nbChannels) * (width * (j) + ((i) - 1)) : indexPixel; \
auto indexPixel_right = ((i) + 1 < width) \
? (nbChannels) * (width * (j) + ((i) + 1)) \
: indexPixel; \
dest = 0; \
if (function == "grad") { \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
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])) + \
(fabs((float)source[indexPixel_right + ch] - \
source[indexPixel + ch]))); \
} \
} else if (function == "gradnorm") { \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
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_left + ch] - \
source[indexPixel + ch]), \
2) + \
std::pow(fabs((float)source[indexPixel_right + ch] - \
source[indexPixel + ch]), \
2)); \
} \
} else if (function == "gradhoriz") { \
/* take the gradient along the horizontal only*/ \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest += ((fabs((float)source[indexPixel_left + ch] - \
source[indexPixel + ch])) + \
(fabs((float)source[indexPixel_right + ch] - \
source[indexPixel + ch]))); \
} \
} else if (function == "gradvertic") { \
/* take the gradient along the vertical only*/ \
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest += ((fabs((float)source[indexPixel_up + ch] - \
source[indexPixel + ch])) + \
(fabs((float)source[indexPixel_down + 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])), \
\
(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])))); \
} \
} else if (function == "sumexpgradcomp") { \
\
for (auto ch = 0; ch < (nbColorChannels); ch++) { \
dest += (std::exp(fabs((float)source[indexPixel_up + ch] - \
source[indexPixel + ch]) / \
255) + \
\
std::exp(fabs((float)source[indexPixel_down + ch] - \
source[indexPixel + ch]) / \
255) + \
\
std::exp(fabs((float)source[indexPixel_left + ch] - \
source[indexPixel + ch]) / \
255) + \
std::exp(fabs((float)source[indexPixel_right + ch] - \
source[indexPixel + ch]) / \
255)); \
} \
} else { \
std::cerr << "no implementation found for function \"" << function << "\"" \
<< std::endl; \
exit(1); \
}}
// Le alpha n'est pas pris en compte dans l'énergie
// Here, we use this /ugly/ macro to avoid defining a function that would be way
// slower...
/** e_1 energy*/
std::vector<float> energy_e1(std::vector<unsigned char> source, int width, std::vector<float> energy_e1(std::vector<unsigned char> source, int width,
int height, int nbChannels) { int height, int nbChannels) {
/* Compute the energy of an image, using the previous macro. */
int nbColorChannels = int nbColorChannels =
nbChannels > 3 ? 3 : nbChannels; // nombre de canaux, excepté le alpha nbChannels > 3 ? 3 : nbChannels; // nombre de canaux, excepté le alpha
std::vector<float> energy(width * height); std::vector<float> energy(width * height);
@ -157,10 +158,10 @@ std::vector<float> energy_e1(std::vector<unsigned char> source, int width,
return energy; return energy;
} }
/** Given the energy value, returns the optimal seam */
template <typename T> template <typename T>
std::vector<int> optimal_seam(std::vector<T> energy, int width, int height, std::vector<int> optimal_seam(std::vector<T> energy, int width, int height,
bool vertical) { bool vertical) {
/* Performs dynamic programming to find the optimal seam given the energy. */
// dyn_energy is indexed by [dim_large*(i : dim_long) + (j : dim_large)] // dyn_energy is indexed by [dim_large*(i : dim_long) + (j : dim_large)]
std::vector<T> dyn_energy(width * height); std::vector<T> dyn_energy(width * height);
@ -170,7 +171,7 @@ std::vector<int> optimal_seam(std::vector<T> energy, int width, int height,
//* Find an end of the minimal connected vertical/horizontal seam //* Find an end of the minimal connected vertical/horizontal seam
for (auto i = 0; i < dim_large; i++) { for (auto i = 0; i < dim_large; i++) {
dyn_energy[i] = energy[vertical ? i : i * width]; dyn_energy[i] = energy[vertical ? i : i*width];
} }
for (auto i = 1; i < dim_long; i++) { // Propagate dyn_energy for (auto i = 1; i < dim_long; i++) { // Propagate dyn_energy
@ -247,12 +248,11 @@ std::vector<int> optimal_seam(std::vector<T> energy, int width, int height,
return result; return result;
} }
/** Carves an image by one seam. Returns the optimal seam used */
template <typename T> template <typename T>
void remove_seam(const std::vector<T> source, std::vector<T> &output, int width, void remove_seam(const std::vector<T> source, std::vector<T> &output, int width,
int height, int nbChannels, bool vertical, int height, int nbChannels, bool vertical,
const std::vector<int> seam) { const std::vector<int> seam) {
/* Carves an image by one seam. (the image can be a real image, or an energy
* vector). */
// remove the given seam from the image, the result is in output // remove the given seam from the image, the result is in output
// the output must have at least the right size! // the output must have at least the right size!
int dim_large = vertical ? width : height; int dim_large = vertical ? width : height;
@ -272,11 +272,8 @@ void remove_seam(const std::vector<T> source, std::vector<T> &output, int width,
} }
} }
// The two next functions are used to update the energy after one seamcarving // It would be preferable to use templates only for the value assignation but
// step on the image. It recomputes the energy along the seam. It would be // this is in fact far less efficient
// preferable to use templates only for the value assignation but this is in
// fact far less efficient
void recompute_energy_along_seam(std::vector<unsigned char> carved_img, void recompute_energy_along_seam(std::vector<unsigned char> carved_img,
std::vector<float> &output_energy, std::vector<float> &output_energy,
std::vector<int> opt_seam, int width, std::vector<int> opt_seam, int width,
@ -290,7 +287,6 @@ void recompute_energy_along_seam(std::vector<unsigned char> carved_img,
for (auto j0 = 0; j0 < dim_long; j0++) { for (auto j0 = 0; j0 < dim_long; j0++) {
auto i0 = opt_seam[j0]; auto i0 = opt_seam[j0];
// recompute the energy on a square around (i0,j0) that was removed.
for (auto i_offset = -max_step - 1; i_offset <= max_step + 1; i_offset++) { for (auto i_offset = -max_step - 1; i_offset <= max_step + 1; i_offset++) {
for (auto j_offset = -max_step - 1; j_offset <= max_step + 1; for (auto j_offset = -max_step - 1; j_offset <= max_step + 1;
j_offset++) { j_offset++) {
@ -321,7 +317,6 @@ void recompute_energy_along_seam(
for (auto j0 = 0; j0 < dim_long; j0++) { for (auto j0 = 0; j0 < dim_long; j0++) {
auto i0 = opt_seam[j0]; auto i0 = opt_seam[j0];
// recompute the energy on a square around (i0,j0) that was removed.
for (auto i_offset = -max_step - 1; i_offset <= max_step + 1; i_offset++) { for (auto i_offset = -max_step - 1; i_offset <= max_step + 1; i_offset++) {
for (auto j_offset = -max_step - 1; j_offset <= max_step + 1; for (auto j_offset = -max_step - 1; j_offset <= max_step + 1;
j_offset++) { j_offset++) {
@ -332,23 +327,24 @@ void recompute_energy_along_seam(
// if the pixel is to be removed, we keep the energy at 0. // if the pixel is to be removed, we keep the energy at 0.
if (output_energy[width * y + x].first != 0) if (output_energy[width * y + x].first != 0)
compute_energy_for_pixel(carved_img, newWidth, newHeight, x, y, compute_energy_for_pixel(carved_img, newWidth, newHeight, x, y,
nbChannels, nbColorChannels, nbChannels, nbColorChannels,
output_energy[width * y + x].second); output_energy[width * y + x].second);
} }
} }
} }
} }
} }
/** Carves an image and its energy by one seam, and recomputes the energy.
Returns the optimal seam used */
template <typename T> template <typename T>
std::vector<int> carving_step(const std::vector<unsigned char> source_img, std::vector<int> carving_step(const std::vector<unsigned char> source_img,
std::vector<T> source_energy, std::vector<T> source_energy,
std::vector<unsigned char> &output_img, std::vector<unsigned char> &output_img,
std::vector<T> &output_energy, int width, std::vector<T> &output_energy, int width,
int height, int nbChannels, int nbColorChannels, int height, int nbChannels, int nbColorChannels,
bool vertical) { bool vertical) {
/** Carves an image and its energy by one seam, and recomputes the energy.
Returns the optimal seam used */
std::vector<int> opt_seam = std::vector<int> opt_seam =
optimal_seam(source_energy, width, height, vertical); optimal_seam(source_energy, width, height, vertical);
@ -364,15 +360,18 @@ std::vector<int> carving_step(const std::vector<unsigned char> source_img,
return opt_seam; return opt_seam;
} }
auto seam_carving(unsigned char *source, int width, int height, int nbChannels, auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
int nbSeams, bool vertical, unsigned char *mask = nullptr) { int nbSeams, bool vertical, unsigned char *mask = nullptr) {
/* Performs seam Carving */
int nbColorChannels = nbChannels > 3 ? 3 : nbChannels; int nbColorChannels = nbChannels > 3 ? 3 : nbChannels;
int curWidth = width; int curWidth = width;
int curHeight = height; int curHeight = height;
int dim_large = vertical ? width : height; int dim_large = vertical ? width : height;
int dim_long = vertical ? height : width; int dim_long = vertical ? height : width;
// dim_long=longueur des seam // dim_long=longueur des seam
std::vector<unsigned char> source_img(width * height * nbChannels); std::vector<unsigned char> source_img(width * height * nbChannels);
// Contains at each step the carved image // Contains at each step the carved image
std::vector<float> source_energy(width * height); std::vector<float> source_energy(width * height);
@ -390,9 +389,11 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
// Contains the initial energy, only for "test_energy" // Contains the initial energy, only for "test_energy"
std::vector<unsigned char> test_energy_output(width * height * nbChannels); std::vector<unsigned char> test_energy_output(width * height * nbChannels);
// Final output for "test_energy" // Final output for "test_energy"
for (auto i = 0; i < width * height * nbChannels; i++) { for (auto i = 0; i < width * height * nbChannels; i++) {
source_img[i] = source[i]; source_img[i] = source[i];
} }
source_energy = energy_e1(source_img, width, height, nbChannels); source_energy = energy_e1(source_img, width, height, nbChannels);
if (mask) if (mask)
masked_energy = mask_energy(source_energy, width, height, mask); masked_energy = mask_energy(source_energy, width, height, mask);
@ -440,7 +441,6 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
auto seam_index = 0; auto seam_index = 0;
while (seam_index++ < nbSeams || until_mask_removal) { while (seam_index++ < nbSeams || until_mask_removal) {
// Main loop of the program: we remove a seam until the end.
std::vector<int> opt_seam; std::vector<int> opt_seam;
if (mask) { if (mask) {
opt_seam = carving_step(source_img, masked_energy, output_img, opt_seam = carving_step(source_img, masked_energy, output_img,
@ -448,8 +448,8 @@ auto seam_carving(unsigned char *source, int width, int height, int nbChannels,
nbChannels, nbColorChannels, vertical); nbChannels, nbColorChannels, vertical);
if (until_mask_removal && if (until_mask_removal &&
!does_seam_remove_mask(masked_energy, curWidth, curHeight, nbChannels, !does_seam_remove_mask(masked_energy, curWidth, curHeight, nbChannels, opt_seam,
opt_seam, vertical)) vertical))
break; break;
} else { } else {
@ -503,6 +503,7 @@ int main(int argc, char **argv) {
std::string outputImage = "output.png"; std::string outputImage = "output.png";
int nbSeams = DEFAULT_SEAMS; int nbSeams = DEFAULT_SEAMS;
bool vertical = false; bool vertical = false;
app.add_option("-s,--source", sourceImage, "Source image") app.add_option("-s,--source", sourceImage, "Source image")
->required() ->required()
->check(CLI::ExistingFile); ->check(CLI::ExistingFile);
@ -512,10 +513,6 @@ int main(int argc, char **argv) {
app.add_option("-n,--nb-seams", nbSeams, "Number of seams") app.add_option("-n,--nb-seams", nbSeams, "Number of seams")
->check(CLI::Number); ->check(CLI::Number);
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_option("--max-step", max_step, "Max width of step to find a seam") app.add_option("--max-step", max_step, "Max width of step to find a seam")
->check(CLI::Number); ->check(CLI::Number);
@ -524,9 +521,11 @@ int main(int argc, char **argv) {
app.add_flag("--silent", silent, "No verbose messages"); app.add_flag("--silent", silent, "No verbose messages");
app.add_flag("--show-seams", show_seams, app.add_flag("--show-seams", show_seams,
"Don't resize image, just try the specified energy function"); "Don't resize image, just try the specified energy function");
app.add_option( app.add_option("-f,--function", function,
"-f,--function", function, "The function to apply to compute the energy");
"The function to apply to compute the energy (default: gradient)"); 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); CLI11_PARSE(app, argc, argv);
// Image loading // Image loading

View File

@ -1,6 +1,6 @@
#include <iostream> #include <iostream>
#include <random>
#include <sstream> #include <sstream>
#include <random>
#include <vector> #include <vector>
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
@ -10,29 +10,28 @@
#include "utils.hpp" #include "utils.hpp"
bool does_seam_remove_mask(std::vector<std::pair<int, float>> energy, int width, bool nearly_equal(float a, float b) {
int height, int nbChannels, return std::nextafter(a, std::numeric_limits<float>::lowest()) <= b &&
std::vector<int> opt_seam, bool vertical) std::nextafter(a, std::numeric_limits<float>::max()) >= b;
/* returns true if and only if the given seam removes one pixel to remove given }
by the mask*/
{ bool does_seam_remove_mask(std::vector<std::pair<int, float>> energy, int width, int height, int nbChannels,
std::vector<int> opt_seam, bool vertical)
{
int dim_large = vertical ? width : height; int dim_large = vertical ? width : height;
int dim_long = vertical ? height : width; int dim_long = vertical ? height : width;
for (int i = 0; i < dim_long; i++) { for (int i=0; i < dim_long; i++) {
if (energy[im_index(i, opt_seam[i])].first == 0) if (energy[im_index(i, opt_seam[i])].first == 0) return true;
return true;
} }
return false; return false;
} }
std::vector<std::pair<int, float>> mask_energy(std::vector<float> energy, std::vector<std::pair<int, float>> mask_energy(std::vector<float> energy,
int width, int height, int width, int height, unsigned char* mask) {
unsigned char *mask) { std::vector<std::pair<int, float>> output(width*height);
/* transform the computed energy to the energy associated to the mask*/
std::vector<std::pair<int, float>> output(width * height);
for (auto i = 0; i < width * height; i++) { for (auto i=0; i < width*height; i++) {
if (mask[i] == 0) // We want to remove the pixel if (mask[i] == 0) // We want to remove the pixel
energy[i] = 0.; energy[i] = 0.;
@ -42,23 +41,20 @@ std::vector<std::pair<int, float>> mask_energy(std::vector<float> energy,
return output; return output;
} }
/* Here we overload the operators on pairs of int and float, to ease the std::pair<int, float> operator+(std::pair<int, float>& p1, std::pair<int, float>& p2) {
* readability of the code for removing a part of an image using a mask. */ return {
std::pair<int, float> operator+(std::pair<int, float> &p1, // If one of the two pixels is "protected" (ie 2), we want to prevent this line removing
std::pair<int, float> &p2) { // Else, we want to keep the information of "is there a pixel to remove in this seam" (ie 0)
return {// If one of the two pixels is "protected" (ie 2), we want to prevent (p1.first==2) || (p2.first==2) ? 2 : std::min(p1.first, p2.first),
// this line removing p1.second+p2.second
// 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<int, float> &p1, std::pair<int, float> &p2) { void operator+=(std::pair<int, float>& p1, std::pair<int, float>& p2) {
p1 = p1 + p2; p1 = p1+p2;
} }
bool operator==(std::pair<int, float> &p1, std::pair<int, float> &p2) { bool operator==(std::pair<int, float>& p1, std::pair<int, float>& p2) {
return (p1.first == p2.first && p1.second == p2.second); return (p1.first==p2.first && p1.second == p2.second);
} }
std::string str_of_e(std::pair<int, float> energy) { std::string str_of_e(std::pair<int, float> energy) {

View File

@ -4,49 +4,57 @@
// : dim_long, b : dim_large // : dim_long, b : dim_large
#define im_index(a, b) (vertical ? (width * a + b) : (width * b + a)) #define im_index(a, b) (vertical ? (width * a + b) : (width * b + a))
bool does_seam_remove_mask(std::vector<std::pair<int, float>> energy, int width, // Check if two floats are nearly equal
int height, int nbChannels, bool nearly_equal(float a, float b);
std::vector<int> opt_seam, bool vertical);
bool does_seam_remove_mask(std::vector<std::pair<int, float>> energy, int width, int height, int nbChannels,
std::vector<int> opt_seam, bool vertical);
std::vector<std::pair<int, float>> mask_energy(std::vector<float> energy, std::vector<std::pair<int, float>> mask_energy(std::vector<float> energy,
int width, int height, int width, int height, unsigned char* mask);
unsigned char *mask);
//* Operators overwrites //* Operators overwrites
std::pair<int, float> operator+(std::pair<int, float> &p1, std::pair<int, float> operator+(std::pair<int, float>& p1, std::pair<int, float>& p2);
std::pair<int, float> &p2);
void operator+=(std::pair<int, float> &p1, std::pair<int, float> &p2); void operator+=(std::pair<int, float>& p1, std::pair<int, float>& p2);
bool operator==(std::pair<int, float> &p1, std::pair<int, float> &p2); bool operator==(std::pair<int, float>& p1, std::pair<int, float>& p2);
// Unfortunately, std::cout << (std::pair<>) does not work and can't be
// rewritten // Unfortunately, std::cout << (std::pair<>) does not work and can't be rewritten
std::string str_of_e(std::pair<int, float> energy); std::string str_of_e(std::pair<int, float> energy);
std::string str_of_e(float energy); std::string str_of_e(float energy);
namespace limits {
struct max_energy {
template <class T> operator T() { return std::numeric_limits<T>::max(); }
operator std::pair<int, float>() {
return {3, std::numeric_limits<float>::max()};
}
};
struct null_energy { namespace limits {
template <class T> operator T() { return 0.; } struct max_energy {
operator std::pair<int, float>() { return {0, limits::null_energy()}; } template<class T> operator T() {
}; return std::numeric_limits<T>::max();
} // namespace limits }
operator std::pair<int, float>() {
return {3, std::numeric_limits<float>::max()};
}
};
struct null_energy {
template<class T> operator T() {
return 0.;
}
operator std::pair<int, float>() {
return {0, limits::null_energy()};
}
};
}
template <typename T> template <typename T>
void compute_seam_tot(std::vector<int> opt_seam, std::vector<T> energy, void compute_seam_tot(std::vector<int> opt_seam, std::vector<T> energy, int width, int height, bool vertical) {
int width, int height, bool vertical) {
int dim_long = vertical ? height : width; int dim_long = vertical ? height : width;
T sum = limits::null_energy(); T sum = limits::null_energy();
for (int i = 0; i < dim_long; i++) { for (int i=0; i < dim_long; i++) {
sum += energy[im_index(i, opt_seam[i])]; sum += energy[im_index(i, opt_seam[i])];
// std::cout << str_of_e(sum); // std::cout << str_of_e(sum);
} }