1d8732610ff48dc57063559d55ea3c84775e30e41John Reck/*
2d8732610ff48dc57063559d55ea3c84775e30e41John Reck * Copyright (C) 2013 The Android Open Source Project
3d8732610ff48dc57063559d55ea3c84775e30e41John Reck *
4d8732610ff48dc57063559d55ea3c84775e30e41John Reck * Licensed under the Apache License, Version 2.0 (the "License");
5d8732610ff48dc57063559d55ea3c84775e30e41John Reck * you may not use this file except in compliance with the License.
6d8732610ff48dc57063559d55ea3c84775e30e41John Reck * You may obtain a copy of the License at
7d8732610ff48dc57063559d55ea3c84775e30e41John Reck *
8d8732610ff48dc57063559d55ea3c84775e30e41John Reck *      http://www.apache.org/licenses/LICENSE-2.0
9d8732610ff48dc57063559d55ea3c84775e30e41John Reck *
10d8732610ff48dc57063559d55ea3c84775e30e41John Reck * Unless required by applicable law or agreed to in writing, software
11d8732610ff48dc57063559d55ea3c84775e30e41John Reck * distributed under the License is distributed on an "AS IS" BASIS,
12d8732610ff48dc57063559d55ea3c84775e30e41John Reck * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d8732610ff48dc57063559d55ea3c84775e30e41John Reck * See the License for the specific language governing permissions and
14d8732610ff48dc57063559d55ea3c84775e30e41John Reck * limitations under the License.
15d8732610ff48dc57063559d55ea3c84775e30e41John Reck */
16d8732610ff48dc57063559d55ea3c84775e30e41John Reck
17d8732610ff48dc57063559d55ea3c84775e30e41John Reckpackage com.android.photos;
18d8732610ff48dc57063559d55ea3c84775e30e41John Reck
19c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reckimport android.annotation.TargetApi;
20f5ef4801465a48ce3e33e1a75568060b2cf61185John Reckimport android.content.Context;
21d8732610ff48dc57063559d55ea3c84775e30e41John Reckimport android.graphics.Bitmap;
22f5ef4801465a48ce3e33e1a75568060b2cf61185John Reckimport android.graphics.Bitmap.Config;
23d8732610ff48dc57063559d55ea3c84775e30e41John Reckimport android.graphics.BitmapFactory;
24d8732610ff48dc57063559d55ea3c84775e30e41John Reckimport android.graphics.BitmapRegionDecoder;
25f5ef4801465a48ce3e33e1a75568060b2cf61185John Reckimport android.graphics.Canvas;
26d8732610ff48dc57063559d55ea3c84775e30e41John Reckimport android.graphics.Rect;
27f5ef4801465a48ce3e33e1a75568060b2cf61185John Reckimport android.os.Build;
28f5ef4801465a48ce3e33e1a75568060b2cf61185John Reckimport android.os.Build.VERSION_CODES;
29d8732610ff48dc57063559d55ea3c84775e30e41John Reckimport android.util.Log;
30f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck
31c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reckimport com.android.gallery3d.common.BitmapUtils;
32f5ef4801465a48ce3e33e1a75568060b2cf61185John Reckimport com.android.gallery3d.glrenderer.BasicTexture;
33f5ef4801465a48ce3e33e1a75568060b2cf61185John Reckimport com.android.gallery3d.glrenderer.BitmapTexture;
34d8732610ff48dc57063559d55ea3c84775e30e41John Reckimport com.android.photos.views.TiledImageRenderer;
35d8732610ff48dc57063559d55ea3c84775e30e41John Reck
36d8732610ff48dc57063559d55ea3c84775e30e41John Reckimport java.io.IOException;
37d8732610ff48dc57063559d55ea3c84775e30e41John Reck
38f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck/**
39f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using
40f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck * {@link BitmapRegionDecoder} to wrap a local file
41f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck */
42c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
43d8732610ff48dc57063559d55ea3c84775e30e41John Reckpublic class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
44d8732610ff48dc57063559d55ea3c84775e30e41John Reck
45f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    private static final String TAG = "BitmapRegionTileSource";
46d8732610ff48dc57063559d55ea3c84775e30e41John Reck
47f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    private static final boolean REUSE_BITMAP =
48f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
49c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck    private static final int GL_SIZE_LIMIT = 2048;
50c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck    // This must be no larger than half the size of the GL_SIZE_LIMIT
51c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck    // due to decodePreview being allowed to be up to 2x the size of the target
52f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    private static final int MAX_PREVIEW_SIZE = 1024;
53d8732610ff48dc57063559d55ea3c84775e30e41John Reck
54f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    BitmapRegionDecoder mDecoder;
55f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    int mWidth;
56f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    int mHeight;
57f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    int mTileSize;
58f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    private BasicTexture mPreview;
59f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    private final int mRotation;
60f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck
61f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    // For use only by getTile
62f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    private Rect mWantRegion = new Rect();
63f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    private Rect mOverlapRegion = new Rect();
64f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    private BitmapFactory.Options mOptions;
65f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    private Canvas mCanvas;
66f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck
67f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) {
68f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mTileSize = TiledImageRenderer.suggestedTileSize(context);
69f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mRotation = rotation;
70d8732610ff48dc57063559d55ea3c84775e30e41John Reck        try {
71d8732610ff48dc57063559d55ea3c84775e30e41John Reck            mDecoder = BitmapRegionDecoder.newInstance(path, true);
72f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            mWidth = mDecoder.getWidth();
73f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            mHeight = mDecoder.getHeight();
74d8732610ff48dc57063559d55ea3c84775e30e41John Reck        } catch (IOException e) {
75d8732610ff48dc57063559d55ea3c84775e30e41John Reck            Log.w("BitmapRegionTileSource", "ctor failed", e);
76d8732610ff48dc57063559d55ea3c84775e30e41John Reck        }
77f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mOptions = new BitmapFactory.Options();
78f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
79f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mOptions.inPreferQualityOverSpeed = true;
80f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mOptions.inTempStorage = new byte[16 * 1024];
81f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        if (previewSize != 0) {
82f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
83f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            // Although this is the same size as the Bitmap that is likely already
84f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            // loaded, the lifecycle is different and interactions are on a different
85f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            // thread. Thus to simplify, this source will decode its own bitmap.
86c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck            Bitmap preview = decodePreview(path, previewSize);
87c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck            if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
88f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck                mPreview = new BitmapTexture(preview);
89f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            } else {
90f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck                Log.w(TAG, String.format(
91f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck                        "Failed to create preview of apropriate size! "
92c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck                        + " in: %dx%d, out: %dx%d",
93c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck                        mWidth, mHeight,
94f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck                        preview.getWidth(), preview.getHeight()));
95f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            }
96f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        }
97d8732610ff48dc57063559d55ea3c84775e30e41John Reck    }
98d8732610ff48dc57063559d55ea3c84775e30e41John Reck
99d8732610ff48dc57063559d55ea3c84775e30e41John Reck    @Override
100d8732610ff48dc57063559d55ea3c84775e30e41John Reck    public int getTileSize() {
101f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        return mTileSize;
102d8732610ff48dc57063559d55ea3c84775e30e41John Reck    }
103d8732610ff48dc57063559d55ea3c84775e30e41John Reck
104d8732610ff48dc57063559d55ea3c84775e30e41John Reck    @Override
105d8732610ff48dc57063559d55ea3c84775e30e41John Reck    public int getImageWidth() {
106f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        return mWidth;
107d8732610ff48dc57063559d55ea3c84775e30e41John Reck    }
108d8732610ff48dc57063559d55ea3c84775e30e41John Reck
109d8732610ff48dc57063559d55ea3c84775e30e41John Reck    @Override
110d8732610ff48dc57063559d55ea3c84775e30e41John Reck    public int getImageHeight() {
111f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        return mHeight;
112f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    }
113f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck
114f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    @Override
115f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    public BasicTexture getPreview() {
116f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        return mPreview;
117f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    }
118f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck
119f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    @Override
120f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    public int getRotation() {
121f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        return mRotation;
122d8732610ff48dc57063559d55ea3c84775e30e41John Reck    }
123d8732610ff48dc57063559d55ea3c84775e30e41John Reck
124d8732610ff48dc57063559d55ea3c84775e30e41John Reck    @Override
125d8732610ff48dc57063559d55ea3c84775e30e41John Reck    public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
126d8732610ff48dc57063559d55ea3c84775e30e41John Reck        int tileSize = getTileSize();
127f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        if (!REUSE_BITMAP) {
128f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            return getTileWithoutReusingBitmap(level, x, y, tileSize);
129f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        }
130d8732610ff48dc57063559d55ea3c84775e30e41John Reck
131f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        int t = tileSize << level;
132f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mWantRegion.set(x, y, x + t, y + t);
133d8732610ff48dc57063559d55ea3c84775e30e41John Reck
134d8732610ff48dc57063559d55ea3c84775e30e41John Reck        if (bitmap == null) {
135d8732610ff48dc57063559d55ea3c84775e30e41John Reck            bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
136d8732610ff48dc57063559d55ea3c84775e30e41John Reck        }
137d8732610ff48dc57063559d55ea3c84775e30e41John Reck
138f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mOptions.inSampleSize = (1 << level);
139f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mOptions.inBitmap = bitmap;
140d8732610ff48dc57063559d55ea3c84775e30e41John Reck
141d8732610ff48dc57063559d55ea3c84775e30e41John Reck        try {
142f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            bitmap = mDecoder.decodeRegion(mWantRegion, mOptions);
143d8732610ff48dc57063559d55ea3c84775e30e41John Reck        } finally {
144f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            if (mOptions.inBitmap != bitmap && mOptions.inBitmap != null) {
145f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck                mOptions.inBitmap = null;
146d8732610ff48dc57063559d55ea3c84775e30e41John Reck            }
147d8732610ff48dc57063559d55ea3c84775e30e41John Reck        }
148d8732610ff48dc57063559d55ea3c84775e30e41John Reck
149d8732610ff48dc57063559d55ea3c84775e30e41John Reck        if (bitmap == null) {
150d8732610ff48dc57063559d55ea3c84775e30e41John Reck            Log.w("BitmapRegionTileSource", "fail in decoding region");
151d8732610ff48dc57063559d55ea3c84775e30e41John Reck        }
152d8732610ff48dc57063559d55ea3c84775e30e41John Reck        return bitmap;
153d8732610ff48dc57063559d55ea3c84775e30e41John Reck    }
154f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck
155f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    private Bitmap getTileWithoutReusingBitmap(
156f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            int level, int x, int y, int tileSize) {
157f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck
158f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        int t = tileSize << level;
159f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mWantRegion.set(x, y, x + t, y + t);
160f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck
161f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mOverlapRegion.set(0, 0, mWidth, mHeight);
162f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck
163f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mOptions.inSampleSize = (1 << level);
164f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        Bitmap bitmap = mDecoder.decodeRegion(mOverlapRegion, mOptions);
165f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck
166f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        if (bitmap == null) {
167f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            Log.w(TAG, "fail in decoding region");
168f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        }
169f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck
170f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        if (mWantRegion.equals(mOverlapRegion)) {
171f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            return bitmap;
172f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        }
173f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck
174f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
175f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        if (mCanvas == null) {
176f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck            mCanvas = new Canvas();
177f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        }
178f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mCanvas.setBitmap(result);
179f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mCanvas.drawBitmap(bitmap,
180f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck                (mOverlapRegion.left - mWantRegion.left) >> level,
181f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck                (mOverlapRegion.top - mWantRegion.top) >> level, null);
182f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        mCanvas.setBitmap(null);
183f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck        return result;
184f5ef4801465a48ce3e33e1a75568060b2cf61185John Reck    }
185c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck
186c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck    /**
187c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck     * Note that the returned bitmap may have a long edge that's longer
188c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck     * than the targetSize, but it will always be less than 2x the targetSize
189c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck     */
190c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck    private Bitmap decodePreview(String file, int targetSize) {
191c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        float scale = (float) targetSize / Math.max(mWidth, mHeight);
192c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        mOptions.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
193c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        mOptions.inJustDecodeBounds = false;
194c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck
195c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        Bitmap result = BitmapFactory.decodeFile(file, mOptions);
196c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        if (result == null) {
197c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck            return null;
198c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        }
199c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck
200c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        // We need to resize down if the decoder does not support inSampleSize
201c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        // or didn't support the specified inSampleSize (some decoders only do powers of 2)
202c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight()));
203c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck
204c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        if (scale <= 0.5) {
205c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck            result = BitmapUtils.resizeBitmapByScale(result, scale, true);
206c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        }
207c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        return ensureGLCompatibleBitmap(result);
208c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck    }
209c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck
210c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck    private static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) {
211c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        if (bitmap == null || bitmap.getConfig() != null) {
212c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck            return bitmap;
213c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        }
214c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false);
215c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        bitmap.recycle();
216c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck        return newBitmap;
217c31aee6a67b08716b916906b2db2096e1b929b4aJohn Reck    }
218d8732610ff48dc57063559d55ea3c84775e30e41John Reck}
219