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