diff --git a/kernel-bio-converter/pom.xml b/kernel-bio-converter/pom.xml
index 5dcb9b1..c55a8b4 100644
--- a/kernel-bio-converter/pom.xml
+++ b/kernel-bio-converter/pom.xml
@@ -451,13 +451,13 @@
+ * {@link ImageIO#read} returns {@code null} when no reader is registered, + * which happens when the ImageIO registry was initialised before a nested + * (Spring Boot fat JAR) class loader exposed the jai-imageio SPI. In that + * case the registry is rescanned and, failing that, the JPEG2000 reader + * is used directly. + *
+ * + * @param imageData JPEG2000 encoded bytes + * @return decoded image, never {@code null} + * @throws IOException if the bytes cannot be read + * @throws ConversionException if the bytes are not decodable JPEG2000 + */ + public BufferedImage readJpeg2000(byte[] imageData) throws IOException { + BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData)); + if (image == null) { + ImageIO.scanForPlugins(); + image = ImageIO.read(new ByteArrayInputStream(imageData)); + } + if (image == null) + image = readWithJ2KReader(imageData); + if (image == null) + throw new ConversionException(ConverterErrorCode.COULD_NOT_READ_ISO_IMAGE_DATA_EXCEPTION.getErrorCode(), + ConverterErrorCode.COULD_NOT_READ_ISO_IMAGE_DATA_EXCEPTION.getErrorMessage() + + ": no JPEG2000 reader could decode the image"); + return image; + } + + private static BufferedImage readWithJ2KReader(byte[] imageData) throws IOException { + J2KImageReaderSpi spi = new J2KImageReaderSpi(); + try (ImageInputStream input = ImageIO.createImageInputStream(new ByteArrayInputStream(imageData))) { + if (input == null || !spi.canDecodeInput(input)) + return null; + ImageReader reader = spi.createReaderInstance(); + try { + reader.setInput(input, true, true); + return reader.read(0); + } finally { + reader.dispose(); + } + } + } + /** * Encodes a {@link BufferedImage} to JPEG or PNG bytes for the given target. * diff --git a/kernel-bio-converter/src/test/java/io/mosip/kernel/bio/converter/ConverterServiceImplTest.java b/kernel-bio-converter/src/test/java/io/mosip/kernel/bio/converter/ConverterServiceImplTest.java index 727ccbf..6cf1182 100644 --- a/kernel-bio-converter/src/test/java/io/mosip/kernel/bio/converter/ConverterServiceImplTest.java +++ b/kernel-bio-converter/src/test/java/io/mosip/kernel/bio/converter/ConverterServiceImplTest.java @@ -173,4 +173,52 @@ void decodeIrisImage_ioExceptionDuringRead_throwsConversionException() throws Ex assertEquals(ConverterErrorCode.COULD_NOT_READ_ISO_IMAGE_DATA_EXCEPTION.getErrorCode(), exception.getErrorCode()); } + + private static byte[] sampleJpeg2000() throws IOException { + BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_BYTE_GRAY); + java.io.ByteArrayOutputStream out = new java.io.ByteArrayOutputStream(); + assertTrue(ImageIO.write(image, "jpeg2000", out)); + return out.toByteArray(); + } + + @Test + void readJpeg2000_validImage_returnsImage() throws Exception { + BufferedImage image = converterService.readJpeg2000(sampleJpeg2000()); + + assertEquals(16, image.getWidth()); + } + + @Test + void readJpeg2000_registryMissingReader_fallsBackToJ2KReader() throws Exception { + byte[] jp2 = sampleJpeg2000(); + try (var imageIo = mockStatic(ImageIO.class, org.mockito.Mockito.CALLS_REAL_METHODS)) { + imageIo.when(() -> ImageIO.read(any(java.io.InputStream.class))).thenReturn(null); + + BufferedImage image = converterService.decodeFingerImage(jp2, FingerImageCompressionType.JPEG_2000_LOSS_LESS); + + assertEquals(16, image.getHeight()); + byte[] png = converterService.convertBufferedImageToBytes(TargetFormatCode.IMAGE_PNG, image); + assertTrue(png.length > 0); + } + } + + @Test + void readJpeg2000_undecodableBytes_throwsConversionException() { + byte[] garbage = "not-a-jpeg2000-image".getBytes(StandardCharsets.UTF_8); + + ConversionException exception = assertThrows(ConversionException.class, + () -> converterService.decodeFaceImage(garbage, ImageDataType.JPEG2000_LOSSY)); + + assertEquals(ConverterErrorCode.COULD_NOT_READ_ISO_IMAGE_DATA_EXCEPTION.getErrorCode(), exception.getErrorCode()); + } + + @Test + void readJpeg2000_irisUndecodableBytes_throwsConversionException() { + byte[] garbage = new byte[] { 1, 2, 3, 4 }; + + ConversionException exception = assertThrows(ConversionException.class, + () -> converterService.decodeIrisImage(garbage, ImageFormat.MONO_JPEG2000)); + + assertEquals(ConverterErrorCode.COULD_NOT_READ_ISO_IMAGE_DATA_EXCEPTION.getErrorCode(), exception.getErrorCode()); + } } \ No newline at end of file