Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: jp2 jpeg converter #2

Merged
merged 6 commits into from
Oct 24, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class EIdReader(context: Context) {
}
if (allFaceImageInfos.isNotEmpty()) {
val faceImageInfo = allFaceImageInfos.iterator().next()
val image = bitmapUtil.getImage(faceImageInfo)
val image = bitmapUtil.getImage(faceImageInfo.imageInputStream, faceImageInfo.imageLength,faceImageInfo.mimeType)
nfcResult.originalFacePhoto = image
}
if (includeRawData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.nfc.NfcAdapter
import android.nfc.Tag
import android.nfc.tech.IsoDep
import android.os.Build
import android.provider.Settings
import android.util.Base64
import android.util.Log
import com.facebook.react.bridge.ActivityEventListener
import com.facebook.react.bridge.LifecycleEventListener
Expand All @@ -23,15 +26,24 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.modules.core.DeviceEventManagerModule
import io.twentysixty.rn.eidreader.dto.MrzInfo
import io.twentysixty.rn.eidreader.utils.BitmapUtil
import io.twentysixty.rn.eidreader.utils.JsonToReactMap
import io.twentysixty.rn.eidreader.utils.serializeToMap
import io.twentysixty.rn.eidreader.dto.MrzInfo
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.jmrtd.BACKey
import org.jmrtd.BACKeySpec
import org.json.JSONObject
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import jj2000.j2k.decoder.Decoder
import jj2000.j2k.util.ParameterList
import org.jmrtd.lds.AbstractImageInfo
import java.io.IOException


class EIdReaderModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext), LifecycleEventListener, ActivityEventListener {
Expand Down Expand Up @@ -273,6 +285,42 @@ class EIdReaderModule(reactContext: ReactApplicationContext) :
}
}

@ReactMethod
fun imageDataUrlToJpegDataUrl(dataUrl:String, promise: Promise){
try {
val dataSplit = dataUrl.split(";base64,")
if(dataSplit.size != 2){
promise.reject("Cannot imageDataUrlToJpegDataUrl image because is not a valid dataurl")
return
}
val mimeType = dataSplit[0].split(":")[1]
if(!mimeType.startsWith("image/")){
promise.reject("Couldn't convert $mimeType to JPEG")
return
}
if(mimeType == "image/jpeg"){
promise.resolve(dataUrl)
return
}
val dataContent = dataSplit[1]
val bitmapUtil = BitmapUtil(reactApplicationContext)
val decoded = Base64.decode(dataContent,Base64.DEFAULT)
val nfcImage = bitmapUtil.getImage(decoded.inputStream(), decoded.size, mimeType)
if (nfcImage.bitmap != null) {
val byteArrayOutputStream = ByteArrayOutputStream()
nfcImage.bitmap!!.compress(Bitmap.CompressFormat.JPEG, 70, byteArrayOutputStream)
val bytes = byteArrayOutputStream.toByteArray()
promise.resolve("data:image/jpeg;base64,"+ Base64.encodeToString(bytes, Base64.CRLF))
return
}
else promise.reject("Cannot imageDataUrlToJpegDataUrl image")

} catch (e: IOException) {
promise.reject("Cannot imageDataUrlToJpegDataUrl image")
return
}
}

