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 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;
80 private Context mContext = null;
81 private Uri mUri = null;
83 private Rect mOriginalBounds = null;
84 private static int mZoomOrientation = ORI_NORMAL;
86 public ImageLoader(FilterShowActivity activity, Context context) {
89 mCache = new DelayedPresetCache(this, 30);
90 mHiresCache = new DelayedPresetCache(this, 3);
93 public static int getZoomOrientation() {
94 return mZoomOrientation;
97 public FilterShowActivity getActivity() {
101 public void loadBitmap(Uri uri,int size) {
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();
109 mOriginalBitmapLarge = loadScaledBitmap(uri, size);
113 public Uri getUri() {
117 public Rect getOriginalBounds() {
118 return mOriginalBounds;
121 public static int getOrientation(Context context, Uri uri) {
122 if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
123 return getOrientationFromPath(uri.getPath());
126 Cursor cursor = null;
128 cursor = context.getContentResolver().query(uri,
130 MediaStore.Images.ImageColumns.ORIENTATION
133 if (cursor.moveToNext()){
134 int ori = cursor.getInt(0);
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;
147 } catch (SQLiteException e){
148 return ExifInterface.ORIENTATION_UNDEFINED;
150 Utils.closeSilently(cursor);
154 static int getOrientationFromPath(String path) {
155 int orientation = -1;
157 ExifInterface EXIF = new ExifInterface(path);
158 orientation = EXIF.getAttributeInt(ExifInterface.TAG_ORIENTATION,
160 } catch (IOException e) {
166 private void updateBitmaps() {
167 if (mOrientation > 1) {
168 mOriginalBitmapSmall = rotateToPortrait(mOriginalBitmapSmall, mOrientation);
169 mOriginalBitmapLarge = rotateToPortrait(mOriginalBitmapLarge, mOrientation);
171 mZoomOrientation = mOrientation;
172 mCache.setOriginalBitmap(mOriginalBitmapSmall);
173 mHiresCache.setOriginalBitmap(mOriginalBitmapLarge);
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) {
191 matrix.setRotate(90,w/2f,h/2f);
194 matrix.setRotate(180,w/2f,h/2f);
197 matrix.setRotate(270,w/2f,h/2f);
200 matrix.preScale(-1, 1);
203 matrix.preScale(1, -1);
206 matrix.setRotate(90,w/2f,h/2f);
207 matrix.preScale(1, -1);
210 matrix.setRotate(270,w/2f,h/2f);
211 matrix.preScale(1, -1);
218 return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
219 bitmap.getHeight(), matrix, true);
222 private void closeStream(Closeable stream) {
223 if (stream != null) {
226 } catch (IOException e) {
232 private Bitmap loadRegionBitmap(Uri uri, Rect bounds) {
233 InputStream is = null;
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) {
248 static final int MAX_BITMAP_DIM = 2048;
249 private Bitmap loadScaledBitmap(Uri uri, int size) {
250 InputStream is = null;
252 is = mContext.getContentResolver().openInputStream(uri);
253 Log.v(LOGTAG, "loading uri " + uri.getPath() + " input stream: "
255 BitmapFactory.Options o = new BitmapFactory.Options();
256 o.inJustDecodeBounds = true;
257 BitmapFactory.decodeStream(is, null, o);
259 int width_tmp = o.outWidth;
260 int height_tmp = o.outHeight;
262 mOriginalBounds = new Rect(0, 0, width_tmp, height_tmp);
266 if (width_tmp <= MAX_BITMAP_DIM && height_tmp <= MAX_BITMAP_DIM) {
267 if (width_tmp / 2 < size || height_tmp / 2 < size) {
276 // decode with inSampleSize
277 BitmapFactory.Options o2 = new BitmapFactory.Options();
278 o2.inSampleSize = scale;
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) {
293 public Bitmap getBackgroundBitmap(Resources resources) {
294 if (mBackgroundBitmap == null) {
295 mBackgroundBitmap = BitmapFactory.decodeResource(resources,
296 R.drawable.filtershow_background);
298 return mBackgroundBitmap;
302 public Bitmap getOriginalBitmapSmall() {
303 return mOriginalBitmapSmall;
306 public Bitmap getOriginalBitmapLarge() {
307 return mOriginalBitmapLarge;
310 public void addListener(ImageShow imageShow) {
311 if (!mListeners.contains(imageShow)) {
312 mListeners.add(imageShow);
314 mHiresCache.addObserver(imageShow);
317 private void warnListeners() {
318 mActivity.runOnUiThread(mWarnListenersRunnable);
321 private Runnable mWarnListenersRunnable = new Runnable() {
325 for (int i = 0; i < mListeners.size(); i++) {
326 ImageShow imageShow = mListeners.elementAt(i);
327 imageShow.imageLoaded();
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,
336 Bitmap bmp = mZoomCache.getImage(imagePreset, bounds);
337 if (force || bmp == null) {
338 bmp = loadRegionBitmap(mUri, bounds);
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);
354 public Bitmap getImageForPreset(ImageShow caller, ImagePreset imagePreset,
356 if (mOriginalBitmapSmall == null) {
359 if (mOriginalBitmapLarge == null) {
363 Bitmap filteredImage = null;
366 filteredImage = mHiresCache.get(imagePreset);
368 filteredImage = mCache.get(imagePreset);
371 if (filteredImage == null) {
373 mHiresCache.prepare(imagePreset);
374 mHiresCache.addObserver(caller);
376 mCache.prepare(imagePreset);
377 mCache.addObserver(caller);
380 return filteredImage;
383 public void resetImageForPreset(ImagePreset imagePreset, ImageShow caller) {
384 mHiresCache.reset(imagePreset);
385 mCache.reset(imagePreset);
386 mZoomCache.reset(imagePreset);
389 public void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity,
391 preset.setIsHighQuality(true);
392 preset.setScaleFactor(1.0f);
393 new SaveCopyTask(mContext, mUri, destination, new SaveCopyTask.Callback() {
396 public void onComplete(Uri result) {
397 filterShowActivity.completeSaveImage(result);
403 public void setAdapter(HistoryAdapter adapter) {
407 public HistoryAdapter getHistory() {
411 public XMPMeta getXmpObject() {
413 InputStream is = mContext.getContentResolver().openInputStream(getUri());
414 return XmpUtilHelper.extractXMPMeta(is);
415 } catch (FileNotFoundException e) {
421 * Determine if this is a light cycle 360 image
423 * @return true if it is a light Cycle image that is full 360
425 public boolean queryLightCycle360() {
427 InputStream is = mContext.getContentResolver().openInputStream(getUri());
428 XMPMeta meta = XmpUtilHelper.extractXMPMeta(is);
432 String name = meta.getPacketHeader();
434 String namespace = "http://ns.google.com/photos/1.0/panorama/";
435 String cropWidthName = "GPano:CroppedAreaImageWidthPixels";
436 String fullWidthName = "GPano:FullPanoWidthPixels";
438 if (!meta.doesPropertyExist(namespace, cropWidthName)) {
441 if (!meta.doesPropertyExist(namespace, fullWidthName)) {
445 Integer cropValue = meta.getPropertyInteger(namespace, cropWidthName);
446 Integer fullValue = meta.getPropertyInteger(namespace, fullWidthName);
448 // Definition of a 360:
449 // GFullPanoWidthPixels == CroppedAreaImageWidthPixels
450 if (cropValue != null && fullValue != null) {
451 return cropValue.equals(fullValue);
455 } catch (XMPException e) {
458 } catch (FileNotFoundException e) {