384 lines
9.1 KiB
Java
384 lines
9.1 KiB
Java
/*
|
|
* Copyright (C) 2012 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package it.sephiroth.android.library.exif2;
|
|
|
|
import android.util.Log;
|
|
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.nio.ByteOrder;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* This class stores the EXIF header in IFDs according to the JPEG
|
|
* specification. It is the result produced by {@link ExifReader}.
|
|
*
|
|
* @see ExifReader
|
|
* @see IfdData
|
|
*/
|
|
class ExifData {
|
|
private static final String TAG = "ExifData";
|
|
private static final byte[] USER_COMMENT_ASCII = { 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00 };
|
|
private static final byte[] USER_COMMENT_JIS = { 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
private static final byte[] USER_COMMENT_UNICODE = { 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00 };
|
|
|
|
private List<ExifParser.Section> mSections;
|
|
private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
|
|
private final ByteOrder mByteOrder;
|
|
private byte[] mThumbnail;
|
|
private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
|
|
private int qualityGuess = 0;
|
|
private int imageLength = -1, imageWidth = -1;
|
|
private short jpegProcess = 0;
|
|
public int mUncompressedDataPosition = 0;
|
|
|
|
ExifData( ByteOrder order ) {
|
|
mByteOrder = order;
|
|
}
|
|
|
|
/**
|
|
* Gets the compressed thumbnail. Returns null if there is no compressed
|
|
* thumbnail.
|
|
*
|
|
* @see #hasCompressedThumbnail()
|
|
*/
|
|
protected byte[] getCompressedThumbnail() {
|
|
return mThumbnail;
|
|
}
|
|
|
|
/**
|
|
* Sets the compressed thumbnail.
|
|
*/
|
|
protected void setCompressedThumbnail( byte[] thumbnail ) {
|
|
mThumbnail = thumbnail;
|
|
}
|
|
|
|
/**
|
|
* Returns true it this header contains a compressed thumbnail.
|
|
*/
|
|
protected boolean hasCompressedThumbnail() {
|
|
return mThumbnail != null;
|
|
}
|
|
|
|
/**
|
|
* Adds an uncompressed strip.
|
|
*/
|
|
protected void setStripBytes( int index, byte[] strip ) {
|
|
if( index < mStripBytes.size() ) {
|
|
mStripBytes.set( index, strip );
|
|
}
|
|
else {
|
|
for( int i = mStripBytes.size(); i < index; i++ ) {
|
|
mStripBytes.add( null );
|
|
}
|
|
mStripBytes.add( strip );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the strip count.
|
|
*/
|
|
protected int getStripCount() {
|
|
return mStripBytes.size();
|
|
}
|
|
|
|
/**
|
|
* Gets the strip at the specified index.
|
|
*
|
|
* @exceptions #IndexOutOfBoundException
|
|
*/
|
|
protected byte[] getStrip( int index ) {
|
|
return mStripBytes.get( index );
|
|
}
|
|
|
|
/**
|
|
* Returns true if this header contains uncompressed strip.
|
|
*/
|
|
protected boolean hasUncompressedStrip() {
|
|
return mStripBytes.size() != 0;
|
|
}
|
|
|
|
/**
|
|
* Gets the byte order.
|
|
*/
|
|
protected ByteOrder getByteOrder() {
|
|
return mByteOrder;
|
|
}
|
|
|
|
/**
|
|
* Adds IFD data. If IFD data of the same type already exists, it will be
|
|
* replaced by the new data.
|
|
*/
|
|
protected void addIfdData( IfdData data ) {
|
|
mIfdDatas[data.getId()] = data;
|
|
}
|
|
|
|
/**
|
|
* Returns the tag with a given TID in the given IFD if the tag exists.
|
|
* Otherwise returns null.
|
|
*/
|
|
protected ExifTag getTag( short tag, int ifd ) {
|
|
IfdData ifdData = mIfdDatas[ifd];
|
|
return ( ifdData == null ) ? null : ifdData.getTag( tag );
|
|
}
|
|
|
|
/**
|
|
* Adds the given ExifTag to its default IFD and returns an existing ExifTag
|
|
* with the same TID or null if none exist.
|
|
*/
|
|
protected ExifTag addTag( ExifTag tag ) {
|
|
if( tag != null ) {
|
|
int ifd = tag.getIfd();
|
|
return addTag( tag, ifd );
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Adds the given ExifTag to the given IFD and returns an existing ExifTag
|
|
* with the same TID or null if none exist.
|
|
*/
|
|
protected ExifTag addTag( ExifTag tag, int ifdId ) {
|
|
if( tag != null && ExifTag.isValidIfd( ifdId ) ) {
|
|
IfdData ifdData = getOrCreateIfdData( ifdId );
|
|
return ifdData.setTag( tag );
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns the {@link IfdData} object corresponding to a given IFD or
|
|
* generates one if none exist.
|
|
*/
|
|
protected IfdData getOrCreateIfdData( int ifdId ) {
|
|
IfdData ifdData = mIfdDatas[ifdId];
|
|
if( ifdData == null ) {
|
|
ifdData = new IfdData( ifdId );
|
|
mIfdDatas[ifdId] = ifdData;
|
|
}
|
|
return ifdData;
|
|
}
|
|
|
|
/**
|
|
* Removes the thumbnail and its related tags. IFD1 will be removed.
|
|
*/
|
|
protected void removeThumbnailData() {
|
|
clearThumbnailAndStrips();
|
|
mIfdDatas[IfdId.TYPE_IFD_1] = null;
|
|
}
|
|
|
|
protected void clearThumbnailAndStrips() {
|
|
mThumbnail = null;
|
|
mStripBytes.clear();
|
|
}
|
|
|
|
/**
|
|
* Removes the tag with a given TID and IFD.
|
|
*/
|
|
protected void removeTag( short tagId, int ifdId ) {
|
|
IfdData ifdData = mIfdDatas[ifdId];
|
|
if( ifdData == null ) {
|
|
return;
|
|
}
|
|
ifdData.removeTag( tagId );
|
|
}
|
|
|
|
/**
|
|
* Decodes the user comment tag into string as specified in the EXIF
|
|
* standard. Returns null if decoding failed.
|
|
*/
|
|
protected String getUserComment() {
|
|
IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
|
|
if( ifdData == null ) {
|
|
return null;
|
|
}
|
|
ExifTag tag = ifdData.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_USER_COMMENT ) );
|
|
if( tag == null ) {
|
|
return null;
|
|
}
|
|
if( tag.getComponentCount() < 8 ) {
|
|
return null;
|
|
}
|
|
|
|
byte[] buf = new byte[tag.getComponentCount()];
|
|
tag.getBytes( buf );
|
|
|
|
byte[] code = new byte[8];
|
|
System.arraycopy( buf, 0, code, 0, 8 );
|
|
|
|
try {
|
|
if( Arrays.equals( code, USER_COMMENT_ASCII ) ) {
|
|
return new String( buf, 8, buf.length - 8, "US-ASCII" );
|
|
}
|
|
else if( Arrays.equals( code, USER_COMMENT_JIS ) ) {
|
|
return new String( buf, 8, buf.length - 8, "EUC-JP" );
|
|
}
|
|
else if( Arrays.equals( code, USER_COMMENT_UNICODE ) ) {
|
|
return new String( buf, 8, buf.length - 8, "UTF-16" );
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
} catch( UnsupportedEncodingException e ) {
|
|
Log.w( TAG, "Failed to decode the user comment" );
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all {@link ExifTag}s in the ExifData or null if there
|
|
* are none.
|
|
*/
|
|
protected List<ExifTag> getAllTags() {
|
|
ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
|
|
for( IfdData d : mIfdDatas ) {
|
|
if( d != null ) {
|
|
ExifTag[] tags = d.getAllTags();
|
|
if( tags != null ) {
|
|
for( ExifTag t : tags ) {
|
|
ret.add( t );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if( ret.size() == 0 ) {
|
|
return null;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all {@link ExifTag}s in a given IFD or null if there
|
|
* are none.
|
|
*/
|
|
protected List<ExifTag> getAllTagsForIfd( int ifd ) {
|
|
IfdData d = mIfdDatas[ifd];
|
|
if( d == null ) {
|
|
return null;
|
|
}
|
|
ExifTag[] tags = d.getAllTags();
|
|
if( tags == null ) {
|
|
return null;
|
|
}
|
|
ArrayList<ExifTag> ret = new ArrayList<ExifTag>( tags.length );
|
|
for( ExifTag t : tags ) {
|
|
ret.add( t );
|
|
}
|
|
if( ret.size() == 0 ) {
|
|
return null;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Returns a list of all {@link ExifTag}s with a given TID or null if there
|
|
* are none.
|
|
*/
|
|
protected List<ExifTag> getAllTagsForTagId( short tag ) {
|
|
ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
|
|
for( IfdData d : mIfdDatas ) {
|
|
if( d != null ) {
|
|
ExifTag t = d.getTag( tag );
|
|
if( t != null ) {
|
|
ret.add( t );
|
|
}
|
|
}
|
|
}
|
|
if( ret.size() == 0 ) {
|
|
return null;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals( Object obj ) {
|
|
if( this == obj ) {
|
|
return true;
|
|
}
|
|
if( obj == null ) {
|
|
return false;
|
|
}
|
|
if( obj instanceof ExifData ) {
|
|
ExifData data = (ExifData) obj;
|
|
if( data.mByteOrder != mByteOrder ||
|
|
data.mStripBytes.size() != mStripBytes.size() ||
|
|
! Arrays.equals( data.mThumbnail, mThumbnail ) ) {
|
|
return false;
|
|
}
|
|
for( int i = 0; i < mStripBytes.size(); i++ ) {
|
|
if( ! Arrays.equals( data.mStripBytes.get( i ), mStripBytes.get( i ) ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
for( int i = 0; i < IfdId.TYPE_IFD_COUNT; i++ ) {
|
|
IfdData ifd1 = data.getIfdData( i );
|
|
IfdData ifd2 = getIfdData( i );
|
|
if( ifd1 != ifd2 && ifd1 != null && ! ifd1.equals( ifd2 ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the {@link IfdData} object corresponding to a given IFD if it
|
|
* exists or null.
|
|
*/
|
|
protected IfdData getIfdData( int ifdId ) {
|
|
if( ExifTag.isValidIfd( ifdId ) ) {
|
|
return mIfdDatas[ifdId];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected void setQualityGuess( final int qualityGuess ) {
|
|
this.qualityGuess = qualityGuess;
|
|
}
|
|
|
|
public int getQualityGuess() {
|
|
return qualityGuess;
|
|
}
|
|
|
|
protected void setImageSize( final int imageWidth, final int imageLength ) {
|
|
this.imageWidth = imageWidth;
|
|
this.imageLength = imageLength;
|
|
}
|
|
|
|
public int[] getImageSize() {
|
|
return new int[]{ imageWidth, imageLength };
|
|
}
|
|
|
|
public void setJpegProcess( final short jpegProcess ) {
|
|
this.jpegProcess = jpegProcess;
|
|
}
|
|
|
|
public short getJpegProcess() {
|
|
return this.jpegProcess;
|
|
}
|
|
|
|
public void setSections( final List<ExifParser.Section> sections ) {
|
|
mSections = sections;
|
|
}
|
|
|
|
public List<ExifParser.Section> getSections() {
|
|
return mSections;
|
|
}
|
|
}
|