1/*
2 * Copyright (C) 2013 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
17package com.android.gallery3d.filtershow.imageshow;
18
19import android.animation.Animator;
20import android.animation.ValueAnimator;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Matrix;
24import android.graphics.Point;
25import android.graphics.Rect;
26import android.graphics.RectF;
27import android.net.Uri;
28
29import com.android.gallery3d.exif.ExifTag;
30import com.android.gallery3d.filtershow.FilterShowActivity;
31import com.android.gallery3d.filtershow.cache.BitmapCache;
32import com.android.gallery3d.filtershow.cache.ImageLoader;
33import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
34import com.android.gallery3d.filtershow.filters.FilterRepresentation;
35import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation;
36import com.android.gallery3d.filtershow.filters.FilterUserPresetRepresentation;
37import com.android.gallery3d.filtershow.filters.ImageFilter;
38import com.android.gallery3d.filtershow.history.HistoryItem;
39import com.android.gallery3d.filtershow.history.HistoryManager;
40import com.android.gallery3d.filtershow.pipeline.Buffer;
41import com.android.gallery3d.filtershow.pipeline.ImagePreset;
42import com.android.gallery3d.filtershow.pipeline.RenderingRequest;
43import com.android.gallery3d.filtershow.pipeline.RenderingRequestCaller;
44import com.android.gallery3d.filtershow.pipeline.SharedBuffer;
45import com.android.gallery3d.filtershow.pipeline.SharedPreset;
46import com.android.gallery3d.filtershow.state.StateAdapter;
47
48import java.util.List;
49import java.util.Vector;
50
51public class MasterImage implements RenderingRequestCaller {
52
53    private static final String LOGTAG = "MasterImage";
54    private boolean DEBUG  = false;
55    private static final boolean DISABLEZOOM = false;
56    public static final int SMALL_BITMAP_DIM = 160;
57    public static final int MAX_BITMAP_DIM = 900;
58    private static MasterImage sMasterImage = null;
59
60    private boolean mSupportsHighRes = false;
61
62    private ImageFilter mCurrentFilter = null;
63    private ImagePreset mPreset = null;
64    private ImagePreset mLoadedPreset = null;
65    private ImagePreset mGeometryOnlyPreset = null;
66    private ImagePreset mFiltersOnlyPreset = null;
67
68    private SharedBuffer mPreviewBuffer = new SharedBuffer();
69    private SharedPreset mPreviewPreset = new SharedPreset();
70
71    private Bitmap mOriginalBitmapSmall = null;
72    private Bitmap mOriginalBitmapLarge = null;
73    private Bitmap mOriginalBitmapHighres = null;
74    private Bitmap mTemporaryThumbnail = null;
75    private int mOrientation;
76    private Rect mOriginalBounds;
77    private final Vector<ImageShow> mLoadListeners = new Vector<ImageShow>();
78    private Uri mUri = null;
79    private int mZoomOrientation = ImageLoader.ORI_NORMAL;
80
81    private Bitmap mGeometryOnlyBitmap = null;
82    private Bitmap mFiltersOnlyBitmap = null;
83    private Bitmap mPartialBitmap = null;
84    private Bitmap mHighresBitmap = null;
85    private Bitmap mPreviousImage = null;
86    private int mShadowMargin = 15; // not scaled, fixed in the asset
87    private Rect mPartialBounds = new Rect();
88
89    private ValueAnimator mAnimator = null;
90    private float mMaskScale = 1;
91    private boolean mOnGoingNewLookAnimation = false;
92    private float mAnimRotationValue = 0;
93    private float mCurrentAnimRotationStartValue = 0;
94    private float mAnimFraction = 0;
95    private int mCurrentLookAnimation = 0;
96    public static final int CIRCLE_ANIMATION = 1;
97    public static final int ROTATE_ANIMATION = 2;
98    public static final int MIRROR_ANIMATION = 3;
99
100    private HistoryManager mHistory = null;
101    private StateAdapter mState = null;
102
103    private FilterShowActivity mActivity = null;
104
105    private Vector<ImageShow> mObservers = new Vector<ImageShow>();
106    private FilterRepresentation mCurrentFilterRepresentation;
107
108    private float mScaleFactor = 1.0f;
109    private float mMaxScaleFactor = 3.0f; // TODO: base this on the current view / image
110    private Point mTranslation = new Point();
111    private Point mOriginalTranslation = new Point();
112
113    private Point mImageShowSize = new Point();
114
115    private boolean mShowsOriginal;
116    private List<ExifTag> mEXIF;
117    private BitmapCache mBitmapCache = new BitmapCache();
118
119    private MasterImage() {
120    }
121
122    // TODO: remove singleton
123    public static void setMaster(MasterImage master) {
124        sMasterImage = master;
125    }
126
127    public static MasterImage getImage() {
128        if (sMasterImage == null) {
129            sMasterImage = new MasterImage();
130        }
131        return sMasterImage;
132    }
133
134    public Bitmap getOriginalBitmapSmall() {
135        return mOriginalBitmapSmall;
136    }
137
138    public Bitmap getOriginalBitmapLarge() {
139        return mOriginalBitmapLarge;
140    }
141
142    public Bitmap getOriginalBitmapHighres() {
143        if (mOriginalBitmapHighres == null) {
144            return mOriginalBitmapLarge;
145        }
146        return mOriginalBitmapHighres;
147    }
148
149    public void setOriginalBitmapHighres(Bitmap mOriginalBitmapHighres) {
150        this.mOriginalBitmapHighres = mOriginalBitmapHighres;
151    }
152
153    public int getOrientation() {
154        return mOrientation;
155    }
156
157    public Rect getOriginalBounds() {
158        return mOriginalBounds;
159    }
160
161    public void setOriginalBounds(Rect r) {
162        mOriginalBounds = r;
163    }
164
165    public Uri getUri() {
166        return mUri;
167    }
168
169    public void setUri(Uri uri) {
170        mUri = uri;
171    }
172
173    public int getZoomOrientation() {
174        return mZoomOrientation;
175    }
176
177    public void addListener(ImageShow imageShow) {
178        if (!mLoadListeners.contains(imageShow)) {
179            mLoadListeners.add(imageShow);
180        }
181    }
182
183    public void warnListeners() {
184        mActivity.runOnUiThread(mWarnListenersRunnable);
185    }
186
187    private Runnable mWarnListenersRunnable = new Runnable() {
188        @Override
189        public void run() {
190            for (int i = 0; i < mLoadListeners.size(); i++) {
191                ImageShow imageShow = mLoadListeners.elementAt(i);
192                imageShow.imageLoaded();
193            }
194            invalidatePreview();
195        }
196    };
197
198    public boolean loadBitmap(Uri uri, int size) {
199        setUri(uri);
200        mEXIF = ImageLoader.getExif(getActivity(), uri);
201        mOrientation = ImageLoader.getMetadataOrientation(mActivity, uri);
202        Rect originalBounds = new Rect();
203        mOriginalBitmapLarge = ImageLoader.loadOrientedConstrainedBitmap(uri, mActivity,
204                Math.min(MAX_BITMAP_DIM, size),
205                mOrientation, originalBounds);
206        setOriginalBounds(originalBounds);
207        if (mOriginalBitmapLarge == null) {
208            return false;
209        }
210        int sw = SMALL_BITMAP_DIM;
211        int sh = (int) (sw * (float) mOriginalBitmapLarge.getHeight() / mOriginalBitmapLarge
212                .getWidth());
213        mOriginalBitmapSmall = Bitmap.createScaledBitmap(mOriginalBitmapLarge, sw, sh, true);
214        mZoomOrientation = mOrientation;
215        warnListeners();
216        return true;
217    }
218
219    public void setSupportsHighRes(boolean value) {
220        mSupportsHighRes = value;
221    }
222
223    public void addObserver(ImageShow observer) {
224        if (mObservers.contains(observer)) {
225            return;
226        }
227        mObservers.add(observer);
228    }
229
230    public void removeObserver(ImageShow observer) {
231        mObservers.remove(observer);
232    }
233
234    public void setActivity(FilterShowActivity activity) {
235        mActivity = activity;
236    }
237
238    public FilterShowActivity getActivity() {
239        return mActivity;
240    }
241
242    public synchronized ImagePreset getPreset() {
243        return mPreset;
244    }
245
246    public synchronized ImagePreset getGeometryPreset() {
247        return mGeometryOnlyPreset;
248    }
249
250    public synchronized ImagePreset getFiltersOnlyPreset() {
251        return mFiltersOnlyPreset;
252    }
253
254    public synchronized void setPreset(ImagePreset preset,
255                                       FilterRepresentation change,
256                                       boolean addToHistory) {
257        if (DEBUG) {
258            preset.showFilters();
259        }
260        mPreset = preset;
261        mPreset.fillImageStateAdapter(mState);
262        if (addToHistory) {
263            HistoryItem historyItem = new HistoryItem(mPreset, change);
264            mHistory.addHistoryItem(historyItem);
265        }
266        updatePresets(true);
267        resetGeometryImages(false);
268        mActivity.updateCategories();
269    }
270
271    public void onHistoryItemClick(int position) {
272        HistoryItem historyItem = mHistory.getItem(position);
273        // We need a copy from the history
274        ImagePreset newPreset = new ImagePreset(historyItem.getImagePreset());
275        // don't need to add it to the history
276        setPreset(newPreset, historyItem.getFilterRepresentation(), false);
277        mHistory.setCurrentPreset(position);
278    }
279
280    public HistoryManager getHistory() {
281        return mHistory;
282    }
283
284    public StateAdapter getState() {
285        return mState;
286    }
287
288    public void setHistoryManager(HistoryManager adapter) {
289        mHistory = adapter;
290    }
291
292    public void setStateAdapter(StateAdapter adapter) {
293        mState = adapter;
294    }
295
296    public void setCurrentFilter(ImageFilter filter) {
297        mCurrentFilter = filter;
298    }
299
300    public ImageFilter getCurrentFilter() {
301        return mCurrentFilter;
302    }
303
304    public synchronized boolean hasModifications() {
305        // TODO: We need to have a better same effects check to see if two
306        // presets are functionally the same. Right now, we are relying on a
307        // stricter check as equals().
308        ImagePreset loadedPreset = getLoadedPreset();
309        if (mPreset == null) {
310            if (loadedPreset == null) {
311                return false;
312            } else {
313                return loadedPreset.hasModifications();
314            }
315        } else {
316            if (loadedPreset == null) {
317                return mPreset.hasModifications();
318            } else {
319                return !mPreset.equals(loadedPreset);
320            }
321        }
322    }
323
324    public SharedBuffer getPreviewBuffer() {
325        return mPreviewBuffer;
326    }
327
328    public SharedPreset getPreviewPreset() {
329        return mPreviewPreset;
330    }
331
332    public Bitmap getFilteredImage() {
333        mPreviewBuffer.swapConsumerIfNeeded(); // get latest bitmap
334        Buffer consumer = mPreviewBuffer.getConsumer();
335        if (consumer != null) {
336            return consumer.getBitmap();
337        }
338        return null;
339    }
340
341    public Bitmap getFiltersOnlyImage() {
342        return mFiltersOnlyBitmap;
343    }
344
345    public Bitmap getGeometryOnlyImage() {
346        return mGeometryOnlyBitmap;
347    }
348
349    public Bitmap getPartialImage() {
350        return mPartialBitmap;
351    }
352
353    public Rect getPartialBounds() {
354        return mPartialBounds;
355    }
356
357    public Bitmap getHighresImage() {
358        if (mHighresBitmap == null) {
359            return getFilteredImage();
360        }
361        return mHighresBitmap;
362    }
363
364    public Bitmap getPreviousImage() {
365        return mPreviousImage;
366    }
367
368    public ImagePreset getCurrentPreset() {
369        return getPreviewBuffer().getConsumer().getPreset();
370    }
371
372    public float getMaskScale() {
373        return mMaskScale;
374    }
375
376    public void setMaskScale(float scale) {
377        mMaskScale = scale;
378        notifyObservers();
379    }
380
381    public float getAnimRotationValue() {
382        return mAnimRotationValue;
383    }
384
385    public void setAnimRotation(float rotation) {
386        mAnimRotationValue = mCurrentAnimRotationStartValue + rotation;
387        notifyObservers();
388    }
389
390    public void setAnimFraction(float fraction) {
391        mAnimFraction = fraction;
392    }
393
394    public float getAnimFraction() {
395        return mAnimFraction;
396    }
397
398    public boolean onGoingNewLookAnimation() {
399        return mOnGoingNewLookAnimation;
400    }
401
402    public int getCurrentLookAnimation() {
403        return mCurrentLookAnimation;
404    }
405
406    public void resetAnimBitmap() {
407        mBitmapCache.cache(mPreviousImage);
408        mPreviousImage = null;
409    }
410
411    public void onNewLook(FilterRepresentation newRepresentation) {
412        if (getFilteredImage() == null) {
413            return;
414        }
415        if (mAnimator != null) {
416            mAnimator.cancel();
417            if (mCurrentLookAnimation == ROTATE_ANIMATION) {
418                mCurrentAnimRotationStartValue += 90;
419            }
420        } else {
421            resetAnimBitmap();
422            mPreviousImage = mBitmapCache.getBitmapCopy(getFilteredImage(), BitmapCache.NEW_LOOK);
423        }
424        if (newRepresentation instanceof FilterUserPresetRepresentation) {
425            mCurrentLookAnimation = CIRCLE_ANIMATION;
426            mAnimator = ValueAnimator.ofFloat(0, 1);
427            mAnimator.setDuration(650);
428        }
429        if (newRepresentation instanceof FilterRotateRepresentation) {
430            mCurrentLookAnimation = ROTATE_ANIMATION;
431            mAnimator = ValueAnimator.ofFloat(0, 90);
432            mAnimator.setDuration(500);
433        }
434        if (newRepresentation instanceof FilterMirrorRepresentation) {
435            mCurrentLookAnimation = MIRROR_ANIMATION;
436            mAnimator = ValueAnimator.ofFloat(1, 0, -1);
437            mAnimator.setDuration(500);
438        }
439        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
440            @Override
441            public void onAnimationUpdate(ValueAnimator animation) {
442                if (mCurrentLookAnimation == CIRCLE_ANIMATION) {
443                    setMaskScale((Float) animation.getAnimatedValue());
444                } else if (mCurrentLookAnimation == ROTATE_ANIMATION
445                        || mCurrentLookAnimation == MIRROR_ANIMATION) {
446                    setAnimRotation((Float) animation.getAnimatedValue());
447                    setAnimFraction(animation.getAnimatedFraction());
448                }
449            }
450        });
451        mAnimator.addListener(new Animator.AnimatorListener() {
452            @Override
453            public void onAnimationStart(Animator animation) {
454                mOnGoingNewLookAnimation = true;
455            }
456
457            @Override
458            public void onAnimationEnd(Animator animation) {
459                mOnGoingNewLookAnimation = false;
460                mCurrentAnimRotationStartValue = 0;
461                mAnimator = null;
462                notifyObservers();
463            }
464
465            @Override
466            public void onAnimationCancel(Animator animation) {
467
468            }
469
470            @Override
471            public void onAnimationRepeat(Animator animation) {
472
473            }
474        });
475        mAnimator.start();
476        notifyObservers();
477    }
478
479    public void notifyObservers() {
480        for (ImageShow observer : mObservers) {
481            observer.invalidate();
482        }
483    }
484
485    public void resetGeometryImages(boolean force) {
486        if (mPreset == null) {
487            return;
488        }
489        ImagePreset newPresetGeometryOnly = new ImagePreset(mPreset);
490        newPresetGeometryOnly.setDoApplyFilters(false);
491        newPresetGeometryOnly.setDoApplyGeometry(true);
492        if (force || mGeometryOnlyPreset == null
493                || !newPresetGeometryOnly.equals(mGeometryOnlyPreset)) {
494            mGeometryOnlyPreset = newPresetGeometryOnly;
495            RenderingRequest.post(mActivity, null,
496                    mGeometryOnlyPreset, RenderingRequest.GEOMETRY_RENDERING, this);
497        }
498        ImagePreset newPresetFiltersOnly = new ImagePreset(mPreset);
499        newPresetFiltersOnly.setDoApplyFilters(true);
500        newPresetFiltersOnly.setDoApplyGeometry(false);
501        if (force || mFiltersOnlyPreset == null
502                || !newPresetFiltersOnly.same(mFiltersOnlyPreset)) {
503            mFiltersOnlyPreset = newPresetFiltersOnly;
504            RenderingRequest.post(mActivity, null,
505                    mFiltersOnlyPreset, RenderingRequest.FILTERS_RENDERING, this);
506        }
507    }
508
509    public void updatePresets(boolean force) {
510        invalidatePreview();
511    }
512
513    public FilterRepresentation getCurrentFilterRepresentation() {
514        return mCurrentFilterRepresentation;
515    }
516
517    public void setCurrentFilterRepresentation(FilterRepresentation currentFilterRepresentation) {
518        mCurrentFilterRepresentation = currentFilterRepresentation;
519    }
520
521    public void invalidateFiltersOnly() {
522        mFiltersOnlyPreset = null;
523        invalidatePreview();
524    }
525
526    public void invalidatePartialPreview() {
527        if (mPartialBitmap != null) {
528            mBitmapCache.cache(mPartialBitmap);
529            mPartialBitmap = null;
530            notifyObservers();
531        }
532    }
533
534    public void invalidateHighresPreview() {
535        if (mHighresBitmap != null) {
536            mBitmapCache.cache(mHighresBitmap);
537            mHighresBitmap = null;
538            notifyObservers();
539        }
540    }
541
542    public void invalidatePreview() {
543        if (mPreset == null) {
544            return;
545        }
546
547        mPreviewPreset.enqueuePreset(mPreset);
548        mPreviewBuffer.invalidate();
549        invalidatePartialPreview();
550        invalidateHighresPreview();
551        needsUpdatePartialPreview();
552        needsUpdateHighResPreview();
553        mActivity.getProcessingService().updatePreviewBuffer();
554    }
555
556    public void setImageShowSize(int w, int h) {
557        if (mImageShowSize.x != w || mImageShowSize.y != h) {
558            mImageShowSize.set(w, h);
559            float maxWidth = mOriginalBounds.width() / (float) w;
560            float maxHeight = mOriginalBounds.height() / (float) h;
561            mMaxScaleFactor = Math.max(3.f, Math.max(maxWidth, maxHeight));
562            needsUpdatePartialPreview();
563            needsUpdateHighResPreview();
564        }
565    }
566
567    public Matrix originalImageToScreen() {
568        return computeImageToScreen(null, 0, true);
569    }
570
571    public Matrix computeImageToScreen(Bitmap bitmapToDraw,
572                                       float rotate,
573                                       boolean applyGeometry) {
574        if (getOriginalBounds() == null
575                || mImageShowSize.x == 0
576                || mImageShowSize.y == 0) {
577            return null;
578        }
579
580        Matrix m = null;
581        float scale = 1f;
582        float translateX = 0;
583        float translateY = 0;
584
585        if (applyGeometry) {
586            GeometryMathUtils.GeometryHolder holder = GeometryMathUtils.unpackGeometry(
587                    mPreset.getGeometryFilters());
588            m = GeometryMathUtils.getCropSelectionToScreenMatrix(null, holder,
589                    getOriginalBounds().width(), getOriginalBounds().height(),
590                    mImageShowSize.x, mImageShowSize.y);
591        } else if (bitmapToDraw != null) {
592            m = new Matrix();
593            RectF size = new RectF(0, 0,
594                    bitmapToDraw.getWidth(),
595                    bitmapToDraw.getHeight());
596            scale = mImageShowSize.x / size.width();
597            if (size.width() < size.height()) {
598                scale = mImageShowSize.y / size.height();
599            }
600            translateX = (mImageShowSize.x - (size.width() * scale)) / 2.0f;
601            translateY = (mImageShowSize.y - (size.height() * scale)) / 2.0f;
602        } else {
603            return null;
604        }
605
606        Point translation = getTranslation();
607        m.postScale(scale, scale);
608        m.postRotate(rotate, mImageShowSize.x / 2.0f, mImageShowSize.y / 2.0f);
609        m.postTranslate(translateX, translateY);
610        m.postTranslate(mShadowMargin, mShadowMargin);
611        m.postScale(getScaleFactor(), getScaleFactor(),
612                mImageShowSize.x / 2.0f,
613                mImageShowSize.y / 2.0f);
614        m.postTranslate(translation.x * getScaleFactor(),
615                        translation.y * getScaleFactor());
616        return m;
617    }
618
619    private Matrix getImageToScreenMatrix(boolean reflectRotation) {
620        if (getOriginalBounds() == null || mImageShowSize.x == 0 || mImageShowSize.y == 0) {
621            return new Matrix();
622        }
623        Matrix m = GeometryMathUtils.getImageToScreenMatrix(mPreset.getGeometryFilters(),
624                reflectRotation, getOriginalBounds(), mImageShowSize.x, mImageShowSize.y);
625        if (m == null) {
626            m = new Matrix();
627            m.reset();
628            return m;
629        }
630        Point translate = getTranslation();
631        float scaleFactor = getScaleFactor();
632        m.postTranslate(translate.x, translate.y);
633        m.postScale(scaleFactor, scaleFactor, mImageShowSize.x / 2.0f, mImageShowSize.y / 2.0f);
634        return m;
635    }
636
637    private Matrix getScreenToImageMatrix(boolean reflectRotation) {
638        Matrix m = getImageToScreenMatrix(reflectRotation);
639        Matrix invert = new Matrix();
640        m.invert(invert);
641        return invert;
642    }
643
644    public void needsUpdateHighResPreview() {
645        if (!mSupportsHighRes) {
646            return;
647        }
648        if (mActivity.getProcessingService() == null) {
649            return;
650        }
651        if (mPreset == null) {
652            return;
653        }
654        mActivity.getProcessingService().postHighresRenderingRequest(mPreset,
655                getScaleFactor(), this);
656        invalidateHighresPreview();
657    }
658
659    public void needsUpdatePartialPreview() {
660        if (mPreset == null) {
661            return;
662        }
663        if (!mPreset.canDoPartialRendering()) {
664            invalidatePartialPreview();
665            return;
666        }
667        Matrix originalToScreen = MasterImage.getImage().originalImageToScreen();
668        if (originalToScreen == null) {
669            return;
670        }
671        Matrix screenToOriginal = new Matrix();
672        originalToScreen.invert(screenToOriginal);
673        RectF bounds = new RectF(0, 0,
674                mImageShowSize.x + 2 * mShadowMargin,
675                mImageShowSize.y + 2 * mShadowMargin);
676        screenToOriginal.mapRect(bounds);
677        Rect rBounds = new Rect();
678        bounds.roundOut(rBounds);
679
680        mActivity.getProcessingService().postFullresRenderingRequest(mPreset,
681                getScaleFactor(), rBounds,
682                new Rect(0, 0, mImageShowSize.x, mImageShowSize.y), this);
683        invalidatePartialPreview();
684    }
685
686    @Override
687    public void available(RenderingRequest request) {
688        if (request.getBitmap() == null) {
689            return;
690        }
691
692        boolean needsCheckModification = false;
693        if (request.getType() == RenderingRequest.GEOMETRY_RENDERING) {
694            mBitmapCache.cache(mGeometryOnlyBitmap);
695            mGeometryOnlyBitmap = request.getBitmap();
696            needsCheckModification = true;
697        }
698        if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
699            mBitmapCache.cache(mFiltersOnlyBitmap);
700            mFiltersOnlyBitmap = request.getBitmap();
701            notifyObservers();
702            needsCheckModification = true;
703        }
704        if (request.getType() == RenderingRequest.PARTIAL_RENDERING
705                && request.getScaleFactor() == getScaleFactor()) {
706            mBitmapCache.cache(mPartialBitmap);
707            mPartialBitmap = request.getBitmap();
708            mPartialBounds.set(request.getBounds());
709            notifyObservers();
710            needsCheckModification = true;
711        }
712        if (request.getType() == RenderingRequest.HIGHRES_RENDERING) {
713            mBitmapCache.cache(mHighresBitmap);
714            mHighresBitmap = request.getBitmap();
715            notifyObservers();
716            needsCheckModification = true;
717        }
718        if (needsCheckModification) {
719            mActivity.enableSave(hasModifications());
720        }
721    }
722
723    public static void reset() {
724        sMasterImage = null;
725    }
726
727    public float getScaleFactor() {
728        return mScaleFactor;
729    }
730
731    public void setScaleFactor(float scaleFactor) {
732        if (DISABLEZOOM) {
733            return;
734        }
735        if (scaleFactor == mScaleFactor) {
736            return;
737        }
738        mScaleFactor = scaleFactor;
739        invalidatePartialPreview();
740    }
741
742    public Point getTranslation() {
743        return mTranslation;
744    }
745
746    public void setTranslation(Point translation) {
747        if (DISABLEZOOM) {
748            mTranslation.x = 0;
749            mTranslation.y = 0;
750            return;
751        }
752        mTranslation.x = translation.x;
753        mTranslation.y = translation.y;
754        needsUpdatePartialPreview();
755    }
756
757    public Point getOriginalTranslation() {
758        return mOriginalTranslation;
759    }
760
761    public void setOriginalTranslation(Point originalTranslation) {
762        if (DISABLEZOOM) {
763            return;
764        }
765        mOriginalTranslation.x = originalTranslation.x;
766        mOriginalTranslation.y = originalTranslation.y;
767    }
768
769    public void resetTranslation() {
770        mTranslation.x = 0;
771        mTranslation.y = 0;
772        needsUpdatePartialPreview();
773    }
774
775    public Bitmap getTemporaryThumbnailBitmap() {
776        if (mTemporaryThumbnail == null
777                && getOriginalBitmapSmall() != null) {
778            mTemporaryThumbnail = getOriginalBitmapSmall().copy(Bitmap.Config.ARGB_8888, true);
779            Canvas canvas = new Canvas(mTemporaryThumbnail);
780            canvas.drawARGB(200, 80, 80, 80);
781        }
782        return mTemporaryThumbnail;
783    }
784
785    public Bitmap getThumbnailBitmap() {
786        return getOriginalBitmapSmall();
787    }
788
789    public Bitmap getLargeThumbnailBitmap() {
790        return getOriginalBitmapLarge();
791    }
792
793    public float getMaxScaleFactor() {
794        if (DISABLEZOOM) {
795            return 1;
796        }
797        return mMaxScaleFactor;
798    }
799
800    public void setMaxScaleFactor(float maxScaleFactor) {
801        mMaxScaleFactor = maxScaleFactor;
802    }
803
804    public boolean supportsHighRes() {
805        return mSupportsHighRes;
806    }
807
808    public void setShowsOriginal(boolean value) {
809        mShowsOriginal = value;
810        notifyObservers();
811    }
812
813    public boolean showsOriginal() {
814        return mShowsOriginal;
815    }
816
817    public void setLoadedPreset(ImagePreset preset) {
818        mLoadedPreset = preset;
819    }
820
821    public ImagePreset getLoadedPreset() {
822        return mLoadedPreset;
823    }
824
825    public List<ExifTag> getEXIF() {
826        return mEXIF;
827    }
828
829    public BitmapCache getBitmapCache() {
830        return mBitmapCache;
831    }
832
833    public boolean hasTinyPlanet() {
834        return mPreset.contains(FilterRepresentation.TYPE_TINYPLANET);
835    }
836}
837