From f258d62a1f3c5aaccde870a8075028c7bcc0c856 Mon Sep 17 00:00:00 2001 From: aoezyetimoglu Date: Wed, 3 Nov 2021 15:01:41 +0100 Subject: [PATCH 1/2] RED-2414 Wrong scaling for Images in Reports - not fixed excel part --- .../ExcelTemplateReportGenerationService.java | 161 ++++++++++++++++-- .../service/WordReportGenerationService.java | 18 +- .../RedactionReportIntegrationTest.java | 10 +- .../test/resources/templates/TestReport.xlsx | Bin 0 -> 4966 bytes 4 files changed, 172 insertions(+), 17 deletions(-) create mode 100644 redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/TestReport.xlsx diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelTemplateReportGenerationService.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelTemplateReportGenerationService.java index 7b28342..0e0a947 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelTemplateReportGenerationService.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelTemplateReportGenerationService.java @@ -14,9 +14,14 @@ import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.*; +import org.apache.poi.util.Dimension2DDouble; +import org.apache.poi.util.Units; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.stereotype.Service; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -27,6 +32,8 @@ import java.util.regex.Pattern; import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.*; +import javax.imageio.ImageIO; + @Slf4j @Service @RequiredArgsConstructor @@ -232,30 +239,31 @@ public class ExcelTemplateReportGenerationService { for (Cell cell : row) { if (cell.getStringCellValue().contains(imagePlaceholder.getPlaceholder())) { try (ByteArrayInputStream is = new ByteArrayInputStream(imagePlaceholder.getImage())) { - int pictureIdx = workbook.addPicture(is, XSSFWorkbook.PICTURE_TYPE_JPEG); + + ByteArrayInputStream scaledImage = getScaledImage(is, sheet.getColumnWidthInPixels(cell.getColumnIndex()), convertPointsToPixel(cell.getRow().getHeightInPoints())); + is.reset(); + + int pictureIdx = workbook.addPicture(scaledImage, XSSFWorkbook.PICTURE_TYPE_JPEG); +// scaledImage.reset(); + //Returns an object that handles instantiating concrete classes CreationHelper helper = workbook.getCreationHelper(); //Creates the top-level drawing patriarch. - Drawing drawing = sheet.createDrawingPatriarch(); //Create an anchor that is attached to the worksheet ClientAnchor anchor = helper.createClientAnchor(); +// anchor.setAnchorType(ClientAnchor.AnchorType.DONT_MOVE_AND_RESIZE); anchor.setCol1(cell.getColumnIndex()); anchor.setRow1(cell.getRowIndex()); anchor.setCol2(cell.getColumnIndex() + 1); anchor.setRow2(cell.getRowIndex() + 1); + Drawing drawing = sheet.createDrawingPatriarch(); drawing.createPicture(anchor, pictureIdx); - //set width to n character widths = count characters * 256 - int widthUnits = 6 * 5 * 256; - sheet.setColumnWidth(cell.getColumnIndex(), widthUnits); + //sheet.setColumnWidth(cell.getColumnIndex(), (int) dim.getWidth()); - //set height to n points in twips = n * 20 - short heightUnits = 6 * 20; - cell.getRow().setHeightInPoints(heightUnits); - -// pict.resize(); +// cell.getRow().setHeight((short) dim.getHeight()); cell.setCellValue(""); } @@ -265,6 +273,138 @@ public class ExcelTemplateReportGenerationService { } + private float convertPointsToPixel(float points) { + return points/0.75f; + } + + + private ByteArrayInputStream getScaledImage(ByteArrayInputStream imageByteArrayInputStream, float cellWidth, float cellHeight) throws IOException { + BufferedImage image = ImageIO.read(imageByteArrayInputStream); + + double imageWidth = image.getWidth(); + double imageHeight = image.getHeight(); + + double imageWidthScaleFactor; + double imageHeightScaleFactor; + + double imageRatio = imageWidth/imageHeight; + +// if(cellRatio <= 1 && imageRatio <=1) { +// // cell and image are landscape +// +// +// } else if(cellRatio <= 1 && imageRatio <=1) { +// // cell and image are portrait +// +// } else if(cellRatio <= 1 && imageRatio <=1) { +// // cell is landscape and image is portrait +// +// } else if(cellRatio <= 1 && imageRatio <=1) { +// // cell is portrait and image is landscape +// +// } + + AffineTransform at = new AffineTransform(); + + imageWidthScaleFactor = cellWidth / imageWidth; + imageHeightScaleFactor = cellHeight / imageHeight; + + if(imageWidthScaleFactor <= 1 && imageHeightScaleFactor > 1) { + imageWidth = imageWidth * imageWidthScaleFactor; + imageHeight = imageHeight * imageWidthScaleFactor; + + at.scale(imageWidthScaleFactor, imageRatio); + + } else if(imageWidthScaleFactor > 1 && imageHeightScaleFactor <= 1) { + imageHeight = imageHeight * imageHeightScaleFactor; + imageWidth = imageWidth * imageHeightScaleFactor; + + at.scale(imageHeightScaleFactor, imageHeightScaleFactor/imageWidthScaleFactor); + + } else if(imageWidthScaleFactor <= 1 && imageHeightScaleFactor <= 1) { + if(imageWidthScaleFactor <= imageHeightScaleFactor) { + imageWidth = imageWidth * imageWidthScaleFactor; + imageHeight = imageHeight * imageWidthScaleFactor; + + at.scale(imageWidthScaleFactor, imageWidthScaleFactor/imageHeightScaleFactor); + } else { + imageHeight = imageHeight * imageHeightScaleFactor; + imageWidth = imageWidth * imageHeightScaleFactor; + + at.scale(imageHeightScaleFactor, imageHeightScaleFactor/imageWidthScaleFactor); + } + + } else { + at.scale(1/imageHeightScaleFactor, 1/imageWidthScaleFactor); + } + +// AffineTransform at = new AffineTransform(); +// at.scale(1/imageWidthScaleFactor, 1/imageHeightScaleFactor); + AffineTransformOp scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR); + BufferedImage scaledImage = new BufferedImage((int) imageWidth, (int) imageHeight, image.getType()); + scaledImage = scaleOp.filter(image, scaledImage); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + ImageIO.write(scaledImage, "jpeg", os); + //os.reset(); + return new ByteArrayInputStream(os.toByteArray()); + } + + + +// private ByteArrayInputStream getScaledImage(ByteArrayInputStream imageByteArrayInputStream, float cellWidth, float cellHeight) throws IOException { +// BufferedImage image = ImageIO.read(imageByteArrayInputStream); +// +// double imageWidth = image.getWidth(); +// +// double imageHeight = image.getHeight(); +// +// double cellRatio = cellWidth/cellHeight; +// double imageRatio = imageWidth/imageHeight; +// +// boolean scaleWidth = imageWidth > cellWidth; +// boolean scaleHeight = imageHeight > cellHeight; +// +// double imageWidthScaleFactor; +// double imageHeightScaleFactor; +// +// if (scaleWidth && scaleHeight) { +// // image width and height is greater than cell +// if (imageRatio < cellRatio) { +// // scale width +// imageWidthScaleFactor = cellWidth / imageWidth; +// imageHeightScaleFactor = imageWidthScaleFactor / imageRatio; +// } else { +// // scale height +// imageHeightScaleFactor = cellHeight / imageHeight; +// imageWidthScaleFactor = imageHeightScaleFactor * imageRatio; +// } +// } else if (scaleWidth) { +// // only image width is greater than cell +// imageWidthScaleFactor = cellWidth / imageWidth; +// imageHeightScaleFactor = imageWidthScaleFactor / imageRatio; +// } else if (scaleHeight) { +// // only image height is greater than cell +// imageHeightScaleFactor = cellHeight / imageHeight; +// imageWidthScaleFactor = imageHeightScaleFactor * imageRatio; +// } else { +// // nothing to do, image fits in cell +// imageWidthScaleFactor = 1; +// imageHeightScaleFactor = 1; +// } +// +// AffineTransform at = new AffineTransform(); +// at.scale(imageWidthScaleFactor, imageHeightScaleFactor); +// AffineTransformOp scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR); +// BufferedImage scaledImage = new BufferedImage((int) (imageWidth * imageWidthScaleFactor), (int) (imageHeight * imageHeightScaleFactor), image.getType()); +// scaledImage = scaleOp.filter(image, scaledImage); +// +// ByteArrayOutputStream os = new ByteArrayOutputStream(); +// ImageIO.write(scaledImage, "jpeg", os); +// //os.reset(); +// return new ByteArrayInputStream(os.toByteArray()); +// } +// private String getPlaceholderValue(String placeholder, Dossier project, FileModel fileStatus, Map fileAttributePlaceholders, Map dossierAttributesPlaceholders) { @@ -303,7 +443,6 @@ public class ExcelTemplateReportGenerationService { throw new RuntimeException("unknown placeholder"); } - @SneakyThrows public byte[] toByteArray(XSSFWorkbook workbook) { diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/WordReportGenerationService.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/WordReportGenerationService.java index ad87f42..9875fa7 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/WordReportGenerationService.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/WordReportGenerationService.java @@ -18,10 +18,12 @@ import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.util.Units; import org.apache.poi.xwpf.usermodel.*; import org.springframework.stereotype.Service; +import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -31,6 +33,8 @@ import java.util.regex.Pattern; import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.*; +import javax.imageio.ImageIO; + @Service @RequiredArgsConstructor public class WordReportGenerationService { @@ -196,7 +200,8 @@ public class WordReportGenerationService { XWPFRun run = p.getRuns().get(0); run.setText("", 0); run.addBreak(); - run.addPicture(is2, XWPFDocument.PICTURE_TYPE_JPEG, "image.jpg", Units.toEMU(200), Units.toEMU(200)); // 200x200 pixels + Dimension2DDouble dim = getImageDimension(is2); + run.addPicture(is2, XWPFDocument.PICTURE_TYPE_JPEG, "image.jpg", Units.toEMU(dim.getWidth()), Units.toEMU(dim.getHeight())); int size = p.getRuns().size(); for (int i = 1; i < size; i++) { p.removeRun(1); @@ -328,6 +333,17 @@ public class WordReportGenerationService { } + private Dimension2DDouble getImageDimension(ByteArrayInputStream bais) throws IOException { + BufferedImage bufImg = ImageIO.read(bais); + bais.reset(); + + double width = bufImg.getWidth(); + double height = bufImg.getHeight(); + + return new Dimension2DDouble(width, height); + } + + private void setText(XWPFTableCell cell, String value) { cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER); diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/java/com/iqser/red/service/redaction/report/v1/server/RedactionReportIntegrationTest.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/java/com/iqser/red/service/redaction/report/v1/server/RedactionReportIntegrationTest.java index 1797807..0e5617c 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/java/com/iqser/red/service/redaction/report/v1/server/RedactionReportIntegrationTest.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/java/com/iqser/red/service/redaction/report/v1/server/RedactionReportIntegrationTest.java @@ -129,7 +129,7 @@ public class RedactionReportIntegrationTest { .label("label2") .editable(true) .type(DossierAttributeType.IMAGE) - .placeholder("{{dossier.attribute.image}}") + .placeholder("{{dossier.attribute.Signature}}") .build(); @@ -209,7 +209,7 @@ public class RedactionReportIntegrationTest { String templateId = "templateId"; String dossierId = "dossierId"; - ClassPathResource templateResource = new ClassPathResource("templates/Excel Report.xlsx"); + ClassPathResource templateResource = new ClassPathResource("templates/TestReport.xlsx"); XSSFWorkbook workbook = new XSSFWorkbook(templateResource.getInputStream()); ClassPathResource legalBasisMappingResource = new ClassPathResource("files/legalBasisMapping.json"); @@ -219,7 +219,7 @@ public class RedactionReportIntegrationTest { List reportEntries2 = redactionLogConverterService.convertAndSort(redactionLog2, legalBasisMapping); DossierAttributeConfig dossierAttributeConfig = new DossierAttributeConfig("id", "label", true, "{{dossier.attribute.name}}", DossierAttributeType.TEXT, dossierTemplateId);// - DossierAttributeConfig dossierAttributeConfig2 = new DossierAttributeConfig("id2", "label2", false, "{{dossier.attribute.image}}", DossierAttributeType.IMAGE, dossierTemplateId); + DossierAttributeConfig dossierAttributeConfig2 = new DossierAttributeConfig("id2", "label2", false, "{{dossier.attribute.Signature}}", DossierAttributeType.IMAGE, dossierTemplateId); when(dossierAttributesConfigClient.getDossierAttributes(dossierTemplateId)).thenReturn(List.of(dossierAttributeConfig, dossierAttributeConfig2)); DossierAttribute dossierAttribute = new DossierAttribute(dossierId, dossierAttributeConfig.getId(), "Michael"); @@ -245,7 +245,7 @@ public class RedactionReportIntegrationTest { excelTemplateReportGenerationService.generateReport(reportEntries2, "dossierTemplateId", workbook, fileModel2, dossier, true); byte[] excelTemplateReport = excelTemplateReportGenerationService.toByteArray(workbook); - try (FileOutputStream fileOutputStream = new FileOutputStream(getTemporaryDirectory() + "/report_excel_template.xlsx")) { + try (FileOutputStream fileOutputStream = new FileOutputStream(getTemporaryDirectory() + "/test_report_excel_template.xlsx")) { fileOutputStream.write(excelTemplateReport); } } @@ -272,7 +272,7 @@ public class RedactionReportIntegrationTest { List reportEntries2 = redactionLogConverterService.convertAndSort(redactionLog2, legalBasisMapping); DossierAttributeConfig dossierAttributeConfig = new DossierAttributeConfig("id", "label", true, "{{dossier.attribute.name}}", DossierAttributeType.TEXT, dossierTemplateId); - DossierAttributeConfig dossierAttributeConfig2 = new DossierAttributeConfig("id2", "label2", false, "{{dossier.attribute.image}}", DossierAttributeType.IMAGE, dossierTemplateId); + DossierAttributeConfig dossierAttributeConfig2 = new DossierAttributeConfig("id2", "label2", false, "{{dossier.attribute.Signature}}", DossierAttributeType.IMAGE, dossierTemplateId); when(dossierAttributesConfigClient.getDossierAttributes(dossierTemplateId)).thenReturn(List.of(dossierAttributeConfig, dossierAttributeConfig2)); DossierAttribute dossierAttribute = new DossierAttribute(dossierId, "id", "Michael"); DossierAttribute dossierAttribute2 = new DossierAttribute(dossierId, "id2", Base64.getEncoder().encodeToString(IOUtils.toByteArray(imageResource.getInputStream()))); diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/TestReport.xlsx b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/TestReport.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..09e80a4b4d6575e00839f662f3261ac7be2a3715 GIT binary patch literal 4966 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 Date: Mon, 8 Nov 2021 14:12:36 +0100 Subject: [PATCH 2/2] RED-2414 Wrong scaling for Images in Reports --- .../ExcelTemplateReportGenerationService.java | 219 ++++++------------ .../report/v1/server/service/PixelUtil.java | 29 +++ .../RedactionReportIntegrationTest.java | 11 +- .../test/resources/files/exampleImage2.jpeg | Bin 0 -> 6910 bytes .../test/resources/files/exampleImage3.jpeg | Bin 0 -> 4052 bytes .../test/resources/files/exampleImage4.jpeg | Bin 0 -> 8652 bytes .../test/resources/templates/TestReport2.xlsx | Bin 0 -> 4950 bytes 7 files changed, 98 insertions(+), 161 deletions(-) create mode 100644 redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/PixelUtil.java create mode 100644 redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/files/exampleImage2.jpeg create mode 100644 redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/files/exampleImage3.jpeg create mode 100644 redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/files/exampleImage4.jpeg create mode 100644 redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/templates/TestReport2.xlsx diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelTemplateReportGenerationService.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelTemplateReportGenerationService.java index 0e0a947..5a603cd 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelTemplateReportGenerationService.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/ExcelTemplateReportGenerationService.java @@ -1,5 +1,48 @@ package com.iqser.red.service.redaction.report.v1.server.service; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.DOSSIER_NAME_PLACEHOLDER; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.EXCERPT_PLACEHOLDER; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.FILE_NAME_PLACEHOLDER; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.FORMAT_DATE_ENG; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.FORMAT_DATE_ENG_PLACEHOLDER; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.FORMAT_DATE_GER; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.FORMAT_DATE_GER_PLACEHOLDER; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.FORMAT_DATE_ISO; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.FORMAT_DATE_ISO_PLACEHOLDER; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.FORMAT_TIME_ISO; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.FORMAT_TIME_ISO_PLACEHOLDER; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.JUSTIFICATION_PARAGRAPH_PLACEHOLDER; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.JUSTIFICATION_PLACEHOLDER; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.JUSTIFICATION_REASON_PLACEHOLDER; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.PAGE_PLACEHOLDER; +import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.PARAGRAPH_PLACEHOLDER; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; + +import javax.imageio.ImageIO; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.CreationHelper; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.Picture; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Service; + import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.DossierAttributeConfig; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.Dossier; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.DossierAttribute; @@ -10,29 +53,10 @@ import com.iqser.red.service.redaction.report.v1.server.client.DossierAttributes import com.iqser.red.service.redaction.report.v1.server.client.FileAttributesConfigClient; import com.iqser.red.service.redaction.report.v1.server.model.ImagePlaceholder; import com.iqser.red.service.redaction.report.v1.server.model.ReportRedactionEntry; + import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import org.apache.poi.ss.usermodel.*; -import org.apache.poi.util.Dimension2DDouble; -import org.apache.poi.util.Units; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; -import org.springframework.stereotype.Service; - -import java.awt.geom.AffineTransform; -import java.awt.image.AffineTransformOp; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.time.OffsetDateTime; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Pattern; - -import static com.iqser.red.service.redaction.report.v1.server.service.PlaceholderService.*; - -import javax.imageio.ImageIO; @Slf4j @Service @@ -240,30 +264,29 @@ public class ExcelTemplateReportGenerationService { if (cell.getStringCellValue().contains(imagePlaceholder.getPlaceholder())) { try (ByteArrayInputStream is = new ByteArrayInputStream(imagePlaceholder.getImage())) { - ByteArrayInputStream scaledImage = getScaledImage(is, sheet.getColumnWidthInPixels(cell.getColumnIndex()), convertPointsToPixel(cell.getRow().getHeightInPoints())); +// ByteArrayInputStream scaledImage = getScaledImage(is, PixelUtil.widthUnits2Pixel((short) sheet.getColumnWidth(cell.getColumnIndex())), PixelUtil.heightUnits2Pixel(cell.getRow().getHeight())); +// is.reset(); + + double factor = calculateScale(is, PixelUtil.widthUnits2Pixel((short) sheet.getColumnWidth(cell.getColumnIndex())), PixelUtil.heightUnits2Pixel(cell.getRow().getHeight())); is.reset(); - int pictureIdx = workbook.addPicture(scaledImage, XSSFWorkbook.PICTURE_TYPE_JPEG); -// scaledImage.reset(); + int pictureIdx = workbook.addPicture(is, XSSFWorkbook.PICTURE_TYPE_JPEG); + is.reset(); //Returns an object that handles instantiating concrete classes CreationHelper helper = workbook.getCreationHelper(); - //Creates the top-level drawing patriarch. //Create an anchor that is attached to the worksheet ClientAnchor anchor = helper.createClientAnchor(); -// anchor.setAnchorType(ClientAnchor.AnchorType.DONT_MOVE_AND_RESIZE); + anchor.setAnchorType(ClientAnchor.AnchorType.MOVE_AND_RESIZE); anchor.setCol1(cell.getColumnIndex()); anchor.setRow1(cell.getRowIndex()); - anchor.setCol2(cell.getColumnIndex() + 1); - anchor.setRow2(cell.getRowIndex() + 1); + //Creates the top-level drawing patriarch. Drawing drawing = sheet.createDrawingPatriarch(); - drawing.createPicture(anchor, pictureIdx); - //sheet.setColumnWidth(cell.getColumnIndex(), (int) dim.getWidth()); - -// cell.getRow().setHeight((short) dim.getHeight()); + Picture picture = drawing.createPicture(anchor, pictureIdx); + picture.resize(factor); cell.setCellValue(""); } @@ -273,138 +296,26 @@ public class ExcelTemplateReportGenerationService { } - private float convertPointsToPixel(float points) { - return points/0.75f; - } + private double calculateScale(ByteArrayInputStream imageByteArrayInputStream, float cellWidth, float cellHeight) throws IOException { + BufferedImage img = ImageIO.read(imageByteArrayInputStream); + double imageWidth = img.getWidth(); + double imageHeight = img.getHeight(); - private ByteArrayInputStream getScaledImage(ByteArrayInputStream imageByteArrayInputStream, float cellWidth, float cellHeight) throws IOException { - BufferedImage image = ImageIO.read(imageByteArrayInputStream); - - double imageWidth = image.getWidth(); - double imageHeight = image.getHeight(); - - double imageWidthScaleFactor; - double imageHeightScaleFactor; - - double imageRatio = imageWidth/imageHeight; - -// if(cellRatio <= 1 && imageRatio <=1) { -// // cell and image are landscape -// -// -// } else if(cellRatio <= 1 && imageRatio <=1) { -// // cell and image are portrait -// -// } else if(cellRatio <= 1 && imageRatio <=1) { -// // cell is landscape and image is portrait -// -// } else if(cellRatio <= 1 && imageRatio <=1) { -// // cell is portrait and image is landscape -// -// } - - AffineTransform at = new AffineTransform(); - - imageWidthScaleFactor = cellWidth / imageWidth; - imageHeightScaleFactor = cellHeight / imageHeight; - - if(imageWidthScaleFactor <= 1 && imageHeightScaleFactor > 1) { - imageWidth = imageWidth * imageWidthScaleFactor; - imageHeight = imageHeight * imageWidthScaleFactor; - - at.scale(imageWidthScaleFactor, imageRatio); - - } else if(imageWidthScaleFactor > 1 && imageHeightScaleFactor <= 1) { - imageHeight = imageHeight * imageHeightScaleFactor; - imageWidth = imageWidth * imageHeightScaleFactor; - - at.scale(imageHeightScaleFactor, imageHeightScaleFactor/imageWidthScaleFactor); - - } else if(imageWidthScaleFactor <= 1 && imageHeightScaleFactor <= 1) { - if(imageWidthScaleFactor <= imageHeightScaleFactor) { - imageWidth = imageWidth * imageWidthScaleFactor; - imageHeight = imageHeight * imageWidthScaleFactor; - - at.scale(imageWidthScaleFactor, imageWidthScaleFactor/imageHeightScaleFactor); - } else { - imageHeight = imageHeight * imageHeightScaleFactor; - imageWidth = imageWidth * imageHeightScaleFactor; - - at.scale(imageHeightScaleFactor, imageHeightScaleFactor/imageWidthScaleFactor); - } + double widthFactor = (double) cellWidth/ imageWidth; + double heightFactor = (double) cellHeight/ imageHeight; + if (imageWidth < cellWidth && imageHeight < cellHeight) { + return 1; + } else if (cellWidth > cellHeight) { + return heightFactor; } else { - at.scale(1/imageHeightScaleFactor, 1/imageWidthScaleFactor); + return widthFactor; } -// AffineTransform at = new AffineTransform(); -// at.scale(1/imageWidthScaleFactor, 1/imageHeightScaleFactor); - AffineTransformOp scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR); - BufferedImage scaledImage = new BufferedImage((int) imageWidth, (int) imageHeight, image.getType()); - scaledImage = scaleOp.filter(image, scaledImage); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - ImageIO.write(scaledImage, "jpeg", os); - //os.reset(); - return new ByteArrayInputStream(os.toByteArray()); } - -// private ByteArrayInputStream getScaledImage(ByteArrayInputStream imageByteArrayInputStream, float cellWidth, float cellHeight) throws IOException { -// BufferedImage image = ImageIO.read(imageByteArrayInputStream); -// -// double imageWidth = image.getWidth(); -// -// double imageHeight = image.getHeight(); -// -// double cellRatio = cellWidth/cellHeight; -// double imageRatio = imageWidth/imageHeight; -// -// boolean scaleWidth = imageWidth > cellWidth; -// boolean scaleHeight = imageHeight > cellHeight; -// -// double imageWidthScaleFactor; -// double imageHeightScaleFactor; -// -// if (scaleWidth && scaleHeight) { -// // image width and height is greater than cell -// if (imageRatio < cellRatio) { -// // scale width -// imageWidthScaleFactor = cellWidth / imageWidth; -// imageHeightScaleFactor = imageWidthScaleFactor / imageRatio; -// } else { -// // scale height -// imageHeightScaleFactor = cellHeight / imageHeight; -// imageWidthScaleFactor = imageHeightScaleFactor * imageRatio; -// } -// } else if (scaleWidth) { -// // only image width is greater than cell -// imageWidthScaleFactor = cellWidth / imageWidth; -// imageHeightScaleFactor = imageWidthScaleFactor / imageRatio; -// } else if (scaleHeight) { -// // only image height is greater than cell -// imageHeightScaleFactor = cellHeight / imageHeight; -// imageWidthScaleFactor = imageHeightScaleFactor * imageRatio; -// } else { -// // nothing to do, image fits in cell -// imageWidthScaleFactor = 1; -// imageHeightScaleFactor = 1; -// } -// -// AffineTransform at = new AffineTransform(); -// at.scale(imageWidthScaleFactor, imageHeightScaleFactor); -// AffineTransformOp scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR); -// BufferedImage scaledImage = new BufferedImage((int) (imageWidth * imageWidthScaleFactor), (int) (imageHeight * imageHeightScaleFactor), image.getType()); -// scaledImage = scaleOp.filter(image, scaledImage); -// -// ByteArrayOutputStream os = new ByteArrayOutputStream(); -// ImageIO.write(scaledImage, "jpeg", os); -// //os.reset(); -// return new ByteArrayInputStream(os.toByteArray()); -// } -// private String getPlaceholderValue(String placeholder, Dossier project, FileModel fileStatus, Map fileAttributePlaceholders, Map dossierAttributesPlaceholders) { diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/PixelUtil.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/PixelUtil.java new file mode 100644 index 0000000..538b471 --- /dev/null +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/main/java/com/iqser/red/service/redaction/report/v1/server/service/PixelUtil.java @@ -0,0 +1,29 @@ +package com.iqser.red.service.redaction.report.v1.server.service; + +public class PixelUtil { + + public static final short EXCEL_COLUMN_WIDTH_FACTOR = 256; + public static final short EXCEL_ROW_HEIGHT_FACTOR = 20; + public static final int UNIT_OFFSET_LENGTH = 7; + public static final int[] UNIT_OFFSET_MAP = new int[] { 0, 36, 73, 109, 146, 182, 219 }; + + public static short pixel2WidthUnits(int pxs) { + short widthUnits = (short) (EXCEL_COLUMN_WIDTH_FACTOR * (pxs / UNIT_OFFSET_LENGTH)); + widthUnits += UNIT_OFFSET_MAP[pxs % UNIT_OFFSET_LENGTH]; + return widthUnits; + } + + public static int widthUnits2Pixel(short widthUnits) { + int pixels = (widthUnits / EXCEL_COLUMN_WIDTH_FACTOR) * UNIT_OFFSET_LENGTH; + int offsetWidthUnits = widthUnits % EXCEL_COLUMN_WIDTH_FACTOR; + pixels += Math.floor((float) offsetWidthUnits / ((float) EXCEL_COLUMN_WIDTH_FACTOR / UNIT_OFFSET_LENGTH)); + return pixels; + } + + public static int heightUnits2Pixel(short heightUnits) { + int pixels = heightUnits / EXCEL_ROW_HEIGHT_FACTOR; + int offsetWidthUnits = heightUnits % EXCEL_ROW_HEIGHT_FACTOR; + pixels += Math.floor((float) offsetWidthUnits / ((float) EXCEL_ROW_HEIGHT_FACTOR / UNIT_OFFSET_LENGTH)); + return pixels; + } +} \ No newline at end of file diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/java/com/iqser/red/service/redaction/report/v1/server/RedactionReportIntegrationTest.java b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/java/com/iqser/red/service/redaction/report/v1/server/RedactionReportIntegrationTest.java index 0e5617c..05e41a5 100644 --- a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/java/com/iqser/red/service/redaction/report/v1/server/RedactionReportIntegrationTest.java +++ b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/java/com/iqser/red/service/redaction/report/v1/server/RedactionReportIntegrationTest.java @@ -12,7 +12,6 @@ import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.List; -import java.util.ListIterator; import java.util.Map; import org.apache.commons.io.IOUtils; @@ -34,12 +33,10 @@ import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.Re import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.Dossier; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.DossierAttribute; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.DossierAttributeType; -import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileAttribute; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileAttributeConfig; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileAttributeType; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.dossier.file.FileModel; import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.legalbasis.LegalBasis; -import com.iqser.red.service.persistence.service.v1.api.model.dossiertemplate.legalbasis.LegalBasisMapping; import com.iqser.red.service.redaction.report.v1.api.model.ReportType; import com.iqser.red.service.redaction.report.v1.server.client.DossierAttributesClient; import com.iqser.red.service.redaction.report.v1.server.client.DossierAttributesConfigClient; @@ -412,11 +409,11 @@ public class RedactionReportIntegrationTest { public void testIuclidReport() { String dossierTemplateId = "dossierTemplateId"; - ClassPathResource redactionLogResource = new ClassPathResource("files/redactionLog.json"); + ClassPathResource redactionLogResource = new ClassPathResource("files/S11RedactionLog.json"); RedactionLog redactionLog = objectMapper.readValue(redactionLogResource.getInputStream(), RedactionLog.class); - ClassPathResource legalBasisMappingResource = new ClassPathResource("files/legalBasisMapping.json"); + ClassPathResource legalBasisMappingResource = new ClassPathResource("files/S1116LegalBasis.json"); List legalBasisMapping = objectMapper.readValue(legalBasisMappingResource.getInputStream(), new TypeReference<>() { }); @@ -433,7 +430,7 @@ public class RedactionReportIntegrationTest { when(fileAttributesConfigClient.getFileAttributeConfigs(dossierTemplateId)).thenReturn(new ArrayList<>()); - FileModel fileStatus = FileModel.builder().filename("filename").build(); + FileModel fileStatus = FileModel.builder().filename("VV123456").build(); Dossier project = Dossier.builder().id("dossierId").dossierName("projectName").build(); @@ -455,7 +452,7 @@ public class RedactionReportIntegrationTest { .build(); byte[] report = wordReportGenerationService.generateReport(ReportType.WORD_SINGLE_FILE, reportEntries, dossierTemplateId, reportTemplate, fileStatus, project); - try (FileOutputStream fileOutputStream = new FileOutputStream(getTemporaryDirectory() + "/iuclid_report.docx")) { + try (FileOutputStream fileOutputStream = new FileOutputStream(getTemporaryDirectory() + "/iuclid_report_2.docx")) { fileOutputStream.write(report); } } diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/files/exampleImage2.jpeg b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/files/exampleImage2.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..e08bec8f0d7f92299b495cfba84e30372b336b38 GIT binary patch literal 6910 zcmb7oRaBJ$v-S&{?#>OH?vRucq#L9|Kw#4)4T4C6bmyj1Qo4~AK{}+nn@yK+{QhtK z7w7Ukv({Y9Gj%mvIYl`D0RaFI{t5811cU$>sAwc4#AGBSBxL^uIR*GX zLPkbG2B!F@D8RH_j9_YZ2pJiZ5EDB$kAQ#x1%v2YQGN+7egPf?EG#TMTs#N?0YreE zft~;VjF)Zz9}Or&U;`o00f_hrAbfIi~^wkBgOCl1P~$; z2n87t1p$Qo&jaCKB#0J?fJaip%mtY)Fd@Gdg^;)BTyv60%G`AWo`_0JPofo6*ZVJ; z90B;>nE?bu5P*dIk7C9LKmg)@0s3#@e;hsmEsrFj22!Az3vU9Q6#Qiozykfhiuiy8 zaDPhO9vT>vG}5hlITF!Kjma2lvtE0T{nM;ZCOJ2QlheDPv1Z)D3JFwn_LH<;Nt}=9 zFfiaRN4%Ja`i&<+fb{qw4)yhAouMbbk|;EXG4eGO z7iu<6OAqCGLkc^1Cvgw~YNMPaU(UhEGeg2+)t+wEu<&r%scApGdU_-N))od2o#RUE z*U@7%YB5YlU$X7AGnu(t#g%ipbPpIsZ5oC(m%ga^qXu%O2`Yl+A|i>{-dx<=8k?Jb zQF3jz7uW_cKmPhu@B%=v7+Oo{gmPExvf`TBP4zGbgXXi8$v|8FIZTku)_GPc{%TnG(_&HQgmQ=J9zQDWK+aKJQ7KE*7q!+BJ>}wiE zZK5n^)A&5p_7Q4~6Lr-2P@JnB424$Wz75V%6n|d@KO4$1_7-5@q;TMkbvaI`d^8xw zj4DhF*9^)uQ%G@4<{aTF*B7EA`Swc(328S}PedizOM@*OOLq~~H0x*{VS?>*nW6i6 z4B|tDV3Z}ZzajzwXTh`x*TagvX`%BbPLJgN7Qs)0$=!!3FF;fMb@ND`c7MxhT4H&@^H6(S;a<>VR-EUk1FCcrFKols z6jtx6ZNj?vXegvrc8BgoYhp{ZPvwJ z3`i}9J$p~N?BoTI{e$vYS_I|kH{esF5YE2lT!m-}315MS|x%^WT zS4j0OJ875+Cb%#t*D`62Y96Ztvtg>E!OVesqc~|+sYz7Z6?_lm8@aU{0(A03I~Y#s zoL3M(l2U6;`}Z!W1=j2cNv`}D>H7fulseGMv3SyE~yaOIWp8-)C*_4NO*1W zjiNi39{yJb*=PdA&T*~p+J&1m^3kh(ecdXLS8CY)NegVFSFz=#{VldWu+F{jGf%%pQYp~&0TW)ICY5yzTQp*t1shKaYUm`prOw%K_vx-*)BiQH`4MdhE^$d91BBwJdhpYvv|$o@6hI2 z=349#LsXowLrE%_oQrv3x^D2o5H2^vCrT-%I3m<3L#LvxJQJ#{%Q~@8SMwj4-+vop z`m$hlc6(R&qe@>t7LI}<2BD8)v%)`2!HEr`*a6Yw)ELK~(5k-aJTs*>DetPDDNGcG z=k;8r<@nIQ0MzmmKb!t+*B{Ktjwz{!`Q84FzMMY4{!3aG`R&BSuQNV#j$R9_*gaHnF4avxXEMm}=c>8qo&~}s^+)E?JL6OcvTT?%YRuos`5XS<`V&5^ z9m!9$Gg22L(Wre~oH)L9!MlwM3v?jqeoVtDVYsd*O}0`!8}>|&h+mV|PRf)q_EgbN zK9;Yf@GZ6!G>B64sAw;}$z*BWwAhz{@@*us?|_nRoHbG(_4xE|uFn-yR|3hj96`4I z!UH1QSz;J(doAz4FS1F4yGL-2ihlAv>w5V9BH^%7v9*P*ThqLqTDO}aN1od6q`RaB zLu!2^3Uc}~z)I{_ORRjPcaFN$tgXZNiIH2*xojRu%zF0H7)n=XEgb6?x=W(JLUTY% ztlfOHoBV{}t4*5sT3`}oI9GtvL1){6{XVIol5WEABNXMk`|il(cOP_#MHCog7z?Uo z1A6-C=TeSAq3y8SB>x$d5Owl7$;13g+R|6qi%W-IOweHsD_QngKF0k1t5ru)A`wpC zWku8gl2wFOlk(*%a@eYfSLmKMD2=Gzjj(JV9sNC&Rd)WVC~D-`aR*O>JjR!bld;H% zBoS?2s>b5zi|*|^0}}dMEr-g&t~=4qk6B+-p)WwvJ;W^cL4gG4_UJqGiHCht8^Jzv zmjF}2O&t*{h;-4HvXROpicOys=X`y`8uyi+pei@2%9*4Z`mn^wlM-LfDT{hinZ~Yo zpm;DULC9W%P0#Mv^?;j#Z~YQP8%4N4^7qJ3vjfH_Z+*3x%&glpq(>re@-5YC`CU)U zWbKK|gmSmV(VyO+Wczl0JZ@*Zav3?6t;;A??-A8PLy5gm-i9lW^0YKiC)n&(ABF<_ z@`wdWAk@)O1s_5wdi|ye{@dtWFq`dzD&odxj&M{+;UUIMO9AbGXCcV6cbm+gpaIM$d+$7I8 zJpp6y3HM_{z-DlnlG|Gm=^l)(G8Og8LQ+da$3xlO%KoEnt96DpgMjbrM*YPun1@cp z{eZ%udip~8PN2M_altpKh(8!{5`Twqy?ifqky0d~D(?k|a=RGuu$`hoOXzTmx})1L zSyH6ULl06E{bHt)ffRnx_2bFY@w2BfQV}%yaWv4jxXSirjo5c- zNq6*@xskr6DPe*SA#8~4GOiA!V1=0UrupKDDmNWyr3?R>L0s}q@<%rcckujuI3iqW zp@3Lu5#!XYa#!(lp=^KYM0_D%f!y0Iw65uPU7`N7p6CluG5)+Hv8_uV!PL`vb^uyj z{rV}6@C6{LF8kTwrgP)iO#D}dnMK#C_$aYBIuO4LVrgwYWUMCWRzA7h-ei!*Zm7Kd+KJETcyeE(yrr}IJBLz;bdNzk=&mHdLW+r`{ev)8LeqK5v~ukaiTtn-xlp-e zx#ZZ9Ja!5#EISZ5b~r0h)RG9mW~>?|jc+HSAj> zYo`}rhCA(DT`L;PNwYfFqYrstkJ{Po8o&KapC$`|09cb>Sa5aF6%_bZMi)h@ja zBSDhFtXq9)Nw9^RZJSdka$km<(-bS=kw{7SsOM?=! z2j=0gk}jN$OFzdC=p}BhGY~qZg8dmyDuN9SDt>i1zK+l>`56_3|Aj{)lOgBaXcIZ` zYI^yh+$Qy=M{I17@;tY@(a?hTe9GeMLLPudp!TEK8YSwz)@g&SI2twq9n)>1*WcEn z{B5|ELBi{>c@sg;MEdZD_CrY|`!Yyr2!5SsBY3nv6RNRgzUfH!7)6|Ok7_{0$upp+ zI}FEqfWh2bZeNJeD*o2KsqnOQgQ{(cuVMnwukGKb79gzsn+iRb z9pk}niZ}1G<-?RGR6YHnry!x{x$G`sY1kOdfimYZ>)AtTj$Ifp*&dr(i{Gun=qhpE ze5>6r;e4R%PjsqW?6Bv9gxG?SN5{{Vh!#)0*zuFeAY4pSWa7}%Cq_z4>k4imI7WXl zB&mjBStPqCmV8iRsN0#i?0c73%`--W=*}lvR`e6H!!DIWk-9ajRIOJ~kNrsMWz)H) z0h8R#=r&&*;>w_qd$K4okctCho~Y==)AzOQ8IBAsO9?OQl6}ntc5MUtZ|E;~jI`e^-S8#+nbH2~8EnflOYK{U zbiIzsYswfr#|VFZ6*gUhon>_E7WI1hp1Dtgu}Gb713T-Z-PW4MsqdJpBJZb@2{cdv z#CpTd{6k`ra<*dxqKaamnQt;im9c#9-@qu&`zyAlFz5FS;4vGKe(D3d71H{|KSHZh z4-4B{X^9=LZ{+wk(l$U}pqYExqgO%F(ypT`gx9XhPk+#H$yKjV)Dw^GpF|Cd$*YWJ zE4z>50n249d_+Y=(s4My;wQ=oK*l!j_Cm7Y^z*{ zuW(TgfkIz^UY-HVY0VRarH_ymc4;QDCl`B##t{*ufVY8eWi{I=5qMZY(RC*4BbCm@_24WKY&Aj9kKTa%qzD5=(#K!!Fs(f>MIPKjUw=dL9`e$*;kp@4p%~ zvK8BG)bflExNYY|DT_`ns)-19oIMuRdKW%qsxyvF4+J@FqRPl`Qa-HA^mCbDwM~qw zm=U(_K=%`?wDBAaz0*k#{VlM%hnS#dA2n0@XKfc&nf!Q5DyWP>lr8$VN2}{ExO1k)WjZEjNAUsTdZu!)L@VGPS(*Qe#TQ+ zu9!G;p}>U(Q89o^o$+#P?S#-U|4U!nm@35YNl$U&~ z8pCjQ<^1d<-zZW0Jk%T6nBG^I#r!D$X*D2@_}WwN!|OZo2oLpiG;+pjr!77;O~HtS z_4ZU@w`aeLMhS)Q8$a{smINq`-TVn}FzxtfOIGjF-W1;7#Y^aP=+;w54_0%0svvfi zJtJr=`CMt?m^Jq)pCqQ~CS~qPeV*>xmRI(&=HFCuqo3i@gl!66ES29n?`73Dz5w9~ zNyN~qS0{9${X4(`Sh(1QEV^G_;zM^S-eoFhD+%}({uY`C8MEu9sg%fS%_aK${zBgw zvbVr^$yJp-&g+ztKOv{%MKsFSQM^FSKFfYgRIW6NQ~UIkg!N4@1Dq<&mTYB?wzi;D zYFQHVaZ~aIz+4l}PK8zw-ct{Y+qeGPSu*l9OZoOIBjntN?ylshG zu{FX9uC?ZuGDY+5-ol1DrWN?5{FE4+Y;G(Ll&nw$FoB@R2if41#cfhvbTB@M0~eo_ z?8q$hO0JU=x8m6Q|HpFkCgh6&V?iLK zY!uYJ`vP3FJUF;T3}b#pp5QhsGkgIEwxn0d#8XvX0GGMn>H>x5tGGMU0y$_SOpmX= zJlu0Vecb44-gyw><%V&kg&q&zsgowQAT4as9##0scvs(vap&GJk z!+t#&okWEgT5F(ZeXyy^3OaCClk|chsz+Mj$mLv%YUX{)JciqDA2xO89Zs%gh@$;rx7BVR-snN}N zJmg*5dTtG4}&_8GZTD(Rnhxlk}sBHR3!q4m{oP^Q`! zqdI60IOM2O8G2e_+H;BtDqB5gWpfXMEloH)dC0g#*;n4tTH!EoECEVlG znL@@`gqGFhIv*Pdgwq3$FpzC+pO!gR7t2+d0fzJ!;23uSZLm%8Z;W1<51%h3=B0^p z^`F};f31j#$Ewqnf$hE)Y$IOxk&pWr!^fFjtRX_ig96obNRn>{H?!_WlVhaSmXF(< z*e*(*=rbMV1k+M&p4gSy!CU6H?<4HxPnGsFTYSh${pf(Hw+sQM`XFDfkRp_hl`CIJn*kXK|P%DzvHiyc+xlL7$*c)W}~-nMH6DfQP(X! zvYgb;Cr^XbY6uhZKpp6CW{de30Eg7^<0Fdt@6sus=cr8=jZCbTg0=1yQU6A{gaXl;835Kn6B9ez11M*X%C-ZmL>%d`cz>Y$3y*BZsgWt+K zQ}lcMO+2gWmO_!w0H^$JrFrTt-9>KTHTxa*>!ZI~q|Gd$Fgmzc5F-0f+q*0PN3)ll zhf#^lyZlW`_ql@AN9;Y%wBstoD8R*-Ww!z>^jq%Y-qTLDV?0lrO(tyPsoMy;-$+=X zns$A=_fMt|1AqBd6xc*atW#ttmY9*2BRB94X$WIsuY3nQdx;Er1!oC3HW^8(BtXlC zW^AT$ruC#t)crZZa2#Gjs1Cr%!DJ2CMM5Ja+%M#|x0Y3_kmg_fT@!4MHTpnn&Kmzz z*z4n>rMPlAL2pi(4d+4pWQ=_Pi`~1SyCLZx%qlP|?>Qtgr*IdgR>}Za?xnoA^8*Iu zY^I*uU8MbN<+xvfpAxSz(hEexgf3RtE<}xq%r-mbb|&(keivF~(A+w@m2FvgN9NW< zdqh*2`BZdA(94wY#0F68Hx0?RG5N@dKmGmU9kT#aIf z#Ex@r%7(4HW+bY!Xz4!|gjj$_EYo@6B1to_k}TUdQM!vvNvjl~y6#P^$8vC<$_LvG z>chA)Y8$5tMAcdT5XYM1FONvp=9%<-3~+H{T~D&sQ>4F~baMU2A`1d0HC)Wc5lw`A zin+cB|JgAWD4Zwqc}7uPdbxLDP)_EhQ_UaIsZW5LWP8jVTW1vmSt^*~zwVgR#_}8q!8P8|)m@FZP<{rtMaC9?1;Ei&VJCDXrHYHZR^ba6H7AT5 zNTj**c%;f<>`^cx zYheE8=-onge+>3aLG9m@RFR(E4}~l(2HMNY;pn$4^DDSP(cDu$anAXWP4_i-^6;*8 zqCcO-uIwtlbOD9<*SROuN4|eipSx;=!q+Y{OGgsRKIq6DW)ncG{hhx!Rqh1=e0T5c z{6LqHVJ8zsWS5yJUD@y0%+*${`kpH0CaxhCDgNL5T??4{f)rZk{ASao-Kp7v)frFG z<0~)A?-*B%(4$fe+l%$$CAx-#VV<7iz zlk9#WJ>Vx@pv4<9h)groynxuW%EGdbuvTs!s^#}YpsK1d;qgen-(xb_^Dfqo=miLk N*8ZkvqCfbu@IPrf>OBAe literal 0 HcmV?d00001 diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/files/exampleImage3.jpeg b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/files/exampleImage3.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2c5ce48c29821ce6849d4bc24d9921af1f92ba4e GIT binary patch literal 4052 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 literal 0 HcmV?d00001 diff --git a/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/files/exampleImage4.jpeg b/redaction-report-service-v1/redaction-report-service-server-v1/src/test/resources/files/exampleImage4.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..6573f5a2264f3154e06b606465ab0a620e8e974c GIT binary patch literal 8652 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