companion object {
const val NAME = "EIdReader"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,17 @@ import java.io.ByteArrayOutputStream
import java.io.FileInputStream

class BitmapUtil(private val context: Context) {
fun getImage(imageInfo: AbstractImageInfo): NfcImage {
fun getImage(imageInputStream: InputStream, imageLength: Int, mimeType: String): NfcImage {
val image = NfcImage()
val imageLength = imageInfo.imageLength
val dataInputStream = DataInputStream(imageInfo.imageInputStream)
val dataInputStream = DataInputStream(imageInputStream)
val buffer = ByteArray(imageLength)
try {
dataInputStream.readFully(buffer, 0, imageLength)
val inputStream: InputStream = ByteArrayInputStream(buffer, 0, imageLength)
val bitmapImage = decodeImage(imageInfo.mimeType, inputStream)
image.bitmap = bitmapImage
val base64Image = Base64.encodeToString(buffer, Base64.DEFAULT)
image.base64 = base64Image
} catch (e: IOException) {
e.printStackTrace()
}

dataInputStream.readFully(buffer, 0, imageLength)
val inputStream: InputStream = ByteArrayInputStream(buffer, 0, imageLength)
val bitmapImage = decodeImage(mimeType, inputStream)
image.bitmap = bitmapImage
val base64Image = Base64.encodeToString(buffer, Base64.DEFAULT)
image.base64 = base64Image
return image
}

Expand All @@ -49,6 +45,12 @@ class BitmapUtil(private val context: Context) {
) || mimeType.equals("image/jpeg2000", ignoreCase = true)
) {

// Delete any existing temporary file
val tempJp2 = File(context.cacheDir, "temp.jp2")
if (tempJp2.exists()) tempJp2.delete()
val tempPpm = File(context.cacheDir, "temp.ppm")
if (tempPpm.exists()) tempPpm.delete()

// Save jp2 file
val output: OutputStream = FileOutputStream(File(context.cacheDir, "temp.jp2"))
val buffer = ByteArray(1024)
Expand All @@ -68,7 +70,6 @@ class BitmapUtil(private val context: Context) {
}
}
parameters = ParameterList(defaults)
parameters.setProperty("rate", "3")
parameters.setProperty("o", context.cacheDir.toString() + "/temp.ppm")
parameters.setProperty("debug", "on")
parameters.setProperty("i", context.cacheDir.toString() + "/temp.jp2")
Expand Down
24 changes: 24 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
Text,
ScrollView,
TouchableOpacity,
Image,
} from 'react-native';
import EIdReader, {
type EIdReadResult,
} from '@2060.io/react-native-eid-reader';
import { lena } from './data';

export default function App() {
const [result, setResult] = React.useState<EIdReadResult>();
const [convertedImage, setConvertedImage] = React.useState<
string | undefined
>(undefined);

React.useEffect(() => {
EIdReader.addOnTagDiscoveredListener(() => {
Expand Down Expand Up @@ -49,13 +54,23 @@
});
};

const imageDataUrlToJpegDataUrl = () => {
EIdReader.imageDataUrlToJpegDataUrl(lena)
.then((data) => {
setConvertedImage(`${data}`);
})
.catch((error) => {
console.error('error', error);
});
};

const stopReading = () => {
EIdReader.stopReading();
};

const openNfcSettings = async () => {
try {
const result = await EIdReader.openNfcSettings();

Check warning on line 73 in example/src/App.tsx

View workflow job for this annotation

GitHub Actions / lint

'result' is already declared in the upper scope on line 17 column 10
console.log(result);
} catch (e) {
console.log(e);
Expand All @@ -64,7 +79,7 @@

const isNfcSupported = async () => {
try {
const result = await EIdReader.isNfcSupported();

Check warning on line 82 in example/src/App.tsx

View workflow job for this annotation

GitHub Actions / lint

'result' is already declared in the upper scope on line 17 column 10
console.log(result);
} catch (e) {
console.log(e);
Expand All @@ -73,7 +88,7 @@

const isNfcEnabled = async () => {
try {
const result = await EIdReader.isNfcEnabled();

Check warning on line 91 in example/src/App.tsx

View workflow job for this annotation

GitHub Actions / lint

'result' is already declared in the upper scope on line 17 column 10
console.log(result);
} catch (e) {
console.log(e);
Expand All @@ -85,6 +100,12 @@
<ScrollView style={styles.container}>
<View style={styles.box}>
<View style={styles.buttonContainer}>
<TouchableOpacity
onPress={imageDataUrlToJpegDataUrl}
style={styles.button}
>
<Text style={styles.buttonText}>Convert</Text>
</TouchableOpacity>
<TouchableOpacity onPress={startReading} style={styles.button}>
<Text style={styles.buttonText}>Start Reading</Text>
</TouchableOpacity>
Expand All @@ -108,6 +129,9 @@
<Text style={styles.text}>{JSON.stringify(result, null, 2)}</Text>
</View>
</ScrollView>
{convertedImage && (
<Image source={{ uri: convertedImage }} width={100} height={100} />
)}
</>
);
}
Expand Down
11 changes: 11 additions & 0 deletions example/src/data.ts

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions ios/EidReader.mm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#import <Foundation/Foundation.h>

#import "React/RCTBridgeModule.h"
#import "React/RCTEventEmitter.h"
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface RCT_EXTERN_MODULE(EIdReader, RCTEventEmitter)

Expand All @@ -20,6 +20,10 @@ @interface RCT_EXTERN_MODULE(EIdReader, RCTEventEmitter)

RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(stopReading);

RCT_EXTERN_METHOD(imageDataUrlToJpegDataUrl:(NSString)dataUrl
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject);

+ (BOOL)requiresMainQueueSetup
{
return NO;
Expand Down
31 changes: 31 additions & 0 deletions ios/EidReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,37 @@ class EIdReader: RCTEventEmitter {

}

@objc(imageDataUrlToJpegDataUrl:withResolver:withRejecter:)
func imageDataUrlToJpegDataUrl(
dataUrl: NSString,
resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) {
let dataSplit = (dataUrl as String).components(separatedBy: ";base64,")
if(dataSplit.count != 2){
reject("@ConvertError", "Cannot imageDataUrlToJpegDataUrl image because is not a valid dataurl", nil)
return
}
if let mimeType = dataSplit.first?.replacingOccurrences(of: "data:", with: ""){
if(!mimeType.hasPrefix("image/")){
reject("@ConvertError", "Couldn't convert \(mimeType) to JPEG", nil)
return
}
if(mimeType == "image/jpeg"){
resolve(dataUrl)
return
}
let dataContent = dataSplit[1]
if let newData = Data(base64Encoded: dataContent) {
if let jpegData = UIImage(data: newData)?.jpegData(compressionQuality: 1.0)?.base64EncodedString(){
resolve("data:image/jpeg;base64,\(jpegData)")
return
}
}
}
reject("@ConvertError", "Convert image data URL to JPEG image data url error", nil)
}

@objc(stopReading)
func stopReading() -> Void {
// TODO
Expand Down
4 changes: 4 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ export default class EIdReader {
return EIdReaderNativeModule.openNfcSettings();
}

static imageDataUrlToJpegDataUrl(dataUrl: string): Promise<string> {
return EIdReaderNativeModule.imageDataUrlToJpegDataUrl(dataUrl);
}

private static addListener(
event: EIdReaderEvent,
callback: (data: any) => void
Expand Down
Loading