OSDN Git Service

Merge "refactor the geometry code" into gb-ub-photos-bryce
[android-x86/packages-apps-Gallery2.git] / src / com / android / gallery3d / filtershow / cache / ImageLoader.java
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.gallery3d.filtershow.cache;
18
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.res.Resources;
23 import android.database.Cursor;
24 import android.database.sqlite.SQLiteException;
25 import android.graphics.Bitmap;
26 import android.graphics.BitmapFactory;
27 import android.graphics.BitmapRegionDecoder;
28 import android.graphics.Matrix;
29 import android.graphics.Rect;
30 import android.graphics.Bitmap.CompressFormat;
31 import android.graphics.RectF;
32 import android.media.ExifInterface;
33 import android.net.Uri;
34 import android.provider.MediaStore;
35 import android.util.Log;
36
37 import com.adobe.xmp.XMPException;
38 import com.adobe.xmp.XMPMeta;
39
40 import com.android.gallery3d.R;
41 import com.android.gallery3d.common.Utils;
42 import com.android.gallery3d.exif.ExifInvalidFormatException;
43 import com.android.gallery3d.exif.ExifParser;
44 import com.android.gallery3d.exif.ExifTag;
45 import com.android.gallery3d.filtershow.CropExtras;
46 import com.android.gallery3d.filtershow.FilterShowActivity;
47 import com.android.gallery3d.filtershow.HistoryAdapter;
48 import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
49 import com.android.gallery3d.filtershow.imageshow.ImageCrop;
50 import com.android.gallery3d.filtershow.imageshow.ImageShow;
51 import com.android.gallery3d.filtershow.imageshow.MasterImage;
52 import com.android.gallery3d.filtershow.presets.ImagePreset;
53 import com.android.gallery3d.filtershow.tools.BitmapTask;
54 import com.android.gallery3d.filtershow.tools.SaveCopyTask;
55 import com.android.gallery3d.util.InterruptableOutputStream;
56 import com.android.gallery3d.util.XmpUtilHelper;
57
58 import java.io.Closeable;
59 import java.io.File;
60 import java.io.FileInputStream;
61 import java.io.FileNotFoundException;
62 import java.io.IOException;
63 import java.io.InputStream;
64 import java.io.OutputStream;
65 import java.util.Vector;
66 import java.util.concurrent.locks.ReentrantLock;
67
68
69 // TODO: this class has waaaay to much bitmap copying.  Cleanup.
70 public class ImageLoader {
71
72     private static final String LOGTAG = "ImageLoader";
73     private final Vector<ImageShow> mListeners = new Vector<ImageShow>();
74     private Bitmap mOriginalBitmapSmall = null;
75     private Bitmap mOriginalBitmapLarge = null;
76     private Bitmap mBackgroundBitmap = null;
77
78     private final ZoomCache mZoomCache = new ZoomCache();
79
80     private int mOrientation = 0;
81     private HistoryAdapter mAdapter = null;
82
83     private FilterShowActivity mActivity = null;
84
85     public static final String JPEG_MIME_TYPE = "image/jpeg";
86
87     public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos";
88     public static final int DEFAULT_COMPRESS_QUALITY = 95;
89
90     public static final int ORI_NORMAL = ExifInterface.ORIENTATION_NORMAL;
91     public static final int ORI_ROTATE_90 = ExifInterface.ORIENTATION_ROTATE_90;
92     public static final int ORI_ROTATE_180 = ExifInterface.ORIENTATION_ROTATE_180;
93     public static final int ORI_ROTATE_270 = ExifInterface.ORIENTATION_ROTATE_270;
94     public static final int ORI_FLIP_HOR = ExifInterface.ORIENTATION_FLIP_HORIZONTAL;
95     public static final int ORI_FLIP_VERT = ExifInterface.ORIENTATION_FLIP_VERTICAL;
96     public static final int ORI_TRANSPOSE = ExifInterface.ORIENTATION_TRANSPOSE;
97     public static final int ORI_TRANSVERSE = ExifInterface.ORIENTATION_TRANSVERSE;
98
99     private static final int BITMAP_LOAD_BACKOUT_ATTEMPTS = 5;
100     private Context mContext = null;
101     private Uri mUri = null;
102
103     private Rect mOriginalBounds = null;
104     private static int mZoomOrientation = ORI_NORMAL;
105
106     private ReentrantLock mLoadingLock = new ReentrantLock();
107
108     public ImageLoader(FilterShowActivity activity, Context context) {
109         mActivity = activity;
110         mContext = context;
111     }
112
113     public static int getZoomOrientation() {
114         return mZoomOrientation;
115     }
116
117     public FilterShowActivity getActivity() {
118         return mActivity;
119     }
120
121     public boolean loadBitmap(Uri uri, int size) {
122         mLoadingLock.lock();
123         mUri = uri;
124         mOrientation = getOrientation(mContext, uri);
125         mOriginalBitmapSmall = loadScaledBitmap(uri, 160);
126         if (mOriginalBitmapSmall == null) {
127             // Couldn't read the bitmap, let's exit
128             mLoadingLock.unlock();
129             return false;
130         }
131         mOriginalBitmapLarge = loadScaledBitmap(uri, size);
132         if (mOriginalBitmapLarge == null) {
133             mLoadingLock.unlock();
134             return false;
135         }
136         updateBitmaps();
137         // TODO: cleanup
138         GeometryMetadata geo = MasterImage.getImage().getPreset().mGeoData;
139         float w = mOriginalBitmapLarge.getWidth();
140         float h = mOriginalBitmapLarge.getHeight();
141         RectF r = new RectF(0, 0, w, h);
142         geo.setPhotoBounds(r);
143         geo.setCropBounds(r);
144         MasterImage.getImage().getPreset().setGeometry(geo);
145         MasterImage.getImage().notifyGeometryChange();
146         mLoadingLock.unlock();
147         return true;
148     }
149
150     public Uri getUri() {
151         return mUri;
152     }
153
154     public Rect getOriginalBounds() {
155         return mOriginalBounds;
156     }
157
158     public static int getOrientation(Context context, Uri uri) {
159         if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
160             String mimeType = context.getContentResolver().getType(uri);
161             if (mimeType != ImageLoader.JPEG_MIME_TYPE) {
162                 return -1;
163             }
164             String path = uri.getPath();
165             int orientation = -1;
166             InputStream is = null;
167             try {
168                 is = new FileInputStream(path);
169                 ExifParser parser = ExifParser.parse(is, ExifParser.OPTION_IFD_0);
170                 int event = parser.next();
171                 while (event != ExifParser.EVENT_END) {
172                     if (event == ExifParser.EVENT_NEW_TAG) {
173                         ExifTag tag = parser.getTag();
174                         if (tag.getTagId() == ExifTag.TAG_ORIENTATION) {
175                             orientation = (int) tag.getValueAt(0);
176                             break;
177                         }
178                     }
179                     event = parser.next();
180                 }
181             } catch (IOException e) {
182                 e.printStackTrace();
183             } catch (ExifInvalidFormatException e) {
184                 e.printStackTrace();
185             } finally {
186                 Utils.closeSilently(is);
187             }
188             return orientation;
189         }
190         Cursor cursor = null;
191         try {
192             cursor = context.getContentResolver().query(uri,
193                     new String[] {
194                         MediaStore.Images.ImageColumns.ORIENTATION
195                     },
196                     null, null, null);
197             if (cursor.moveToNext()) {
198                 int ori = cursor.getInt(0);
199
200                 switch (ori) {
201                     case 0:
202                         return ORI_NORMAL;
203                     case 90:
204                         return ORI_ROTATE_90;
205                     case 270:
206                         return ORI_ROTATE_270;
207                     case 180:
208                         return ORI_ROTATE_180;
209                     default:
210                         return -1;
211                 }
212             } else {
213                 return -1;
214             }
215         } catch (SQLiteException e) {
216             return ExifInterface.ORIENTATION_UNDEFINED;
217         } catch (IllegalArgumentException e) {
218             return ExifInterface.ORIENTATION_UNDEFINED;
219         } finally {
220             Utils.closeSilently(cursor);
221         }
222     }
223
224     private void updateBitmaps() {
225         if (mOrientation > 1) {
226             mOriginalBitmapSmall = rotateToPortrait(mOriginalBitmapSmall, mOrientation);
227             mOriginalBitmapLarge = rotateToPortrait(mOriginalBitmapLarge, mOrientation);
228         }
229         mZoomOrientation = mOrientation;
230         warnListeners();
231     }
232
233     public Bitmap decodeImage(int id, BitmapFactory.Options options) {
234         return BitmapFactory.decodeResource(mContext.getResources(), id, options);
235     }
236
237     public static Bitmap rotateToPortrait(Bitmap bitmap, int ori) {
238         Matrix matrix = new Matrix();
239         int w = bitmap.getWidth();
240         int h = bitmap.getHeight();
241         if (ori == ORI_ROTATE_90 ||
242                 ori == ORI_ROTATE_270 ||
243                 ori == ORI_TRANSPOSE ||
244                 ori == ORI_TRANSVERSE) {
245             int tmp = w;
246             w = h;
247             h = tmp;
248         }
249         switch (ori) {
250             case ORI_ROTATE_90:
251                 matrix.setRotate(90, w / 2f, h / 2f);
252                 break;
253             case ORI_ROTATE_180:
254                 matrix.setRotate(180, w / 2f, h / 2f);
255                 break;
256             case ORI_ROTATE_270:
257                 matrix.setRotate(270, w / 2f, h / 2f);
258                 break;
259             case ORI_FLIP_HOR:
260                 matrix.preScale(-1, 1);
261                 break;
262             case ORI_FLIP_VERT:
263                 matrix.preScale(1, -1);
264                 break;
265             case ORI_TRANSPOSE:
266                 matrix.setRotate(90, w / 2f, h / 2f);
267                 matrix.preScale(1, -1);
268                 break;
269             case ORI_TRANSVERSE:
270                 matrix.setRotate(270, w / 2f, h / 2f);
271                 matrix.preScale(1, -1);
272                 break;
273             case ORI_NORMAL:
274             default:
275                 return bitmap;
276         }
277
278         return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
279                 bitmap.getHeight(), matrix, true);
280     }
281
282     private Bitmap loadRegionBitmap(Uri uri, BitmapFactory.Options options, Rect bounds) {
283         InputStream is = null;
284         try {
285             is = mContext.getContentResolver().openInputStream(uri);
286             BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
287             return decoder.decodeRegion(bounds, options);
288         } catch (FileNotFoundException e) {
289             Log.e(LOGTAG, "FileNotFoundException: " + uri);
290         } catch (Exception e) {
291             e.printStackTrace();
292         } finally {
293             Utils.closeSilently(is);
294         }
295         return null;
296     }
297
298     static final int MAX_BITMAP_DIM = 900;
299
300     private Bitmap loadScaledBitmap(Uri uri, int size) {
301         InputStream is = null;
302         try {
303             is = mContext.getContentResolver().openInputStream(uri);
304             Log.v(LOGTAG, "loading uri " + uri.getPath() + " input stream: "
305                     + is);
306             BitmapFactory.Options o = new BitmapFactory.Options();
307             o.inJustDecodeBounds = true;
308             BitmapFactory.decodeStream(is, null, o);
309
310             int width_tmp = o.outWidth;
311             int height_tmp = o.outHeight;
312
313             mOriginalBounds = new Rect(0, 0, width_tmp, height_tmp);
314
315             int scale = 1;
316             while (true) {
317                 if (width_tmp <= MAX_BITMAP_DIM && height_tmp <= MAX_BITMAP_DIM) {
318                     if (width_tmp / 2 < size || height_tmp / 2 < size) {
319                         break;
320                     }
321                 }
322                 width_tmp /= 2;
323                 height_tmp /= 2;
324                 scale *= 2;
325             }
326
327             // decode with inSampleSize
328             BitmapFactory.Options o2 = new BitmapFactory.Options();
329             o2.inSampleSize = scale;
330             o2.inMutable = true;
331
332             Utils.closeSilently(is);
333             is = mContext.getContentResolver().openInputStream(uri);
334             return BitmapFactory.decodeStream(is, null, o2);
335         } catch (FileNotFoundException e) {
336             Log.e(LOGTAG, "FileNotFoundException: " + uri);
337         } catch (Exception e) {
338             e.printStackTrace();
339         } finally {
340             Utils.closeSilently(is);
341         }
342         return null;
343     }
344
345     public Bitmap getBackgroundBitmap(Resources resources) {
346         if (mBackgroundBitmap == null) {
347             mBackgroundBitmap = BitmapFactory.decodeResource(resources,
348                     R.drawable.filtershow_background);
349         }
350         return mBackgroundBitmap;
351
352     }
353
354     public Bitmap getOriginalBitmapSmall() {
355         return mOriginalBitmapSmall;
356     }
357
358     public Bitmap getOriginalBitmapLarge() {
359         return mOriginalBitmapLarge;
360     }
361
362     public void addListener(ImageShow imageShow) {
363         mLoadingLock.lock();
364         if (!mListeners.contains(imageShow)) {
365             mListeners.add(imageShow);
366         }
367         mLoadingLock.unlock();
368     }
369
370     private void warnListeners() {
371         mActivity.runOnUiThread(mWarnListenersRunnable);
372     }
373
374     private Runnable mWarnListenersRunnable = new Runnable() {
375
376         @Override
377         public void run() {
378             for (int i = 0; i < mListeners.size(); i++) {
379                 ImageShow imageShow = mListeners.elementAt(i);
380                 imageShow.imageLoaded();
381             }
382         }
383     };
384
385     // FIXME: this currently does the loading + filtering on the UI thread --
386     // need to move this to a background thread.
387     public Bitmap getScaleOneImageForPreset(ImageShow caller, ImagePreset imagePreset, Rect bounds,
388                                             Rect destination, boolean force) {
389         mLoadingLock.lock();
390         Bitmap bmp = mZoomCache.getImage(imagePreset, bounds);
391         if (force || bmp == null) {
392             BitmapFactory.Options options = new BitmapFactory.Options();
393             options.inMutable = true;
394             if (destination != null) {
395                 if (bounds.width() > destination.width()) {
396                     int sampleSize = 1;
397                     int w = bounds.width();
398                     while (w > destination.width()) {
399                         sampleSize *= 2;
400                         w /= sampleSize;
401                     }
402                     options.inSampleSize = sampleSize;
403                 }
404             }
405             bmp = loadRegionBitmap(mUri, options, bounds);
406             if (destination != null) {
407                 mLoadingLock.unlock();
408                 return bmp;
409             }
410             if (bmp != null) {
411                 float scaleFactor = imagePreset.getScaleFactor();
412                 imagePreset.setScaleFactor(1.0f);
413                 bmp = imagePreset.apply(bmp);
414                 imagePreset.setScaleFactor(scaleFactor);
415                 mZoomCache.setImage(imagePreset, bounds, bmp);
416                 mLoadingLock.unlock();
417                 return bmp;
418             }
419         }
420         mLoadingLock.unlock();
421         return bmp;
422     }
423
424     public void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity,
425             File destination) {
426         preset.setQuality(ImagePreset.QUALITY_FINAL);
427         preset.setScaleFactor(1.0f);
428         new SaveCopyTask(mContext, mUri, destination, new SaveCopyTask.Callback() {
429
430             @Override
431             public void onComplete(Uri result) {
432                 filterShowActivity.completeSaveImage(result);
433             }
434
435         }).execute(preset);
436     }
437
438     public static Bitmap loadMutableBitmap(Context context, Uri sourceUri) {
439         BitmapFactory.Options options = new BitmapFactory.Options();
440         return loadMutableBitmap(context, sourceUri, options);
441     }
442
443     public static Bitmap loadMutableBitmap(Context context, Uri sourceUri,
444             BitmapFactory.Options options) {
445         // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't
446         // exist)
447         options.inMutable = true;
448
449         Bitmap bitmap = decodeUriWithBackouts(context, sourceUri, options);
450         if (bitmap == null) {
451             return null;
452         }
453         int orientation = ImageLoader.getOrientation(context, sourceUri);
454         bitmap = ImageLoader.rotateToPortrait(bitmap, orientation);
455         return bitmap;
456     }
457
458     public static Bitmap decodeUriWithBackouts(Context context, Uri sourceUri,
459             BitmapFactory.Options options) {
460         boolean noBitmap = true;
461         int num_tries = 0;
462         InputStream is = getInputStream(context, sourceUri);
463
464         if (options.inSampleSize < 1) {
465             options.inSampleSize = 1;
466         }
467         // Stopgap fix for low-memory devices.
468         Bitmap bmap = null;
469         while (noBitmap) {
470             if (is == null) {
471                 return null;
472             }
473             try {
474                 // Try to decode, downsample if low-memory.
475                 bmap = BitmapFactory.decodeStream(is, null, options);
476                 noBitmap = false;
477             } catch (java.lang.OutOfMemoryError e) {
478                 // Try 5 times before failing for good.
479                 if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
480                     throw e;
481                 }
482                 is = null;
483                 bmap = null;
484                 System.gc();
485                 is = getInputStream(context, sourceUri);
486                 options.inSampleSize *= 2;
487             }
488         }
489         Utils.closeSilently(is);
490         return bmap;
491     }
492
493     private static InputStream getInputStream(Context context, Uri sourceUri) {
494         InputStream is = null;
495         try {
496             is = context.getContentResolver().openInputStream(sourceUri);
497         } catch (FileNotFoundException e) {
498             Log.w(LOGTAG, "could not load bitmap ", e);
499             Utils.closeSilently(is);
500             is = null;
501         }
502         return is;
503     }
504
505     public static Bitmap decodeResourceWithBackouts(Resources res, BitmapFactory.Options options,
506             int id) {
507         boolean noBitmap = true;
508         int num_tries = 0;
509         if (options.inSampleSize < 1) {
510             options.inSampleSize = 1;
511         }
512         // Stopgap fix for low-memory devices.
513         Bitmap bmap = null;
514         while (noBitmap) {
515             try {
516                 // Try to decode, downsample if low-memory.
517                 bmap = BitmapFactory.decodeResource(
518                         res, id, options);
519                 noBitmap = false;
520             } catch (java.lang.OutOfMemoryError e) {
521                 // Try 5 times before failing for good.
522                 if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
523                     throw e;
524                 }
525                 bmap = null;
526                 System.gc();
527                 options.inSampleSize *= 2;
528             }
529         }
530         return bmap;
531     }
532
533     public void returnFilteredResult(ImagePreset preset,
534             final FilterShowActivity filterShowActivity) {
535         preset.setQuality(ImagePreset.QUALITY_FINAL);
536         preset.setScaleFactor(1.0f);
537
538         BitmapTask.Callbacks<ImagePreset> cb = new BitmapTask.Callbacks<ImagePreset>() {
539
540             @Override
541             public void onComplete(Bitmap result) {
542                 filterShowActivity.onFilteredResult(result);
543             }
544
545             @Override
546             public void onCancel() {
547             }
548
549             @Override
550             public Bitmap onExecute(ImagePreset param) {
551                 if (param == null || mUri == null) {
552                     return null;
553                 }
554                 BitmapFactory.Options options = new BitmapFactory.Options();
555                 boolean noBitmap = true;
556                 int num_tries = 0;
557                 if (options.inSampleSize < 1) {
558                     options.inSampleSize = 1;
559                 }
560                 Bitmap bitmap = null;
561                 // Stopgap fix for low-memory devices.
562                 while (noBitmap) {
563                     try {
564                         // Try to do bitmap operations, downsample if low-memory
565                         bitmap = loadMutableBitmap(mContext, mUri, options);
566                         if (bitmap == null) {
567                             Log.w(LOGTAG, "Failed to save image!");
568                             return null;
569                         }
570                         bitmap = param.applyGeometry(bitmap);
571                         bitmap = param.apply(bitmap);
572                         noBitmap = false;
573                     } catch (java.lang.OutOfMemoryError e) {
574                         // Try 5 times before failing for good.
575                         if (++num_tries >= 5) {
576                             throw e;
577                         }
578                         bitmap = null;
579                         System.gc();
580                         options.inSampleSize *= 2;
581                     }
582                 }
583                 return bitmap;
584             }
585         };
586
587         (new BitmapTask<ImagePreset>(cb)).execute(preset);
588     }
589
590     private String getFileExtension(String requestFormat) {
591         String outputFormat = (requestFormat == null)
592                 ? "jpg"
593                 : requestFormat;
594         outputFormat = outputFormat.toLowerCase();
595         return (outputFormat.equals("png") || outputFormat.equals("gif"))
596                 ? "png" // We don't support gif compression.
597                 : "jpg";
598     }
599
600     private CompressFormat convertExtensionToCompressFormat(String extension) {
601         return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
602     }
603
604     public void saveToUri(Bitmap bmap, Uri uri, final String outputFormat,
605             final FilterShowActivity filterShowActivity) {
606
607         OutputStream out = null;
608         try {
609             out = filterShowActivity.getContentResolver().openOutputStream(uri);
610         } catch (FileNotFoundException e) {
611             Log.w(LOGTAG, "cannot write output", e);
612             out = null;
613         } finally {
614             if (bmap == null || out == null) {
615                 return;
616             }
617         }
618
619         final InterruptableOutputStream ios = new InterruptableOutputStream(out);
620
621         BitmapTask.Callbacks<Bitmap> cb = new BitmapTask.Callbacks<Bitmap>() {
622
623             @Override
624             public void onComplete(Bitmap result) {
625                 filterShowActivity.done();
626             }
627
628             @Override
629             public void onCancel() {
630                 ios.interrupt();
631             }
632
633             @Override
634             public Bitmap onExecute(Bitmap param) {
635                 CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(outputFormat));
636                 param.compress(cf, DEFAULT_COMPRESS_QUALITY, ios);
637                 Utils.closeSilently(ios);
638                 return null;
639             }
640         };
641
642         (new BitmapTask<Bitmap>(cb)).execute(bmap);
643     }
644
645     public void setAdapter(HistoryAdapter adapter) {
646         mAdapter = adapter;
647     }
648
649     public HistoryAdapter getHistory() {
650         return mAdapter;
651     }
652
653     public XMPMeta getXmpObject() {
654         try {
655             InputStream is = mContext.getContentResolver().openInputStream(getUri());
656             return XmpUtilHelper.extractXMPMeta(is);
657         } catch (FileNotFoundException e) {
658             return null;
659         }
660     }
661
662     /**
663      * Determine if this is a light cycle 360 image
664      *
665      * @return true if it is a light Cycle image that is full 360
666      */
667     public boolean queryLightCycle360() {
668         InputStream is = null;
669         try {
670             is = mContext.getContentResolver().openInputStream(getUri());
671             XMPMeta meta = XmpUtilHelper.extractXMPMeta(is);
672             if (meta == null) {
673                 return false;
674             }
675             String name = meta.getPacketHeader();
676             String namespace = "http://ns.google.com/photos/1.0/panorama/";
677             String cropWidthName = "GPano:CroppedAreaImageWidthPixels";
678             String fullWidthName = "GPano:FullPanoWidthPixels";
679
680             if (!meta.doesPropertyExist(namespace, cropWidthName)) {
681                 return false;
682             }
683             if (!meta.doesPropertyExist(namespace, fullWidthName)) {
684                 return false;
685             }
686
687             Integer cropValue = meta.getPropertyInteger(namespace, cropWidthName);
688             Integer fullValue = meta.getPropertyInteger(namespace, fullWidthName);
689
690             // Definition of a 360:
691             // GFullPanoWidthPixels == CroppedAreaImageWidthPixels
692             if (cropValue != null && fullValue != null) {
693                 return cropValue.equals(fullValue);
694             }
695
696             return false;
697         } catch (FileNotFoundException e) {
698             return false;
699         } catch (XMPException e) {
700             return false;
701         } finally {
702             Utils.closeSilently(is);
703         }
704     }
705 }