ZeroPass / dmrtd

Dart library for reading Biometric Passport
Other
43 stars 19 forks source link

How can i read images from passport ? #9

Closed talha-inozu closed 1 year ago

talha-inozu commented 1 year ago

Hi,

I am trying to read image data from passport. How can i take photos from passport with using dmrtd ? I try to take bytearray from dg2 and transform to image with using Image.memory(bytes) but it doesnt work. İt give wrong data error. İs it another data in dg2 other than faceimages ?

eliasto commented 1 year ago

Image data are generally encoded in jp2 (jpeg 2000). Flutter can't decrypt this kind of format natively. You have to convert it in a readable format before using it.

talha-inozu commented 1 year ago

Image data are generally encoded in jp2 (jpeg 2000). Flutter can't decrypt this kind of format natively. You have to convert it in a readable format before using it.

My problem is about understading the data which comes from dg2 tag, ı can convert the format there is not a problem at there but in dg2, a lot of informations are there and ı dont know how to parse it for take photo byte array

eliasto commented 1 year ago

Image data are generally encoded in jp2 (jpeg 2000). Flutter can't decrypt this kind of format natively. You have to convert it in a readable format before using it.

My problem is about understading the data which comes from dg2 tag, ı can convert the format there is not a problem at there but in dg2, a lot of informations are there and ı dont know how to parse it for take photo byte array

You can find the documentation here: https://www.icao.int/publications/documents/9303_p10_cons_en.pdf

talha-inozu commented 1 year ago

Image data are generally encoded in jp2 (jpeg 2000). Flutter can't decrypt this kind of format natively. You have to convert it in a readable format before using it.

My problem is about understading the data which comes from dg2 tag, ı can convert the format there is not a problem at there but in dg2, a lot of informations are there and ı dont know how to parse it for take photo byte array

You can find the documentation here: https://www.icao.int/publications/documents/9303_p10_cons_en.pdf

Thank you some much this what ı exactly want it. Also is there any code example for extracting images from dg2 ?

smlu commented 1 year ago

@talha-inozu the develop branch has implemented parsing of EF.DG2 file. When parsing succeeds the raw image data is assigned to member imageData. The type of image format can be retrieved via imageType member.

talha-inozu commented 1 year ago

@talha-inozu the develop branch has implemented parsing of EF.DG2 file. When parsing succeeds the raw image data is assigned to member imageData. The type of image format can be retrieved via imageType member.

Thank you so much this is what ı exactly search for.

Taym95 commented 1 year ago

Hi @talha-inozu , imageData seems incorrect did you manage to get correct image?

norbertkross commented 12 months ago

how do I convert the jpeg 2000 to jpeg in flutter. Anyone been able to achieve this? @eliasto @smlu

smlu commented 12 months ago

I believe no one has made dart converter for jpeg2000. Your best bet is probably using OpenJpeg C library through FFI or create native Flutter plugin for android (JP2ForAndroid) and iOS.

socialsmoker223 commented 9 months ago

I believe no one has made dart converter for jpeg2000. Your best bet is probably using OpenJpeg C library through FFI or create native Flutter plugin for android (JP2ForAndroid) and iOS.

Thank you for your suggestion! I'm interested in using the OpenJpeg C library through FFI to work with JPEG2000. However, I'm currently facing a challenge in figuring out how to pass the Uint8List data from Dart to a C function that expects a void* pointer for the p_stream. I tried with ffi.Pointer<Uint8> but failed to decode it.

Could you provide more details or guidance on how to properly convert and pass the Uint8List data as a void* pointer in C? Any insights or code examples would be greatly appreciated.

Taym95 commented 9 months ago

I believe no one has made dart converter for jpeg2000. Your best bet is probably using OpenJpeg C library through FFI or create native Flutter plugin for android (JP2ForAndroid) and iOS.

Thank you for your suggestion! I'm interested in using the OpenJpeg C library through FFI to work with JPEG2000. However, I'm currently facing a challenge in figuring out how to pass the Uint8List data from Dart to a C function that expects a void* pointer for the p_stream. I tried with ffi.Pointer<Uint8> but failed to decode it.

Could you provide more details or guidance on how to properly convert and pass the Uint8List data as a void* pointer in C? Any insights or code examples would be greatly appreciated.

share with me your repo and I will make PR to help

socialsmoker223 commented 9 months ago

share with me your repo and I will make PR to help

Thank you for your response @Taym95. This is my repo openjpeg_ffi. I think the problem is within the C function.

zamirszn commented 6 months ago

im also trying to achieve this , has anyone been able to do this please

zamirszn commented 6 months ago

ive been able to display the images, in case someone wants to do this in future , all you need Platfom Channels heres a sample code

the imageData from dmrtd is in Uint8List after parsing, call the method channel

dart

const imageChannel = MethodChannel('image_channel');

Future<Uint8List> decodeImage(Uint8List jp2ImageData,) async {
  Uint8List decodedImageData;

  final result = await imageChannel
      .invokeMethod('decodeImage', {'jp2ImageData': jp2ImageData});
  decodedImageData = Uint8List.fromList(result);

  return decodedImageData;
}

add this in a kotlin file (imageutil.kt for example) kotlin

package com.your.packagenamehere

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import io.flutter.plugin.common.MethodChannel
import com.gemalto.jp2.JP2Decoder
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream

object ImageUtil {

    fun decodeImage(context: Context?, jp2ImageData: ByteArray, result: MethodChannel.Result) {
        // Convert JP2 ByteArray to InputStream
        val inputStream: InputStream = ByteArrayInputStream(jp2ImageData)

        // Use the existing decodeImage function
        val decodedBitmap = decodeImage(context, "image/jp2", inputStream)

        // Convert the Bitmap to a ByteArray for sending back to Flutter
        val byteArrayOutputStream = ByteArrayOutputStream()
        decodedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
        val byteArray = byteArrayOutputStream.toByteArray()

        // Send the decoded image back to Flutter
        result.success(byteArray)
    }

    fun decodeImage(context: Context?, mimeType: String, inputStream: InputStream?): Bitmap {
        return if (mimeType.equals("image/jp2", ignoreCase = true) || mimeType.equals(
                "image/jpeg2000",
                ignoreCase = true
            )
        ) {
            JP2Decoder(inputStream).decode()
        } else {
            // Add other decoding logic if needed
            BitmapFactory.decodeStream(inputStream)
        }
    }
}

then in your mainActivity.kt register the method

package com.your.packagenamehere

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.MethodChannel

import android.content.Context
import com.scansolutions.mrzflutterplugin_example.ImageUtil

class MainActivity : FlutterActivity() {

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "image_channel")
            .setMethodCallHandler { call, result ->
                if (call.method == "decodeImage") {
                    val jp2ImageData = call.argument<ByteArray?>("jp2ImageData")
                    if (jp2ImageData != null) {
                        ImageUtil.decodeImage(applicationContext, jp2ImageData, result)
                    } else {
                        result.error("INVALID_ARGUMENT", "jp2ImageData is null", null)
                    }
                } else {
                    result.notImplemented()
                }
            }
    }
} 

then lastly in app directory gradle.build file add this dep

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    // Gemalto JP2 Decoder
    // https://mvnrepository.com/artifact/com.gemalto.jp2/jp2-android
    implementation 'com.gemalto.jp2:jp2-android:1.0.3'

    // // JNBIS (WSQ Decoder)
    // implementation 'org.jnbis:jnbis:1.7'
}

decodeImage() method call the native code which converts the imageData from jpeg2000 to jpg, returns the image data (Uint8List) and you can simply display with Image.memory(imageData) or anything

santoni-holding commented 3 months ago

ive been able to display the images, in case someone wants to do this in future , all you need Platfom Channels heres a sample code

the imageData from dmrtd is in Uint8List after parsing, call the method channel

dart

const imageChannel = MethodChannel('image_channel');

Future<Uint8List> decodeImage(Uint8List jp2ImageData,) async {
  Uint8List decodedImageData;

  final result = await imageChannel
      .invokeMethod('decodeImage', {'jp2ImageData': jp2ImageData});
  decodedImageData = Uint8List.fromList(result);

  return decodedImageData;
}

add this in a kotlin file (imageutil.kt for example) kotlin

package com.your.packagenamehere

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import io.flutter.plugin.common.MethodChannel
import com.gemalto.jp2.JP2Decoder
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream

object ImageUtil {

