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 com.android.gallery3d.common.Utils;
20
21import android.graphics.Bitmap;
22import android.graphics.Bitmap.Config;
23import android.opengl.GLUtils;
24
25import java.util.HashMap;
26import javax.microedition.khronos.opengles.GL11;
27import javax.microedition.khronos.opengles.GL11Ext;
28
29// UploadedTextures use a Bitmap for the content of the texture.
30//
31// Subclasses should implement onGetBitmap() to provide the Bitmap and
32// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
33// is not needed anymore.
34//
35// isContentValid() is meaningful only when the isLoaded() returns true.
36// It means whether the content needs to be updated.
37//
38// The user of this class should call recycle() when the texture is not
39// needed anymore.
40//
41// By default an UploadedTexture is opaque (so it can be drawn faster without
42// blending). The user or subclass can override it using setOpaque().
43abstract class UploadedTexture extends BasicTexture {
44
45    // To prevent keeping allocation the borders, we store those used borders here.
46    // Since the length will be power of two, it won't use too much memory.
47    private static HashMap<BorderKey, Bitmap> sBorderLines =
48            new HashMap<BorderKey, Bitmap>();
49    private static BorderKey sBorderKey = new BorderKey();
50
51    @SuppressWarnings("unused")
52    private static final String TAG = "Texture";
53    private boolean mContentValid = true;
54    private boolean mOpaque = true;
55    private boolean mThrottled = false;
56    private static int sUploadedCount;
57    private static final int UPLOAD_LIMIT = 100;
58
59    protected Bitmap mBitmap;
60    private int mBorder;
61
62    protected UploadedTexture() {
63        this(false);
64    }
65
66    protected UploadedTexture(boolean hasBorder) {
67        super(null, 0, STATE_UNLOADED);
68        if (hasBorder) {
69            setBorder(true);
70            mBorder = 1;
71        }
72    }
73
74    private static class BorderKey implements Cloneable {
75        public boolean vertical;
76        public Config config;
77        public int length;
78
79        @Override
80        public int hashCode() {
81            int x = config.hashCode() ^ length;
82            return vertical ? x : -x;
83        }
84
85        @Override
86        public boolean equals(Object object) {
87            if (!(object instanceof BorderKey)) return false;
88            BorderKey o = (BorderKey) object;
89            return vertical == o.vertical
90                    && config == o.config && length == o.length;
91        }
92
93        @Override
94        public BorderKey clone() {
95            try {
96                return (BorderKey) super.clone();
97            } catch (CloneNotSupportedException e) {
98                throw new AssertionError(e);
99            }
100        }
101    }
102
103    protected void setThrottled(boolean throttled) {
104        mThrottled = throttled;
105    }
106
107    private static Bitmap getBorderLine(
108            boolean vertical, Config config, int length) {
109        BorderKey key = sBorderKey;
110        key.vertical = vertical;
111        key.config = config;
112        key.length = length;
113        Bitmap bitmap = sBorderLines.get(key);
114        if (bitmap == null) {
115            bitmap = vertical
116                    ? Bitmap.createBitmap(1, length, config)
117                    : Bitmap.createBitmap(length, 1, config);
118            sBorderLines.put(key.clone(), bitmap);
119        }
120        return bitmap;
121    }
122
123    private Bitmap getBitmap() {
124        if (mBitmap == null) {
125            mBitmap = onGetBitmap();
126            int w = mBitmap.getWidth() + mBorder * 2;
127            int h = mBitmap.getHeight() + mBorder * 2;
128            if (mWidth == UNSPECIFIED) {
129                setSize(w, h);
130            } else if (mWidth != w || mHeight != h) {
131                throw new IllegalStateException(String.format(
132                        "cannot change size: this = %s, orig = %sx%s, new = %sx%s",
133                        toString(), mWidth, mHeight, w, h));
134            }
135        }
136        return mBitmap;
137    }
138
139    private void freeBitmap() {
140        Utils.assertTrue(mBitmap != null);
141        onFreeBitmap(mBitmap);
142        mBitmap = null;
143    }
144
145    @Override
146    public int getWidth() {
147        if (mWidth == UNSPECIFIED) getBitmap();
148        return mWidth;
149    }
150
151    @Override
152    public int getHeight() {
153        if (mWidth == UNSPECIFIED) getBitmap();
154        return mHeight;
155    }
156
157    protected abstract Bitmap onGetBitmap();
158
159    protected abstract void onFreeBitmap(Bitmap bitmap);
160
161    protected void invalidateContent() {
162        if (mBitmap != null) freeBitmap();
163        mContentValid = false;
164    }
165
166    /**
167     * Whether the content on GPU is valid.
168     */
169    public boolean isContentValid(GLCanvas canvas) {
170        return isLoaded(canvas) && mContentValid;
171    }
172
173    /**
174     * Updates the content on GPU's memory.
175     * @param canvas
176     */
177    public void updateContent(GLCanvas canvas) {
178        if (!isLoaded(canvas)) {
179            if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
180                return;
181            }
182            uploadToCanvas(canvas);
183        } else if (!mContentValid) {
184            Bitmap bitmap = getBitmap();
185            int format = GLUtils.getInternalFormat(bitmap);
186            int type = GLUtils.getType(bitmap);
187            canvas.getGLInstance().glBindTexture(GL11.GL_TEXTURE_2D, mId);
188            GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, mBorder, mBorder,
189                    bitmap, format, type);
190            freeBitmap();
191            mContentValid = true;
192        }
193    }
194
195    public static void resetUploadLimit() {
196        sUploadedCount = 0;
197    }
198
199    public static boolean uploadLimitReached() {
200        return sUploadedCount > UPLOAD_LIMIT;
201    }
202
203    static int[] sTextureId = new int[1];
204    static float[] sCropRect = new float[4];
205
206    private void uploadToCanvas(GLCanvas canvas) {
207        GL11 gl = canvas.getGLInstance();
208
209        Bitmap bitmap = getBitmap();
210        if (bitmap != null) {
211            try {
212                int bWidth = bitmap.getWidth();
213                int bHeight = bitmap.getHeight();
214                int width = bWidth + mBorder * 2;
215                int height = bHeight + mBorder * 2;
216                int texWidth = getTextureWidth();
217                int texHeight = getTextureHeight();
218                // Define a vertically flipped crop rectangle for
219                // OES_draw_texture.
220                // The four values in sCropRect are: left, bottom, width, and
221                // height. Negative value of width or height means flip.
222                sCropRect[0] = mBorder;
223                sCropRect[1] = mBorder + bHeight;
224                sCropRect[2] = bWidth;
225                sCropRect[3] = -bHeight;
226
227                // Upload the bitmap to a new texture.
228                gl.glGenTextures(1, sTextureId, 0);
229                gl.glBindTexture(GL11.GL_TEXTURE_2D, sTextureId[0]);
230                gl.glTexParameterfv(GL11.GL_TEXTURE_2D,
231                        GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0);
232                gl.glTexParameteri(GL11.GL_TEXTURE_2D,
233                        GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
234                gl.glTexParameteri(GL11.GL_TEXTURE_2D,
235                        GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
236                gl.glTexParameterf(GL11.GL_TEXTURE_2D,
237                        GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
238                gl.glTexParameterf(GL11.GL_TEXTURE_2D,
239                        GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
240
241                if (bWidth == texWidth && bHeight == texHeight) {
242                    GLUtils.texImage2D(GL11.GL_TEXTURE_2D, 0, bitmap, 0);
243                } else {
244                    int format = GLUtils.getInternalFormat(bitmap);
245                    int type = GLUtils.getType(bitmap);
246                    Config config = bitmap.getConfig();
247
248                    gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format,
249                            texWidth, texHeight, 0, format, type, null);
250                    GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
251                            mBorder, mBorder, bitmap, format, type);
252
253                    if (mBorder > 0) {
254                        // Left border
255                        Bitmap line = getBorderLine(true, config, texHeight);
256                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
257                                0, 0, line, format, type);
258
259                        // Top border
260                        line = getBorderLine(false, config, texWidth);
261                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
262                                0, 0, line, format, type);
263                    }
264
265                    // Right border
266                    if (mBorder + bWidth < texWidth) {
267                        Bitmap line = getBorderLine(true, config, texHeight);
268                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
269                                mBorder + bWidth, 0, line, format, type);
270                    }
271
272                    // Bottom border
273                    if (mBorder + bHeight < texHeight) {
274                        Bitmap line = getBorderLine(false, config, texWidth);
275                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
276                                0, mBorder + bHeight, line, format, type);
277                    }
278                }
279            } finally {
280                freeBitmap();
281            }
282            // Update texture state.
283            setAssociatedCanvas(canvas);
284            mId = sTextureId[0];
285            mState = UploadedTexture.STATE_LOADED;
286            mContentValid = true;
287        } else {
288            mState = STATE_ERROR;
289            throw new RuntimeException("Texture load fail, no bitmap");
290        }
291    }
292
293    @Override
294    protected boolean onBind(GLCanvas canvas) {
295        updateContent(canvas);
296        return isContentValid(canvas);
297    }
298
299    public void setOpaque(boolean isOpaque) {
300        mOpaque = isOpaque;
301    }
302
303    public boolean isOpaque() {
304        return mOpaque;
305    }
306
307    @Override
308    public void recycle() {
309        super.recycle();
310        if (mBitmap != null) freeBitmap();
311    }
312}
313