org.jmrtd.lds.iso19794.FaceInfo Maven / Gradle / Ivy
/*
* JMRTD - A Java API for accessing machine readable travel documents.
*
* Copyright (C) 2006 - 2018 The JMRTD team
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id: FaceInfo.java 1818 2019-08-02 12:59:22Z martijno $
*/
package org.jmrtd.lds.iso19794;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jmrtd.cbeff.BiometricDataBlock;
import org.jmrtd.cbeff.CBEFFInfo;
import org.jmrtd.cbeff.ISO781611;
import org.jmrtd.cbeff.StandardBiometricHeader;
import org.jmrtd.lds.AbstractListInfo;
import org.jmrtd.lds.iso19794.FaceImageInfo.EyeColor;
import org.jmrtd.lds.iso19794.FaceImageInfo.FeaturePoint;
import net.sf.scuba.data.Gender;
/**
* A facial record consists of a facial record header and one or more facial record datas.
* See 5.1 of ISO 19794-5.
*
* @author The JMRTD team ([email protected])
*
* @version $Revision: 1818 $
*/
public class FaceInfo extends AbstractListInfo implements BiometricDataBlock {
private static final long serialVersionUID = -6053206262773400725L;
private static final Logger LOGGER = Logger.getLogger("org.jmrtd");
/** Facial Record Header 'F', 'A', 'C', 0x00. Section 5.4, Table 2 of ISO/IEC 19794-5. */
private static final int FORMAT_IDENTIFIER = 0x46414300;
/** Version number '0', '1', '0', 0x00. Section 5.4, Table 2 of ISO/IEC 19794-5. */
private static final int VERSION_NUMBER = 0x30313000;
/** Format owner identifier of ISO/IEC JTC1/SC37. */
private static final int FORMAT_OWNER_VALUE = 0x0101;
/**
* ISO/IEC JTC1/SC37 uses 0x0008 according to IBIA.
* Also see supplement to Doc 9303: R3-p1_v2_sII_0001.
* (ISO FCD 19794-5 specified this as 0x0501).
*/
private static final int FORMAT_TYPE_VALUE = 0x0008;
private StandardBiometricHeader sbh;
/**
* Constructs a face info from a list of face image infos.
*
* @param faceImageInfos face image infos
*/
public FaceInfo(List faceImageInfos) {
this(null, faceImageInfos);
}
/**
* Constructs a face info from a list of face image infos.
*
* @param sbh the standard biometric header to use
* @param faceImageInfos face image infos
*/
public FaceInfo(StandardBiometricHeader sbh, List faceImageInfos) {
this.sbh = sbh;
addAll(faceImageInfos);
}
/**
* Constructs a face info from binary encoding.
*
* @param inputStream an input stream
*
* @throws IOException when decoding fails
*/
public FaceInfo(InputStream inputStream) throws IOException {
this(null, inputStream);
}
/**
* Constructs a face info from binary encoding.
*
* @param sbh the standard biometric header to use
* @param inputStream an input stream
*
* @throws IOException when decoding fails
*/
public FaceInfo(StandardBiometricHeader sbh, InputStream inputStream) throws IOException {
this.sbh = sbh;
readObject(inputStream);
}
/**
* Reads the facial record from an input stream. Note that the standard biometric header
* has already been read.
*
* @param inputStream the input stream
*/
@Override
public void readObject(InputStream inputStream) throws IOException {
DataInputStream dataInputStream = inputStream instanceof DataInputStream ? (DataInputStream)inputStream : new DataInputStream(inputStream);
/* Facial Record Header (14) */
int fac0 = dataInputStream.readInt(); // header (e.g. "FAC", 0x00) /* 4 */
if (fac0 != FORMAT_IDENTIFIER) {
LOGGER.log(Level.WARNING, "'FAC' marker expected! Found " + Integer.toHexString(fac0));
if (fac0 == 0x0000000C) {
/* Magic JP2 header. Best effort, assume this is a single image. */
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(bOut);
dataOutputStream.writeInt(fac0);
int imageLength = (int)dataInputStream.readShort();
dataOutputStream.writeShort(imageLength);
int totalBytesRead = 0;
while (totalBytesRead < imageLength) {
byte[] buffer = new byte[2048];
int chunkSize = dataInputStream.read(buffer);
if (chunkSize < 0) {
break;
}
bOut.write(buffer);
totalBytesRead += chunkSize;
}
/* Construct header with default values. */
FaceImageInfo imageInfo = new FaceImageInfo(
Gender.UNKNOWN,
EyeColor.UNSPECIFIED,
0x00,
FaceImageInfo.HAIR_COLOR_UNSPECIFIED,
FaceImageInfo.EXPRESSION_UNSPECIFIED,
new int[] { 0, 0, 0}, new int[] {0, 0, 0},
FaceImageInfo.IMAGE_DATA_TYPE_JPEG2000,
FaceImageInfo.IMAGE_COLOR_SPACE_UNSPECIFIED,
FaceImageInfo.SOURCE_TYPE_UNSPECIFIED,
0x00,
0,
new FeaturePoint[] { },
0, 0,
new ByteArrayInputStream(bOut.toByteArray()), imageLength, FaceImageInfo.IMAGE_DATA_TYPE_JPEG2000);
add(imageInfo);
return;
}
}
int version = dataInputStream.readInt(); // version in ASCII (e.g. "010" 0x00) /* + 4 = 8 */
if (version != VERSION_NUMBER) {
throw new IllegalArgumentException("'010' version number expected! Found " + Integer.toHexString(version));
}
long recordLength = dataInputStream.readInt() & 0xFFFFFFFFL; /* + 4 = 12 */
long headerLength = 14; /* 4 + 4 + 4 + 2 */
long dataLength = recordLength - headerLength;
long constructedDataLength = 0L;
int count = dataInputStream.readUnsignedShort(); /* + 2 = 14 */
for (int i = 0; i < count; i++) {
FaceImageInfo imageInfo = new FaceImageInfo(inputStream);
constructedDataLength += imageInfo.getRecordLength();
add(imageInfo);
}
if (dataLength != constructedDataLength) {
LOGGER.warning("ConstructedDataLength and dataLength differ: "
+ "dataLength = " + dataLength
+ ", constructedDataLength = " + constructedDataLength);
}
}
/**
* Writes the facial record to an output stream. Note that the standard biometric header
* (part of CBEFF structure) is not written here.
*
* @param outputStream an output stream
*/
@Override
public void writeObject(OutputStream outputStream) throws IOException {
int headerLength = 14; /* 4 + 4 + 4 + 2 (Section 5.4 of ISO/IEC 19794-5) */
long dataLength = 0;
List faceImageInfos = getSubRecords();
for (FaceImageInfo faceImageInfo: faceImageInfos) {
dataLength += faceImageInfo.getRecordLength();
}
long recordLength = headerLength + dataLength;
DataOutputStream dataOut = outputStream instanceof DataOutputStream ? (DataOutputStream)outputStream : new DataOutputStream(outputStream);
dataOut.writeInt(FORMAT_IDENTIFIER); /* 4 */
dataOut.writeInt(VERSION_NUMBER); /* + 4 = 8 */
/*
* The (4 byte) Length of Record field shall
* be the combined length in bytes for the record.
* This is the entire length of the record including
* the Facial Record Header and Facial Record Data.
*/
dataOut.writeInt((int)(recordLength & 0x00000000FFFFFFFFL)); /* + 4 = 12 */
/* Number of facial record data blocks. */
dataOut.writeShort(faceImageInfos.size()); /* + 2 = 14 */
for (FaceImageInfo faceImageInfo: faceImageInfos) {
faceImageInfo.writeObject(dataOut);
}
}
/**
* Returns the standard biometric header of this biometric data block.
*
* @return the standard biometric header
*/
public StandardBiometricHeader getStandardBiometricHeader() {
if (sbh == null) {
byte[] biometricType = { (byte)CBEFFInfo.BIOMETRIC_TYPE_FACIAL_FEATURES };
byte[] biometricSubtype = { (byte)CBEFFInfo.BIOMETRIC_SUBTYPE_NONE };
byte[] formatOwner = { (byte)((FORMAT_OWNER_VALUE & 0xFF00) >> 8), (byte)(FORMAT_OWNER_VALUE & 0xFF) };
byte[] formatType = { (byte)((FORMAT_TYPE_VALUE & 0xFF00) >> 8), (byte)(FORMAT_TYPE_VALUE & 0xFF) };
SortedMap elements = new TreeMap();
elements.put(ISO781611.BIOMETRIC_TYPE_TAG, biometricType);
elements.put(ISO781611.BIOMETRIC_SUBTYPE_TAG, biometricSubtype);
elements.put(ISO781611.FORMAT_OWNER_TAG, formatOwner);
elements.put(ISO781611.FORMAT_TYPE_TAG, formatType);
sbh = new StandardBiometricHeader(elements);
}
return sbh;
}
/**
* Returns the face image infos embedded in this face info.
*
* @return the embedded face image infos
*/
public List getFaceImageInfos() {
return getSubRecords();
}
/**
* Adds a face image info to this face info.
*
* @param faceImageInfo the face image info to add
*/
public void addFaceImageInfo(FaceImageInfo faceImageInfo) {
add(faceImageInfo);
}
/**
* Removes a face image info from this face info.
*
* @param index the index of the face image info to remove
*/
public void removeFaceImageInfo(int index) {
remove(index);
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("FaceInfo [");
List records = getSubRecords();
for (FaceImageInfo record: records) {
result.append(record.toString());
}
result.append("]");
return result.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((sbh == null) ? 0 : sbh.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
FaceInfo other = (FaceInfo)obj;
if (sbh == null) {
return other.sbh == null;
}
return sbh == other.sbh || sbh.equals(other.sbh);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy