OSDN Git Service

Merge "Fix issue where aspect bounds don't get applied." into gb-ub-photos-arches
[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.res.Resources;
22 import android.database.Cursor;
23 import android.database.sqlite.SQLiteException;
24 import android.graphics.Bitmap;
25 import android.graphics.BitmapFactory;
26 import android.graphics.BitmapRegionDecoder;
27 import android.graphics.Matrix;
28 import android.graphics.Rect;
29 import android.media.ExifInterface;
30 import android.net.Uri;
31 import android.provider.MediaStore;
32 import android.util.Log;
33
34 import com.adobe.xmp.XMPException;
35 import com.adobe.xmp.XMPMeta;
36
37 import com.android.gallery3d.R;
38 import com.android.gallery3d.common.Utils;
39 import com.android.gallery3d.filtershow.FilterShowActivity;
40 import com.android.gallery3d.filtershow.HistoryAdapter;
41 import com.android.gallery3d.filtershow.imageshow.ImageCrop;
42 import com.android.gallery3d.filtershow.imageshow.ImageShow;
43 import com.android.gallery3d.filtershow.presets.ImagePreset;
44 import com.android.gallery3d.filtershow.tools.SaveCopyTask;
45 import com.android.gallery3d.util.XmpUtilHelper;
46
47 import java.io.Closeable;
48 import java.io.File;
49 import java.io.FileNotFoundException;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.util.Vector;
53
54 public class ImageLoader {
55
56     private static final String LOGTAG = "ImageLoader";
57     private final Vector<ImageShow> mListeners = new Vector<ImageShow>();
58     private Bitmap mOriginalBitmapSmall = null;
59     private Bitmap mOriginalBitmapLarge = null;
60     private Bitmap mBackgroundBitmap = null;
61
62     private Cache mCache = null;
63     private Cache mHiresCache = null;
64     private final ZoomCache mZoomCache = new ZoomCache();
65
66     private int mOrientation = 0;
67     private HistoryAdapter mAdapter = null;
68
69     private FilterShowActivity mActivity = null;
70
71     private static final int ORI_NORMAL     = ExifInterface.ORIENTATION_NORMAL;
72     private static final int ORI_ROTATE_90  = ExifInterface.ORIENTATION_ROTATE_90;
73     private static final int ORI_ROTATE_180 = ExifInterface.ORIENTATION_ROTATE_180;
74     private static final int ORI_ROTATE_270 = ExifInterface.ORIENTATION_ROTATE_270;
75     private static final int ORI_FLIP_HOR   = ExifInterface.ORIENTATION_FLIP_HORIZONTAL;
76     private static final int ORI_FLIP_VERT  = ExifInterface.ORIENTATION_FLIP_VERTICAL;
77     private static final int ORI_TRANSPOSE  = ExifInterface.ORIENTATION_TRANSPOSE;
78     private static final int ORI_TRANSVERSE = ExifInterface.ORIENTATION_TRANSVERSE;
79
80     private Context mContext = null;
81     private Uri mUri = null;
82
83     private Rect mOriginalBounds = null;
84
85     public ImageLoader(FilterShowActivity activity, Context context) {
86         mActivity = activity;
87         mContext = context;
88         mCache = new DelayedPresetCache(this, 30);
89         mHiresCache = new DelayedPresetCache(this, 3);
90     }
91
92     public FilterShowActivity getActivity() {
93         return mActivity;
94     }
95
96     public void loadBitmap(Uri uri,int size) {
97         mUri = uri;
98         mOrientation = getOrientation(mContext, uri);
99         mOriginalBitmapSmall = loadScaledBitmap(uri, 160);
100         if (mOriginalBitmapSmall == null) {
101             // Couldn't read the bitmap, let's exit
102             mActivity.cannotLoadImage();
103         }
104         mOriginalBitmapLarge = loadScaledBitmap(uri, size);
105         updateBitmaps();
106     }
107
108     public Uri getUri() {
109         return mUri;
110     }
111
112     public Rect getOriginalBounds() {
113         return mOriginalBounds;
114     }
115
116     public static int getOrientation(Context context, Uri uri) {
117         if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
118             return getOrientationFromPath(uri.getPath());
119         }
120
121         Cursor cursor = null;
122         try {
123             cursor = context.getContentResolver().query(uri,
124                     new String[] {
125                         MediaStore.Images.ImageColumns.ORIENTATION
126                     },
127                     null, null, null);
128             if (cursor.moveToNext()){
129               int ori =   cursor.getInt(0);
130
131               switch (ori){
132                   case 0:   return ORI_NORMAL;
133                   case 90:  return ORI_ROTATE_90;
134                   case 270: return ORI_ROTATE_270;
135                   case 180: return ORI_ROTATE_180;
136                   default:
137                       return -1;
138               }
139             } else{
140                 return -1;
141             }
142         } catch (SQLiteException e){
143             return ExifInterface.ORIENTATION_UNDEFINED;
144         } finally {
145             Utils.closeSilently(cursor);
146         }
147     }
148
149     static int getOrientationFromPath(String path) {
150         int orientation = -1;
151         try {
152             ExifInterface EXIF = new ExifInterface(path);
153             orientation = EXIF.getAttributeInt(ExifInterface.TAG_ORIENTATION,
154                     1);
155         } catch (IOException e) {
156             e.printStackTrace();
157         }
158         return orientation;
159     }
160
161     private void updateBitmaps() {
162         if (mOrientation > 1) {
163             mOriginalBitmapSmall = rotateToPortrait(mOriginalBitmapSmall, mOrientation);
164             mOriginalBitmapLarge = rotateToPortrait(mOriginalBitmapLarge, mOrientation);
165         }
166         mCache.setOriginalBitmap(mOriginalBitmapSmall);
167         mHiresCache.setOriginalBitmap(mOriginalBitmapLarge);
168         warnListeners();
169     }
170
171     public static Bitmap rotateToPortrait(Bitmap bitmap,int ori) {
172            Matrix matrix = new Matrix();
173            int w = bitmap.getWidth();
174            int h = bitmap.getHeight();
175            if (ori == ORI_ROTATE_90 ||
176                    ori == ORI_ROTATE_270 ||
177                    ori == ORI_TRANSPOSE||
178                    ori == ORI_TRANSVERSE) {
179                int tmp = w;
180                w = h;
181                h = tmp;
182            }
183            switch(ori){
184                case ORI_ROTATE_90:
185                    matrix.setRotate(90,w/2f,h/2f);
186                    break;
187                case ORI_ROTATE_180:
188                    matrix.setRotate(180,w/2f,h/2f);
189                    break;
190                case ORI_ROTATE_270:
191                    matrix.setRotate(270,w/2f,h/2f);
192                    break;
193                case ORI_FLIP_HOR:
194                    matrix.preScale(-1, 1);
195                    break;
196               case ORI_FLIP_VERT:
197                    matrix.preScale(1, -1);
198                    break;
199                case ORI_TRANSPOSE:
200                    matrix.setRotate(90,w/2f,h/2f);
201                    matrix.preScale(1, -1);
202                    break;
203                case ORI_TRANSVERSE:
204                    matrix.setRotate(270,w/2f,h/2f);
205                    matrix.preScale(1, -1);
206                    break;
207                case ORI_NORMAL:
208                default:
209                    return bitmap;
210             }
211
212         return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
213                 bitmap.getHeight(), matrix, true);
214     }
215
216     private void closeStream(Closeable stream) {
217         if (stream != null) {
218             try {
219                 stream.close();
220             } catch (IOException e) {
221                 e.printStackTrace();
222             }
223         }
224     }
225
226     private Bitmap loadRegionBitmap(Uri uri, Rect bounds) {
227         InputStream is = null;
228         try {
229             is = mContext.getContentResolver().openInputStream(uri);
230             BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
231             return decoder.decodeRegion(bounds, null);
232         } catch (FileNotFoundException e) {
233             Log.e(LOGTAG, "FileNotFoundException: " + uri);
234         } catch (Exception e) {
235             e.printStackTrace();
236         } finally {
237             closeStream(is);
238         }
239         return null;
240     }
241
242     static final int MAX_BITMAP_DIM = 2048;
243     private Bitmap loadScaledBitmap(Uri uri, int size) {
244         InputStream is = null;
245         try {
246             is = mContext.getContentResolver().openInputStream(uri);
247             Log.v(LOGTAG, "loading uri " + uri.getPath() + " input stream: "
248                     + is);
249             BitmapFactory.Options o = new BitmapFactory.Options();
250             o.inJustDecodeBounds = true;
251             BitmapFactory.decodeStream(is, null, o);
252
253             int width_tmp = o.outWidth;
254             int height_tmp = o.outHeight;
255
256             mOriginalBounds = new Rect(0, 0, width_tmp, height_tmp);
257
258             int scale = 1;
259             while (true) {
260                 if (width_tmp <= MAX_BITMAP_DIM && height_tmp <= MAX_BITMAP_DIM) {
261                     if (width_tmp / 2 < size || height_tmp / 2 < size) {
262                         break;
263                     }
264                 }
265                 width_tmp /= 2;
266                 height_tmp /= 2;
267                 scale *= 2;
268             }
269
270             // decode with inSampleSize
271             BitmapFactory.Options o2 = new BitmapFactory.Options();
272             o2.inSampleSize = scale;
273
274             closeStream(is);
275             is = mContext.getContentResolver().openInputStream(uri);
276             return BitmapFactory.decodeStream(is, null, o2);
277         } catch (FileNotFoundException e) {
278             Log.e(LOGTAG, "FileNotFoundException: " + uri);
279         } catch (Exception e) {
280             e.printStackTrace();
281         } finally {
282             closeStream(is);
283         }
284         return null;
285     }
286
287     public Bitmap getBackgroundBitmap(Resources resources) {
288         if (mBackgroundBitmap == null) {
289             mBackgroundBitmap = BitmapFactory.decodeResource(resources,
290                     R.drawable.filtershow_background);
291         }
292         return mBackgroundBitmap;
293
294     }
295
296     public Bitmap getOriginalBitmapSmall() {
297         return mOriginalBitmapSmall;
298     }
299
300     public Bitmap getOriginalBitmapLarge() {
301         return mOriginalBitmapLarge;
302     }
303
304     public void addListener(ImageShow imageShow) {
305         if (!mListeners.contains(imageShow)) {
306             mListeners.add(imageShow);
307         }
308         mHiresCache.addObserver(imageShow);
309     }
310
311     private void warnListeners() {
312         mActivity.runOnUiThread(mWarnListenersRunnable);
313     }
314
315     private Runnable mWarnListenersRunnable = new Runnable() {
316
317         @Override
318         public void run() {
319             for (int i = 0; i < mListeners.size(); i++) {
320                 ImageShow imageShow = mListeners.elementAt(i);
321                 imageShow.imageLoaded();
322             }
323         }
324     };
325
326     // TODO: this currently does the loading + filtering on the UI thread -- need to
327     // move this to a background thread.
328     public Bitmap getScaleOneImageForPreset(ImageShow caller, ImagePreset imagePreset, Rect bounds,
329             boolean force) {
330         Bitmap bmp = mZoomCache.getImage(imagePreset, bounds);
331         if (force || bmp == null) {
332             bmp = loadRegionBitmap(mUri, bounds);
333             if (bmp != null) {
334                 // TODO: this workaround for RS might not be needed ultimately
335                 Bitmap bmp2 = bmp.copy(Bitmap.Config.ARGB_8888, true);
336                 float scaleFactor = imagePreset.getScaleFactor();
337                 imagePreset.setScaleFactor(1.0f);
338                 bmp2 = imagePreset.apply(bmp2);
339                 imagePreset.setScaleFactor(scaleFactor);
340                 mZoomCache.setImage(imagePreset, bounds, bmp2);
341                 return bmp2;
342             }
343         }
344         return bmp;
345     }
346
347     // Caching method
348     public Bitmap getImageForPreset(ImageShow caller, ImagePreset imagePreset,
349             boolean hiRes) {
350         if (mOriginalBitmapSmall == null) {
351             return null;
352         }
353         if (mOriginalBitmapLarge == null) {
354             return null;
355         }
356
357         Bitmap filteredImage = null;
358
359         if (hiRes) {
360             filteredImage = mHiresCache.get(imagePreset);
361         } else {
362             filteredImage = mCache.get(imagePreset);
363         }
364
365         if (filteredImage == null) {
366             if (hiRes) {
367                 mHiresCache.prepare(imagePreset);
368                 mHiresCache.addObserver(caller);
369             } else {
370                 mCache.prepare(imagePreset);
371                 mCache.addObserver(caller);
372             }
373         }
374         return filteredImage;
375     }
376
377     public void resetImageForPreset(ImagePreset imagePreset, ImageShow caller) {
378         mHiresCache.reset(imagePreset);
379         mCache.reset(imagePreset);
380         mZoomCache.reset(imagePreset);
381     }
382
383     public void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity,
384             File destination) {
385         preset.setIsHighQuality(true);
386         preset.setScaleFactor(1.0f);
387         new SaveCopyTask(mContext, mUri, destination, new SaveCopyTask.Callback() {
388
389             @Override
390             public void onComplete(Uri result) {
391                 filterShowActivity.completeSaveImage(result);
392             }
393
394         }).execute(preset);
395     }
396
397     public void setAdapter(HistoryAdapter adapter) {
398         mAdapter = adapter;
399     }
400
401     public HistoryAdapter getHistory() {
402         return mAdapter;
403     }
404
405     public XMPMeta getXmpObject() {
406         try {
407             InputStream is = mContext.getContentResolver().openInputStream(getUri());
408             return XmpUtilHelper.extractXMPMeta(is);
409         } catch (FileNotFoundException e) {
410             return null;
411         }
412     }
413
414     /**
415      * Determine if this is a light cycle 360 image
416      *
417      * @return true if it is a light Cycle image that is full 360
418      */
419     public boolean queryLightCycle360() {
420         try {
421             InputStream is = mContext.getContentResolver().openInputStream(getUri());
422             XMPMeta meta = XmpUtilHelper.extractXMPMeta(is);
423             if (meta == null) {
424                 return false;
425             }
426             String name = meta.getPacketHeader();
427             try {
428                 String namespace = "http://ns.google.com/photos/1.0/panorama/";
429                 String cropWidthName = "GPano:CroppedAreaImageWidthPixels";
430                 String fullWidthName = "GPano:FullPanoWidthPixels";
431
432                 if (!meta.doesPropertyExist(namespace, cropWidthName)) {
433                     return false;
434                 }
435                 if (!meta.doesPropertyExist(namespace, fullWidthName)) {
436                     return false;
437                 }
438
439                 Integer cropValue = meta.getPropertyInteger(namespace, cropWidthName);
440                 Integer fullValue = meta.getPropertyInteger(namespace, fullWidthName);
441
442                 // Definition of a 360:
443                 // GFullPanoWidthPixels == CroppedAreaImageWidthPixels
444                 if (cropValue != null && fullValue != null) {
445                     return cropValue.equals(fullValue);
446                 }
447
448                 return false;
449             } catch (XMPException e) {
450                 return false;
451             }
452         } catch (FileNotFoundException e) {
453             return false;
454         }
455     }
456
457 }