1/*
2 * Copyright (C) 2010 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.ui;
18
19import android.annotation.TargetApi;
20import android.graphics.Bitmap;
21import android.graphics.Bitmap.Config;
22import android.graphics.BitmapFactory;
23import android.graphics.BitmapRegionDecoder;
24import android.graphics.Canvas;
25import android.graphics.Rect;
26
27import com.android.gallery3d.common.ApiHelper;
28import com.android.gallery3d.common.Utils;
29import com.android.photos.data.GalleryBitmapPool;
30
31public class TileImageViewAdapter implements TileImageView.TileSource {
32    private static final String TAG = "TileImageViewAdapter";
33    protected ScreenNail mScreenNail;
34    protected boolean mOwnScreenNail;
35    protected BitmapRegionDecoder mRegionDecoder;
36    protected int mImageWidth;
37    protected int mImageHeight;
38    protected int mLevelCount;
39
40    public TileImageViewAdapter() {
41    }
42
43    public synchronized void clear() {
44        mScreenNail = null;
45        mImageWidth = 0;
46        mImageHeight = 0;
47        mLevelCount = 0;
48        mRegionDecoder = null;
49    }
50
51    // Caller is responsible to recycle the ScreenNail
52    public synchronized void setScreenNail(
53            ScreenNail screenNail, int width, int height) {
54        Utils.checkNotNull(screenNail);
55        mScreenNail = screenNail;
56        mImageWidth = width;
57        mImageHeight = height;
58        mRegionDecoder = null;
59        mLevelCount = 0;
60    }
61
62    public synchronized void setRegionDecoder(BitmapRegionDecoder decoder) {
63        mRegionDecoder = Utils.checkNotNull(decoder);
64        mImageWidth = decoder.getWidth();
65        mImageHeight = decoder.getHeight();
66        mLevelCount = calculateLevelCount();
67    }
68
69    private int calculateLevelCount() {
70        return Math.max(0, Utils.ceilLog2(
71                (float) mImageWidth / mScreenNail.getWidth()));
72    }
73
74    // Gets a sub image on a rectangle of the current photo. For example,
75    // getTile(1, 50, 50, 100, 3, pool) means to get the region located
76    // at (50, 50) with sample level 1 (ie, down sampled by 2^1) and the
77    // target tile size (after sampling) 100 with border 3.
78    //
79    // From this spec, we can infer the actual tile size to be
80    // 100 + 3x2 = 106, and the size of the region to be extracted from the
81    // photo to be 200 with border 6.
82    //
83    // As a result, we should decode region (50-6, 50-6, 250+6, 250+6) or
84    // (44, 44, 256, 256) from the original photo and down sample it to 106.
85    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
86    @Override
87    public Bitmap getTile(int level, int x, int y, int tileSize) {
88        if (!ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER) {
89            return getTileWithoutReusingBitmap(level, x, y, tileSize);
90        }
91
92        int t = tileSize << level;
93
94        Rect wantRegion = new Rect(x, y, x + t, y + t);
95
96        boolean needClear;
97        BitmapRegionDecoder regionDecoder = null;
98
99        synchronized (this) {
100            regionDecoder = mRegionDecoder;
101            if (regionDecoder == null) return null;
102
103            // We need to clear a reused bitmap, if wantRegion is not fully
104            // within the image.
105            needClear = !new Rect(0, 0, mImageWidth, mImageHeight)
106                    .contains(wantRegion);
107        }
108
109        Bitmap bitmap = GalleryBitmapPool.getInstance().get(tileSize, tileSize);
110        if (bitmap != null) {
111            if (needClear) bitmap.eraseColor(0);
112        } else {
113            bitmap = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
114        }
115
116        BitmapFactory.Options options = new BitmapFactory.Options();
117        options.inPreferredConfig = Config.ARGB_8888;
118        options.inPreferQualityOverSpeed = true;
119        options.inSampleSize =  (1 << level);
120        options.inBitmap = bitmap;
121
122        try {
123            // In CropImage, we may call the decodeRegion() concurrently.
124            synchronized (regionDecoder) {
125                bitmap = regionDecoder.decodeRegion(wantRegion, options);
126            }
127        } finally {
128            if (options.inBitmap != bitmap && options.inBitmap != null) {
129                GalleryBitmapPool.getInstance().put(options.inBitmap);
130                options.inBitmap = null;
131            }
132        }
133
134        if (bitmap == null) {
135            Log.w(TAG, "fail in decoding region");
136        }
137        return bitmap;
138    }
139
140    private Bitmap getTileWithoutReusingBitmap(
141            int level, int x, int y, int tileSize) {
142        int t = tileSize << level;
143        Rect wantRegion = new Rect(x, y, x + t, y + t);
144
145        BitmapRegionDecoder regionDecoder;
146        Rect overlapRegion;
147
148        synchronized (this) {
149            regionDecoder = mRegionDecoder;
150            if (regionDecoder == null) return null;
151            overlapRegion = new Rect(0, 0, mImageWidth, mImageHeight);
152            Utils.assertTrue(overlapRegion.intersect(wantRegion));
153        }
154
155        BitmapFactory.Options options = new BitmapFactory.Options();
156        options.inPreferredConfig = Config.ARGB_8888;
157        options.inPreferQualityOverSpeed = true;
158        options.inSampleSize =  (1 << level);
159        Bitmap bitmap = null;
160
161        // In CropImage, we may call the decodeRegion() concurrently.
162        synchronized (regionDecoder) {
163            bitmap = regionDecoder.decodeRegion(overlapRegion, options);
164        }
165
166        if (bitmap == null) {
167            Log.w(TAG, "fail in decoding region");
168        }
169
170        if (wantRegion.equals(overlapRegion)) return bitmap;
171
172        Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
173        Canvas canvas = new Canvas(result);
174        canvas.drawBitmap(bitmap,
175                (overlapRegion.left - wantRegion.left) >> level,
176                (overlapRegion.top - wantRegion.top) >> level, null);
177        return result;
178    }
179
180
181    @Override
182    public ScreenNail getScreenNail() {
183        return mScreenNail;
184    }
185
186    @Override
187    public int getImageHeight() {
188        return mImageHeight;
189    }
190
191    @Override
192    public int getImageWidth() {
193        return mImageWidth;
194    }
195
196    @Override
197    public int getLevelCount() {
198        return mLevelCount;
199    }
200}
201