OSDN Git Service

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