画像リサイズ時にExif情報を参照して画像を自動的に回転させる

This commit is contained in:
tateisu 2017-05-06 06:39:10 +09:00
parent 14dc3dc20f
commit e22c420f84
27 changed files with 6779 additions and 40 deletions

View File

@ -16,6 +16,7 @@
<w>reblog</w>
<w>reblogged</w>
<w>reblogs</w>
<w>sephiroth</w>
<w>styler</w>
<w>subwaytooter</w>
<w>swipy</w>

View File

@ -9,6 +9,7 @@
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/exif" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />

View File

@ -4,6 +4,7 @@
<modules>
<module fileurl="file://$PROJECT_DIR$/SubwayTooter.iml" filepath="$PROJECT_DIR$/SubwayTooter.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
<module fileurl="file://$PROJECT_DIR$/exif/exif.iml" filepath="$PROJECT_DIR$/exif/exif.iml" />
</modules>
</component>
</project>

View File

@ -62,8 +62,7 @@ dependencies {
compile 'com.github.woxthebox:draglistview:1.4.3'
compile 'com.github.omadahealth:swipy:1.2.3@aar'
compile 'com.jrummyapps:colorpicker:2.1.6'
// compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
// compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
compile 'com.github.kenglxn.QRGen:android:2.2.0'
compile project(':exif')
}

View File