    fun decodeImage(context: Context?, jp2ImageData: ByteArray, result: MethodChannel.Result) {
        // Convert JP2 ByteArray to InputStream
        val inputStream: InputStream = ByteArrayInputStream(jp2ImageData)

        // Use the existing decodeImage function
        val decodedBitmap = decodeImage(context, "image/jp2", inputStream)

        // Convert the Bitmap to a ByteArray for sending back to Flutter
        val byteArrayOutputStream = ByteArrayOutputStream()
        decodedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
        val byteArray = byteArrayOutputStream.toByteArray()

        // Send the decoded image back to Flutter
        result.success(byteArray)
    }

    fun decodeImage(context: Context?, mimeType: String, inputStream: InputStream?): Bitmap {
        return if (mimeType.equals("image/jp2", ignoreCase = true) || mimeType.equals(
                "image/jpeg2000",
                ignoreCase = true
            )
        ) {
            JP2Decoder(inputStream).decode()
        } else {
            // Add other decoding logic if needed
            BitmapFactory.decodeStream(inputStream)
        }
    }
}

then in your mainActivity.kt register the method

package com.your.packagenamehere

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.MethodChannel

import android.content.Context
import com.scansolutions.mrzflutterplugin_example.ImageUtil

class MainActivity : FlutterActivity() {

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "image_channel")
            .setMethodCallHandler { call, result ->
                if (call.method == "decodeImage") {
                    val jp2ImageData = call.argument<ByteArray?>("jp2ImageData")
                    if (jp2ImageData != null) {
                        ImageUtil.decodeImage(applicationContext, jp2ImageData, result)
                    } else {
                        result.error("INVALID_ARGUMENT", "jp2ImageData is null", null)
                    }
                } else {
                    result.notImplemented()
                }
            }
    }
} 

then lastly in app directory gradle.build file add this dep

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    // Gemalto JP2 Decoder
    // https://mvnrepository.com/artifact/com.gemalto.jp2/jp2-android
    implementation 'com.gemalto.jp2:jp2-android:1.0.3'

    // // JNBIS (WSQ Decoder)
    // implementation 'org.jnbis:jnbis:1.7'
}

decodeImage() method call the native code which converts the imageData from jpeg2000 to jpg, returns the image data (Uint8List) and you can simply display with Image.memory(imageData) or anything

Your Code is exactly what i need and I am extremely happy having found it, although it is not completely working for me. If i pass the DG2 MDTDS ByteArray (Uint8List) to the Platform Channel, I receive the error:

"E/OpenJPEG(11299): Unknown file format" on the method JP2Decoder(inputStream).decode()

I made sure the ByteStream is there (10160 bytes), but i always get the same error, even though i did not touch the Stream from the DG2 Tag. Did you maybe encounter the same error, or rather do you have any idea on how to fix it?

Thanks in advance!

zamirszn commented 3 months ago

ive been able to display the images, in case someone wants to do this in future , all you need Platfom Channels heres a sample code

the imageData from dmrtd is in Uint8List after parsing, call the method channel

dart

const imageChannel = MethodChannel('image_channel');

Future<Uint8List> decodeImage(Uint8List jp2ImageData,) async {
  Uint8List decodedImageData;

  final result = await imageChannel
      .invokeMethod('decodeImage', {'jp2ImageData': jp2ImageData});
  decodedImageData = Uint8List.fromList(result);

  return decodedImageData;
}

add this in a kotlin file (imageutil.kt for example) kotlin

package com.your.packagenamehere

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import io.flutter.plugin.common.MethodChannel
import com.gemalto.jp2.JP2Decoder
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream

object ImageUtil {

    fun decodeImage(context: Context?, jp2ImageData: ByteArray, result: MethodChannel.Result) {
        // Convert JP2 ByteArray to InputStream
        val inputStream: InputStream = ByteArrayInputStream(jp2ImageData)

        // Use the existing decodeImage function
        val decodedBitmap = decodeImage(context, "image/jp2", inputStream)

        // Convert the Bitmap to a ByteArray for sending back to Flutter
        val byteArrayOutputStream = ByteArrayOutputStream()
        decodedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
        val byteArray = byteArrayOutputStream.toByteArray()

        // Send the decoded image back to Flutter
        result.success(byteArray)
    }

