diff --git a/mosip-identity-certify-plugin/pom.xml b/mosip-identity-certify-plugin/pom.xml index 5ba256e..127d70b 100644 --- a/mosip-identity-certify-plugin/pom.xml +++ b/mosip-identity-certify-plugin/pom.xml @@ -170,6 +170,38 @@ jackson-annotations 2.15.4 + + io.mosip.image.compressor + image-compressor + 0.1.0 + + + org.hibernate.validator + hibernate-validator-annotation-processor + + + org.bytedeco + opencv-platform + + + + + io.mosip.kernel + kernel-biometrics-api + 1.3.0 + provided + + + org.json + json + 20241224 + + + org.bytedeco + opencv-platform + 4.9.0-1.5.10 + compile + diff --git a/mosip-identity-certify-plugin/src/main/java/io/mosip/certify/mosipid/integration/helper/ImageCompressorUtil.java b/mosip-identity-certify-plugin/src/main/java/io/mosip/certify/mosipid/integration/helper/ImageCompressorUtil.java new file mode 100644 index 0000000..2dcd14d --- /dev/null +++ b/mosip-identity-certify-plugin/src/main/java/io/mosip/certify/mosipid/integration/helper/ImageCompressorUtil.java @@ -0,0 +1,119 @@ +package io.mosip.certify.mosipid.integration.helper; + +import io.mosip.biometrics.util.CommonUtil; +import io.mosip.certify.api.exception.DataProviderExchangeException; +import io.mosip.certify.mosipid.integration.service.ImageCompressorServiceImpl; +import io.mosip.kernel.biometrics.entities.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Base64; + +@Component +@Slf4j +public class ImageCompressorUtil { + private final ImageCompressorServiceImpl service; + + @Autowired + public ImageCompressorUtil(ImageCompressorServiceImpl service) { + this.service = service; + } + + @Value("${mosip.certify.image-compressor.image.max-allowed-size:4096}") + private int maxAllowedImageSize; + + @Value("${mosip.certify.image-compressor.image.max-retry-attempts:3}") + private int maxRetryAttempts; + + + public byte[] compressImage(byte[] imageBytes) { + return service.doResizeAndCompress(imageBytes); + } + + public String extractAndCompressImage(String imageData) throws DataProviderExchangeException { + try { + // --- Require Data URI with prefix only --- + if (imageData == null || imageData.isBlank() || !imageData.startsWith("data:") || !imageData.contains(";") + || !imageData.contains(",")) { + throw new IllegalArgumentException("Invalid image format. Upload a proper image type."); + } + + // Basic structure guards + int colon = imageData.indexOf(':'); // should be 4 ("data:") + int semi = imageData.indexOf(';'); + int comma = imageData.indexOf(','); + if (colon < 0 || semi < 0 || comma < 0 || colon >= semi || semi >= comma) { + throw new IllegalArgumentException("Invalid image format. Upload a proper image type."); + } + + // Extract MIME (e.g., image/png, image/jpeg) + String mimeType = imageData.substring(colon + 1, semi).trim(); + + // Extract the format (e.g., "png", "jpeg", "jpg"); default "" if malformed + int slash = mimeType.indexOf('/'); + String formatName = (slash >= 0 && slash < mimeType.length() - 1) + ? mimeType.substring(slash + 1).toLowerCase() + : ""; + + // Fallback rule: anything other than png/jpeg/jpg → force JPEG + boolean isPng = "png".equals(formatName); + boolean usePng = isPng; // only true when explicitly PNG + + // Extract Base64 payload and decode + String base64Data = imageData.substring(comma + 1).trim(); + byte[] inputBytes = Base64.getDecoder().decode(base64Data); + + // Compress (assumed JP2 output) + int attempts = 0; + byte[] jp2Bytes; + + while (true) { + jp2Bytes = compressImage(inputBytes); + attempts++; + + if (jp2Bytes.length <= maxAllowedImageSize) { + break; + } + if (attempts >= maxRetryAttempts) { + throw new DataProviderExchangeException( + "FACE_IMAGE_TOO_LARGE", + "Unable to compress image with available compression. Check size or quality of the input image." + ); + } + + // use the last compressed output as the next input + inputBytes = jp2Bytes; + } + + // Convert JP2 → desired output format + final byte[] outBytes; + final String outMime; + if (usePng) { + outBytes = CommonUtil.convertJP2ToPNGBytes(jp2Bytes); + outMime = "image/png"; + } else { + outBytes = CommonUtil.convertJP2ToJPEGBytes(jp2Bytes); + outMime = "image/jpeg"; + } + + // Encode and return as Data URI + final String b64 = Base64.getEncoder().encodeToString(outBytes); + return "data:" + outMime + ";base64," + b64; + + } catch (IllegalArgumentException iae) { + log.error("ERROR_PARSING_IMAGE_DATA", iae); + throw new DataProviderExchangeException("ERROR_PARSING_IMAGE_DATA", iae.getMessage()); + } catch (DataProviderExchangeException e) { + log.error("MAX_ATTEMPTS_REACHED", e); + throw e; + } catch (Exception e) { + log.error("Image compression failed", e); + throw new DataProviderExchangeException( + "ERROR_COMPRESSING_IMAGE", + "Failed to compress image data. Check the image format and other properties." + ); + } + } +} \ No newline at end of file diff --git a/mosip-identity-certify-plugin/src/main/java/io/mosip/certify/mosipid/integration/service/IdaDataProviderPluginImpl.java b/mosip-identity-certify-plugin/src/main/java/io/mosip/certify/mosipid/integration/service/IdaDataProviderPluginImpl.java index fbac2e2..ab47ba9 100644 --- a/mosip-identity-certify-plugin/src/main/java/io/mosip/certify/mosipid/integration/service/IdaDataProviderPluginImpl.java +++ b/mosip-identity-certify-plugin/src/main/java/io/mosip/certify/mosipid/integration/service/IdaDataProviderPluginImpl.java @@ -5,6 +5,7 @@ import io.mosip.certify.api.exception.DataProviderExchangeException; import io.mosip.certify.api.spi.DataProviderPlugin; import io.mosip.certify.mosipid.integration.dto.*; +import io.mosip.certify.mosipid.integration.helper.ImageCompressorUtil; import io.mosip.certify.mosipid.integration.helper.TransactionHelper; import io.mosip.esignet.api.dto.*; import io.mosip.esignet.api.exception.KycExchangeException; @@ -14,6 +15,8 @@ import io.mosip.kernel.keymanagerservice.entity.KeyAlias; import io.mosip.kernel.keymanagerservice.helper.KeymanagerDBHelper; import lombok.extern.slf4j.Slf4j; +import org.bytedeco.javacpp.Loader; +import org.bytedeco.opencv.opencv_java; import org.json.JSONException; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; @@ -41,6 +44,21 @@ public class IdaDataProviderPluginImpl implements DataProviderPlugin { // TODO: Clean up code // TODO: Write unit tests + static { + /** + * load OpenCV library nu.pattern.OpenCV.loadShared(); + * System.loadLibrary(org.opencv.core.Core.NATIVE_LIBRARY_NAME); + */ + /** + * In Java >= 12 it is no longer possible to use addLibraryPath, which modifies + * the ClassLoader's static usr_paths field. There does not seem to be any way + * around this so we fall back to loadLocally() and return. + */ + nu.pattern.OpenCV.loadLocally(); + Loader.load(opencv_java.class); + System.setProperty("OPENCV_IO_ENABLE_JASPER", "1"); + } + private static final String ACCESS_TOKEN_HASH = "accessTokenHash"; public static final String SIGNATURE_HEADER_NAME = "signature"; public static final String AUTHORIZATION_HEADER_NAME = "Authorization"; @@ -93,6 +111,9 @@ public class IdaDataProviderPluginImpl implements DataProviderPlugin { @Autowired TransactionHelper transactionHelper; + @Autowired + private ImageCompressorUtil imageCompressorUtil; + private Base64.Decoder urlSafeDecoder = Base64.getUrlDecoder(); @Override @@ -106,7 +127,14 @@ public JSONObject fetchData(Map identityDetails) throws DataProv Map claims = decodeClaimsFromJwt(encryptedKyc); log.debug("JWT Claims: {}", claims); - return new JSONObject(claims); + JSONObject jsonRes = new JSONObject(claims); + + if(jsonRes.has("picture")) { + String imageData = jsonRes.getString("picture"); + String compressedImageData = imageCompressorUtil.extractAndCompressImage(imageData); + jsonRes.put("compressedPicture", compressedImageData); + } + return jsonRes; } } catch (JSONException | JsonProcessingException e) { diff --git a/mosip-identity-certify-plugin/src/main/java/io/mosip/certify/mosipid/integration/service/ImageCompressorServiceImpl.java b/mosip-identity-certify-plugin/src/main/java/io/mosip/certify/mosipid/integration/service/ImageCompressorServiceImpl.java new file mode 100644 index 0000000..711df1a --- /dev/null +++ b/mosip-identity-certify-plugin/src/main/java/io/mosip/certify/mosipid/integration/service/ImageCompressorServiceImpl.java @@ -0,0 +1,23 @@ +package io.mosip.certify.mosipid.integration.service; + +import io.mosip.image.compressor.sdk.service.ImageCompressionService; +import io.mosip.kernel.biometrics.constant.BiometricType; +import io.mosip.kernel.biometrics.entities.BiometricRecord; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; + +@Component +public class ImageCompressorServiceImpl extends ImageCompressionService { + @Autowired + public ImageCompressorServiceImpl(Environment env) { + super(env, new BiometricRecord(), List.of(BiometricType.FACE), new HashMap<>()); + } + + public byte[] doResizeAndCompress(byte[] imageBytes) { + return resizeAndCompress(imageBytes); + } +} \ No newline at end of file