画像リサイズ時にExif情報を参照して画像を自動的に回転させる
This commit is contained in:
parent
14dc3dc20f
commit
e22c420f84
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
|
@ -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')
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -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'
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="it.sephiroth.android.library.exif2">
|
||||
|
||||
<application>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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()] );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
include ':app'
|
||||
include ':app', ':exif'
|
||||
|
|
Loading…
Reference in New Issue