This repository has been archived on 2026-10-11. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
SnapEdit/backend/src/main/java/pdl/backend/Image/ImageProcessing.java
T
2023-04-14 11:22:17 +02:00

1040 lines
30 KiB
Java

package pdl.backend.Image;
import boofcv.struct.border.BorderType;
import boofcv.struct.image.GrayU8;
import boofcv.struct.image.Planar;
import pdl.backend.Area;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import boofcv.concurrency.BoofConcurrency;
import java.util.concurrent.atomic.AtomicInteger;
public final class ImageProcessing {
private static final AtomicInteger counter = new AtomicInteger(-1);
private static int nbSteps = 0;
private ImageProcessing() {
}
public static int getProgress() {
if (nbSteps == 0)
return -1;
if (counter.get() == -1)
return -1;
return Math.round(((float) ImageProcessing.counter.get() / (float) nbSteps) * 100);
}
/**
*
* Retourne les valeurs r, g et b d'un pixel d'une image placés en paramètres
*
* @param input L'image
* @param x La ligne
* @param y La colonne
*/
public static int[] getRGBValue(Planar<GrayU8> input, int x, int y) {
int[] rgb = new int[3];
for (int i = 0; i < rgb.length; i++) {
rgb[i] = input.getBand(i).get(x, y);
}
return rgb;
}
/**
*
* Colore le pixel donné de l'image donnée avec les valeurs rgb données
*
* @param input L'image
* @param x La ligne
* @param y La colonne
* @param rgb Tableau de taille 3 avec les valeurs de r, g, et b
*/
public static void setRGBValue(Planar<GrayU8> input, int x, int y, int[] rgb) {
for (int i = 0; i < rgb.length; i++) {
input.getBand(i).set(x, y, rgb[i]);
}
}
/**
*
* Convertit une image en couleur en une image en niveaux de gris
*
* @param input l'image d'entrée en couleur
*/
public static void convertColorToGray(Planar<GrayU8> input) {
BoofConcurrency.loopFor(0, input.height, y -> {
for (int x = 0; x < input.width; x++) {
int[] rgb = getRGBValue(input, x, y);
int gray = (int) (0.3 * rgb[0] + 0.59 * rgb[1] + 0.11 * rgb[2]);
setRGBValue(input, x, y, new int[] { gray, gray, gray });
}
});
}
/**
*
* Permet de changer la luminosité d'une image
*
* @param input L'image pour laquelle la luminosité doit être
* modifiée.
* @param delta La valeur du décalage à appliquer à la luminosité de l'image.
*/
public static void changeLuminosity(Planar<GrayU8> input, int delta) {
nbSteps = input.height;
BoofConcurrency.loopFor(0, input.height, y -> {
for (int x = 0; x < input.width; x++) {
int[] rgb = getRGBValue(input, x, y);
for (int i = 0; i < rgb.length; i++) {
if (rgb[i] + delta > 255) {
rgb[i] = 255;
} else if (rgb[i] + delta < 0) {
rgb[i] = 0;
} else {
rgb[i] += delta;
}
}
setRGBValue(input, x, y, rgb);
}
counter.incrementAndGet();
});
counter.set(-1);
}
/**
* Étale l'histogramme d'une pour une meilleure
* répartition des couleurs
*
* @param input L'image à étaler.
*/
public static void histogram(Planar<GrayU8> input) {
nbSteps = input.height;
int h[] = new int[256];
Planar<GrayU8> grayInput = input.clone();
convertColorToGray(grayInput);
// On remplit le tableau avec le nombre de pixels pour chaque niveau de
// gris
BoofConcurrency.loopFor(0, input.height, y -> {
for (int x = 0; x < input.width; x++) {
int gl = grayInput.getBand(0).get(x, y);
h[gl]++;
}
});
// On remplit le tableau avec la cumulée de l'histogramme
int C[] = new int[256];
C[0] = h[0];
for (int i = 1; i < 256; i++) {
C[i] = C[i - 1] + h[i];
}
int LUT[] = new int[256];
// Création de la table de LUT qui stocke la transformation de l'histogramme
// pour chaque niveau de gris
for (int i = 0; i < 256; i++) {
LUT[i] = C[i] * 255 / (input.height * input.width);
}
// Transformation de l'image en utilisant la table de LUT
BoofConcurrency.loopFor(0, input.height, y -> {
for (int x = 0; x < input.width; x++) {
for (int j = 0; j < input.getNumBands(); j++) {
int val = input.getBand(j).get(x, y);
input.getBand(j).set(x, y, LUT[val]);
}
}
counter.incrementAndGet();
});
counter.set(-1);
}
/**
*
* Passe un pixel de l'espace de couleurs RGB à l'espace de couleurs HSV :
*
* @param r la composante rouge du pixel
* @param g la composante verte du pixel
* @param b la composante bleue du pixel
* @param hsv le tableau de sortie contenant les valeurs de h, s et v
*/
public static void rgbToHsv(int r, int g, int b, float[] hsv) {
int max = Math.max(r, Math.max(g, b));
int min = Math.min(r, Math.min(g, b));
// Trouver valeur de h
if (max == min) {
hsv[0] = 0;
} else if (max == r) {
hsv[0] = (60 * (g - b) / (max - min) + 360) % 360;
} else if (max == g) {
hsv[0] = 60 * (b - r) / (max - min) + 120;
} else {
hsv[0] = 60 * (r - g) / (max - min) + 240;
}
// Trouver valeur de s
if (max == 0) {
hsv[1] = 0;
} else {
hsv[1] = 1 - (min / max);
}
// Trouver valeur de v
hsv[2] = max;
}
/**
*
* Passe un pixel de l'espace de couleurs HSV à l'espace de couleurs RGB :
*
* @param h la composante hue du pixel
* @param s la composante saturation du pixel
* @param v la composante value du pixel
* @param rgb le tableau de sortie contenant les valeurs de r, g et b
*/
public static void hsvToRgb(float h, float s, float v, int[] rgb) {
int t_i = (int) Math.floor(h / 60) % 6;
float f = h / 60 - t_i;
float l = v * (1 - s);
float m = v * (1 - f * s);
float n = v * (1 - (1 - f) * s);
switch (t_i) {
case 0:
rgb[0] = (int) Math.round(v);
rgb[1] = (int) Math.round(n);
rgb[2] = (int) Math.round(l);
break;
case 1:
rgb[0] = (int) Math.round(m);
rgb[1] = (int) Math.round(v);
rgb[2] = (int) Math.round(l);
break;
case 2:
rgb[0] = (int) Math.round(l);
rgb[1] = (int) Math.round(v);
rgb[2] = (int) Math.round(n);
break;
case 3:
rgb[0] = (int) Math.round(l);
rgb[1] = (int) Math.round(m);
rgb[2] = (int) Math.round(v);
break;
case 4:
rgb[0] = (int) Math.round(n);
rgb[1] = (int) Math.round(l);
rgb[2] = (int) Math.round(v);
break;
case 5:
rgb[0] = (int) Math.round(v);
rgb[1] = (int) Math.round(l);
rgb[2] = (int) Math.round(m);
break;
}
}
/**
* Applique une teinte à une image.
*
* @param hue La valeur de la teinte à appliquer
* @param input L'image à modifier
*/
public static void colorFilter(Planar<GrayU8> input, int hue) {
if (hue < 0 || hue > 360) {
throw new IllegalArgumentException("Hue doit être compris entre 0 et 360");
}
nbSteps = input.height;
BoofConcurrency.loopFor(0, input.height, j -> {
for (int i = 0; i < input.width; i++) {
float[] hsv = new float[3];
int[] pixelValue = getRGBValue(input, i, j);
rgbToHsv(pixelValue[0], pixelValue[1], pixelValue[2], hsv);
hsv[0] = hue;
int[] rgb = new int[3];
hsvToRgb(hsv[0], hsv[1], hsv[2], rgb);
setRGBValue(input, i, j, rgb);
}
counter.incrementAndGet();
});
counter.set(-1);
}
/**
*
* Calcule et stocke dans output l'image de la norme du gradient
* calculé sur input par convolution avec les filtres de Sobel.
*
* @param input L'image d'entrée en niveaux de gris.
*/
public static void gradientImageSobel(Planar<GrayU8> input) {
nbSteps = input.height;
Planar<GrayU8> output = input.createSameShape();
int h1[][] = { { -1, -2, -1 }, { 0, 0, 0 }, { 1, 2, 1 } };
int h2[][] = { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } };
int half = h1.length / 2;
Planar<GrayU8> inputGray = input.clone();
convertColorToGray(inputGray);
BoofConcurrency.loopFor(0, input.height, y -> {
for (int x = 0; x < input.width; x++) {
if (y < half || x < half || y >= input.height - half || x >= input.width - half) {
int gl = inputGray.getBand(0).get(x, y);
int[] rgb = { gl, gl, gl };
setRGBValue(output, x, y, rgb);
continue;
}
int currentJ = 0;
int currentI = 0;
int Gx = 0;
int Gy = 0;
for (int j = y - half; j <= y + half; j++) {
for (int i = x - half; i <= x + half; i++) {
Gx += inputGray.getBand(0).get(i, j) * h1[currentJ][currentI];
Gy += inputGray.getBand(0).get(i, j) * h2[currentJ][currentI];
currentI++;
}
currentI = 0;
currentJ++;
}
int value = (int) Math.sqrt(Gx * Gx + Gy * Gy);
if (value < 0)
value = 0;
if (value > 255)
value = 255;
int[] rgb = { value, value, value };
setRGBValue(output, x, y, rgb);
}
counter.incrementAndGet();
});
input.setTo(output);
counter.set(-1);
}
/**
* Applique un filtre moyenneur de taille indiquée à l'image
* passée en paramètre et stocke le résultat dans l'image de sortie.
*
* @param input image d'entrée
* @param size taille du filtre à appliquer (doit être impaire)
* @param borderType type de bord à utiliser pour la convolution
*/
public static void meanFilter(Planar<GrayU8> input, int size, BorderType borderType) {
// Vérification que la taille est impaire
if (size % 2 == 0 || size < 0) {
throw new IllegalArgumentException(
"La taille doit être impaire et positive");
}
int[][] kernel = new int[size][size];
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
kernel[i][j] = 1;
}
}
convolution(input, kernel, borderType);
}
/**
*
* Applique un filtre gaussien de taille et de valeur données sur une image et
* stocke le résultat dans l'image de sortie.
*
* @param input L'image d'entrée
* @param size La taille du filtre à appliquer (doit être impaire)
* @param sigma La valeur du sigma à utiliser pour le filtre gaussien.
* @param borderType Le type de bord à utiliser pour la convolution
*/
public static void gaussienFilter(Planar<GrayU8> input, int size, double sigma, BorderType borderType) {
if (size % 2 == 0 || size < 0 || sigma <= 0) {
throw new IllegalArgumentException(
"La taille doit être impaire et positive et sigma doit être strictement positif");
}
double[][] kernel = new double[size][size];
int half = size / 2;
double sum = 0;
for (int x = -half; x <= half; ++x) {
for (int y = -half; y <= half; ++y) {
double gaussianValue = 1 / (2 * Math.PI * sigma * sigma)
* Math.exp(-(x * x + y * y) / (2 * sigma * sigma));
kernel[x + half][y + half] = gaussianValue;
sum += gaussianValue;
}
}
// On récupère le pourcentage de chaque valeur par rapport à la somme
for (int x = -half; x <= half; ++x) {
for (int y = -half; y <= half; ++y) {
kernel[x + half][y + half] = kernel[x + half][y + half] / sum;
}
}
int[][] kernelInt = new int[size][size];
double factor = 1 / kernel[0][0];
// Si le facteur est trop grand (la valeur du coin est très très petite par
// rapport au reste), on le fixe à 1 million
if (factor > 1000000) {
factor = 1000000;
}
// normalisation
for (int x = 0; x < size; ++x) {
for (int y = 0; y < size; ++y) {
kernelInt[x][y] = (int) (kernel[x][y] * factor);
}
}
convolution(input, kernelInt, borderType);
}
public static int[][] createGaussienKernel(int size, double sigma) {
// Vérification que la taille est impaire
if (size % 2 == 0 || size < 0) {
throw new IllegalArgumentException("La taille doit être impaire et positive");
}
double[][] kernel = new double[size][size];
int half = size / 2;
double sum = 0;
for (int x = -half; x <= half; ++x) {
for (int y = -half; y <= half; ++y) {
double gaussianValue = 1 / (2 * Math.PI * sigma * sigma)
* Math.exp(-(x * x + y * y) / (2 * sigma * sigma));
kernel[x + half][y + half] = gaussianValue;
sum += gaussianValue;
}
}
// We get the percentage of each value compared to the sum of all values
for (int x = -half; x <= half; ++x) {
for (int y = -half; y <= half; ++y) {
kernel[x + half][y + half] = kernel[x + half][y + half] / sum;
}
}
int[][] kernelInt = new int[size][size];
double factor = 1 / kernel[0][0];
// If the factor is too big (corner value is very very small compared to all
// other values), we need to reduce it
if (factor > 1000000) {
factor = 1000000;
}
// normalisation
for (int x = 0; x < size; ++x) {
for (int y = 0; y < size; ++y) {
kernelInt[x][y] = (int) (kernel[x][y] * factor);
}
}
return kernelInt;
}
/**
*
* Effectue une convolution sur une image avec le noyau
* donné et stocke le résultat dans l'image de sortie.
*
* @param input L'image d'entrée
* @param kernel Le noyau de convolution à utiliser.
* @param borderType Le type de bord à utiliser pour la convolution
*/
public static void convolution(Planar<GrayU8> input, int[][] kernel, BorderType borderType) {
nbSteps = input.height;
Planar<GrayU8> copy = input.clone();
int size = kernel.length;
int half = kernel.length / 2;
int coefs = 0;
// Calcul du nombre total de coefficients dans le noyau
for (int i = 0; i < kernel.length; i++) {
for (int j = 0; j < kernel[i].length; j++) {
coefs += kernel[i][j];
}
}
final int totalCoefs = coefs;
// Parcours de l'image d'entrée et application de la convolution
BoofConcurrency.loopFor(0, input.height, y -> {
for (int x = 0; x < input.width; x++) {
// On traite d'abord les bords
if (y < half || x < half || y >= input.height - half || x >= input.width -
half) {
if (borderType == BorderType.SKIP) {
setRGBValue(input, x, y, getRGBValue(copy, x, y));
} else if (borderType == BorderType.ZERO) {
setRGBValue(input, x, y, new int[] { 0, 0, 0 });
} else {
int totalValue[] = new int[3];
int coefsUsed = 0;
for (int j = y - half; j < y + half + 1; j++) {
int tmpj = j;
if (j < 0 || j >= input.height) {
if (borderType == BorderType.EXTENDED) {
tmpj = j < 0 ? 0 : input.height - 1;
} else if (borderType == BorderType.NORMALIZED) {
continue;
} else if (borderType == BorderType.REFLECT) {
tmpj = j < 0 ? -j : (input.height - 1) - (j - (input.height - 1));
} else if (borderType == BorderType.WRAP) {
tmpj = j < 0 ? j + (input.height - 1) : (j - (input.height - 1));
}
}
for (int i = x - size / 2; i < x + size / 2 + 1; i++) {
int tmpi = i;
if (i < 0 || i >= input.width) {
if (borderType == BorderType.EXTENDED) {
tmpi = i < 0 ? 0 : input.width - 1;
} else if (borderType == BorderType.NORMALIZED) {
continue;
} else if (borderType == BorderType.REFLECT) {
tmpi = i < 0 ? -i : (input.width - 1) - (i - (input.width - 1));
} else if (borderType == BorderType.WRAP) {
tmpi = i < 0 ? i + (input.width - 1) : (i - (input.width - 1));
}
}
for (int k = 0; k < totalValue.length; k++) {
totalValue[k] += copy.getBand(k).get(tmpi, tmpj)
* kernel[i + (-x + half)][j + (-y + half)];
}
coefsUsed += kernel[i + (-x + half)][j + (-y + half)];
}
}
for (int k = 0; k < totalValue.length; k++) {
totalValue[k] /= coefsUsed;
}
setRGBValue(input, x, y, totalValue);
}
continue;
}
int currentJ = 0;
int currentI = 0;
int totalValue[] = new int[3];
// Parcours du voisinage défini par le noyau
for (int j = y - half; j <= y + half; j++) {
for (int i = x - half; i <= x + half; i++) {
for (int k = 0; k < totalValue.length; k++) {
totalValue[k] += copy.getBand(k).get(i, j) * kernel[currentJ][currentI];
}
currentI++;
}
currentI = 0;
currentJ++;
}
for (int k = 0; k < totalValue.length; k++) {
totalValue[k] /= totalCoefs;
}
setRGBValue(input, x, y, totalValue);
}
counter.incrementAndGet();
});
counter.set(-1);
}
/**
* Inverse les couleurs de l'image donnée en paramètre.
*
* @param input L'image à inverser
*/
public static void negativeFilter(Planar<GrayU8> input) {
nbSteps = input.height;
BoofConcurrency.loopFor(0, input.height, y -> {
for (int x = 0; x < input.width; x++) {
for (int j = 0; j < input.getNumBands(); j++) {
int gl = input.getBand(j).get(x, y);
input.getBand(j).set(x, y, 255 - gl);
}
}
counter.incrementAndGet();
});
counter.set(-1);
}
/**
* Effectue un filtre de type sépia sur l'image donnée en paramètre.
*
* @param input L'image à modifier
*/
public static void sepiaFilter(Planar<GrayU8> input) {
if (input.getNumBands() == 1) {
return;
}
nbSteps = input.height;
BoofConcurrency.loopFor(0, input.height, y -> {
for (int x = 0; x < input.width; x++) {
int[] rgb = getRGBValue(input, x, y);
int r = rgb[0];
int g = rgb[1];
int b = rgb[2];
int tr = (int) (0.393 * r + 0.769 * g + 0.189 * b);
int tg = (int) (0.349 * r + 0.686 * g + 0.168 * b);
int tb = (int) (0.272 * r + 0.534 * g + 0.131 * b);
if (tr > 255) {
tr = 255;
}
if (tg > 255) {
tg = 255;
}
if (tb > 255) {
tb = 255;
}
setRGBValue(input, x, y, new int[] { tr, tg, tb });
}
counter.incrementAndGet();
});
counter.set(-1);
}
public static enum FillingType {
SKIP, CONVOLUTION, LEFT, RIGHT, TOP, BOTTOM
}
/**
* Donne une valeur de pixel unique en fonction de sa position dans l'image afin
* de créer une map des pixels à traiter
*
* @param i coordonnée x du pixel
* @param j coordonnée y du pixel
* @return valeur unique du pixel
*/
public static int mapValue(int i, int j) {
return j + (i + j) * (i + j + 1) / 2;
}
/**
* Indique si le pixel doit être pris en compte lors de la convolution
*
* @param input image d'entrée
* @param x coordonnée x du pixel
* @param y coordonnée y du pixel
* @param xMin coordonnée x minimale de la zone
* @param xMax coordonnée x maximale de la zone
* @param yMin coordonnée y minimale de la zone
* @param yMax coordonnée y maximale de la zone
* @param map map des pixels à traiter
* @return
*/
public static boolean isInImage(Planar<GrayU8> input, int x, int y, int xMin, int xMax, int yMin, int yMax,
Map<Integer, Boolean> map) {
if (x < 0 || x >= input.width || y < 0 || y >= input.height) {
// outside the image
return false;
}
if (x >= xMin && x <= xMax && y >= yMin && y <= yMax) {
// inside the zone
if (map.get(mapValue(x, y))) {
return true;
}
return false;
}
return true;
}
/**
* Donne la valeur de convolution d'un pixel
*
* @param input l'image d'entrée
* @param x coordonnée x du pixel
* @param y coordonnée y du pixel
* @param xMin coordonnée x minimale de la zone
* @param xMax coordonnée x maximale de la zone
* @param yMin coordonnée y minimale de la zone
* @param yMax coordonnée y maximale de la zone
* @param kernel le noyau de convolution
* @param map la map des pixels de la zone
* @return la valeur rgb du pixel
*/
public static int[] getConvolutionValue(Planar<GrayU8> input, int x, int y, int xMin, int xMax, int yMin, int yMax,
int[][] kernel, Map<Integer, Boolean> map) {
int[] rgb = new int[3];
int half = kernel.length / 2;
int values[] = new int[3];
int coefs = 0;
for (int i = x - half; i <= x + half; i++) {
for (int j = y - half; j <= y + half; j++) {
if (isInImage(input, i, j, xMin, xMax, yMin, yMax, map)) {
rgb = getRGBValue(input, i, j);
for (int k = 0; k < 3; k++) {
values[k] += rgb[k] * kernel[i - x + half][j - y + half];
}
coefs += kernel[i - x + half][j - y + half];
}
}
}
for (int k = 0; k < 3; k++) {
values[k] /= coefs;
}
return values;
}
/**
*
* Retire une zone de l'image en utilisant la méthode donnée en paramètre.
*
* @param input L'image d'entrée
* @param output L'image de sortie
* @param area Les coordonnées de la zone à supprimer
* @param filling Le type de remplissage
*/
public static void deleteArea(Planar<GrayU8> input, Area area, FillingType filling) {
if (area.isEmpty()) {
throw new IllegalArgumentException("La zone est vide");
}
int xMin = area.getxMin();
int xMax = area.getxMax();
int yMin = area.getyMin();
int yMax = area.getyMax();
if (xMin < 0 || xMax > input.width || yMin < 0 || yMax > input.height) {
throw new IllegalArgumentException("Les coordonnées sont en dehors de l'image");
}
if (filling == FillingType.CONVOLUTION) {
int[][] kernel = createGaussienKernel(5, 1);
Map<Integer, Boolean> map = new HashMap<Integer, Boolean>();
for (int i = xMin; i <= xMax; i++) {
for (int j = yMin; j <= yMax; j++) {
map.put(mapValue(i, j), false);
setRGBValue(input, i, j, new int[] { 0, 0, 0 });
}
}
int xStart = xMin;
int yStart = yMin;
int xEnd = xMax;
int yEnd = yMax;
while (xStart <= xEnd && yStart <= yEnd) {
for (int x = xStart; x <= xEnd; x++) {
setRGBValue(input, x, yStart,
getConvolutionValue(input, x, yStart, xMin, xMax, yMin, yMax, kernel, map));
map.put(mapValue(x, yStart), true);
}
for (int y = yStart + 1; y <= yEnd; y++) {
setRGBValue(input, xEnd, y,
getConvolutionValue(input, xEnd, y, xMin, xMax, yMin, yMax, kernel, map));
map.put(mapValue(xEnd, y), true);
}
if (yStart < yEnd) {
for (int x = xEnd - 1; x >= xStart; x--) {
setRGBValue(input, x, yEnd,
getConvolutionValue(input, x, yEnd, xMin, xMax, yMin, yMax, kernel, map));
map.put(mapValue(x, yEnd), true);
}
}
if (xStart < xEnd) {
for (int y = yEnd - 1; y > yStart; y--) {
setRGBValue(input, xStart, y,
getConvolutionValue(input, xStart, y, xMin, xMax, yMin, yMax, kernel, map));
map.put(mapValue(xStart, y), true);
}
}
xStart++;
yStart++;
xEnd--;
yEnd--;
}
}
for (int i = xMin; i <= xMax; i++) {
for (int j = yMin; j <= yMax; j++) {
if (filling == FillingType.SKIP) {
setRGBValue(input, i, j, new int[] { 0, 0, 0 });
} else if (filling == FillingType.LEFT) {
int ind = xMin - (i - xMin);
if (ind < 0) {
ind = -ind;
}
while (ind > 2 * xMin) {
ind -= 2 * xMin;
}
if (ind > xMin) {
ind = xMin - (ind - xMin);
}
setRGBValue(input, i, j, getRGBValue(input, ind, j));
} else if (filling == FillingType.RIGHT) {
int ind = xMax;
boolean decrease = false;
for (int k = xMax; k >= i; k--) {
if (decrease) {
ind--;
if (ind == xMax) {
decrease = false;
ind++;
}
} else {
ind++;
if (ind == input.width) {
decrease = true;
ind--;
}
}
}
setRGBValue(input, i, j, getRGBValue(input, ind, j));
} else if (filling == FillingType.TOP) {
int ind = yMin - (j - yMin);
if (ind < 0) {
ind = -ind;
}
while (ind > 2 * yMin) {
ind -= 2 * yMin;
}
if (ind > yMin) {
ind = yMin - (ind - yMin);
}
setRGBValue(input, i, j, getRGBValue(input, i, ind));
} else if (filling == FillingType.BOTTOM) {
int ind = yMax;
boolean decrease = false;
for (int k = yMax; k >= j; k--) {
if (decrease) {
ind--;
if (ind == yMax) {
decrease = false;
ind++;
}
} else {
ind++;
if (ind == input.height) {
decrease = true;
ind--;
}
}
}
setRGBValue(input, i, j, getRGBValue(input, i, ind));
} else {
setRGBValue(input, i, j, getRGBValue(input, i, j));
}
}
}
}
/**
* Effectue un filtre de type bruit gaussien sur l'image donnée en paramètre.
*
* @param input L'image à modifier
* @param intensity L'intensité du bruit
*/
public static void gaussianNoiseFilter(Planar<GrayU8> input, int intensity) {
nbSteps = input.height;
BoofConcurrency.loopFor(0, input.height, y -> {
for (int x = 0; x < input.width; x++) {
int rgb[] = getRGBValue(input, x, y);
for (int i = 0; i < input.getNumBands(); i++) {
Random rand = new Random();
// Genere une valeur de bruit aleatoire a partir d'une distribution gaussienne
double noise = rand.nextGaussian() * intensity * 10;
rgb[i] = (int) Math.round(rgb[i] + noise);
// On vérifie que la valeur est comprise entre 0 et 255
rgb[i] = Math.max(0, Math.min(255, rgb[i]));
if (input.getNumBands() == 1) {
rgb[1] = rgb[0];
rgb[2] = rgb[0];
break;
}
}
setRGBValue(input, x, y, rgb);
}
counter.incrementAndGet();
});
counter.set(-1);
}
/**
* Rogne l'image en fonction de l'area donnée en paramètre
*
* @param input L'image à rogner
* @param area L'area à utiliser pour rogner l'image
*/
public static void crop(Planar<GrayU8> input, Area area) {
if (area.isEmpty())
throw new IllegalArgumentException("La zone est vide");
int xMin = area.getxMin();
int xMax = area.getxMax();
int yMin = area.getyMin();
int yMax = area.getyMax();
if (xMin < 0 || xMax > input.width || yMin < 0 || yMax > input.height) {
throw new IllegalArgumentException("Les coordonnées sont en dehors de l'image");
}
nbSteps = xMax - xMin + 1;
Planar<GrayU8> output = new Planar<>(GrayU8.class, xMax - xMin + 1, yMax - yMin + 1, input.getNumBands());
BoofConcurrency.loopFor(xMin, xMax, i -> {
for (int j = yMin; j <= yMax; j++) {
setRGBValue(output, i - xMin, j - yMin, getRGBValue(input, i, j));
}
counter.incrementAndGet();
});
counter.set(-1);
input.setTo(output);
}
/**
*
* retorune la valeur seuil d'en dessous (0-60-120-180-240-300)
*
* @param h le niveau hue de la couleur du pixel (en HSV)
*/
private static int getFloorLevel(float h) {
int floorLevel = 0;
while (floorLevel + 60 < h)
floorLevel += 60;
return floorLevel;
}
/**
*
* Applique une nouvelle couleur à la place de la couleur choisie si keep est
* faux
* ou à la place de toutes les autres couleurs si keep est vrai.
*
* @param input L'image d'entrée
* @param oldColor La couleur choisie en hexadecimal
* @param range définit la gamme de couleur choisie, elle peut aller de 0 à
* 179.
* @param newColor La couleur donnée qui va être appliquée
* @param keep Permet de choisir entre garder la couleur choisie puis
* modifie tout le reste ou modifie uniquement la couleur
* choisie
*/
public static void changeColoration(Planar<GrayU8> input, String hexOldColor, int range, String newColor,
boolean keep) {
int r = Integer.valueOf(hexOldColor.substring(0, 2), 16);
int g = Integer.valueOf(hexOldColor.substring(2, 4), 16);
int b = Integer.valueOf(hexOldColor.substring(4, 6), 16);
float hsvTMP[] = new float[3];
rgbToHsv(r, g, b, hsvTMP);
float hue = hsvTMP[0];
float tmpMin = ((hue - range) + 360) % 360;
float tmpMax = (hue + range) % 360;
System.out.println("aimed hue : " + hue);
System.out.println("tmpMin = " + tmpMin + "; tmpMax = " + tmpMax + "; keep = " + keep);
if (tmpMin > tmpMax) {
float tmp = tmpMin;
tmpMin = tmpMax;
tmpMax = tmp;
keep = !keep;
}
System.out.println("tmpMin = " + tmpMin + "; tmpMax = " + tmpMax + "; keep = " + keep);
int deltaColor = 0;
switch (newColor) {
case "RED":
deltaColor = 0;
break;
case "YELLOW":
deltaColor = 60;
break;
case "GREEN":
deltaColor = 120;
break;
case "CYAN":
deltaColor = 180;
break;
case "BLUE":
deltaColor = 240;
break;
case "MAGENTA":
deltaColor = 300;
break;
default:
break;
}
// oldColor is the name of the color we want to modify / keep
// colorLevel is the floor of a 60-digit-wide range (RED = 0-59, YELLOW =
// 60-119, etc...) representing with numbers oldColor
// newColor is the name of the color we want to apply on each concerned pixel
// newColorDelta is the ammount we will add to obtain the desired shade of the
// new color
// example : old color is CYAN, we want to modify it, the new color is MAGENTA :
// colorLevel = 180, we take a pixel, hue is 214, we get the shade by
// substracting it the colorLevel :
// shade = 214 - 180 = 34
// we now add the delta to the shade and we get the desired hue that we will
// apply :
// newHue = shade + newColorDelta = 34 + 300 = 334. We then just modify the hue
// of the pixel
// and we finaly finished modifying the color of the pixel while keeping its
// shade
for (int y = 0; y < input.height; y++) {
for (int x = 0; x < input.width; x++) {
int rgb[] = new int[3];
float hsv[] = new float[3];
for (int band = 0; band < 3; band++)
rgb[band] = input.getBand(band).get(x, y);
rgbToHsv(rgb[0], rgb[1], rgb[2], hsv);
float h = hsv[0];
float s = hsv[1];
float v = hsv[2];
boolean test1 = keep && (h >= tmpMin && h < tmpMax);
boolean test2 = (!keep) && (h < tmpMin || h >= tmpMax);
if (test1 || test2) {
continue;
} else {
if (newColor == "GREY") {
int gl = 0;
double coeff[] = { 0.3, 0.59, 0.11 };
for (int band = 0; band < 3; band++)
gl += (coeff[band] * rgb[band]);
for (int band = 0; band < 3; band++)
input.getBand(band).set(x, y, gl);
} else {
h = (h - getFloorLevel(h)) + deltaColor;
hsvToRgb(h, s, v, rgb);
for (int band = 0; band < 3; band++)
input.getBand(band).set(x, y, rgb[band]);
}
}
}
}
}
/**
*
* Applique une nouvelle couleur à la place de la couleur choisie si keep est
* faux
* ou à la place de toutes les autres couleurs si keep est vrai.
*
* @param input L'image d'entrée
* @param intensity L'intensité de la saturation variant de 0 à 1
*/
public static void saturation(Planar<GrayU8> input, double intensity) {
for (int y = 0; y < input.height; y++) {
for (int x = 0; x < input.width; x++) {
int rgb[] = new int[3];
float hsv[] = new float[3];
float s;
for (int band = 0; band < 3; band++) {
rgb[band] = input.getBand(band).get(x, y);
}
rgbToHsv(rgb[0], rgb[1], rgb[2], hsv);
s = (float) (hsv[1] * intensity);
s = Math.min(Math.max(s, 0.0f), 1.0f);
hsvToRgb(hsv[0], s, hsv[2], rgb);
for (int band = 0; band < 3; band++) {
input.getBand(band).set(x, y, rgb[band]);
}
}
}
}
public static void vignette(Planar<GrayU8> input, double power) {
int centerX = input.width / 2;
int centerY = input.height / 2;
double maxDist = Math.sqrt(centerX * centerX + centerY * centerY);
for (int y = 0; y < input.height; y++) {
for (int x = 0; x < input.width; x++) {
double dist = Math.sqrt((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY));
double vignette = 1.0 - Math.pow(dist / maxDist, power);
for (int band = 0; band < 3; band++) {
int colorLevel = (int) (input.getBand(band).get(x, y) * vignette);
if (colorLevel > 255)
colorLevel = 255;
input.getBand(band).set(x, y, colorLevel);
}
}
}
}
}