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 
17 package com.android.gallery3d.glrenderer;
18 
19 import android.graphics.Bitmap;
20 import android.graphics.Bitmap.Config;
21 import android.opengl.GLUtils;
22 
23 import junit.framework.Assert;
24 
25 import java.util.HashMap;
26 
27 import javax.microedition.khronos.opengles.GL11;
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().
43 public abstract 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 
55     // indicate this textures is being uploaded in background
56     private boolean mIsUploading = false;
57     private boolean mOpaque = true;
58     private boolean mThrottled = false;
59     private static int sUploadedCount;
60     private static final int UPLOAD_LIMIT = 100;
61 
62     protected Bitmap mBitmap;
63     private int mBorder;
64 
UploadedTexture()65     protected UploadedTexture() {
66         this(false);
67     }
68 
UploadedTexture(boolean hasBorder)69     protected UploadedTexture(boolean hasBorder) {
70         super(null, 0, STATE_UNLOADED);
71         if (hasBorder) {
72             setBorder(true);
73             mBorder = 1;
74         }
75     }
76 
setIsUploading(boolean uploading)77     protected void setIsUploading(boolean uploading) {
78         mIsUploading = uploading;
79     }
80 
isUploading()81     public boolean isUploading() {
82         return mIsUploading;
83     }
84 
85     private static class BorderKey implements Cloneable {
86         public boolean vertical;
87         public Config config;
88         public int length;
89 
90         @Override
hashCode()91         public int hashCode() {
92             int x = config.hashCode() ^ length;
93             return vertical ? x : -x;
94         }
95 
96         @Override
equals(Object object)97         public boolean equals(Object object) {
98             if (!(object instanceof BorderKey)) return false;
99             BorderKey o = (BorderKey) object;
100             return vertical == o.vertical
101                     && config == o.config && length == o.length;
102         }
103 
104         @Override
clone()105         public BorderKey clone() {
106             try {
107                 return (BorderKey) super.clone();
108             } catch (CloneNotSupportedException e) {
109                 throw new AssertionError(e);
110             }
111         }
112     }
113 
setThrottled(boolean throttled)114     protected void setThrottled(boolean throttled) {
115         mThrottled = throttled;
116     }
117 
getBorderLine( boolean vertical, Config config, int length)118     private static Bitmap getBorderLine(
119             boolean vertical, Config config, int length) {
120         BorderKey key = sBorderKey;
121         key.vertical = vertical;
122         key.config = config;
123         key.length = length;
124         Bitmap bitmap = sBorderLines.get(key);
125         if (bitmap == null) {
126             bitmap = vertical
127                     ? Bitmap.createBitmap(1, length, config)
128                     : Bitmap.createBitmap(length, 1, config);
129             sBorderLines.put(key.clone(), bitmap);
130         }
131         return bitmap;
132     }
133 
getBitmap()134     private Bitmap getBitmap() {
135         if (mBitmap == null) {
136             mBitmap = onGetBitmap();
137             int w = mBitmap.getWidth() + mBorder * 2;
138             int h = mBitmap.getHeight() + mBorder * 2;
139             if (mWidth == UNSPECIFIED) {
140                 setSize(w, h);
141             }
142         }
143         return mBitmap;
144     }
145 
freeBitmap()146     private void freeBitmap() {
147         Assert.assertTrue(mBitmap != null);
148         onFreeBitmap(mBitmap);
149         mBitmap = null;
150     }
151 
152     @Override
getWidth()153     public int getWidth() {
154         if (mWidth == UNSPECIFIED) getBitmap();
155         return mWidth;
156     }
157 
158     @Override
getHeight()159     public int getHeight() {
160         if (mWidth == UNSPECIFIED) getBitmap();
161         return mHeight;
162     }
163 
onGetBitmap()164     protected abstract Bitmap onGetBitmap();
165 
onFreeBitmap(Bitmap bitmap)166     protected abstract void onFreeBitmap(Bitmap bitmap);
167 
invalidateContent()168     protected void invalidateContent() {
169         if (mBitmap != null) freeBitmap();
170         mContentValid = false;
171         mWidth = UNSPECIFIED;
172         mHeight = UNSPECIFIED;
173     }
174 
175     /**
176      * Whether the content on GPU is valid.
177      */
isContentValid()178     public boolean isContentValid() {
179         return isLoaded() && mContentValid;
180     }
181 
182     /**
183      * Updates the content on GPU's memory.
184      * @param canvas
185      */
updateContent(GLCanvas canvas)186     public void updateContent(GLCanvas canvas) {
187         if (!isLoaded()) {
188             if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
189                 return;
190             }
191             uploadToCanvas(canvas);
192         } else if (!mContentValid) {
193             Bitmap bitmap = getBitmap();
194             int format = GLUtils.getInternalFormat(bitmap);
195             int type = GLUtils.getType(bitmap);
196             canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
197             freeBitmap();
198             mContentValid = true;
199         }
200     }
201 
resetUploadLimit()202     public static void resetUploadLimit() {
203         sUploadedCount = 0;
204     }
205 
uploadLimitReached()206     public static boolean uploadLimitReached() {
207         return sUploadedCount > UPLOAD_LIMIT;
208     }
209 
uploadToCanvas(GLCanvas canvas)210     private void uploadToCanvas(GLCanvas canvas) {
211 
212         Bitmap bitmap = getBitmap();
213         if (bitmap != null) {
214             try {
215                 int bWidth = bitmap.getWidth();
216                 int bHeight = bitmap.getHeight();
217                 int width = bWidth + mBorder * 2;
218                 int height = bHeight + mBorder * 2;
219                 int texWidth = getTextureWidth();
220                 int texHeight = getTextureHeight();
221 
222                 Assert.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
223 
224                 // Upload the bitmap to a new texture.
225                 mId = canvas.getGLId().generateTexture();
226                 canvas.setTextureParameters(this);
227 
228                 if (bWidth == texWidth && bHeight == texHeight) {
229                     canvas.initializeTexture(this, bitmap);
230                 } else {
231                     int format = GLUtils.getInternalFormat(bitmap);
232                     int type = GLUtils.getType(bitmap);
233                     Config config = bitmap.getConfig();
234 
235                     canvas.initializeTextureSize(this, format, type);
236                     canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
237 
238                     if (mBorder > 0) {
239                         // Left border
240                         Bitmap line = getBorderLine(true, config, texHeight);
241                         canvas.texSubImage2D(this, 0, 0, line, format, type);
242 
243                         // Top border
244                         line = getBorderLine(false, config, texWidth);
245                         canvas.texSubImage2D(this, 0, 0, line, format, type);
246                     }
247 
248                     // Right border
249                     if (mBorder + bWidth < texWidth) {
250                         Bitmap line = getBorderLine(true, config, texHeight);
251                         canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type);
252                     }
253 
254                     // Bottom border
255                     if (mBorder + bHeight < texHeight) {
256                         Bitmap line = getBorderLine(false, config, texWidth);
257                         canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type);
258                     }
259                 }
260             } finally {
261                 freeBitmap();
262             }
263             // Update texture state.
264             setAssociatedCanvas(canvas);
265             mState = STATE_LOADED;
266             mContentValid = true;
267         } else {
268             mState = STATE_ERROR;
269             throw new RuntimeException("Texture load fail, no bitmap");
270         }
271     }
272 
273     @Override
onBind(GLCanvas canvas)274     protected boolean onBind(GLCanvas canvas) {
275         updateContent(canvas);
276         return isContentValid();
277     }
278 
279     @Override
getTarget()280     protected int getTarget() {
281         return GL11.GL_TEXTURE_2D;
282     }
283 
setOpaque(boolean isOpaque)284     public void setOpaque(boolean isOpaque) {
285         mOpaque = isOpaque;
286     }
287 
288     @Override
isOpaque()289     public boolean isOpaque() {
290         return mOpaque;
291     }
292 
293     @Override
recycle()294     public void recycle() {
295         super.recycle();
296         if (mBitmap != null) freeBitmap();
297     }
298 }
299