    fun decodeImage(context: Context?, mimeType: String, inputStream: InputStream?): Bitmap {
        return if (mimeType.equals("image/jp2", ignoreCase = true) || mimeType.equals(
                "image/jpeg2000",
                ignoreCase = true
            )
        ) {
            JP2Decoder(inputStream).decode()
        } else {
            // Add other decoding logic if needed
            BitmapFactory.decodeStream(inputStream)
        }
    }
}

then in your mainActivity.kt register the method

package com.your.packagenamehere

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.plugin.common.MethodChannel

import android.content.Context
import com.scansolutions.mrzflutterplugin_example.ImageUtil

class MainActivity : FlutterActivity() {

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "image_channel")
            .setMethodCallHandler { call, result ->
                if (call.method == "decodeImage") {
                    val jp2ImageData = call.argument<ByteArray?>("jp2ImageData")
                    if (jp2ImageData != null) {
                        ImageUtil.decodeImage(applicationContext, jp2ImageData, result)
                    } else {
                        result.error("INVALID_ARGUMENT", "jp2ImageData is null", null)
                    }
                } else {
                    result.notImplemented()
                }
            }
    }
} 

then lastly in app directory gradle.build file add this dep

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    // Gemalto JP2 Decoder
    // https://mvnrepository.com/artifact/com.gemalto.jp2/jp2-android
    implementation 'com.gemalto.jp2:jp2-android:1.0.3'

    // // JNBIS (WSQ Decoder)
    // implementation 'org.jnbis:jnbis:1.7'
}

decodeImage() method call the native code which converts the imageData from jpeg2000 to jpg, returns the image data (Uint8List) and you can simply display with Image.memory(imageData) or anything

Your Code is exactly what i need and I am extremely happy having found it, although it is not completely working for me. If i pass the DG2 MDTDS ByteArray (Uint8List) to the Platform Channel, I receive the error:

"E/OpenJPEG(11299): Unknown file format" on the method JP2Decoder(inputStream).decode()

I made sure the ByteStream is there (10160 bytes), but i always get the same error, even though i did not touch the Stream from the DG2 Tag. Did you maybe encounter the same error, or rather do you have any idea on how to fix it?

Thanks in advance!

hello, I'm not sure I had that error , I can't totally remember but what I encountered might be a bug , the dmrtd lib was detecting a jpeg image as jp2 , so you might want to check before converting . Another issue was I had to convert the byte array to hex then check if the data is an image by check the FFD8 and FFD9 markers

santoni-holding commented 3 months ago

Hello, thank you so much for your help, I managed to read it correctly by parsing the output with the following class (my offset was off by a little)!

