2 * Copyright (C) 2013 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.imageshow;
19 import android.graphics.Bitmap;
20 import android.graphics.Matrix;
21 import android.graphics.Point;
22 import android.graphics.Rect;
23 import android.graphics.RectF;
24 import android.net.Uri;
25 import android.os.Handler;
26 import android.os.Message;
28 import com.android.gallery3d.filtershow.FilterShowActivity;
29 import com.android.gallery3d.filtershow.cache.ImageLoader;
30 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
31 import com.android.gallery3d.filtershow.filters.ImageFilter;
32 import com.android.gallery3d.filtershow.history.HistoryItem;
33 import com.android.gallery3d.filtershow.history.HistoryManager;
34 import com.android.gallery3d.filtershow.pipeline.Buffer;
35 import com.android.gallery3d.filtershow.pipeline.FilteringPipeline;
36 import com.android.gallery3d.filtershow.pipeline.ImagePreset;
37 import com.android.gallery3d.filtershow.pipeline.RenderingRequest;
38 import com.android.gallery3d.filtershow.pipeline.RenderingRequestCaller;
39 import com.android.gallery3d.filtershow.pipeline.SharedBuffer;
40 import com.android.gallery3d.filtershow.pipeline.SharedPreset;
41 import com.android.gallery3d.filtershow.state.StateAdapter;
43 import java.util.Vector;
45 public class MasterImage implements RenderingRequestCaller {
47 private static final String LOGTAG = "MasterImage";
48 private boolean DEBUG = false;
49 private static final boolean DISABLEZOOM = false;
50 public static final int SMALL_BITMAP_DIM = 160;
51 public static final int MAX_BITMAP_DIM = 900;
52 private static MasterImage sMasterImage = null;
54 private boolean mSupportsHighRes = false;
56 private ImageFilter mCurrentFilter = null;
57 private ImagePreset mPreset = null;
58 private ImagePreset mLoadedPreset = null;
59 private ImagePreset mGeometryOnlyPreset = null;
60 private ImagePreset mFiltersOnlyPreset = null;
62 private SharedBuffer mPreviewBuffer = new SharedBuffer();
63 private SharedPreset mPreviewPreset = new SharedPreset();
65 private Bitmap mOriginalBitmapSmall = null;
66 private Bitmap mOriginalBitmapLarge = null;
67 private Bitmap mOriginalBitmapHighres = null;
68 private int mOrientation;
69 private Rect mOriginalBounds;
70 private final Vector<ImageShow> mLoadListeners = new Vector<ImageShow>();
71 private Uri mUri = null;
72 private int mZoomOrientation = ImageLoader.ORI_NORMAL;
74 private Bitmap mGeometryOnlyBitmap = null;
75 private Bitmap mFiltersOnlyBitmap = null;
76 private Bitmap mPartialBitmap = null;
77 private Bitmap mHighresBitmap = null;
79 private HistoryManager mHistory = null;
80 private StateAdapter mState = null;
82 private FilterShowActivity mActivity = null;
84 private Vector<ImageShow> mObservers = new Vector<ImageShow>();
85 private FilterRepresentation mCurrentFilterRepresentation;
86 private Vector<GeometryListener> mGeometryListeners = new Vector<GeometryListener>();
88 private GeometryMetadata mPreviousGeometry = null;
90 private float mScaleFactor = 1.0f;
91 private float mMaxScaleFactor = 3.0f; // TODO: base this on the current view / image
92 private Point mTranslation = new Point();
93 private Point mOriginalTranslation = new Point();
95 private Point mImageShowSize = new Point();
97 private boolean mShowsOriginal;
99 final private static int NEW_GEOMETRY = 1;
101 private final Handler mHandler = new Handler() {
103 public void handleMessage(Message msg) {
113 private MasterImage() {
116 // TODO: remove singleton
117 public static void setMaster(MasterImage master) {
118 sMasterImage = master;
121 public static MasterImage getImage() {
122 if (sMasterImage == null) {
123 sMasterImage = new MasterImage();
128 public Bitmap getOriginalBitmapSmall() {
129 return mOriginalBitmapSmall;
132 public Bitmap getOriginalBitmapLarge() {
133 return mOriginalBitmapLarge;
136 public Bitmap getOriginalBitmapHighres() {
137 return mOriginalBitmapHighres;
140 public void setOriginalBitmapHighres(Bitmap mOriginalBitmapHighres) {
141 this.mOriginalBitmapHighres = mOriginalBitmapHighres;
144 public int getOrientation() {
148 public Rect getOriginalBounds() {
149 return mOriginalBounds;
152 public void setOriginalBounds(Rect r) {
156 public Uri getUri() {
160 public void setUri(Uri uri) {
164 public int getZoomOrientation() {
165 return mZoomOrientation;
168 public void addListener(ImageShow imageShow) {
169 if (!mLoadListeners.contains(imageShow)) {
170 mLoadListeners.add(imageShow);
174 public void warnListeners() {
175 mActivity.runOnUiThread(mWarnListenersRunnable);
178 private Runnable mWarnListenersRunnable = new Runnable() {
181 for (int i = 0; i < mLoadListeners.size(); i++) {
182 ImageShow imageShow = mLoadListeners.elementAt(i);
183 imageShow.imageLoaded();
189 public boolean loadBitmap(Uri uri, int size) {
191 mOrientation = ImageLoader.getMetadataOrientation(mActivity, uri);
192 Rect originalBounds = new Rect();
193 mOriginalBitmapLarge = ImageLoader.loadOrientedConstrainedBitmap(uri, mActivity,
194 Math.min(MAX_BITMAP_DIM, size),
195 mOrientation, originalBounds);
196 setOriginalBounds(originalBounds);
197 if (mOriginalBitmapLarge == null) {
200 int sw = SMALL_BITMAP_DIM;
201 int sh = (int) (sw * (float) mOriginalBitmapLarge.getHeight() / mOriginalBitmapLarge
203 mOriginalBitmapSmall = Bitmap.createScaledBitmap(mOriginalBitmapLarge, sw, sh, true);
204 mZoomOrientation = mOrientation;
209 public void setSupportsHighRes(boolean value) {
210 mSupportsHighRes = value;
213 public void addObserver(ImageShow observer) {
214 if (mObservers.contains(observer)) {
217 mObservers.add(observer);
220 public void setActivity(FilterShowActivity activity) {
221 mActivity = activity;
224 public FilterShowActivity getActivity() {
228 public synchronized ImagePreset getPreset() {
232 public synchronized ImagePreset getGeometryPreset() {
233 return mGeometryOnlyPreset;
236 public synchronized ImagePreset getFiltersOnlyPreset() {
237 return mFiltersOnlyPreset;
240 public synchronized void setPreset(ImagePreset preset,
241 FilterRepresentation change,
242 boolean addToHistory) {
244 preset.showFilters();
248 mPreset.fillImageStateAdapter(mState);
250 HistoryItem historyItem = new HistoryItem(mPreset, change);
251 mHistory.addHistoryItem(historyItem);
254 GeometryMetadata geo = mPreset.getGeometry();
255 if (!geo.equals(mPreviousGeometry)) {
256 notifyGeometryChange();
258 mPreviousGeometry = new GeometryMetadata(geo);
259 mActivity.updateCategories();
262 private void setGeometry() {
263 Bitmap image = getOriginalBitmapLarge();
267 float w = image.getWidth();
268 float h = image.getHeight();
269 GeometryMetadata geo = mPreset.getGeometry();
270 RectF pb = geo.getPhotoBounds();
271 if (w == pb.width() && h == pb.height()) {
274 RectF r = new RectF(0, 0, w, h);
275 geo.setPhotoBounds(r);
276 geo.setCropBounds(r);
277 mPreset.setGeometry(geo);
280 public void onHistoryItemClick(int position) {
281 HistoryItem historyItem = mHistory.getItem(position);
282 // We need a copy from the history
283 ImagePreset newPreset = new ImagePreset(historyItem.getImagePreset());
284 // don't need to add it to the history
285 setPreset(newPreset, historyItem.getFilterRepresentation(), false);
286 mHistory.setCurrentPreset(position);
289 public HistoryManager getHistory() {
293 public StateAdapter getState() {
297 public void setHistoryManager(HistoryManager adapter) {
301 public void setStateAdapter(StateAdapter adapter) {
305 public void setCurrentFilter(ImageFilter filter) {
306 mCurrentFilter = filter;
309 public ImageFilter getCurrentFilter() {
310 return mCurrentFilter;
313 public synchronized boolean hasModifications() {
314 // TODO: We need to have a better same effects check to see if two
315 // presets are functionally the same. Right now, we are relying on a
316 // stricter check as equals().
317 ImagePreset loadedPreset = getLoadedPreset();
318 if (mPreset == null) {
319 if (loadedPreset == null) {
322 return loadedPreset.hasModifications();
325 if (loadedPreset == null) {
326 return mPreset.hasModifications();
328 return !mPreset.equals(loadedPreset);
333 public SharedBuffer getPreviewBuffer() {
334 return mPreviewBuffer;
337 public SharedPreset getPreviewPreset() {
338 return mPreviewPreset;
341 public void setOriginalGeometry(Bitmap originalBitmapLarge) {
342 GeometryMetadata geo = getPreset().getGeometry();
343 float w = originalBitmapLarge.getWidth();
344 float h = originalBitmapLarge.getHeight();
345 RectF r = new RectF(0, 0, w, h);
346 geo.setPhotoBounds(r);
347 geo.setCropBounds(r);
348 getPreset().setGeometry(geo);
351 public Bitmap getFilteredImage() {
352 mPreviewBuffer.swapConsumerIfNeeded(); // get latest bitmap
353 Buffer consumer = mPreviewBuffer.getConsumer();
354 if (consumer != null) {
355 return consumer.getBitmap();
360 public Bitmap getFiltersOnlyImage() {
361 return mFiltersOnlyBitmap;
364 public Bitmap getGeometryOnlyImage() {
365 return mGeometryOnlyBitmap;
368 public Bitmap getPartialImage() {
369 return mPartialBitmap;
372 public Bitmap getHighresImage() {
373 return mHighresBitmap;
376 public void notifyObservers() {
377 for (ImageShow observer : mObservers) {
378 observer.invalidate();
382 public void updatePresets(boolean force) {
383 if (force || mGeometryOnlyPreset == null) {
384 ImagePreset newPreset = new ImagePreset(mPreset);
385 newPreset.setDoApplyFilters(false);
386 newPreset.setDoApplyGeometry(true);
387 if (force || mGeometryOnlyPreset == null
388 || !newPreset.same(mGeometryOnlyPreset)) {
389 mGeometryOnlyPreset = newPreset;
390 RenderingRequest.post(getOriginalBitmapLarge(),
391 mGeometryOnlyPreset, RenderingRequest.GEOMETRY_RENDERING, this);
394 if (force || mFiltersOnlyPreset == null) {
395 ImagePreset newPreset = new ImagePreset(mPreset);
396 newPreset.setDoApplyFilters(true);
397 newPreset.setDoApplyGeometry(false);
398 if (force || mFiltersOnlyPreset == null
399 || !newPreset.same(mFiltersOnlyPreset)) {
400 mFiltersOnlyPreset = newPreset;
401 RenderingRequest.post(MasterImage.getImage().getOriginalBitmapLarge(),
402 mFiltersOnlyPreset, RenderingRequest.FILTERS_RENDERING, this);
408 public FilterRepresentation getCurrentFilterRepresentation() {
409 return mCurrentFilterRepresentation;
412 public void setCurrentFilterRepresentation(FilterRepresentation currentFilterRepresentation) {
413 mCurrentFilterRepresentation = currentFilterRepresentation;
416 public void invalidateFiltersOnly() {
417 mFiltersOnlyPreset = null;
418 updatePresets(false);
421 public void invalidatePartialPreview() {
422 if (mPartialBitmap != null) {
423 mPartialBitmap = null;
428 public void invalidateHighresPreview() {
429 if (mHighresBitmap != null) {
430 mHighresBitmap = null;
435 public void invalidatePreview() {
436 mPreviewPreset.enqueuePreset(mPreset);
437 mPreviewBuffer.invalidate();
438 invalidatePartialPreview();
439 invalidateHighresPreview();
440 needsUpdatePartialPreview();
441 needsUpdateHighResPreview();
442 FilteringPipeline.getPipeline().updatePreviewBuffer();
445 public void setImageShowSize(int w, int h) {
446 if (mImageShowSize.x != w || mImageShowSize.y != h) {
447 mImageShowSize.set(w, h);
448 needsUpdatePartialPreview();
449 needsUpdateHighResPreview();
453 private Matrix getImageToScreenMatrix(boolean reflectRotation) {
454 GeometryMetadata geo = mPreset.getGeometry();
455 if (geo == null || getOriginalBounds() == null
456 || mImageShowSize.x == 0) {
459 Matrix m = geo.getOriginalToScreen(reflectRotation,
460 getOriginalBounds().width(),
461 getOriginalBounds().height(), mImageShowSize.x, mImageShowSize.y);
462 Point translate = getTranslation();
463 float scaleFactor = getScaleFactor();
464 m.postTranslate(translate.x, translate.y);
465 m.postScale(scaleFactor, scaleFactor, mImageShowSize.x / 2.0f, mImageShowSize.y / 2.0f);
469 private Matrix getScreenToImageMatrix(boolean reflectRotation) {
470 Matrix m = getImageToScreenMatrix(reflectRotation);
471 Matrix invert = new Matrix();
476 public void needsUpdateHighResPreview() {
477 if (!mSupportsHighRes) {
480 RenderingRequest.post(null, mPreset, RenderingRequest.HIGHRES_RENDERING, this);
481 invalidateHighresPreview();
484 public void needsUpdatePartialPreview() {
485 if (mPreset == null) {
488 if (!mPreset.canDoPartialRendering()) {
489 invalidatePartialPreview();
492 Matrix m = getScreenToImageMatrix(true);
493 RectF r = new RectF(0, 0, mImageShowSize.x, mImageShowSize.y);
494 RectF dest = new RectF();
496 Rect bounds = new Rect();
497 dest.roundOut(bounds);
498 RenderingRequest.post(null, mPreset, RenderingRequest.PARTIAL_RENDERING,
499 this, bounds, new Rect(0, 0, mImageShowSize.x, mImageShowSize.y));
500 invalidatePartialPreview();
504 public void available(RenderingRequest request) {
505 if (request.getBitmap() == null) {
509 boolean needsCheckModification = false;
510 if (request.getType() == RenderingRequest.GEOMETRY_RENDERING) {
511 mGeometryOnlyBitmap = request.getBitmap();
512 needsCheckModification = true;
514 if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
515 mFiltersOnlyBitmap = request.getBitmap();
516 needsCheckModification = true;
518 if (request.getType() == RenderingRequest.PARTIAL_RENDERING
519 && request.getScaleFactor() == getScaleFactor()) {
520 mPartialBitmap = request.getBitmap();
522 needsCheckModification = true;
524 if (request.getType() == RenderingRequest.HIGHRES_RENDERING) {
525 mHighresBitmap = request.getBitmap();
527 needsCheckModification = true;
529 if (needsCheckModification) {
530 mActivity.enableSave(hasModifications());
534 public static void reset() {
538 public void addGeometryListener(GeometryListener listener) {
539 mGeometryListeners.add(listener);
542 public void notifyGeometryChange() {
543 if (mHandler.hasMessages(NEW_GEOMETRY)) {
546 mHandler.sendEmptyMessage(NEW_GEOMETRY);
549 public void hasNewGeometry() {
551 for (GeometryListener listener : mGeometryListeners) {
552 listener.geometryChanged();
556 public float getScaleFactor() {
560 public void setScaleFactor(float scaleFactor) {
564 if (scaleFactor == mScaleFactor) {
567 mScaleFactor = scaleFactor;
568 invalidatePartialPreview();
571 public Point getTranslation() {
575 public void setTranslation(Point translation) {
581 mTranslation.x = translation.x;
582 mTranslation.y = translation.y;
583 needsUpdatePartialPreview();
586 public Point getOriginalTranslation() {
587 return mOriginalTranslation;
590 public void setOriginalTranslation(Point originalTranslation) {
594 mOriginalTranslation.x = originalTranslation.x;
595 mOriginalTranslation.y = originalTranslation.y;
598 public void resetTranslation() {
601 needsUpdatePartialPreview();
604 public Bitmap getThumbnailBitmap() {
605 return getOriginalBitmapSmall();
608 public Bitmap getLargeThumbnailBitmap() {
609 return getOriginalBitmapLarge();
612 public float getMaxScaleFactor() {
616 return mMaxScaleFactor;
619 public void setMaxScaleFactor(float maxScaleFactor) {
620 mMaxScaleFactor = maxScaleFactor;
623 public boolean supportsHighRes() {
624 return mSupportsHighRes;
627 public void setShowsOriginal(boolean value) {
628 mShowsOriginal = value;
632 public boolean showsOriginal() {
633 return mShowsOriginal;
636 public void setLoadedPreset(ImagePreset preset) {
637 mLoadedPreset = preset;
640 public ImagePreset getLoadedPreset() {
641 return mLoadedPreset;