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
32 changes: 32 additions & 0 deletions mosip-identity-certify-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,38 @@
<artifactId>jackson-annotations</artifactId>
<version>2.15.4</version>
</dependency>
<dependency>
<groupId>io.mosip.image.compressor</groupId>
<artifactId>image-compressor</artifactId>
<version>0.1.0</version>
<exclusions>
<exclusion>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
</exclusion>
<exclusion>
<groupId>org.bytedeco</groupId>
<artifactId>opencv-platform</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.mosip.kernel</groupId>
<artifactId>kernel-biometrics-api</artifactId>
<version>1.3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20241224</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv-platform</artifactId>
<version>4.9.0-1.5.10</version>
<scope>compile</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -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."
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -93,6 +111,9 @@ public class IdaDataProviderPluginImpl implements DataProviderPlugin {
@Autowired
TransactionHelper transactionHelper;

@Autowired
private ImageCompressorUtil imageCompressorUtil;

private Base64.Decoder urlSafeDecoder = Base64.getUrlDecoder();

@Override
Expand All @@ -106,7 +127,14 @@ public JSONObject fetchData(Map<String, Object> identityDetails) throws DataProv
Map<String, Object> 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}