Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions kernel-bio-converter/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -451,13 +451,13 @@
</executions>
<configuration>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<generateGitPropertiesFilename>
${project.build.outputDirectory}/service-git.properties</generateGitPropertiesFilename>
<generateGitPropertiesFilename>${project.build.outputDirectory}/service-git.properties</generateGitPropertiesFilename>
<includeOnlyProperties>
<includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty>
<includeOnlyProperty>^git.commit.id.(abbrev|full)$</includeOnlyProperty>
</includeOnlyProperties>
<commitIdGenerationMode>full</commitIdGenerationMode>
<!-- Repo root is converters/; module basedir is kernel-bio-converter/ -->
<dotGitDirectory>${project.basedir}/../.git</dotGitDirectory>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
import java.util.Map;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;

import org.jnbis.api.model.Bitmap;
import org.jnbis.internal.WsqDecoder;
import org.springframework.stereotype.Service;

import com.github.jaiimageio.jpeg2000.impl.J2KImageReaderSpi;

import io.mosip.biometrics.util.CommonUtil;
import io.mosip.biometrics.util.ConvertRequestDto;
import io.mosip.biometrics.util.face.FaceBDIR;
Expand Down Expand Up @@ -155,7 +159,7 @@ public BufferedImage decodeFingerImage(byte[] imageData, int compressionType) th
switch (compressionType) {
case FingerImageCompressionType.JPEG_2000_LOSSY:
case FingerImageCompressionType.JPEG_2000_LOSS_LESS:
return ImageIO.read(new ByteArrayInputStream(imageData));
return readJpeg2000(imageData);
case FingerImageCompressionType.WSQ:
WsqDecoder decoder = new WsqDecoder();
Bitmap bitmap = decoder.decode(imageData);
Expand Down Expand Up @@ -224,7 +228,7 @@ public String convertFaceIsoToImageType(SourceFormatCode sourceCode, String isoD
public BufferedImage decodeFaceImage(byte[] imageData, int imageDataType) throws ConversionException {
try {
if (imageDataType == ImageDataType.JPEG2000_LOSSY || imageDataType == ImageDataType.JPEG2000_LOSS_LESS) {
return ImageIO.read(new ByteArrayInputStream(imageData));
return readJpeg2000(imageData);
} else {
throw new ConversionException(ConverterErrorCode.NOT_SUPPORTED_COMPRESSION_TYPE.getErrorCode(),
ConverterErrorCode.NOT_SUPPORTED_COMPRESSION_TYPE.getErrorMessage());
Expand Down Expand Up @@ -289,7 +293,7 @@ public String convertIrisIsoToImageType(SourceFormatCode sourceCode, String isoD
public BufferedImage decodeIrisImage(byte[] imageData, int imageFormat) throws ConversionException {
try {
if (imageFormat == ImageFormat.MONO_JPEG2000) {
return ImageIO.read(new ByteArrayInputStream(imageData));
return readJpeg2000(imageData);
} else {
throw new ConversionException(ConverterErrorCode.NOT_SUPPORTED_COMPRESSION_TYPE.getErrorCode(),
ConverterErrorCode.NOT_SUPPORTED_COMPRESSION_TYPE.getErrorMessage());
Expand All @@ -300,6 +304,52 @@ public BufferedImage decodeIrisImage(byte[] imageData, int imageFormat) throws C
}
}

/**
* Reads JPEG2000 bytes into a {@link BufferedImage}.
*
* <p>
* {@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.
* </p>
*
* @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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
Loading