@ -13,8 +13,8 @@ import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
@ -56,6 +56,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import it.sephiroth.android.library.exif2.ExifInterface;
import jp.juggler.subwaytooter.api.TootApiClient;
import jp.juggler.subwaytooter.api.TootApiResult;
import jp.juggler.subwaytooter.api.entity.TootAttachment;
@ -781,12 +782,6 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
//noinspection LoopStatementThatDoesntLoop
for( ; ; ){
try{
// 設定からリサイズ指定を読む
int resize_max = list_resize_max[ pref.getInt( Pref.KEY_RESIZE_IMAGE, 4 ) ];
if( resize_max <= 0 ){
log.d( "createOpener: resize not required" );
break;
}
// 画像の種別
boolean is_jpeg = MIME_TYPE_JPEG.equals( mime_type );
@ -796,11 +791,27 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
break;
}
// EXIF回転情報の取得
Integer orientation;
InputStream is = getContentResolver().openInputStream( uri );
if( is == null ){
Utils.showToast( this, false, "could not open image." );
break;
}
try{
ExifInterface exif = new ExifInterface();
exif.readExif( is, ExifInterface.Options.OPTION_IFD_0 | ExifInterface.Options.OPTION_IFD_1 | ExifInterface.Options.OPTION_IFD_EXIF );
orientation = exif.getTagIntValue( ExifInterface.TAG_ORIENTATION );
}finally{
IOUtils.closeQuietly( is );
}
// 画像のサイズを調べる
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inScaled = false;
InputStream is = getContentResolver().openInputStream( uri );
is = getContentResolver().openInputStream( uri );
if( is == null ){
Utils.showToast( this, false, "could not open image." );
break;
@ -819,15 +830,30 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
// 長辺
int size = ( src_width > src_height ? src_width : src_height );
if( size <= resize_max ){
log.d( "createOpener: no need to resize, %s <= %s", size, resize_max );
// 設定からリサイズ指定を読む
int resize_to = list_resize_max[ pref.getInt( Pref.KEY_RESIZE_IMAGE, 4 ) ];
// リサイズも回転も必要がない場合
if( (orientation == null || orientation == 1 )
&& (resize_to <= 0 || size <= resize_to )
){
log.d( "createOpener: no need to resize & rotate" );
break;
}
//noinspection StatementWithEmptyBody
if( size > resize_to ){
// 縮小が必要
}else{
// 縮小は不要
resize_to = size;
}
// inSampleSizeを計算
int bits = 0;
int x = size;
while( x > resize_max * 2 ){
while( x > resize_to * 2 ){
++ bits;
x >>= 1;
}
@ -851,18 +877,79 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
try{
src_width = options.outWidth;
src_height = options.outHeight;
float scale;
int dst_width;
int dst_height;
if( src_width >= src_height ){
dst_width = resize_max;
dst_height = (int) ( 0.5f + src_height / (float) src_width * resize_max );
scale = resize_to / (float)src_width;
dst_width = resize_to;
dst_height = (int) ( 0.5f + src_height / (float) src_width * resize_to );
if( dst_height < 1 ) dst_height = 1;
}else{
dst_height = resize_max;
dst_width = (int) ( 0.5f + src_width / (float) src_height * resize_max );
scale = resize_to /(float)src_height;
dst_height = resize_to;
dst_width = (int) ( 0.5f + src_width / (float) src_height * resize_to );
if( dst_width < 1 ) dst_width = 1;
}
// 64*64ピクセルのBitmap作成
Matrix matrix = new Matrix( );
matrix.reset();
// 画像の中心が原点に来るようにして
matrix.postTranslate( src_width * - 0.5f, src_height * - 0.5f );
// スケーリング
matrix.postScale( scale, scale );
// 回転情報があれば回転
if( orientation != null ){
int tmp;
switch( orientation.shortValue() ){
default:
break;
case 2:
matrix.postScale( 1f, - 1f );
break; // 上下反転
case 3:
matrix.postRotate( 180f );
break; // 180度回転
case 4:
matrix.postScale( - 1f, 1f );
break; // 左右反転
case 5:
tmp= dst_width;
//noinspection SuspiciousNameCombination
dst_width=dst_height;
dst_height=tmp;
matrix.postScale( 1f, - 1f );
matrix.postRotate( - 90f );
break;
case 6:
tmp= dst_width;
//noinspection SuspiciousNameCombination
dst_width=dst_height;
dst_height=tmp;
matrix.postRotate( 90f );
break;
case 7:
tmp= dst_width;
//noinspection SuspiciousNameCombination
dst_width=dst_height;
dst_height=tmp;
matrix.postScale( 1f, - 1f );
matrix.postRotate( 90f );
break;
case 8:
tmp= dst_width;
//noinspection SuspiciousNameCombination
dst_width=dst_height;
dst_height=tmp;
matrix.postRotate( - 90f );
break;
}
}
// 表示領域に埋まるように平行移動
matrix.postTranslate( dst_width * 0.5f, dst_height * 0.5f );
// 出力用Bitmap作成
Bitmap dst = Bitmap.createBitmap( dst_width, dst_height, Bitmap.Config.ARGB_8888 );
if( dst == null ){
Utils.showToast( this, false, "bitmap creation failed." );
@ -872,9 +959,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
Canvas canvas = new Canvas( dst );
Paint paint = new Paint();
paint.setFilterBitmap( true );
Rect rect_src = new Rect( 0, 0, src_width, src_height );
Rect rect_dst = new Rect( 0, 0, dst_width, dst_height );
canvas.drawBitmap( src, rect_src, rect_dst, paint );
canvas.drawBitmap( src, matrix, paint );
File cache_dir = getExternalCacheDir();
if( cache_dir == null ){
Utils.showToast( this, false, "getExternalCacheDir returns null." );
@ -1333,7 +1418,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener {
data.putExtra( EXTRA_POSTED_ACCT, target_account.acct );
data.putExtra( EXTRA_POSTED_STATUS_ID, status.id );
setResult( RESULT_OK ,data );
setResult( RESULT_OK, data );
ActPost.this.finish();
}else{
Utils.showToast( ActPost.this, true, result.error );

View File

@ -83,6 +83,7 @@ public class App1 extends Application {
}
private static class DBOpenHelper extends SQLiteOpenHelper {
private DBOpenHelper(Context context) {
@ -120,6 +121,44 @@ public class App1 extends Application {
}
}
@SuppressWarnings("unused")
private static final CipherSuite[] APPROVED_CIPHER_SUITES = new CipherSuite[] {
// 以下は okhttp 3 のデフォルト
// This is nearly equal to the cipher suites supported in Chrome 51, current as of 2016-05-25.
// All of these suites are available on Android 7.0; earlier releases support a subset of these
// suites. https://github.com/square/okhttp/issues/1972
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
// Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll
// continue to include them until better suites are commonly available. For example, none
// of the better cipher suites listed above shipped with Android 4.4 or Java 7.
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
//https://www.ssllabs.com/ssltest/analyze.html?d=mastodon.cloud&latest
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 ,// mastodon.cloud用 デフォルトにはない
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 , //mastodon.cloud用 デフォルトにはない
// https://www.ssllabs.com/ssltest/analyze.html?d=m.sighash.info
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, // m.sighash.info デフォルトにはない
CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, // m.sighash.info デフォルトにはない
CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, // m.sighash.info デフォルトにはない
CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, // m.sighash.info デフォルトにはない
};
static ImageLoader image_loader;
public static ImageLoader getImageLoader(){
@ -204,23 +243,21 @@ public class App1 extends Application {
}
if( ok_http_client == null ){
// ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.COMPATIBLE_TLS)
// .tlsVersions( TlsVersion.TLS_1_2)
// .cipherSuites(
// CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
// CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
// CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)
// .build();
//
// ArrayList<ConnectionSpec> spec_list = new ArrayList<>( );
// spec_list.add(ConnectionSpec.MODERN_TLS );
// // spec_list.add(ConnectionSpec.COMPATIBLE_TLS );
// spec_list.add(ConnectionSpec.CLEARTEXT );
// ok_http_client = new OkHttpClient.Builder()
// .connectionSpecs( spec_list )
// .build();
// ok_http_client = new OkHttpClient();
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.cipherSuites(APPROVED_CIPHER_SUITES)
.build();
ArrayList<ConnectionSpec> spec_list = new ArrayList<>( );
spec_list.add(spec );
spec_list.add(ConnectionSpec.CLEARTEXT );
ok_http_client = new OkHttpClient.Builder()
.connectionSpecs( spec_list )
.build();
ok_http_client = new OkHttpClient();
}
@ -282,4 +319,7 @@ public class App1 extends Application {
return null;
}
}

View File

@ -148,3 +148,18 @@ 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.
====================================================
sephiroth74/Android-Exif-Extended
https://github.com/sephiroth74/Android-Exif-Extended
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.

1
exif/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

25
exif/build.gradle Normal file
View File

@ -0,0 +1,25 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
minSdkVersion 21
targetSdkVersion 25
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'commons-io:commons-io:2.4'
}

25
exif/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="it.sephiroth.android.library.exif2">
<application>
</application>
</manifest>

View File

@ -0,0 +1,48 @@
/*
* 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 java.io.InputStream;
import java.nio.ByteBuffer;
class ByteBufferInputStream extends InputStream {
private ByteBuffer mBuf;
public ByteBufferInputStream( ByteBuffer buf ) {
mBuf = buf;
}
@Override
public int read() {
if( ! mBuf.hasRemaining() ) {
return - 1;
}
return mBuf.get() & 0xFF;
}
@Override
public int read( byte[] bytes, int off, int len ) {
if( ! mBuf.hasRemaining() ) {
return - 1;
}
len = Math.min( len, mBuf.remaining() );
mBuf.get( bytes, off, len );
return len;
}
}

View File

@ -0,0 +1,156 @@
/*
* 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 java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
class CountedDataInputStream extends FilterInputStream {
// allocate a byte buffer for a long value;
private final byte mByteArray[] = new byte[8];
private final ByteBuffer mByteBuffer = ByteBuffer.wrap( mByteArray );
private int mCount = 0;
private int mEnd = 0;
protected CountedDataInputStream( InputStream in ) {
super( in );
}
public void setEnd( int end ) {
mEnd = end;
}
public int getEnd() {
return mEnd;
}
public int getReadByteCount() {
return mCount;
}
@Override
public int read( byte[] b ) throws IOException {
int r = in.read( b );
mCount += ( r >= 0 ) ? r : 0;
return r;
}
@Override
public int read() throws IOException {
int r = in.read();
mCount += ( r >= 0 ) ? 1 : 0;
return r;
}
@Override
public int read( byte[] b, int off, int len ) throws IOException {
int r = in.read( b, off, len );
mCount += ( r >= 0 ) ? r : 0;
return r;
}
@Override
public long skip( long length ) throws IOException {
long skip = in.skip( length );
mCount += skip;
return skip;
}
public void skipTo( long target ) throws IOException {
long cur = mCount;
long diff = target - cur;
assert ( diff >= 0 );
skipOrThrow( diff );
}
public void skipOrThrow( long length ) throws IOException {
if( skip( length ) != length ) throw new EOFException();
}
public ByteOrder getByteOrder() {
return mByteBuffer.order();
}
public void setByteOrder( ByteOrder order ) {
mByteBuffer.order( order );
}
public int readUnsignedShort() throws IOException {
return readShort() & 0xffff;
}
public short readShort() throws IOException {
readOrThrow( mByteArray, 0, 2 );
mByteBuffer.rewind();
return mByteBuffer.getShort();
}
public byte readByte() throws IOException {
readOrThrow( mByteArray, 0, 1 );
mByteBuffer.rewind();
return mByteBuffer.get();
}
public int readUnsignedByte() throws IOException {
readOrThrow( mByteArray, 0, 1 );
mByteBuffer.rewind();
return (mByteBuffer.get() & 0xff);
}
public void readOrThrow( byte[] b, int off, int len ) throws IOException {
int r = read( b, off, len );
if( r != len ) throw new EOFException();
}
public long readUnsignedInt() throws IOException {
return readInt() & 0xffffffffL;
}
public int readInt() throws IOException {
readOrThrow( mByteArray, 0, 4 );
mByteBuffer.rewind();
return mByteBuffer.getInt();
}
public long readLong() throws IOException {
readOrThrow( mByteArray, 0, 8 );
mByteBuffer.rewind();
return mByteBuffer.getLong();
}
public String readString( int n ) throws IOException {
byte buf[] = new byte[n];
readOrThrow( buf );
return new String( buf, "UTF8" );
}
public void readOrThrow( byte[] b ) throws IOException {
readOrThrow( b, 0, b.length );
}
public String readString( int n, Charset charset ) throws IOException {
byte buf[] = new byte[n];
readOrThrow( buf );
return new String( buf, charset );
}
}

View File

@ -0,0 +1,383 @@
/*
* 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;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
/*
* 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;
public class ExifInvalidFormatException extends Exception {
public ExifInvalidFormatException( String meg ) {
super( meg );
}
}

View File

@ -0,0 +1,379 @@
/*
* 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.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
class ExifOutputStream {
private static final String TAG = "ExifOutputStream";
private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
private static final int STATE_SOI = 0;
private static final int EXIF_HEADER = 0x45786966;
private static final short TIFF_HEADER = 0x002A;
private static final short TIFF_BIG_ENDIAN = 0x4d4d;
private static final short TIFF_LITTLE_ENDIAN = 0x4949;
private static final short TAG_SIZE = 12;
private static final short TIFF_HEADER_SIZE = 8;
private static final int MAX_EXIF_SIZE = 65535;
private final ExifInterface mInterface;
private ExifData mExifData;
private ByteBuffer mBuffer = ByteBuffer.allocate( 4 );
protected ExifOutputStream( ExifInterface iRef ) {
mInterface = iRef;
}
/**
* Gets the Exif header to be written into the JPEF file.
*/
protected ExifData getExifData() {
return mExifData;
}
/**
* Sets the ExifData to be written into the JPEG file. Should be called
* before writing image data.
*/
protected void setExifData( ExifData exifData ) {
mExifData = exifData;
}
private int requestByteToBuffer(
int requestByteCount, byte[] buffer, int offset, int length ) {
int byteNeeded = requestByteCount - mBuffer.position();
int byteToRead = length > byteNeeded ? byteNeeded : length;
mBuffer.put( buffer, offset, byteToRead );
return byteToRead;
}
public void writeExifData( OutputStream out ) throws IOException {
if( mExifData == null ) {
return;
}
Log.v( TAG, "Writing exif data..." );
ArrayList<ExifTag> nullTags = stripNullValueTags( mExifData );
createRequiredIfdAndTag();
int exifSize = calculateAllOffset();
// Log.i(TAG, "exifSize: " + (exifSize + 8));
if( exifSize + 8 > MAX_EXIF_SIZE ) {
throw new IOException( "Exif header is too large (>64Kb)" );
}
BufferedOutputStream outputStream = new BufferedOutputStream( out, STREAMBUFFER_SIZE );
OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream( outputStream );
dataOutputStream.setByteOrder( ByteOrder.BIG_ENDIAN );
dataOutputStream.write( 0xFF );
dataOutputStream.write( JpegHeader.TAG_M_EXIF );
dataOutputStream.writeShort( (short) ( exifSize + 8 ) );
dataOutputStream.writeInt( EXIF_HEADER );
dataOutputStream.writeShort( (short) 0x0000 );
if( mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN ) {
dataOutputStream.writeShort( TIFF_BIG_ENDIAN );
}
else {
dataOutputStream.writeShort( TIFF_LITTLE_ENDIAN );
}
dataOutputStream.setByteOrder( mExifData.getByteOrder() );
dataOutputStream.writeShort( TIFF_HEADER );
dataOutputStream.writeInt( 8 );
writeAllTags( dataOutputStream );
writeThumbnail( dataOutputStream );
for( ExifTag t : nullTags ) {
mExifData.addTag( t );
}
dataOutputStream.flush();
}
private ArrayList<ExifTag> stripNullValueTags( ExifData data ) {
ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
for( ExifTag t : data.getAllTags() ) {
if( t.getValue() == null && ! ExifInterface.isOffsetTag( t.getTagId() ) ) {
data.removeTag( t.getTagId(), t.getIfd() );
nullTags.add( t );
}
}
return nullTags;
}
private void writeThumbnail( OrderedDataOutputStream dataOutputStream ) throws IOException {
if( mExifData.hasCompressedThumbnail() ) {
Log.d( TAG, "writing thumbnail.." );
dataOutputStream.write( mExifData.getCompressedThumbnail() );
}
else if( mExifData.hasUncompressedStrip() ) {
Log.d( TAG, "writing uncompressed strip.." );
for( int i = 0; i < mExifData.getStripCount(); i++ ) {
dataOutputStream.write( mExifData.getStrip( i ) );
}
}
}
private void writeAllTags( OrderedDataOutputStream dataOutputStream ) throws IOException {
writeIfd( mExifData.getIfdData( IfdId.TYPE_IFD_0 ), dataOutputStream );
writeIfd( mExifData.getIfdData( IfdId.TYPE_IFD_EXIF ), dataOutputStream );
IfdData interoperabilityIfd = mExifData.getIfdData( IfdId.TYPE_IFD_INTEROPERABILITY );
if( interoperabilityIfd != null ) {
writeIfd( interoperabilityIfd, dataOutputStream );
}
IfdData gpsIfd = mExifData.getIfdData( IfdId.TYPE_IFD_GPS );
if( gpsIfd != null ) {
writeIfd( gpsIfd, dataOutputStream );
}
IfdData ifd1 = mExifData.getIfdData( IfdId.TYPE_IFD_1 );
if( ifd1 != null ) {
writeIfd( mExifData.getIfdData( IfdId.TYPE_IFD_1 ), dataOutputStream );
}
}
private void writeIfd( IfdData ifd, OrderedDataOutputStream dataOutputStream ) throws IOException {
ExifTag[] tags = ifd.getAllTags();
dataOutputStream.writeShort( (short) tags.length );
for( ExifTag tag : tags ) {
dataOutputStream.writeShort( tag.getTagId() );
dataOutputStream.writeShort( tag.getDataType() );
dataOutputStream.writeInt( tag.getComponentCount() );
// Log.v( TAG, "\n" + tag.toString() );
if( tag.getDataSize() > 4 ) {
dataOutputStream.writeInt( tag.getOffset() );
}
else {
ExifOutputStream.writeTagValue( tag, dataOutputStream );
for( int i = 0, n = 4 - tag.getDataSize(); i < n; i++ ) {
dataOutputStream.write( 0 );
}
}
}
dataOutputStream.writeInt( ifd.getOffsetToNextIfd() );
for( ExifTag tag : tags ) {
if( tag.getDataSize() > 4 ) {
ExifOutputStream.writeTagValue( tag, dataOutputStream );
}
}
}
private int calculateOffsetOfIfd( IfdData ifd, int offset ) {
offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
ExifTag[] tags = ifd.getAllTags();
for( ExifTag tag : tags ) {
if( tag.getDataSize() > 4 ) {
tag.setOffset( offset );
offset += tag.getDataSize();
}
}
return offset;
}
private void createRequiredIfdAndTag() throws IOException {
// IFD0 is required for all file
IfdData ifd0 = mExifData.getIfdData( IfdId.TYPE_IFD_0 );
if( ifd0 == null ) {
ifd0 = new IfdData( IfdId.TYPE_IFD_0 );
mExifData.addIfdData( ifd0 );
}
ExifTag exifOffsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_EXIF_IFD );
if( exifOffsetTag == null ) {
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_EXIF_IFD );
}
ifd0.setTag( exifOffsetTag );
// Exif IFD is required for all files.
IfdData exifIfd = mExifData.getIfdData( IfdId.TYPE_IFD_EXIF );
if( exifIfd == null ) {
exifIfd = new IfdData( IfdId.TYPE_IFD_EXIF );
mExifData.addIfdData( exifIfd );
}
// GPS IFD
IfdData gpsIfd = mExifData.getIfdData( IfdId.TYPE_IFD_GPS );
if( gpsIfd != null ) {
ExifTag gpsOffsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_GPS_IFD );
if( gpsOffsetTag == null ) {
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_GPS_IFD );
}
ifd0.setTag( gpsOffsetTag );
}
// Interoperability IFD
IfdData interIfd = mExifData.getIfdData( IfdId.TYPE_IFD_INTEROPERABILITY );
if( interIfd != null ) {
ExifTag interOffsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_INTEROPERABILITY_IFD );
if( interOffsetTag == null ) {
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_INTEROPERABILITY_IFD );
}
exifIfd.setTag( interOffsetTag );
}
IfdData ifd1 = mExifData.getIfdData( IfdId.TYPE_IFD_1 );
// thumbnail
if( mExifData.hasCompressedThumbnail() ) {
if( ifd1 == null ) {
ifd1 = new IfdData( IfdId.TYPE_IFD_1 );
mExifData.addIfdData( ifd1 );
}
ExifTag offsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT );
if( offsetTag == null ) {
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT );
}
ifd1.setTag( offsetTag );
ExifTag lengthTag = mInterface.buildUninitializedTag( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH );
if( lengthTag == null ) {
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH );
}
lengthTag.setValue( mExifData.getCompressedThumbnail().length );
ifd1.setTag( lengthTag );
// Get rid of tags for uncompressed if they exist.
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_OFFSETS ) );
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_BYTE_COUNTS ) );
}
else if( mExifData.hasUncompressedStrip() ) {
if( ifd1 == null ) {
ifd1 = new IfdData( IfdId.TYPE_IFD_1 );
mExifData.addIfdData( ifd1 );
}
int stripCount = mExifData.getStripCount();
ExifTag offsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_STRIP_OFFSETS );
if( offsetTag == null ) {
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_STRIP_OFFSETS );
}
ExifTag lengthTag = mInterface.buildUninitializedTag( ExifInterface.TAG_STRIP_BYTE_COUNTS );
if( lengthTag == null ) {
throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_STRIP_BYTE_COUNTS );
}
long[] lengths = new long[stripCount];
for( int i = 0; i < mExifData.getStripCount(); i++ ) {
lengths[i] = mExifData.getStrip( i ).length;
}
lengthTag.setValue( lengths );
ifd1.setTag( offsetTag );
ifd1.setTag( lengthTag );
// Get rid of tags for compressed if they exist.
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT ) );
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH ) );
}
else if( ifd1 != null ) {
// Get rid of offset and length tags if there is no thumbnail.
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_OFFSETS ) );
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_BYTE_COUNTS ) );
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT ) );
ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH ) );
}
}
private int calculateAllOffset() {
int offset = TIFF_HEADER_SIZE;
IfdData ifd0 = mExifData.getIfdData( IfdId.TYPE_IFD_0 );
offset = calculateOffsetOfIfd( ifd0, offset );
ifd0.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_EXIF_IFD ) ).setValue( offset );
IfdData exifIfd = mExifData.getIfdData( IfdId.TYPE_IFD_EXIF );
offset = calculateOffsetOfIfd( exifIfd, offset );
IfdData interIfd = mExifData.getIfdData( IfdId.TYPE_IFD_INTEROPERABILITY );
if( interIfd != null ) {
exifIfd.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_INTEROPERABILITY_IFD ) ).setValue( offset );
offset = calculateOffsetOfIfd( interIfd, offset );
}
IfdData gpsIfd = mExifData.getIfdData( IfdId.TYPE_IFD_GPS );
if( gpsIfd != null ) {
ifd0.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_GPS_IFD ) ).setValue( offset );
offset = calculateOffsetOfIfd( gpsIfd, offset );
}
IfdData ifd1 = mExifData.getIfdData( IfdId.TYPE_IFD_1 );
if( ifd1 != null ) {
ifd0.setOffsetToNextIfd( offset );
offset = calculateOffsetOfIfd( ifd1, offset );
}
// thumbnail
if( mExifData.hasCompressedThumbnail() ) {
ifd1.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT ) ).setValue( offset );
offset += mExifData.getCompressedThumbnail().length;
}
else if( mExifData.hasUncompressedStrip() ) {
int stripCount = mExifData.getStripCount();
long[] offsets = new long[stripCount];
for( int i = 0; i < mExifData.getStripCount(); i++ ) {
offsets[i] = offset;
offset += mExifData.getStrip( i ).length;
}
ifd1.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_OFFSETS ) ).setValue( offsets );
}
return offset;
}
static void writeTagValue( ExifTag tag, OrderedDataOutputStream dataOutputStream ) throws IOException {
switch( tag.getDataType() ) {
case ExifTag.TYPE_ASCII:
byte buf[] = tag.getStringByte();
if( buf.length == tag.getComponentCount() ) {
buf[buf.length - 1] = 0;
dataOutputStream.write( buf );
}
else {
dataOutputStream.write( buf );
dataOutputStream.write( 0 );
}
break;
case ExifTag.TYPE_LONG:
case ExifTag.TYPE_UNSIGNED_LONG:
for( int i = 0, n = tag.getComponentCount(); i < n; i++ ) {
dataOutputStream.writeInt( (int) tag.getValueAt( i ) );
}
break;
case ExifTag.TYPE_RATIONAL:
case ExifTag.TYPE_UNSIGNED_RATIONAL:
for( int i = 0, n = tag.getComponentCount(); i < n; i++ ) {
dataOutputStream.writeRational( tag.getRational( i ) );
}
break;
case ExifTag.TYPE_UNDEFINED:
case ExifTag.TYPE_UNSIGNED_BYTE:
buf = new byte[tag.getComponentCount()];
tag.getBytes( buf );
dataOutputStream.write( buf );
break;
case ExifTag.TYPE_UNSIGNED_SHORT:
for( int i = 0, n = tag.getComponentCount(); i < n; i++ ) {
dataOutputStream.writeShort( (short) tag.getValueAt( i ) );
}
break;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,116 @@
/*
* 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.IOException;
import java.io.InputStream;
/**
* This class reads the EXIF header of a JPEG file and stores it in
* {@link ExifData}.
*/
class ExifReader {
private static final String TAG = "ExifReader";
private final ExifInterface mInterface;
ExifReader( ExifInterface iRef ) {
mInterface = iRef;
}
/**
* Parses the inputStream and and returns the EXIF data in an
* {@link ExifData}.
*
* @throws ExifInvalidFormatException
* @throws java.io.IOException
*/
protected ExifData read( InputStream inputStream, int options ) throws ExifInvalidFormatException, IOException {
ExifParser parser = ExifParser.parse( inputStream, options, mInterface );
ExifData exifData = new ExifData( parser.getByteOrder() );
exifData.setSections( parser.getSections() );
exifData.mUncompressedDataPosition = parser.getUncompressedDataPosition();
exifData.setQualityGuess( parser.getQualityGuess() );
exifData.setJpegProcess( parser.getJpegProcess() );
final int w = parser.getImageWidth();
final int h = parser.getImageLength();
if( w > 0 && h > 0 ) {
exifData.setImageSize( w, h );
}
ExifTag tag;
int event = parser.next();
while( event != ExifParser.EVENT_END ) {
switch( event ) {
case ExifParser.EVENT_START_OF_IFD:
exifData.addIfdData( new IfdData( parser.getCurrentIfd() ) );
break;
case ExifParser.EVENT_NEW_TAG:
tag = parser.getTag();
if( ! tag.hasValue() ) {
parser.registerForTagValue( tag );
}
else {
// Log.v(TAG, "parsing id " + tag.getTagId() + " = " + tag);
if (parser.isDefinedTag(tag.getIfd(), tag.getTagId())) {
exifData.getIfdData(tag.getIfd()).setTag(tag);
}
else {
Log.w(TAG, "skip tag because not registered in the tag table:" + tag);
}
}
break;
case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
tag = parser.getTag();
if( tag.getDataType() == ExifTag.TYPE_UNDEFINED ) {
parser.readFullTagValue( tag );
}
exifData.getIfdData( tag.getIfd() ).setTag( tag );
break;
case ExifParser.EVENT_COMPRESSED_IMAGE:
byte buf[] = new byte[parser.getCompressedImageSize()];
if( buf.length == parser.read( buf ) ) {
exifData.setCompressedThumbnail( buf );
}
else {
Log.w( TAG, "Failed to read the compressed thumbnail" );
}
break;
case ExifParser.EVENT_UNCOMPRESSED_STRIP:
buf = new byte[parser.getStripSize()];
if( buf.length == parser.read( buf ) ) {
exifData.setStripBytes( parser.getStripIndex(), buf );
}
else {
Log.w( TAG, "Failed to read the strip bytes" );
}
break;
}
event = parser.next();
}
return exifData;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
package it.sephiroth.android.library.exif2;
import java.text.DecimalFormat;
import java.text.NumberFormat;
/**
* Created by alessandro on 20/04/14.
*/
public class ExifUtil {
static final NumberFormat formatter = DecimalFormat.getInstance();
public static String processLensSpecifications( Rational[] values ) {
Rational min_focal = values[0];
Rational max_focal = values[1];
Rational min_f = values[2];
Rational max_f = values[3];
formatter.setMaximumFractionDigits(1);
StringBuilder sb = new StringBuilder();
sb.append( formatter.format( min_focal.toDouble() ) );
sb.append( "-" );
sb.append( formatter.format( max_focal.toDouble() ) );
sb.append( "mm f/" );
sb.append( formatter.format( min_f.toDouble() ) );
sb.append( "-" );
sb.append( formatter.format( max_f.toDouble() ) );
return sb.toString();
}
}

View File

@ -0,0 +1,150 @@
/*
* 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 java.util.HashMap;
import java.util.Map;
/**
* This class stores all the tags in an IFD.
*
* @see ExifData
* @see ExifTag
*/
class IfdData {
private static final int[] sIfds = { IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF, IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS };
private final int mIfdId;
private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
private int mOffsetToNextIfd = 0;
/**
* Creates an IfdData with given IFD ID.
*
* @see IfdId#TYPE_IFD_0
* @see IfdId#TYPE_IFD_1
* @see IfdId#TYPE_IFD_EXIF
* @see IfdId#TYPE_IFD_GPS
* @see IfdId#TYPE_IFD_INTEROPERABILITY
*/
IfdData( int ifdId ) {
mIfdId = ifdId;
}
static protected int[] getIfds() {
return sIfds;
}
/**
* Gets the {@link ExifTag} with given tag id. Return null if there is no
* such tag.
*/
protected ExifTag getTag( short tagId ) {
return mExifTags.get( tagId );
}
/**
* Adds or replaces a {@link ExifTag}.
*/
protected ExifTag setTag( ExifTag tag ) {
tag.setIfd( mIfdId );
return mExifTags.put( tag.getTagId(), tag );
}
protected boolean checkCollision( short tagId ) {
return mExifTags.get( tagId ) != null;
}
/**
* Removes the tag of the given ID
*/
protected void removeTag( short tagId ) {
mExifTags.remove( tagId );
}
/**
* Gets the offset of next IFD.
*/
protected int getOffsetToNextIfd() {
return mOffsetToNextIfd;
}
/**
* Sets the offset of next IFD.
*/
protected void setOffsetToNextIfd( int offset ) {
mOffsetToNextIfd = offset;
}
/**
* Returns true if all tags in this two IFDs are equal. Note that tags of
* IFDs offset or thumbnail offset will be ignored.
*/
@Override
public boolean equals( Object obj ) {
if( this == obj ) {
return true;
}
if( obj == null ) {
return false;
}
if( obj instanceof IfdData ) {
IfdData data = (IfdData) obj;
if( data.getId() == mIfdId && data.getTagCount() == getTagCount() ) {
ExifTag[] tags = data.getAllTags();
for( ExifTag tag : tags ) {
if( ExifInterface.isOffsetTag( tag.getTagId() ) ) {
continue;
}
ExifTag tag2 = mExifTags.get( tag.getTagId() );
if( ! tag.equals( tag2 ) ) {
return false;
}
}
return true;
}
}
return false;
}
/**
* Gets the tags count in the IFD.
*/
protected int getTagCount() {
return mExifTags.size();
}
/**
* Gets the ID of this IFD.
*
* @see IfdId#TYPE_IFD_0
* @see IfdId#TYPE_IFD_1
* @see IfdId#TYPE_IFD_EXIF
* @see IfdId#TYPE_IFD_GPS
* @see IfdId#TYPE_IFD_INTEROPERABILITY
*/
protected int getId() {
return mIfdId;
}
/**
* Get a array the contains all {@link ExifTag} in this IFD.
*/
protected ExifTag[] getAllTags() {
return mExifTags.values().toArray( new ExifTag[mExifTags.size()] );
}
}

View File

@ -0,0 +1,31 @@
/*
* 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;
/**
* The constants of the IFD ID defined in EXIF spec.
*/
public interface IfdId {
public static final int TYPE_IFD_0 = 0;
public static final int TYPE_IFD_1 = 1;
public static final int TYPE_IFD_EXIF = 2;
public static final int TYPE_IFD_INTEROPERABILITY = 3;
public static final int TYPE_IFD_GPS = 4;
/* This is used in ExifData to allocate enough IfdData */
static final int TYPE_IFD_COUNT = 5;
}

View File

@ -0,0 +1,109 @@
/*
* 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;
class JpegHeader {
/** Start Of Image **/
public static final int TAG_SOI = 0xD8;
/** JFIF (JPEG File Interchange Format) */
public static final int TAG_M_JFIF = 0xE0;
/** EXIF table */
public static final int TAG_M_EXIF = 0xE1;
/** Product Information Comment */
public static final int TAG_M_COM = 0xFE;
/** Quantization Table */
public static final int TAG_M_DQT = 0xDB;
/** Start of frame */
public static final int TAG_M_SOF0 = 0xC0;
public static final int TAG_M_SOF1 = 0xC1;
public static final int TAG_M_SOF2 = 0xC2;
public static final int TAG_M_SOF3 = 0xC3;
public static final int TAG_M_DHT = 0xC4;
public static final int TAG_M_SOF5 = 0xC5;
public static final int TAG_M_SOF6 = 0xC6;
public static final int TAG_M_SOF7 = 0xC7;
public static final int TAG_M_SOF9 = 0xC9;
public static final int TAG_M_SOF10 = 0xCA;
public static final int TAG_M_SOF11 = 0xCB;
public static final int TAG_M_SOF13 = 0xCD;
public static final int TAG_M_SOF14 = 0xCE;
public static final int TAG_M_SOF15 = 0xCF;
/** Start Of Scan **/
public static final int TAG_M_SOS = 0xDA;
/** End of Image */
public static final int TAG_M_EOI = 0xD9;
public static final int TAG_M_IPTC = 0xED;
/** default JFIF Header bytes */
public static final byte JFIF_HEADER[] = {
(byte) 0xff, (byte) JpegHeader.TAG_M_JFIF,
0x00, 0x10, 'J', 'F', 'I', 'F',
0x00, 0x01, 0x01, 0x01, 0x01, 0x2C, 0x01,
0x2C, 0x00, 0x00
};
public static final short SOI = (short) 0xFFD8;
public static final short M_EXIF = (short) 0xFFE1;
public static final short M_JFIF = (short) 0xFFE0;
public static final short M_EOI = (short) 0xFFD9;
/**
* SOF (start of frame). All value between M_SOF0 and SOF15 is SOF marker except for M_DHT, JPG,
* and DAC marker.
*/
public static final short M_SOF0 = (short) 0xFFC0;
public static final short M_SOF1 = (short) 0xFFC1;
public static final short M_SOF2 = (short) 0xFFC2;
public static final short M_SOF3 = (short) 0xFFC3;
public static final short M_SOF5 = (short) 0xFFC5;
public static final short M_SOF6 = (short) 0xFFC6;
public static final short M_SOF7 = (short) 0xFFC7;
public static final short M_SOF9 = (short) 0xFFC9;
public static final short M_SOF10 = (short) 0xFFCA;
public static final short M_SOF11 = (short) 0xFFCB;
public static final short M_SOF13 = (short) 0xFFCD;
public static final short M_SOF14 = (short) 0xFFCE;
public static final short M_SOF15 = (short) 0xFFCF;
public static final short M_DHT = (short) 0xFFC4;
public static final short JPG = (short) 0xFFC8;
public static final short DAC = (short) 0xFFCC;
/** Define quantization table */
public static final short M_DQT = (short) 0xFFDB;
/** IPTC marker */
public static final short M_IPTC = (short) 0xFFED;
/** Start of scan (begins compressed data */
public static final short M_SOS = (short) 0xFFDA;
/** Comment section * */
public static final short M_COM = (short) 0xFFFE; // Comment section
public static final boolean isSofMarker( short marker ) {
return marker >= M_SOF0 && marker <= M_SOF15 && marker != M_DHT && marker != JPG && marker != DAC;
}
}

View File

@ -0,0 +1,56 @@
/*
* 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 java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
class OrderedDataOutputStream extends FilterOutputStream {
private final ByteBuffer mByteBuffer = ByteBuffer.allocate( 4 );
public OrderedDataOutputStream( OutputStream out ) {
super( out );
}
public OrderedDataOutputStream setByteOrder( ByteOrder order ) {
mByteBuffer.order( order );
return this;
}
public OrderedDataOutputStream writeShort( short value ) throws IOException {
mByteBuffer.rewind();
mByteBuffer.putShort( value );
out.write( mByteBuffer.array(), 0, 2 );
return this;
}
public OrderedDataOutputStream writeRational( Rational rational ) throws IOException {
writeInt( (int) rational.getNumerator() );
writeInt( (int) rational.getDenominator() );
return this;
}
public OrderedDataOutputStream writeInt( int value ) throws IOException {
mByteBuffer.rewind();
mByteBuffer.putInt( value );
out.write( mByteBuffer.array() );
return this;
}
}

View File

@ -0,0 +1,88 @@
/*
* 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;
/**
* The rational data type of EXIF tag. Contains a pair of longs representing the
* numerator and denominator of a Rational number.
*/
public class Rational {
private final long mNumerator;
private final long mDenominator;
/**
* Create a Rational with a given numerator and denominator.
*
* @param nominator
* @param denominator
*/
public Rational( long nominator, long denominator ) {
mNumerator = nominator;
mDenominator = denominator;
}
/**
* Create a copy of a Rational.
*/
public Rational( Rational r ) {
mNumerator = r.mNumerator;
mDenominator = r.mDenominator;
}
/**
* Gets the numerator of the rational.
*/
public long getNumerator() {
return mNumerator;
}
/**
* Gets the denominator of the rational
*/
public long getDenominator() {
return mDenominator;
}
/**
* Gets the rational value as type double. Will cause a divide-by-zero error
* if the denominator is 0.
*/
public double toDouble() {
return mNumerator / (double) mDenominator;
}
@Override
public boolean equals( Object obj ) {
if( obj == null ) {
return false;
}
if( this == obj ) {
return true;
}
if( obj instanceof Rational ) {
Rational data = (Rational) obj;
return mNumerator == data.mNumerator && mDenominator == data.mDenominator;
}
return false;
}
@Override
public String toString() {
return mNumerator + "/" + mDenominator;
}
}

View File

@ -1 +1 @@
include ':app'
include ':app', ':exif'