2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.gallery3d.filtershow.cache;
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;
34 import com.adobe.xmp.XMPException;
35 import com.adobe.xmp.XMPMeta;
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;
47 import java.io.Closeable;
49 import java.io.FileNotFoundException;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.util.Vector;
54 public class ImageLoader {
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;
62 private Cache mCache = null;
63 private Cache mHiresCache = null;
64 private final ZoomCache mZoomCache = new ZoomCache();
66 private int mOrientation = 0;
67 private HistoryAdapter mAdapter = null;
69 private FilterShowActivity mActivity = null;
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;
80 private Context mContext = null;
81 private Uri mUri = null;
83 private Rect mOriginalBounds = null;
85 public ImageLoader(FilterShowActivity activity, Context context) {
88 mCache = new DelayedPresetCache(this, 30);
89 mHiresCache = new DelayedPresetCache(this, 3);
92 public FilterShowActivity getActivity() {
96 public void loadBitmap(Uri uri,int size) {
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();
104 mOriginalBitmapLarge = loadScaledBitmap(uri, size);
108 public Uri getUri() {
112 public Rect getOriginalBounds() {
113 return mOriginalBounds;
116 public static int getOrientation(Context context, Uri uri) {
117 if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
118 return getOrientationFromPath(uri.getPath());
121 Cursor cursor = null;
123 cursor = context.getContentResolver().query(uri,
125 MediaStore.Images.ImageColumns.ORIENTATION
128 if (cursor.moveToNext()){
129 int ori = cursor.getInt(0);
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;
142 } catch (SQLiteException e){
143 return ExifInterface.ORIENTATION_UNDEFINED;
145 Utils.closeSilently(cursor);
149 static int getOrientationFromPath(String path) {
150 int orientation = -1;
152 ExifInterface EXIF = new ExifInterface(path);
153 orientation = EXIF.getAttributeInt(ExifInterface.TAG_ORIENTATION,
155 } catch (IOException e) {
161 private void updateBitmaps() {
162 if (mOrientation > 1) {
163 mOriginalBitmapSmall = rotateToPortrait(mOriginalBitmapSmall, mOrientation);
164 mOriginalBitmapLarge = rotateToPortrait(mOriginalBitmapLarge, mOrientation);
166 mCache.setOriginalBitmap(mOriginalBitmapSmall);
167 mHiresCache.setOriginalBitmap(mOriginalBitmapLarge);
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) {
185 matrix.setRotate(90,w/2f,h/2f);
188 matrix.setRotate(180,w/2f,h/2f);
191 matrix.setRotate(270,w/2f,h/2f);
194 matrix.preScale(-1, 1);
197 matrix.preScale(1, -1);
200 matrix.setRotate(90,w/2f,h/2f);
201 matrix.preScale(1, -1);
204 matrix.setRotate(270,w/2f,h/2f);
205 matrix.preScale(1, -1);
212 return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
213 bitmap.getHeight(), matrix, true);
216 private void closeStream(Closeable stream) {
217 if (stream != null) {
220 } catch (IOException e) {
226 private Bitmap loadRegionBitmap(Uri uri, Rect bounds) {
227 InputStream is = null;
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) {
242 static final int MAX_BITMAP_DIM = 2048;
243 private Bitmap loadScaledBitmap(Uri uri, int size) {
244 InputStream is = null;
246 is = mContext.getContentResolver().openInputStream(uri);
247 Log.v(LOGTAG, "loading uri " + uri.getPath() + " input stream: "
249 BitmapFactory.Options o = new BitmapFactory.Options();
250 o.inJustDecodeBounds = true;
251 BitmapFactory.decodeStream(is, null, o);
253 int width_tmp = o.outWidth;
254 int height_tmp = o.outHeight;
256 mOriginalBounds = new Rect(0, 0, width_tmp, height_tmp);
260 if (width_tmp <= MAX_BITMAP_DIM && height_tmp <= MAX_BITMAP_DIM) {
261 if (width_tmp / 2 < size || height_tmp / 2 < size) {
270 // decode with inSampleSize
271 BitmapFactory.Options o2 = new BitmapFactory.Options();
272 o2.inSampleSize = scale;
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) {
287 public Bitmap getBackgroundBitmap(Resources resources) {
288 if (mBackgroundBitmap == null) {
289 mBackgroundBitmap = BitmapFactory.decodeResource(resources,
290 R.drawable.filtershow_background);
292 return mBackgroundBitmap;
296 public Bitmap getOriginalBitmapSmall() {
297 return mOriginalBitmapSmall;
300 public Bitmap getOriginalBitmapLarge() {
301 return mOriginalBitmapLarge;
304 public void addListener(ImageShow imageShow) {
305 if (!mListeners.contains(imageShow)) {
306 mListeners.add(imageShow);
308 mHiresCache.addObserver(imageShow);
311 private void warnListeners() {
312 mActivity.runOnUiThread(mWarnListenersRunnable);
315 private Runnable mWarnListenersRunnable = new Runnable() {
319 for (int i = 0; i < mListeners.size(); i++) {
320 ImageShow imageShow = mListeners.elementAt(i);
321 imageShow.imageLoaded();
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,
330 Bitmap bmp = mZoomCache.getImage(imagePreset, bounds);
331 if (force || bmp == null) {
332 bmp = loadRegionBitmap(mUri, bounds);
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);
348 public Bitmap getImageForPreset(ImageShow caller, ImagePreset imagePreset,
350 if (mOriginalBitmapSmall == null) {
353 if (mOriginalBitmapLarge == null) {
357 Bitmap filteredImage = null;
360 filteredImage = mHiresCache.get(imagePreset);
362 filteredImage = mCache.get(imagePreset);
365 if (filteredImage == null) {
367 mHiresCache.prepare(imagePreset);
368 mHiresCache.addObserver(caller);
370 mCache.prepare(imagePreset);
371 mCache.addObserver(caller);
374 return filteredImage;
377 public void resetImageForPreset(ImagePreset imagePreset, ImageShow caller) {
378 mHiresCache.reset(imagePreset);
379 mCache.reset(imagePreset);
380 mZoomCache.reset(imagePreset);
383 public void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity,
385 preset.setIsHighQuality(true);
386 preset.setScaleFactor(1.0f);
387 new SaveCopyTask(mContext, mUri, destination, new SaveCopyTask.Callback() {
390 public void onComplete(Uri result) {
391 filterShowActivity.completeSaveImage(result);
397 public void setAdapter(HistoryAdapter adapter) {
401 public HistoryAdapter getHistory() {
405 public XMPMeta getXmpObject() {
407 InputStream is = mContext.getContentResolver().openInputStream(getUri());
408 return XmpUtilHelper.extractXMPMeta(is);
409 } catch (FileNotFoundException e) {
415 * Determine if this is a light cycle 360 image
417 * @return true if it is a light Cycle image that is full 360
419 public boolean queryLightCycle360() {
421 InputStream is = mContext.getContentResolver().openInputStream(getUri());
422 XMPMeta meta = XmpUtilHelper.extractXMPMeta(is);
426 String name = meta.getPacketHeader();
428 String namespace = "http://ns.google.com/photos/1.0/panorama/";
429 String cropWidthName = "GPano:CroppedAreaImageWidthPixels";
430 String fullWidthName = "GPano:FullPanoWidthPixels";
432 if (!meta.doesPropertyExist(namespace, cropWidthName)) {
435 if (!meta.doesPropertyExist(namespace, fullWidthName)) {
439 Integer cropValue = meta.getPropertyInteger(namespace, cropWidthName);
440 Integer fullValue = meta.getPropertyInteger(namespace, fullWidthName);
442 // Definition of a 360:
443 // GFullPanoWidthPixels == CroppedAreaImageWidthPixels
444 if (cropValue != null && fullValue != null) {
445 return cropValue.equals(fullValue);
449 } catch (XMPException e) {
452 } catch (FileNotFoundException e) {