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