This is the code for parsing if anybody else has this problem (It's a rewritten Dart Version of the similar DataGroup2 Swift File inside the NFCPassportReader Project)


class DataGroupTwo extends DataGroup {
  int nrImages = 0;
  int versionNumber = 0;
  int lengthOfRecord = 0;
  int numberOfFacialImages = 0;
  int facialRecordDataLength = 0;
  int nrFeaturePoints = 0;
  int gender = 0;
  int eyeColor = 0;
  int hairColor = 0;
  int featureMask = 0;
  int expression = 0;
  int poseAngle = 0;
  int poseAngleUncertainty = 0;
  int faceImageType = 0;
  int imageDataType = 0;
  int imageWidth = 0;
  int imageHeight = 0;
  int imageColorSpace = 0;
  int sourceType = 0;
  int deviceType = 0;
  int quality = 0;
  Uint8List imageData = Uint8List(0);

  DataGroupTwo(List data) : super(data);

  @OverRide
  void parse(List data) {
  var tag = getNextTag();
  verifyTag(tag, 0x7F61);
  getNextLength();

  // Tag should be 0x02
  tag = getNextTag();
  verifyTag(tag, 0x02);
  nrImages = getNextValue()[0];

  // Next tag is 0x7F60
  tag = getNextTag();
  verifyTag(tag, 0x7F60);
  getNextLength();

  // Next tag is 0xA1 (Biometric Header Template) - don't care about this
  tag = getNextTag();
  verifyTag(tag, 0xA1);
  getNextValue();

  // Now we get to the good stuff - next tag is either 5F2E or 7F2E
  tag = getNextTag();
  verifyTagOneOf(tag, [0x5F2E, 0x7F2E]);
  var value = getNextValue();

  parseISO19794_5(value);
  }

  void parseISO19794_5(List data) {
  var offset = 4;
  versionNumber = binToInt(data.sublist(offset, offset + 4));
  offset += 4;
  lengthOfRecord = binToInt(data.sublist(offset, offset + 4));
  offset += 4;
  numberOfFacialImages = binToInt(data.sublist(offset, offset + 2));
  offset += 2;

  facialRecordDataLength = binToInt(data.sublist(offset, offset + 4));
  offset += 4;
  nrFeaturePoints = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  gender = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  eyeColor = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  hairColor = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  featureMask = binToInt(data.sublist(offset, offset + 3));
  offset += 3;
  expression = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  poseAngle = binToInt(data.sublist(offset, offset + 3));
  offset += 3;
  poseAngleUncertainty = binToInt(data.sublist(offset, offset + 3));
  offset += 3;

  // Skip over feature points if any are present
  offset += nrFeaturePoints * 8;

  faceImageType = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  imageDataType = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  imageWidth = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  imageHeight = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  imageColorSpace = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  sourceType = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  deviceType = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  quality = binToInt(data.sublist(offset, offset + 2));
  offset += 2;

  imageData = Uint8List.fromList(data.sublist(offset));
  }

  int binToInt(List bytes) {
  return bytes.fold(0, (previous, byte) => (previous << 8) + byte);
  }
}
zamirszn commented 3 months ago

Hello, thank you so much for your help, I managed to read it correctly by parsing the output with the following class (my offset was off by a little)!

This is the code for parsing if anybody else has this problem (It's a rewritten Dart Version of the similar DataGroup2 Swift File inside the NFCPassportReader Project)


class DataGroupTwo extends DataGroup {
  int nrImages = 0;
  int versionNumber = 0;
  int lengthOfRecord = 0;
  int numberOfFacialImages = 0;
  int facialRecordDataLength = 0;
  int nrFeaturePoints = 0;
  int gender = 0;
  int eyeColor = 0;
  int hairColor = 0;
  int featureMask = 0;
  int expression = 0;
  int poseAngle = 0;
  int poseAngleUncertainty = 0;
  int faceImageType = 0;
  int imageDataType = 0;
  int imageWidth = 0;
  int imageHeight = 0;
  int imageColorSpace = 0;
  int sourceType = 0;
  int deviceType = 0;
  int quality = 0;
  Uint8List imageData = Uint8List(0);

  DataGroupTwo(List data) : super(data);

  @OverRide
  void parse(List data) {
  var tag = getNextTag();
  verifyTag(tag, 0x7F61);
  getNextLength();

  // Tag should be 0x02
  tag = getNextTag();
  verifyTag(tag, 0x02);
  nrImages = getNextValue()[0];

  // Next tag is 0x7F60
  tag = getNextTag();
  verifyTag(tag, 0x7F60);
  getNextLength();

  // Next tag is 0xA1 (Biometric Header Template) - don't care about this
  tag = getNextTag();
  verifyTag(tag, 0xA1);
  getNextValue();

  // Now we get to the good stuff - next tag is either 5F2E or 7F2E
  tag = getNextTag();
  verifyTagOneOf(tag, [0x5F2E, 0x7F2E]);
  var value = getNextValue();

  parseISO19794_5(value);
  }

  void parseISO19794_5(List data) {
  var offset = 4;
  versionNumber = binToInt(data.sublist(offset, offset + 4));
  offset += 4;
  lengthOfRecord = binToInt(data.sublist(offset, offset + 4));
  offset += 4;
  numberOfFacialImages = binToInt(data.sublist(offset, offset + 2));
  offset += 2;

  facialRecordDataLength = binToInt(data.sublist(offset, offset + 4));
  offset += 4;
  nrFeaturePoints = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  gender = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  eyeColor = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  hairColor = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  featureMask = binToInt(data.sublist(offset, offset + 3));
  offset += 3;
  expression = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  poseAngle = binToInt(data.sublist(offset, offset + 3));
  offset += 3;
  poseAngleUncertainty = binToInt(data.sublist(offset, offset + 3));
  offset += 3;

  // Skip over feature points if any are present
  offset += nrFeaturePoints * 8;

  faceImageType = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  imageDataType = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  imageWidth = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  imageHeight = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  imageColorSpace = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  sourceType = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  deviceType = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  quality = binToInt(data.sublist(offset, offset + 2));
  offset += 2;

  imageData = Uint8List.fromList(data.sublist(offset));
  }

  int binToInt(List bytes) {
  return bytes.fold(0, (previous, byte) => (previous << 8) + byte);
  }
}

if you are working with swift I have someone looking an iOS implementation to convert the byte data from jp2

santoni-holding commented 3 months ago

Hello, thank you so much for your help, I managed to read it correctly by parsing the output with the following class (my offset was off by a little)! This is the code for parsing if anybody else has this problem (It's a rewritten Dart Version of the similar DataGroup2 Swift File inside the NFCPassportReader Project)


class DataGroupTwo extends DataGroup {
  int nrImages = 0;
  int versionNumber = 0;
  int lengthOfRecord = 0;
  int numberOfFacialImages = 0;
  int facialRecordDataLength = 0;
  int nrFeaturePoints = 0;
  int gender = 0;
  int eyeColor = 0;
  int hairColor = 0;
  int featureMask = 0;
  int expression = 0;
  int poseAngle = 0;
  int poseAngleUncertainty = 0;
  int faceImageType = 0;
  int imageDataType = 0;
  int imageWidth = 0;
  int imageHeight = 0;
  int imageColorSpace = 0;
  int sourceType = 0;
  int deviceType = 0;
  int quality = 0;
  Uint8List imageData = Uint8List(0);

  DataGroupTwo(List data) : super(data);

  @OverRide
  void parse(List data) {
  var tag = getNextTag();
  verifyTag(tag, 0x7F61);
  getNextLength();

  // Tag should be 0x02
  tag = getNextTag();
  verifyTag(tag, 0x02);
  nrImages = getNextValue()[0];

  // Next tag is 0x7F60
  tag = getNextTag();
  verifyTag(tag, 0x7F60);
  getNextLength();

  // Next tag is 0xA1 (Biometric Header Template) - don't care about this
  tag = getNextTag();
  verifyTag(tag, 0xA1);
  getNextValue();

  // Now we get to the good stuff - next tag is either 5F2E or 7F2E
  tag = getNextTag();
  verifyTagOneOf(tag, [0x5F2E, 0x7F2E]);
  var value = getNextValue();

  parseISO19794_5(value);
  }

  void parseISO19794_5(List data) {
  var offset = 4;
  versionNumber = binToInt(data.sublist(offset, offset + 4));
  offset += 4;
  lengthOfRecord = binToInt(data.sublist(offset, offset + 4));
  offset += 4;
  numberOfFacialImages = binToInt(data.sublist(offset, offset + 2));
  offset += 2;

  facialRecordDataLength = binToInt(data.sublist(offset, offset + 4));
  offset += 4;
  nrFeaturePoints = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  gender = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  eyeColor = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  hairColor = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  featureMask = binToInt(data.sublist(offset, offset + 3));
  offset += 3;
  expression = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  poseAngle = binToInt(data.sublist(offset, offset + 3));
  offset += 3;
  poseAngleUncertainty = binToInt(data.sublist(offset, offset + 3));
  offset += 3;

  // Skip over feature points if any are present
  offset += nrFeaturePoints * 8;

  faceImageType = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  imageDataType = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  imageWidth = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  imageHeight = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  imageColorSpace = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  sourceType = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  deviceType = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  quality = binToInt(data.sublist(offset, offset + 2));
  offset += 2;

  imageData = Uint8List.fromList(data.sublist(offset));
  }

  int binToInt(List bytes) {
  return bytes.fold(0, (previous, byte) => (previous << 8) + byte);
  }
}

if you are working with swift I have someone looking an iOS implementation to convert the byte data from jp2

I got it working on iOS and android, using your solution on android and this method on iOS:


public func convertJpeg2000ToJpeg(jp2ImageData: Data) -> Data? {
        // Initialize a CIImage with the JPEG2000 data
        guard let ciImage = CIImage(data: jp2ImageData) else {
            print("Failed to create CIImage")
            return nil
        }

        // Create a context to convert the image
        let context = CIContext(options: nil)

        // Define JPEG compression quality
        let jpegQuality: CGFloat = 0.9 // Adjust based on your quality requirements

        // Get JPEG representation of the CIImage
        guard let jpegData = context.jpegRepresentation(of: ciImage, colorSpace: ciImage.colorSpace ?? CGColorSpaceCreateDeviceRGB()) else {
            print("Failed to get JPEG representation")
            return nil
        }

        return jpegData
    }
zamirszn commented 3 months ago

Hello, thank you so much for your help, I managed to read it correctly by parsing the output with the following class (my offset was off by a little)! This is the code for parsing if anybody else has this problem (It's a rewritten Dart Version of the similar DataGroup2 Swift File inside the NFCPassportReader Project)


class DataGroupTwo extends DataGroup {
  int nrImages = 0;
  int versionNumber = 0;
  int lengthOfRecord = 0;
  int numberOfFacialImages = 0;
  int facialRecordDataLength = 0;
  int nrFeaturePoints = 0;
  int gender = 0;
  int eyeColor = 0;
  int hairColor = 0;
  int featureMask = 0;
  int expression = 0;
  int poseAngle = 0;
  int poseAngleUncertainty = 0;
  int faceImageType = 0;
  int imageDataType = 0;
  int imageWidth = 0;
  int imageHeight = 0;
  int imageColorSpace = 0;
  int sourceType = 0;
  int deviceType = 0;
  int quality = 0;
  Uint8List imageData = Uint8List(0);

  DataGroupTwo(List data) : super(data);

  @OverRide
  void parse(List data) {
  var tag = getNextTag();
  verifyTag(tag, 0x7F61);
  getNextLength();

  // Tag should be 0x02
  tag = getNextTag();
  verifyTag(tag, 0x02);
  nrImages = getNextValue()[0];

  // Next tag is 0x7F60
  tag = getNextTag();
  verifyTag(tag, 0x7F60);
  getNextLength();

  // Next tag is 0xA1 (Biometric Header Template) - don't care about this
  tag = getNextTag();
  verifyTag(tag, 0xA1);
  getNextValue();

  // Now we get to the good stuff - next tag is either 5F2E or 7F2E
  tag = getNextTag();
  verifyTagOneOf(tag, [0x5F2E, 0x7F2E]);
  var value = getNextValue();

  parseISO19794_5(value);
  }

  void parseISO19794_5(List data) {
  var offset = 4;
  versionNumber = binToInt(data.sublist(offset, offset + 4));
  offset += 4;
  lengthOfRecord = binToInt(data.sublist(offset, offset + 4));
  offset += 4;
  numberOfFacialImages = binToInt(data.sublist(offset, offset + 2));
  offset += 2;

  facialRecordDataLength = binToInt(data.sublist(offset, offset + 4));
  offset += 4;
  nrFeaturePoints = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  gender = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  eyeColor = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  hairColor = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  featureMask = binToInt(data.sublist(offset, offset + 3));
  offset += 3;
  expression = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  poseAngle = binToInt(data.sublist(offset, offset + 3));
  offset += 3;
  poseAngleUncertainty = binToInt(data.sublist(offset, offset + 3));
  offset += 3;

  // Skip over feature points if any are present
  offset += nrFeaturePoints * 8;

  faceImageType = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  imageDataType = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  imageWidth = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  imageHeight = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  imageColorSpace = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  sourceType = binToInt(data.sublist(offset, offset + 1));
  offset += 1;
  deviceType = binToInt(data.sublist(offset, offset + 2));
  offset += 2;
  quality = binToInt(data.sublist(offset, offset + 2));
  offset += 2;

  imageData = Uint8List.fromList(data.sublist(offset));
  }

  int binToInt(List bytes) {
  return bytes.fold(0, (previous, byte) => (previous << 8) + byte);
  }
}

if you are working with swift I have someone looking an iOS implementation to convert the byte data from jp2

I got it working on iOS and android, using your solution on android and this method on iOS:


public func convertJpeg2000ToJpeg(jp2ImageData: Data) -> Data? {
        // Initialize a CIImage with the JPEG2000 data
        guard let ciImage = CIImage(data: jp2ImageData) else {
            print("Failed to create CIImage")
            return nil
        }

        // Create a context to convert the image
        let context = CIContext(options: nil)

        // Define JPEG compression quality
        let jpegQuality: CGFloat = 0.9 // Adjust based on your quality requirements

        // Get JPEG representation of the CIImage
        guard let jpegData = context.jpegRepresentation(of: ciImage, colorSpace: ciImage.colorSpace ?? CGColorSpaceCreateDeviceRGB()) else {
            print("Failed to get JPEG representation")
            return nil
        }

        return jpegData
    }

thanks alot