From ddc02ffa15c47fe6a0ebf0171fe65a8283f02f9a Mon Sep 17 00:00:00 2001 From: Kea Horvath Date: Tue, 14 Mar 2023 14:41:52 +0100 Subject: [PATCH 01/10] toutes les fonctions de traitement d'images --- backend/pom.xml | 5 + .../java/pdl/backend/ImageProcessing.java | 394 ++++++++++++++++++ 2 files changed, 399 insertions(+) create mode 100644 backend/src/main/java/pdl/backend/ImageProcessing.java diff --git a/backend/pom.xml b/backend/pom.xml index 41fb5b2..fd7331b 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -27,6 +27,11 @@ spring-boot-starter-test test + + org.boofcv + boofcv-core + 0.41 + diff --git a/backend/src/main/java/pdl/backend/ImageProcessing.java b/backend/src/main/java/pdl/backend/ImageProcessing.java new file mode 100644 index 0000000..ac85693 --- /dev/null +++ b/backend/src/main/java/pdl/backend/ImageProcessing.java @@ -0,0 +1,394 @@ +package pdl.backend; + +import java.awt.image.BufferedImage; +import boofcv.io.image.UtilImageIO; +import boofcv.io.image.ConvertBufferedImage; +import boofcv.struct.image.GrayU8; +import boofcv.struct.border.BorderType; +import boofcv.struct.image.Planar; + +public class ImageProcessing { + + /** + * + * 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 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 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 + * @param output l'image de sortie en niveaux de gris + */ + public static void convertColorToGray(Planar input, Planar output) { + for (int y = 0; y < 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(output, 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 input, int delta) { + for (int y = 0; y < 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); + } + } + } + + /** + * Étale l'histogramme d'une pour une meilleure + * répartition des couleurs + * + * @param input L'image à étaler. + */ + public static void histogram(Planar input) { + int h[] = new int[256]; + Planar output = input.createSameShape(); + convertColorToGray(input, output); + // On remplit le tableau avec le nombre de pixels pour chaque niveau de + // gris + for (int y = 0; y < input.height; y++) { + for (int x = 0; x < input.width; x++) { + int gl = output.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 + for (int y = 0; y < 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]); + } + } + } + } + + + /** + * + * 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(int hue, Planar input) { + for (int j = 0; j < 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); + } + } + } + + /** + * + * 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. + * @param output L'image de sortie en niveaux de gris. + */ + public static void gradientImageSobel(Planar input, Planar output) { + 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 inputGray = input.createSameShape(); + convertColorToGray(input, inputGray); + for (int y = 0; y < 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); + } + } + } + + + + + /** + * Applique un filtre moyenneur de taille indiquée à l'image + * passée en paramètre (le bord de l'image n'est pas modifié) + * + * @param input image d'entrée + * @param output image de sortie + * @param size taille du filtre à appliquer + */ + public static void meanFilter(Planar input, Planar output, int size) { + // Vérification que la taille est impaire + if (size % 2 == 0) { + throw new IllegalArgumentException("La taille doit être impaire"); + } + // Boucle pour parcourir tous les pixels de l'image d'entrée + for (int y = 0; y < input.height; y++) { + for (int x = 0; x < input.width; x++) { + if (y < size / 2 || x < size / 2 || y >= input.height - size / 2 + || x >= input.width - size / 2) { + setRGBValue(output, x, y, getRGBValue(input, x, y)); + continue; + } + int totalValue[] = new int[3]; + // Boucle pour parcourir le carré size x size autour du pixel actuel + for (int j = y - size / 2; j < y + size / 2 + 1; j++) { + for (int i = x - size / 2; i < x + size / 2 + 1; i++) { + for (int k = 0; k < totalValue.length; k++) { + totalValue[k] += input.getBand(k).get(i, j); + } + } + } + // Calcule la moyenne des pixels du carré et l'applique au pixel actuel dans + // l'image de + // sortie + for (int k = 0; k < totalValue.length; k++) { + totalValue[k] /= size * size; + } + setRGBValue(output, x, y, totalValue); + } + } + + } + + /** + * + * 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 output L'image de sortie + * @param kernel Le noyau de convolution à utiliser. + */ + public static void gaussienFilter(Planar input, Planar output, + int[][] kernel) { + int size = kernel.length; + int half = size / 2; + int totalCoefs = 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++) { + totalCoefs += kernel[i][j]; + } + } + // Parcours de l'image d'entrée et application de la convolution + for (int y = 0; y < input.height; y++) { + for (int x = 0; x < input.width; x++) { + // Vérification si le carré sera à l'intérieur de l'image + if (y < half || x < half || y >= input.height - half || x >= input.width - + half) { + setRGBValue(output, x, y, getRGBValue(input, x, y)); + 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] += input.getBand(k).get(i, j) * kernel[currentJ][currentI]; + } + currentI++; + } + currentI = 0; + currentJ++; + } + for (int k = 0; k < totalValue.length; k++) { + totalValue[k] /= totalCoefs; + } + setRGBValue(output, x, y, totalValue); + } + } + } + + public static void main(String[] args) { + if (args.length < 2) { + System.out.println("missing input or output image filename"); + System.exit(-1); + } + final String inputPath = args[0]; + BufferedImage image = UtilImageIO.loadImage(inputPath); + Planar input = ConvertBufferedImage.convertFromPlanar(image, null, true, GrayU8.class); + Planar output = input.createSameShape(); + long beginning = System.nanoTime(); + histogram(input); + long end = System.nanoTime(); + System.out.println("Duration : " + ((end - beginning) / 1000000) + "ms"); + // save output image + final String outputPath = args[1]; + UtilImageIO.saveImage(input, outputPath); + System.out.println("Image saved in: " + outputPath); } + +} From d86c5487899afd7dceb631cedc7845ea1a43450e Mon Sep 17 00:00:00 2001 From: Kea Horvath Date: Tue, 14 Mar 2023 15:02:53 +0100 Subject: [PATCH 02/10] added possibility of using png images --- .../java/pdl/backend/ImageController.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/pdl/backend/ImageController.java b/backend/src/main/java/pdl/backend/ImageController.java index 2695032..001759b 100644 --- a/backend/src/main/java/pdl/backend/ImageController.java +++ b/backend/src/main/java/pdl/backend/ImageController.java @@ -6,6 +6,8 @@ import java.io.InputStream; import java.util.List; import java.util.Optional; +import javax.print.attribute.standard.Media; + import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -37,14 +39,14 @@ public class ImageController { this.imageDao = imageDao; } - @GetMapping(value = "/images/{id}", produces = MediaType.IMAGE_JPEG_VALUE) + @GetMapping(value = "/images/{id}", produces = MediaType.IMAGE_JPEG_VALUE || MediaType.IMAGE_PNG_VALUE) public ResponseEntity getImage(@PathVariable("id") long id) { Optional image = imageDao.retrieve(id); if (image.isPresent()) { InputStream inputStream = new ByteArrayInputStream(image.get().getData()); - return ResponseEntity.ok().contentType(MediaType.IMAGE_JPEG).body(new InputStreamResource(inputStream)); + return ResponseEntity.ok().contentType(image.getMediaType()).body(new InputStreamResource(inputStream)); } return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); } @@ -65,7 +67,7 @@ public class ImageController { public ResponseEntity addImage(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { String contentType = file.getContentType(); - if (contentType != null && !contentType.equals(MediaType.IMAGE_JPEG.toString())) { + if (contentType != null && !contentType.equals(MediaType.IMAGE_JPEG.toString()) && !contentType.equals(MediaType.IMAGE_PNG.toString())) { return new ResponseEntity<>("Only JPEG/PNG file format supported", HttpStatus.UNSUPPORTED_MEDIA_TYPE); } try { @@ -92,4 +94,15 @@ public class ImageController { return nodes; } + @GetMapping(value = "/images/{id}?algorithm={X}&p1=Y&p2=Z") + public ResponseEntity appl(@PathVariable("id") long id) { + + Optional image = imageDao.retrieve(id); + + if (image.isPresent()) { + InputStream inputStream = new ByteArrayInputStream(image.get().getData()); + return ResponseEntity.ok().contentType(MediaType.IMAGE_JPEG).body(new InputStreamResource(inputStream)); + } + return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); + } } From b949a317c31a643cd5923ee908e875730c78d3c7 Mon Sep 17 00:00:00 2001 From: Kea Horvath Date: Tue, 14 Mar 2023 15:13:35 +0100 Subject: [PATCH 03/10] debut du besoin 8 --- .../src/main/java/pdl/backend/ImageController.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/pdl/backend/ImageController.java b/backend/src/main/java/pdl/backend/ImageController.java index 001759b..21c9944 100644 --- a/backend/src/main/java/pdl/backend/ImageController.java +++ b/backend/src/main/java/pdl/backend/ImageController.java @@ -94,13 +94,16 @@ public class ImageController { return nodes; } - @GetMapping(value = "/images/{id}?algorithm={X}&p1=Y&p2=Z") - public ResponseEntity appl(@PathVariable("id") long id) { + @GetMapping(value = "/images/{id}?algorithm={X}&p1={Y}&p2={Z}", produces = MediaType.IMAGE_JPEG_VALUE || MediaType.IMAGE_PNG_VALUE) + public ResponseEntity applyAlgorithm(@PathVariable("id") long id, @PathVariable("X") string algorithm, @PathVariable("Y") int firstParam, @PathVariable("Z") int secondParam) { Optional image = imageDao.retrieve(id); - if (image.isPresent()) { - InputStream inputStream = new ByteArrayInputStream(image.get().getData()); + if (algorithm.equals("changeLuminosity")){ + Optional changedImage = createSameShape(image); + ImageProcessing.changeLuminosity(changedImage, firstParam); + } + InputStream inputStream = new ByteArrayInputStream(changedImage.get().getData()); return ResponseEntity.ok().contentType(MediaType.IMAGE_JPEG).body(new InputStreamResource(inputStream)); } return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); From dbaf3401b836c701dae885f2a8b8051da841e40a Mon Sep 17 00:00:00 2001 From: keahorvath Date: Tue, 14 Mar 2023 20:15:55 +0100 Subject: [PATCH 04/10] removed unnessary imports and main in ImageProcessing and started besoin 8 --- .../java/pdl/backend/ImageController.java | 163 +++++++++++------- .../java/pdl/backend/ImageProcessing.java | 47 ++--- 2 files changed, 108 insertions(+), 102 deletions(-) diff --git a/backend/src/main/java/pdl/backend/ImageController.java b/backend/src/main/java/pdl/backend/ImageController.java index 21c9944..b5b95b2 100644 --- a/backend/src/main/java/pdl/backend/ImageController.java +++ b/backend/src/main/java/pdl/backend/ImageController.java @@ -6,12 +6,17 @@ import java.io.InputStream; import java.util.List; import java.util.Optional; +import javax.imageio.ImageIO; import javax.print.attribute.standard.Media; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import boofcv.io.image.ConvertBufferedImage; +import boofcv.struct.image.GrayU8; +import boofcv.struct.image.Planar; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpStatus; @@ -27,85 +32,113 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; + @RestController public class ImageController { - @Autowired - private ObjectMapper mapper; + @Autowired + private ObjectMapper mapper; - private final ImageDao imageDao; + private final ImageDao imageDao; - public ImageController(ImageDao imageDao) { - this.imageDao = imageDao; - } + public ImageController(ImageDao imageDao) { + this.imageDao = imageDao; + } - @GetMapping(value = "/images/{id}", produces = MediaType.IMAGE_JPEG_VALUE || MediaType.IMAGE_PNG_VALUE) - public ResponseEntity getImage(@PathVariable("id") long id) { + @DeleteMapping(value = "/images/{id}") + public ResponseEntity deleteImage(@PathVariable("id") long id) { - Optional image = imageDao.retrieve(id); + Optional image = imageDao.retrieve(id); - if (image.isPresent()) { - InputStream inputStream = new ByteArrayInputStream(image.get().getData()); - return ResponseEntity.ok().contentType(image.getMediaType()).body(new InputStreamResource(inputStream)); - } - return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); - } + if (image.isPresent()) { + imageDao.delete(image.get()); + return new ResponseEntity<>("Image id=" + id + " deleted.", HttpStatus.OK); + } + return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); + } - @DeleteMapping(value = "/images/{id}") - public ResponseEntity deleteImage(@PathVariable("id") long id) { + @PostMapping(value = "/images") + public ResponseEntity addImage(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { - Optional image = imageDao.retrieve(id); + String contentType = file.getContentType(); + if (contentType != null && !contentType.equals(MediaType.IMAGE_JPEG.toString()) + && !contentType.equals(MediaType.IMAGE_PNG.toString())) { + return new ResponseEntity<>("Only JPEG/PNG file format supported", HttpStatus.UNSUPPORTED_MEDIA_TYPE); + } + try { + imageDao.create(new Image(file.getOriginalFilename(), file.getBytes())); + } catch (IOException e) { + return new ResponseEntity<>("Failure to read file", HttpStatus.NO_CONTENT); + } + return new ResponseEntity<>("Image uploaded", HttpStatus.CREATED); + } - if (image.isPresent()) { - imageDao.delete(image.get()); - return new ResponseEntity<>("Image id=" + id + " deleted.", HttpStatus.OK); - } - return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); - } + @GetMapping(value = "/images", produces = "application/json") + @ResponseBody + public ArrayNode getImageList() { + List images = imageDao.retrieveAll(); + ArrayNode nodes = mapper.createArrayNode(); + for (Image image : images) { + ObjectNode objectNode = mapper.createObjectNode(); + objectNode.put("id", image.getId()); + objectNode.put("name", image.getName()); + objectNode.put("size", image.getSize()); + objectNode.put("type", image.getMediaType().toString()); + nodes.add(objectNode); + } + return nodes; + } - @PostMapping(value = "/images") - public ResponseEntity addImage(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { + @GetMapping(value = "/images/{id}", produces = { MediaType.IMAGE_JPEG_VALUE, + MediaType.IMAGE_PNG_VALUE }) + public ResponseEntity applyAlgorithm(@PathVariable("id") long id, + @RequestParam(required = false) String algorithm, + @RequestParam(required = false) Long p1, @RequestParam(required = false) Long p2) { - String contentType = file.getContentType(); - if (contentType != null && !contentType.equals(MediaType.IMAGE_JPEG.toString()) && !contentType.equals(MediaType.IMAGE_PNG.toString())) { - return new ResponseEntity<>("Only JPEG/PNG file format supported", HttpStatus.UNSUPPORTED_MEDIA_TYPE); - } - try { - imageDao.create(new Image(file.getOriginalFilename(), file.getBytes())); - } catch (IOException e) { - return new ResponseEntity<>("Failure to read file", HttpStatus.NO_CONTENT); - } - return new ResponseEntity<>("Image uploaded", HttpStatus.CREATED); - } + Optional image = imageDao.retrieve(id); + if (image.isPresent()) { + if (algorithm == null) { + InputStream inputStream = new ByteArrayInputStream(image.get().getData()); + return ResponseEntity.ok().contentType(image.get().getMediaType()) + .body(new InputStreamResource(inputStream)); - @GetMapping(value = "/images", produces = "application/json") - @ResponseBody - public ArrayNode getImageList() { - List images = imageDao.retrieveAll(); - ArrayNode nodes = mapper.createArrayNode(); - for (Image image : images) { - ObjectNode objectNode = mapper.createObjectNode(); - objectNode.put("id", image.getId()); - objectNode.put("name", image.getName()); - objectNode.put("size", image.getSize()); - objectNode.put("type", image.getMediaType().toString()); - nodes.add(objectNode); - } - return nodes; - } + } + if (algorithm.equals("changeLuminosity")) { + BufferedImage bufImg; + try { + bufImg = ImageIO.read(new ByteArrayInputStream(image.get().getData())); + } catch (IOException e) { + return new ResponseEntity<>("Image not readable", + HttpStatus.INTERNAL_SERVER_ERROR); + } + Planar input = ConvertBufferedImage.convertFromPlanar(bufImg, null, + true, GrayU8.class); + ImageProcessing.changeLuminosity(input, (int) p1.longValue()); - @GetMapping(value = "/images/{id}?algorithm={X}&p1={Y}&p2={Z}", produces = MediaType.IMAGE_JPEG_VALUE || MediaType.IMAGE_PNG_VALUE) - public ResponseEntity applyAlgorithm(@PathVariable("id") long id, @PathVariable("X") string algorithm, @PathVariable("Y") int firstParam, @PathVariable("Z") int secondParam) { + int width = input.getWidth(); + int height = input.getHeight(); + int numChannels = input.getNumBands(); - Optional image = imageDao.retrieve(id); - if (image.isPresent()) { - if (algorithm.equals("changeLuminosity")){ - Optional changedImage = createSameShape(image); - ImageProcessing.changeLuminosity(changedImage, firstParam); - } - InputStream inputStream = new ByteArrayInputStream(changedImage.get().getData()); - return ResponseEntity.ok().contentType(MediaType.IMAGE_JPEG).body(new InputStreamResource(inputStream)); - } - return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); - } + // Allocate a byte array to hold the pixel data + byte[] imageData = new byte[width * height * numChannels]; + + // Iterate over the planes and copy the pixel data into the byte array + for (int i = 0; i < numChannels; i++) { + byte[] planeData = input.getBand(i).getData(); + System.arraycopy(planeData, 0, imageData, i * width * height, width * + height); + } + + InputStream inputStream = new ByteArrayInputStream(image.get().getData()); + // InputStream inputStream = new ByteArrayInputStream(imageData); + System.out.println("hi"); + return ResponseEntity.ok().contentType(image.get().getMediaType()) + .body(new InputStreamResource(inputStream)); + } + } + return new ResponseEntity<>("Image id=" + id + " not found.", + HttpStatus.NOT_FOUND); + } } diff --git a/backend/src/main/java/pdl/backend/ImageProcessing.java b/backend/src/main/java/pdl/backend/ImageProcessing.java index ac85693..a77fce2 100644 --- a/backend/src/main/java/pdl/backend/ImageProcessing.java +++ b/backend/src/main/java/pdl/backend/ImageProcessing.java @@ -1,10 +1,6 @@ package pdl.backend; -import java.awt.image.BufferedImage; -import boofcv.io.image.UtilImageIO; -import boofcv.io.image.ConvertBufferedImage; import boofcv.struct.image.GrayU8; -import boofcv.struct.border.BorderType; import boofcv.struct.image.Planar; public class ImageProcessing { @@ -13,9 +9,9 @@ public class ImageProcessing { * * 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 + * @param input L'image + * @param x La ligne + * @param y La colonne */ public static int[] getRGBValue(Planar input, int x, int y) { int[] rgb = new int[3]; @@ -29,10 +25,10 @@ public class ImageProcessing { * * 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 + * @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 input, int x, int y, int[] rgb) { for (int i = 0; i < rgb.length; i++) { @@ -127,7 +123,6 @@ public class ImageProcessing { } } - /** * * Passe un pixel de l'espace de couleurs RGB à l'espace de couleurs HSV : @@ -249,7 +244,7 @@ public class ImageProcessing { 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}; + int[] rgb = { gl, gl, gl }; setRGBValue(output, x, y, rgb); continue; } @@ -271,22 +266,19 @@ public class ImageProcessing { value = 0; if (value > 255) value = 255; - int[] rgb = {value, value, value}; + int[] rgb = { value, value, value }; setRGBValue(output, x, y, rgb); } } } - - - /** * Applique un filtre moyenneur de taille indiquée à l'image * passée en paramètre (le bord de l'image n'est pas modifié) * * @param input image d'entrée * @param output image de sortie - * @param size taille du filtre à appliquer + * @param size taille du filtre à appliquer */ public static void meanFilter(Planar input, Planar output, int size) { // Vérification que la taille est impaire @@ -372,23 +364,4 @@ public class ImageProcessing { } } } - - public static void main(String[] args) { - if (args.length < 2) { - System.out.println("missing input or output image filename"); - System.exit(-1); - } - final String inputPath = args[0]; - BufferedImage image = UtilImageIO.loadImage(inputPath); - Planar input = ConvertBufferedImage.convertFromPlanar(image, null, true, GrayU8.class); - Planar output = input.createSameShape(); - long beginning = System.nanoTime(); - histogram(input); - long end = System.nanoTime(); - System.out.println("Duration : " + ((end - beginning) / 1000000) + "ms"); - // save output image - final String outputPath = args[1]; - UtilImageIO.saveImage(input, outputPath); - System.out.println("Image saved in: " + outputPath); } - } From c0580364595676c8f022f7381d8625a89ddedbd5 Mon Sep 17 00:00:00 2001 From: keahorvath Date: Tue, 14 Mar 2023 21:16:30 +0100 Subject: [PATCH 05/10] algo changeLuminosity marche! Il faut faire les exceptions --- .../java/pdl/backend/ImageController.java | 71 +++++++++---------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/backend/src/main/java/pdl/backend/ImageController.java b/backend/src/main/java/pdl/backend/ImageController.java index b5b95b2..b00d884 100644 --- a/backend/src/main/java/pdl/backend/ImageController.java +++ b/backend/src/main/java/pdl/backend/ImageController.java @@ -1,13 +1,14 @@ package pdl.backend; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Optional; +import java.util.Map; import javax.imageio.ImageIO; -import javax.print.attribute.standard.Media; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -33,7 +34,6 @@ import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import java.awt.image.BufferedImage; -import java.awt.image.DataBufferInt; @RestController public class ImageController { @@ -93,52 +93,47 @@ public class ImageController { @GetMapping(value = "/images/{id}", produces = { MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE }) - public ResponseEntity applyAlgorithm(@PathVariable("id") long id, - @RequestParam(required = false) String algorithm, - @RequestParam(required = false) Long p1, @RequestParam(required = false) Long p2) { + public ResponseEntity getImage(@PathVariable("id") long id, + @RequestParam(required = false) Map parameters) { Optional image = imageDao.retrieve(id); if (image.isPresent()) { - if (algorithm == null) { + if (parameters.isEmpty()) { InputStream inputStream = new ByteArrayInputStream(image.get().getData()); return ResponseEntity.ok().contentType(image.get().getMediaType()) .body(new InputStreamResource(inputStream)); } - if (algorithm.equals("changeLuminosity")) { - BufferedImage bufImg; - try { - bufImg = ImageIO.read(new ByteArrayInputStream(image.get().getData())); - } catch (IOException e) { - return new ResponseEntity<>("Image not readable", - HttpStatus.INTERNAL_SERVER_ERROR); + if (parameters.containsKey("algorithm")) { + if (parameters.get("algorithm").equals("changeLuminosity") && parameters.containsKey("delta")) { + BufferedImage bufImg; + try { + bufImg = ImageIO.read(new ByteArrayInputStream(image.get().getData())); + } catch (IOException e) { + return new ResponseEntity<>("Image not readable", + HttpStatus.INTERNAL_SERVER_ERROR); + } + Planar input = ConvertBufferedImage.convertFromPlanar(bufImg, null, + true, GrayU8.class); + ImageProcessing.changeLuminosity(input, Integer.parseInt(parameters.get("delta"))); + bufImg = ConvertBufferedImage.convertTo_U8(input, null, true); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(bufImg, image.get().getMediaType().getSubtype(), baos); + + } catch (IOException e) { + return new ResponseEntity<>("Image couldn't be converted", + HttpStatus.INTERNAL_SERVER_ERROR); + } + byte[] imageData = baos.toByteArray(); + + InputStream inputStream = new ByteArrayInputStream(imageData); + return ResponseEntity.ok().contentType(image.get().getMediaType()) + .body(new InputStreamResource(inputStream)); } - Planar input = ConvertBufferedImage.convertFromPlanar(bufImg, null, - true, GrayU8.class); - ImageProcessing.changeLuminosity(input, (int) p1.longValue()); - - int width = input.getWidth(); - int height = input.getHeight(); - int numChannels = input.getNumBands(); - - // Allocate a byte array to hold the pixel data - byte[] imageData = new byte[width * height * numChannels]; - - // Iterate over the planes and copy the pixel data into the byte array - for (int i = 0; i < numChannels; i++) { - byte[] planeData = input.getBand(i).getData(); - System.arraycopy(planeData, 0, imageData, i * width * height, width * - height); - } - - InputStream inputStream = new ByteArrayInputStream(image.get().getData()); - // InputStream inputStream = new ByteArrayInputStream(imageData); - System.out.println("hi"); - return ResponseEntity.ok().contentType(image.get().getMediaType()) - .body(new InputStreamResource(inputStream)); } + } - return new ResponseEntity<>("Image id=" + id + " not found.", - HttpStatus.NOT_FOUND); + return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); } } From 435f8510feb0d43de61031a89fca0e3af552bd7b Mon Sep 17 00:00:00 2001 From: keahorvath Date: Wed, 15 Mar 2023 16:20:45 +0100 Subject: [PATCH 06/10] added all other functions (errors don't show up on screen). Still need to finish gaussenFilter --- .../java/pdl/backend/ImageController.java | 141 ++++++++++++++---- 1 file changed, 116 insertions(+), 25 deletions(-) diff --git a/backend/src/main/java/pdl/backend/ImageController.java b/backend/src/main/java/pdl/backend/ImageController.java index b00d884..8291ba9 100644 --- a/backend/src/main/java/pdl/backend/ImageController.java +++ b/backend/src/main/java/pdl/backend/ImageController.java @@ -105,32 +105,123 @@ public class ImageController { } if (parameters.containsKey("algorithm")) { - if (parameters.get("algorithm").equals("changeLuminosity") && parameters.containsKey("delta")) { - BufferedImage bufImg; - try { - bufImg = ImageIO.read(new ByteArrayInputStream(image.get().getData())); - } catch (IOException e) { - return new ResponseEntity<>("Image not readable", - HttpStatus.INTERNAL_SERVER_ERROR); - } - Planar input = ConvertBufferedImage.convertFromPlanar(bufImg, null, - true, GrayU8.class); - ImageProcessing.changeLuminosity(input, Integer.parseInt(parameters.get("delta"))); - bufImg = ConvertBufferedImage.convertTo_U8(input, null, true); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - ImageIO.write(bufImg, image.get().getMediaType().getSubtype(), baos); - - } catch (IOException e) { - return new ResponseEntity<>("Image couldn't be converted", - HttpStatus.INTERNAL_SERVER_ERROR); - } - byte[] imageData = baos.toByteArray(); - - InputStream inputStream = new ByteArrayInputStream(imageData); - return ResponseEntity.ok().contentType(image.get().getMediaType()) - .body(new InputStreamResource(inputStream)); + String algo = parameters.get("algorithm"); + BufferedImage bufImg; + try { + bufImg = ImageIO.read(new ByteArrayInputStream(image.get().getData())); + } catch (IOException e) { + return new ResponseEntity<>("Image not readable", + HttpStatus.INTERNAL_SERVER_ERROR); } + Planar input = ConvertBufferedImage.convertFromPlanar(bufImg, null, + true, GrayU8.class); + if (algo.equals("changeLuminosity")) { + if (parameters.containsKey("delta") + && parameters.size() == 2) { + try { + int i = Integer.parseInt(parameters.get("delta")); + ImageProcessing.changeLuminosity(input, i); + } catch (NumberFormatException e) { + return new ResponseEntity<>("Parameter 'delta' should be an integer", + HttpStatus.BAD_REQUEST); + } + } else { + return new ResponseEntity<>( + "Algorithm 'changeLuminosity' requires one parameter 'delta'(integer)", + HttpStatus.BAD_REQUEST); + } + } else if (algo.equals("histogram")) { + if (parameters.size() == 1) { + ImageProcessing.histogram(input); + } else { + return new ResponseEntity<>( + "Algorithm 'histogram' requires no parameters", + HttpStatus.BAD_REQUEST); + } + } else if (algo.equals("colorFilter")) { + if (parameters.containsKey("hue") && parameters.size() == 2) { + try { + int i = Integer.parseInt(parameters.get("hue")); + ImageProcessing.colorFilter(i, input); + // hue entre 0 et 360? + } catch (NumberFormatException e) { + return new ResponseEntity<>("Parameter 'hue' should be an integer", + HttpStatus.BAD_REQUEST); + } + } else { + return new ResponseEntity<>( + "Algorithm 'colorFilter' requires one parameter 'hue'(integer)", + HttpStatus.BAD_REQUEST); + } + } else if (algo.equals("meanFilter")) { + if (parameters.containsKey("size") && parameters.size() == 2) { + try { + int i = Integer.parseInt(parameters.get("size")); + if (i % 2 == 0) { + return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", + HttpStatus.BAD_REQUEST); + } + Planar clone = input.clone(); + ImageProcessing.meanFilter(clone, input, i); + } catch (NumberFormatException e) { + return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", + HttpStatus.BAD_REQUEST); + } + } else { + return new ResponseEntity<>( + "Algorithm 'meanFilter' requires one parameter 'size'(odd positive integer)", + HttpStatus.BAD_REQUEST); + } + } else if (algo.equals("gaussienFilter")) { + if (parameters.containsKey("size") && parameters.size() == 2) { + try { + int i = Integer.parseInt(parameters.get("size")); + if (i % 2 == 0) { + return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", + HttpStatus.BAD_REQUEST); + } + Planar clone = input.clone(); + ImageProcessing.gaussienFilter(clone, input, null); + } catch (NumberFormatException e) { + return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", + HttpStatus.BAD_REQUEST); + } + } else { + return new ResponseEntity<>( + "Algorithm 'gaussienFilter' requires one parameter 'size'(odd positive integer)", + HttpStatus.BAD_REQUEST); + } + } else if (algo.equals("contours")) { + if (parameters.size() == 1) { + Planar clone = input.clone(); + ImageProcessing.gradientImageSobel(clone, input); + } else { + return new ResponseEntity<>( + "Algorithm 'contours' requires no parameters", + HttpStatus.BAD_REQUEST); + } + } else { + return new ResponseEntity<>( + "The requested algorithm '" + parameters.get("algorithm") + "' doesn't exist", + HttpStatus.BAD_REQUEST); + } + bufImg = ConvertBufferedImage.convertTo_U8(input, null, true); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(bufImg, image.get().getMediaType().getSubtype(), baos); + + } catch (IOException e) { + return new ResponseEntity<>("Image couldn't be converted", + HttpStatus.INTERNAL_SERVER_ERROR); + } + byte[] imageData = baos.toByteArray(); + + InputStream inputStream = new ByteArrayInputStream(imageData); + return ResponseEntity.ok().contentType(image.get().getMediaType()) + .body(new InputStreamResource(inputStream)); + } else { + return new ResponseEntity<>("First parameter should be 'algorithm", + HttpStatus.BAD_REQUEST); } } From 50bb009e0da3dd2c5a713aa3160f49e2edc62d41 Mon Sep 17 00:00:00 2001 From: keahorvath Date: Wed, 15 Mar 2023 17:10:40 +0100 Subject: [PATCH 07/10] gaussienFilter ne change pas encore selon la taille --> A faire --- .../java/pdl/backend/ImageController.java | 2 +- .../java/pdl/backend/ImageProcessing.java | 35 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/pdl/backend/ImageController.java b/backend/src/main/java/pdl/backend/ImageController.java index 8291ba9..8c7a53d 100644 --- a/backend/src/main/java/pdl/backend/ImageController.java +++ b/backend/src/main/java/pdl/backend/ImageController.java @@ -181,7 +181,7 @@ public class ImageController { HttpStatus.BAD_REQUEST); } Planar clone = input.clone(); - ImageProcessing.gaussienFilter(clone, input, null); + ImageProcessing.gaussienFilter(clone, input, i); } catch (NumberFormatException e) { return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", HttpStatus.BAD_REQUEST); diff --git a/backend/src/main/java/pdl/backend/ImageProcessing.java b/backend/src/main/java/pdl/backend/ImageProcessing.java index a77fce2..20b7cf7 100644 --- a/backend/src/main/java/pdl/backend/ImageProcessing.java +++ b/backend/src/main/java/pdl/backend/ImageProcessing.java @@ -314,6 +314,36 @@ public class ImageProcessing { } + /** + * TODO + * Calcule le noyau gaussien de taille demandée + * + * @param size taille du noyau gaussien + * @return noyau gaussien + */ + public static int[][] createGaussienKernel(int size) { + // 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]; + double sigma = 4 / 3; + double sum = 0.0; // For accumulating the kernel values + for (int x = 0; x < size; ++x) + for (int y = 0; y < size; ++y) { + kernel[x][y] = 1 / (2 * Math.PI * sigma * sigma) * Math.exp(-(x * x + y * y) / (2 * sigma * sigma)); + + } + + // Normalize the kernel + int[][] kernelk = new int[size][size]; + for (int x = 0; x < size; ++x) + for (int y = 0; y < size; ++y) + kernelk[x][y] = (int) (kernel[x][y] / sum); + + return kernelk; + } + /** * * Effectue une convolution sur une image avec le noyau @@ -324,8 +354,9 @@ public class ImageProcessing { * @param kernel Le noyau de convolution à utiliser. */ public static void gaussienFilter(Planar input, Planar output, - int[][] kernel) { - int size = kernel.length; + int size) { + int[][] kernel = { { 1, 2, 3, 2, 1 }, { 2, 6, 8, 6, 2 }, { 3, 8, 10, 8, 3 }, { 2, 6, 8, 6, 2 }, + { 1, 2, 3, 2, 1 } }; int half = size / 2; int totalCoefs = 0; // Calcul du nombre total de coefficients dans le noyau From aa9229f432a3b17861009e95833f8af1316f2815 Mon Sep 17 00:00:00 2001 From: Theophyl Hoguet Date: Wed, 15 Mar 2023 18:21:11 +0100 Subject: [PATCH 08/10] tab --- .../java/pdl/backend/ImageController.java | 350 +++++++++--------- 1 file changed, 175 insertions(+), 175 deletions(-) diff --git a/backend/src/main/java/pdl/backend/ImageController.java b/backend/src/main/java/pdl/backend/ImageController.java index 8c7a53d..9ba86f1 100644 --- a/backend/src/main/java/pdl/backend/ImageController.java +++ b/backend/src/main/java/pdl/backend/ImageController.java @@ -38,193 +38,193 @@ import java.awt.image.BufferedImage; @RestController public class ImageController { - @Autowired - private ObjectMapper mapper; + @Autowired + private ObjectMapper mapper; - private final ImageDao imageDao; + private final ImageDao imageDao; - public ImageController(ImageDao imageDao) { - this.imageDao = imageDao; - } + public ImageController(ImageDao imageDao) { + this.imageDao = imageDao; + } - @DeleteMapping(value = "/images/{id}") - public ResponseEntity deleteImage(@PathVariable("id") long id) { + @DeleteMapping(value = "/images/{id}") + public ResponseEntity deleteImage(@PathVariable("id") long id) { - Optional image = imageDao.retrieve(id); + Optional image = imageDao.retrieve(id); - if (image.isPresent()) { - imageDao.delete(image.get()); - return new ResponseEntity<>("Image id=" + id + " deleted.", HttpStatus.OK); - } - return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); - } + if (image.isPresent()) { + imageDao.delete(image.get()); + return new ResponseEntity<>("Image id=" + id + " deleted.", HttpStatus.OK); + } + return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); + } - @PostMapping(value = "/images") - public ResponseEntity addImage(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { + @PostMapping(value = "/images") + public ResponseEntity addImage(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { - String contentType = file.getContentType(); - if (contentType != null && !contentType.equals(MediaType.IMAGE_JPEG.toString()) - && !contentType.equals(MediaType.IMAGE_PNG.toString())) { - return new ResponseEntity<>("Only JPEG/PNG file format supported", HttpStatus.UNSUPPORTED_MEDIA_TYPE); - } - try { - imageDao.create(new Image(file.getOriginalFilename(), file.getBytes())); - } catch (IOException e) { - return new ResponseEntity<>("Failure to read file", HttpStatus.NO_CONTENT); - } - return new ResponseEntity<>("Image uploaded", HttpStatus.CREATED); - } + String contentType = file.getContentType(); + if (contentType != null && !contentType.equals(MediaType.IMAGE_JPEG.toString()) + && !contentType.equals(MediaType.IMAGE_PNG.toString())) { + return new ResponseEntity<>("Only JPEG/PNG file format supported", HttpStatus.UNSUPPORTED_MEDIA_TYPE); + } + try { + imageDao.create(new Image(file.getOriginalFilename(), file.getBytes())); + } catch (IOException e) { + return new ResponseEntity<>("Failure to read file", HttpStatus.NO_CONTENT); + } + return new ResponseEntity<>("Image uploaded", HttpStatus.CREATED); + } - @GetMapping(value = "/images", produces = "application/json") - @ResponseBody - public ArrayNode getImageList() { - List images = imageDao.retrieveAll(); - ArrayNode nodes = mapper.createArrayNode(); - for (Image image : images) { - ObjectNode objectNode = mapper.createObjectNode(); - objectNode.put("id", image.getId()); - objectNode.put("name", image.getName()); - objectNode.put("size", image.getSize()); - objectNode.put("type", image.getMediaType().toString()); - nodes.add(objectNode); - } - return nodes; - } + @GetMapping(value = "/images", produces = "application/json") + @ResponseBody + public ArrayNode getImageList() { + List images = imageDao.retrieveAll(); + ArrayNode nodes = mapper.createArrayNode(); + for (Image image : images) { + ObjectNode objectNode = mapper.createObjectNode(); + objectNode.put("id", image.getId()); + objectNode.put("name", image.getName()); + objectNode.put("size", image.getSize()); + objectNode.put("type", image.getMediaType().toString()); + nodes.add(objectNode); + } + return nodes; + } - @GetMapping(value = "/images/{id}", produces = { MediaType.IMAGE_JPEG_VALUE, - MediaType.IMAGE_PNG_VALUE }) - public ResponseEntity getImage(@PathVariable("id") long id, - @RequestParam(required = false) Map parameters) { + @GetMapping(value = "/images/{id}", produces = { MediaType.IMAGE_JPEG_VALUE, + MediaType.IMAGE_PNG_VALUE }) + public ResponseEntity getImage(@PathVariable("id") long id, + @RequestParam(required = false) Map parameters) { - Optional image = imageDao.retrieve(id); - if (image.isPresent()) { - if (parameters.isEmpty()) { - InputStream inputStream = new ByteArrayInputStream(image.get().getData()); - return ResponseEntity.ok().contentType(image.get().getMediaType()) - .body(new InputStreamResource(inputStream)); + Optional image = imageDao.retrieve(id); + if (image.isPresent()) { + if (parameters.isEmpty()) { + InputStream inputStream = new ByteArrayInputStream(image.get().getData()); + return ResponseEntity.ok().contentType(image.get().getMediaType()) + .body(new InputStreamResource(inputStream)); - } - if (parameters.containsKey("algorithm")) { - String algo = parameters.get("algorithm"); - BufferedImage bufImg; - try { - bufImg = ImageIO.read(new ByteArrayInputStream(image.get().getData())); - } catch (IOException e) { - return new ResponseEntity<>("Image not readable", - HttpStatus.INTERNAL_SERVER_ERROR); - } - Planar input = ConvertBufferedImage.convertFromPlanar(bufImg, null, - true, GrayU8.class); - if (algo.equals("changeLuminosity")) { - if (parameters.containsKey("delta") - && parameters.size() == 2) { - try { - int i = Integer.parseInt(parameters.get("delta")); - ImageProcessing.changeLuminosity(input, i); - } catch (NumberFormatException e) { - return new ResponseEntity<>("Parameter 'delta' should be an integer", - HttpStatus.BAD_REQUEST); - } - } else { - return new ResponseEntity<>( - "Algorithm 'changeLuminosity' requires one parameter 'delta'(integer)", - HttpStatus.BAD_REQUEST); - } - } else if (algo.equals("histogram")) { - if (parameters.size() == 1) { - ImageProcessing.histogram(input); - } else { - return new ResponseEntity<>( - "Algorithm 'histogram' requires no parameters", - HttpStatus.BAD_REQUEST); - } - } else if (algo.equals("colorFilter")) { - if (parameters.containsKey("hue") && parameters.size() == 2) { - try { - int i = Integer.parseInt(parameters.get("hue")); - ImageProcessing.colorFilter(i, input); - // hue entre 0 et 360? - } catch (NumberFormatException e) { - return new ResponseEntity<>("Parameter 'hue' should be an integer", - HttpStatus.BAD_REQUEST); - } - } else { - return new ResponseEntity<>( - "Algorithm 'colorFilter' requires one parameter 'hue'(integer)", - HttpStatus.BAD_REQUEST); - } - } else if (algo.equals("meanFilter")) { - if (parameters.containsKey("size") && parameters.size() == 2) { - try { - int i = Integer.parseInt(parameters.get("size")); - if (i % 2 == 0) { - return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", - HttpStatus.BAD_REQUEST); - } - Planar clone = input.clone(); - ImageProcessing.meanFilter(clone, input, i); - } catch (NumberFormatException e) { - return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", - HttpStatus.BAD_REQUEST); - } - } else { - return new ResponseEntity<>( - "Algorithm 'meanFilter' requires one parameter 'size'(odd positive integer)", - HttpStatus.BAD_REQUEST); - } - } else if (algo.equals("gaussienFilter")) { - if (parameters.containsKey("size") && parameters.size() == 2) { - try { - int i = Integer.parseInt(parameters.get("size")); - if (i % 2 == 0) { - return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", - HttpStatus.BAD_REQUEST); - } - Planar clone = input.clone(); - ImageProcessing.gaussienFilter(clone, input, i); - } catch (NumberFormatException e) { - return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", - HttpStatus.BAD_REQUEST); - } - } else { - return new ResponseEntity<>( - "Algorithm 'gaussienFilter' requires one parameter 'size'(odd positive integer)", - HttpStatus.BAD_REQUEST); - } - } else if (algo.equals("contours")) { - if (parameters.size() == 1) { - Planar clone = input.clone(); - ImageProcessing.gradientImageSobel(clone, input); - } else { - return new ResponseEntity<>( - "Algorithm 'contours' requires no parameters", - HttpStatus.BAD_REQUEST); - } - } else { - return new ResponseEntity<>( - "The requested algorithm '" + parameters.get("algorithm") + "' doesn't exist", - HttpStatus.BAD_REQUEST); - } - bufImg = ConvertBufferedImage.convertTo_U8(input, null, true); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - ImageIO.write(bufImg, image.get().getMediaType().getSubtype(), baos); + } + if (parameters.containsKey("algorithm")) { + String algo = parameters.get("algorithm"); + BufferedImage bufImg; + try { + bufImg = ImageIO.read(new ByteArrayInputStream(image.get().getData())); + } catch (IOException e) { + return new ResponseEntity<>("Image not readable", + HttpStatus.INTERNAL_SERVER_ERROR); + } + Planar input = ConvertBufferedImage.convertFromPlanar(bufImg, null, + true, GrayU8.class); + if (algo.equals("changeLuminosity")) { + if (parameters.containsKey("delta") + && parameters.size() == 2) { + try { + int i = Integer.parseInt(parameters.get("delta")); + ImageProcessing.changeLuminosity(input, i); + } catch (NumberFormatException e) { + return new ResponseEntity<>("Parameter 'delta' should be an integer", + HttpStatus.BAD_REQUEST); + } + } else { + return new ResponseEntity<>( + "Algorithm 'changeLuminosity' requires one parameter 'delta'(integer)", + HttpStatus.BAD_REQUEST); + } + } else if (algo.equals("histogram")) { + if (parameters.size() == 1) { + ImageProcessing.histogram(input); + } else { + return new ResponseEntity<>( + "Algorithm 'histogram' requires no parameters", + HttpStatus.BAD_REQUEST); + } + } else if (algo.equals("colorFilter")) { + if (parameters.containsKey("hue") && parameters.size() == 2) { + try { + int i = Integer.parseInt(parameters.get("hue")); + ImageProcessing.colorFilter(i, input); + // hue entre 0 et 360? + } catch (NumberFormatException e) { + return new ResponseEntity<>("Parameter 'hue' should be an integer", + HttpStatus.BAD_REQUEST); + } + } else { + return new ResponseEntity<>( + "Algorithm 'colorFilter' requires one parameter 'hue'(integer)", + HttpStatus.BAD_REQUEST); + } + } else if (algo.equals("meanFilter")) { + if (parameters.containsKey("size") && parameters.size() == 2) { + try { + int i = Integer.parseInt(parameters.get("size")); + if (i % 2 == 0) { + return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", + HttpStatus.BAD_REQUEST); + } + Planar clone = input.clone(); + ImageProcessing.meanFilter(clone, input, i); + } catch (NumberFormatException e) { + return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", + HttpStatus.BAD_REQUEST); + } + } else { + return new ResponseEntity<>( + "Algorithm 'meanFilter' requires one parameter 'size'(odd positive integer)", + HttpStatus.BAD_REQUEST); + } + } else if (algo.equals("gaussienFilter")) { + if (parameters.containsKey("size") && parameters.size() == 2) { + try { + int i = Integer.parseInt(parameters.get("size")); + if (i % 2 == 0) { + return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", + HttpStatus.BAD_REQUEST); + } + Planar clone = input.clone(); + ImageProcessing.gaussienFilter(clone, input, i); + } catch (NumberFormatException e) { + return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", + HttpStatus.BAD_REQUEST); + } + } else { + return new ResponseEntity<>( + "Algorithm 'gaussienFilter' requires one parameter 'size'(odd positive integer)", + HttpStatus.BAD_REQUEST); + } + } else if (algo.equals("contours")) { + if (parameters.size() == 1) { + Planar clone = input.clone(); + ImageProcessing.gradientImageSobel(clone, input); + } else { + return new ResponseEntity<>( + "Algorithm 'contours' requires no parameters", + HttpStatus.BAD_REQUEST); + } + } else { + return new ResponseEntity<>( + "The requested algorithm '" + parameters.get("algorithm") + "' doesn't exist", + HttpStatus.BAD_REQUEST); + } + bufImg = ConvertBufferedImage.convertTo_U8(input, null, true); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(bufImg, image.get().getMediaType().getSubtype(), baos); - } catch (IOException e) { - return new ResponseEntity<>("Image couldn't be converted", - HttpStatus.INTERNAL_SERVER_ERROR); - } - byte[] imageData = baos.toByteArray(); + } catch (IOException e) { + return new ResponseEntity<>("Image couldn't be converted", + HttpStatus.INTERNAL_SERVER_ERROR); + } + byte[] imageData = baos.toByteArray(); - InputStream inputStream = new ByteArrayInputStream(imageData); - return ResponseEntity.ok().contentType(image.get().getMediaType()) - .body(new InputStreamResource(inputStream)); - } else { - return new ResponseEntity<>("First parameter should be 'algorithm", - HttpStatus.BAD_REQUEST); - } + InputStream inputStream = new ByteArrayInputStream(imageData); + return ResponseEntity.ok().contentType(image.get().getMediaType()) + .body(new InputStreamResource(inputStream)); + } else { + return new ResponseEntity<>("First parameter should be 'algorithm", + HttpStatus.BAD_REQUEST); + } - } - return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); - } + } + return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); + } } From 11b64ebd9b718eb94dfa0a476e41a1b279cbea7e Mon Sep 17 00:00:00 2001 From: Theophyl Hoguet Date: Wed, 15 Mar 2023 19:43:34 +0100 Subject: [PATCH 09/10] changes + add old code --- .../java/pdl/backend/ImageController.java | 272 ++++++++---------- 1 file changed, 123 insertions(+), 149 deletions(-) diff --git a/backend/src/main/java/pdl/backend/ImageController.java b/backend/src/main/java/pdl/backend/ImageController.java index 9ba86f1..af937cb 100644 --- a/backend/src/main/java/pdl/backend/ImageController.java +++ b/backend/src/main/java/pdl/backend/ImageController.java @@ -4,6 +4,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.util.List; import java.util.Optional; import java.util.Map; @@ -32,6 +33,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.awt.image.BufferedImage; @@ -49,30 +51,32 @@ public class ImageController { @DeleteMapping(value = "/images/{id}") public ResponseEntity deleteImage(@PathVariable("id") long id) { - - Optional image = imageDao.retrieve(id); - - if (image.isPresent()) { - imageDao.delete(image.get()); - return new ResponseEntity<>("Image id=" + id + " deleted.", HttpStatus.OK); - } - return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); + var img = imageDao.retrieve(id); + if (img.isEmpty()) + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + imageDao.delete(img.get()); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); } - @PostMapping(value = "/images") + @PostMapping(value = "/images", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity addImage(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { - String contentType = file.getContentType(); - if (contentType != null && !contentType.equals(MediaType.IMAGE_JPEG.toString()) - && !contentType.equals(MediaType.IMAGE_PNG.toString())) { - return new ResponseEntity<>("Only JPEG/PNG file format supported", HttpStatus.UNSUPPORTED_MEDIA_TYPE); - } + if (contentType == null + || (!contentType.equals(MediaType.IMAGE_JPEG_VALUE) && !contentType.equals(MediaType.IMAGE_PNG_VALUE))) + return new ResponseEntity<>(HttpStatus.UNSUPPORTED_MEDIA_TYPE); try { - imageDao.create(new Image(file.getOriginalFilename(), file.getBytes())); + Image newImg = new Image(file.getOriginalFilename(), file.getBytes()); + imageDao.create(newImg); + URI newURI = ServletUriComponentsBuilder.fromCurrentRequest().path("/" + newImg.getId()).build().toUri(); + return ResponseEntity.created(newURI).build(); } catch (IOException e) { - return new ResponseEntity<>("Failure to read file", HttpStatus.NO_CONTENT); + return new ResponseEntity<>(HttpStatus.NOT_MODIFIED); } - return new ResponseEntity<>("Image uploaded", HttpStatus.CREATED); + } + + @DeleteMapping(value = "/images") + public ResponseEntity deleteImagesList() { + return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); } @GetMapping(value = "/images", produces = "application/json") @@ -91,140 +95,110 @@ public class ImageController { return nodes; } - @GetMapping(value = "/images/{id}", produces = { MediaType.IMAGE_JPEG_VALUE, - MediaType.IMAGE_PNG_VALUE }) + @GetMapping(value = "/images/{id}", produces = { MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE }) public ResponseEntity getImage(@PathVariable("id") long id, @RequestParam(required = false) Map parameters) { - Optional image = imageDao.retrieve(id); - if (image.isPresent()) { - if (parameters.isEmpty()) { - InputStream inputStream = new ByteArrayInputStream(image.get().getData()); - return ResponseEntity.ok().contentType(image.get().getMediaType()) - .body(new InputStreamResource(inputStream)); - - } - if (parameters.containsKey("algorithm")) { - String algo = parameters.get("algorithm"); - BufferedImage bufImg; - try { - bufImg = ImageIO.read(new ByteArrayInputStream(image.get().getData())); - } catch (IOException e) { - return new ResponseEntity<>("Image not readable", - HttpStatus.INTERNAL_SERVER_ERROR); - } - Planar input = ConvertBufferedImage.convertFromPlanar(bufImg, null, - true, GrayU8.class); - if (algo.equals("changeLuminosity")) { - if (parameters.containsKey("delta") - && parameters.size() == 2) { - try { - int i = Integer.parseInt(parameters.get("delta")); - ImageProcessing.changeLuminosity(input, i); - } catch (NumberFormatException e) { - return new ResponseEntity<>("Parameter 'delta' should be an integer", - HttpStatus.BAD_REQUEST); - } - } else { - return new ResponseEntity<>( - "Algorithm 'changeLuminosity' requires one parameter 'delta'(integer)", - HttpStatus.BAD_REQUEST); - } - } else if (algo.equals("histogram")) { - if (parameters.size() == 1) { - ImageProcessing.histogram(input); - } else { - return new ResponseEntity<>( - "Algorithm 'histogram' requires no parameters", - HttpStatus.BAD_REQUEST); - } - } else if (algo.equals("colorFilter")) { - if (parameters.containsKey("hue") && parameters.size() == 2) { - try { - int i = Integer.parseInt(parameters.get("hue")); - ImageProcessing.colorFilter(i, input); - // hue entre 0 et 360? - } catch (NumberFormatException e) { - return new ResponseEntity<>("Parameter 'hue' should be an integer", - HttpStatus.BAD_REQUEST); - } - } else { - return new ResponseEntity<>( - "Algorithm 'colorFilter' requires one parameter 'hue'(integer)", - HttpStatus.BAD_REQUEST); - } - } else if (algo.equals("meanFilter")) { - if (parameters.containsKey("size") && parameters.size() == 2) { - try { - int i = Integer.parseInt(parameters.get("size")); - if (i % 2 == 0) { - return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", - HttpStatus.BAD_REQUEST); - } - Planar clone = input.clone(); - ImageProcessing.meanFilter(clone, input, i); - } catch (NumberFormatException e) { - return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", - HttpStatus.BAD_REQUEST); - } - } else { - return new ResponseEntity<>( - "Algorithm 'meanFilter' requires one parameter 'size'(odd positive integer)", - HttpStatus.BAD_REQUEST); - } - } else if (algo.equals("gaussienFilter")) { - if (parameters.containsKey("size") && parameters.size() == 2) { - try { - int i = Integer.parseInt(parameters.get("size")); - if (i % 2 == 0) { - return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", - HttpStatus.BAD_REQUEST); - } - Planar clone = input.clone(); - ImageProcessing.gaussienFilter(clone, input, i); - } catch (NumberFormatException e) { - return new ResponseEntity<>("Parameter 'size' should be an odd positive integer", - HttpStatus.BAD_REQUEST); - } - } else { - return new ResponseEntity<>( - "Algorithm 'gaussienFilter' requires one parameter 'size'(odd positive integer)", - HttpStatus.BAD_REQUEST); - } - } else if (algo.equals("contours")) { - if (parameters.size() == 1) { - Planar clone = input.clone(); - ImageProcessing.gradientImageSobel(clone, input); - } else { - return new ResponseEntity<>( - "Algorithm 'contours' requires no parameters", - HttpStatus.BAD_REQUEST); - } - } else { - return new ResponseEntity<>( - "The requested algorithm '" + parameters.get("algorithm") + "' doesn't exist", - HttpStatus.BAD_REQUEST); - } - bufImg = ConvertBufferedImage.convertTo_U8(input, null, true); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - ImageIO.write(bufImg, image.get().getMediaType().getSubtype(), baos); - - } catch (IOException e) { - return new ResponseEntity<>("Image couldn't be converted", - HttpStatus.INTERNAL_SERVER_ERROR); - } - byte[] imageData = baos.toByteArray(); - - InputStream inputStream = new ByteArrayInputStream(imageData); - return ResponseEntity.ok().contentType(image.get().getMediaType()) - .body(new InputStreamResource(inputStream)); - } else { - return new ResponseEntity<>("First parameter should be 'algorithm", - HttpStatus.BAD_REQUEST); - } - + // Récupère l'image correspondante à l'ID fourni + Optional optImage = imageDao.retrieve(id); + if (!optImage.isPresent()) + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + Image image = optImage.get(); + // Si aucun paramètre n'est fourni, renvoie l'image brute + if (parameters.isEmpty()) { + InputStream inputStream = new ByteArrayInputStream(image.getData()); + return ResponseEntity.ok().contentType(image.getMediaType()) + .body(new InputStreamResource(inputStream)); } - return new ResponseEntity<>("Image id=" + id + " not found.", HttpStatus.NOT_FOUND); + // Vérifie la présence du paramètre "algorithm" + if (!parameters.containsKey("algorithm")) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + + String algo = parameters.get("algorithm"); + BufferedImage bufImg; + try { + bufImg = ImageIO.read(new ByteArrayInputStream(image.getData())); + } catch (IOException e) { + System.out.println("Error while reading image"); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + Planar input = ConvertBufferedImage.convertFromPlanar(bufImg, null, true, GrayU8.class); + + // applique l'algo a l'image en fonction des parametres et verifie la validite + // des parametres + switch (algo) { + case "changeLuminosity": + if (!parameters.containsKey("delta") || parameters.size() != 2) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + try { + int i = Integer.parseInt(parameters.get("delta")); + ImageProcessing.changeLuminosity(input, i); + } catch (NumberFormatException e) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + break; + case "histogram": + if (parameters.size() != 1) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + ImageProcessing.histogram(input); + break; + case "colorFilter": + if (!parameters.containsKey("hue") || parameters.size() != 2) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + try { + int i = Integer.parseInt(parameters.get("hue")); + ImageProcessing.colorFilter(i, input); + // hue entre 0 et 360? + } catch (NumberFormatException e) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + break; + case "meanFilter": + if (!parameters.containsKey("size") || parameters.size() != 2) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + try { + int i = Integer.parseInt(parameters.get("size")); + if (i % 2 == 0) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + Planar clone = input.clone(); + ImageProcessing.meanFilter(clone, input, i); + } catch (NumberFormatException e) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + break; + case "gaussienFilter": + if (!parameters.containsKey("size") || parameters.size() != 2) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + try { + int i = Integer.parseInt(parameters.get("size")); + if (i % 2 == 0) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + Planar clone = input.clone(); + ImageProcessing.gaussienFilter(clone, input, i); + } catch (NumberFormatException e) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + break; + case "contours": + if (parameters.size() != 1) + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + Planar clone = input.clone(); + ImageProcessing.gradientImageSobel(clone, input); + break; + default: + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + // transforme l'image en quelque chose que le navigateur puisse lire + bufImg = ConvertBufferedImage.convertTo_U8(input, null, true); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(bufImg, image.getMediaType().getSubtype(), baos); + } catch (IOException e) { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + byte[] imageData = baos.toByteArray(); + InputStream inputStream = new ByteArrayInputStream(imageData); + return ResponseEntity.ok().contentType(image.getMediaType()).body(new InputStreamResource(inputStream)); } -} +} \ No newline at end of file From 5e29da0d210c56d45d21ef61299eefd2fcf50936 Mon Sep 17 00:00:00 2001 From: Theophyl Hoguet Date: Wed, 15 Mar 2023 19:47:43 +0100 Subject: [PATCH 10/10] edit test to match new requests --- backend/src/test/java/pdl/backend/ImageControllerTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/test/java/pdl/backend/ImageControllerTests.java b/backend/src/test/java/pdl/backend/ImageControllerTests.java index 5540a97..da8d9a6 100644 --- a/backend/src/test/java/pdl/backend/ImageControllerTests.java +++ b/backend/src/test/java/pdl/backend/ImageControllerTests.java @@ -69,7 +69,7 @@ public class ImageControllerTests { @Test @Order(6) public void deleteImageShouldReturnSuccess() throws Exception { - this.mockMvc.perform(delete("/images/0")).andDo(print()).andExpect(status().isOk()); + this.mockMvc.perform(delete("/images/0")).andDo(print()).andExpect(status().isNoContent()); } @Test