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.glrenderer;
18
19import android.graphics.Bitmap;
20import android.graphics.Bitmap.Config;
21import android.opengl.GLUtils;
22import android.util.Pair;
23
24import com.android.gallery3d.common.Utils;
25
26import java.util.HashMap;
27
28// UploadedTextures use a Bitmap for the content of the texture.
29//
30// Subclasses should implement onGetBitmap() to provide the Bitmap and
31// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
32// is not needed anymore.
33//
34// isContentValid() is meaningful only when the isLoaded() returns true.
35// It means whether the content needs to be updated.
36//
37// The user of this class should call recycle() when the texture is not
38// needed anymore.
39//
40// By default an UploadedTexture is opaque (so it can be drawn faster without
41// blending). The user or subclass can override it using setOpaque().
42public abstract class UploadedTexture extends BasicTexture {
43
44    // To prevent keeping allocation the borders, we store those used borders here.
45    // Since the length will be power of two, it won't use too much memory.
46    private static HashMap<BorderKey, Bitmap> sBorderLines = new HashMap<BorderKey, Bitmap>();
47
48    private static class BorderKey extends Pair<Config, Integer> {
49        public BorderKey(Config config, boolean vertical, int length) {
50            super(config, vertical ? length : -length);
51        }
52    }
53
54    private boolean mContentValid = true;
55    protected Bitmap mBitmap;
56
57    protected UploadedTexture() {
58        super(null, 0, STATE_UNLOADED);
59    }
60
61    private static Bitmap getBorderLine(boolean vertical, Config config, int length) {
62        BorderKey key = new BorderKey(config, vertical, length);
63        Bitmap bitmap = sBorderLines.get(key);
64        if (bitmap == null) {
65            bitmap = vertical
66                    ? Bitmap.createBitmap(1, length, config)
67                    : Bitmap.createBitmap(length, 1, config);
68            sBorderLines.put(key, bitmap);
69        }
70        return bitmap;
71    }
72
73    private Bitmap getBitmap() {
74        if (mBitmap == null) {
75            mBitmap = onGetBitmap();
76            int w = mBitmap.getWidth();
77            int h = mBitmap.getHeight();
78            if (mWidth == UNSPECIFIED) {
79                setSize(w, h);
80            }
81        }
82        return mBitmap;
83    }
84
85    private void freeBitmap() {
86        Utils.assertTrue(mBitmap != null);
87        onFreeBitmap(mBitmap);
88        mBitmap = null;
89    }
90
91    @Override
92    public int getWidth() {
93        if (mWidth == UNSPECIFIED) getBitmap();
94        return mWidth;
95    }
96
97    @Override
98    public int getHeight() {
99        if (mWidth == UNSPECIFIED) getBitmap();
100        return mHeight;
101    }
102
103    protected abstract Bitmap onGetBitmap();
104
105    protected abstract void onFreeBitmap(Bitmap bitmap);
106
107    protected void invalidateContent() {
108        if (mBitmap != null) freeBitmap();
109        mContentValid = false;
110        mWidth = UNSPECIFIED;
111        mHeight = UNSPECIFIED;
112    }
113
114    /**
115     * Whether the content on GPU is valid.
116     */
117    public boolean isContentValid() {
118        return isLoaded() && mContentValid;
119    }
120
121    /**
122     * Updates the content on GPU's memory.
123     * @param canvas
124     */
125    public void updateContent(GLCanvas canvas) {
126        if (!isLoaded()) {
127            uploadToCanvas(canvas);
128        } else if (!mContentValid) {
129            Bitmap bitmap = getBitmap();
130            int format = GLUtils.getInternalFormat(bitmap);
131            int type = GLUtils.getType(bitmap);
132            canvas.texSubImage2D(this, 0, 0, bitmap, format, type);
133            freeBitmap();
134            mContentValid = true;
135        }
136    }
137
138    private void uploadToCanvas(GLCanvas canvas) {
139        Bitmap bitmap = getBitmap();
140        if (bitmap != null) {
141            try {
142                int bWidth = bitmap.getWidth();
143                int bHeight = bitmap.getHeight();
144                int texWidth = getTextureWidth();
145                int texHeight = getTextureHeight();
146
147                Utils.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
148
149                // Upload the bitmap to a new texture.
150                mId = canvas.getGLId().generateTexture();
151                canvas.setTextureParameters(this);
152
153                if (bWidth == texWidth && bHeight == texHeight) {
154                    canvas.initializeTexture(this, bitmap);
155                } else {
156                    int format = GLUtils.getInternalFormat(bitmap);
157                    int type = GLUtils.getType(bitmap);
158                    Config config = bitmap.getConfig();
159
160                    canvas.initializeTextureSize(this, format, type);
161                    canvas.texSubImage2D(this, 0, 0, bitmap, format, type);
162
163                    // Right border
164                    if (bWidth < texWidth) {
165                        Bitmap line = getBorderLine(true, config, texHeight);
166                        canvas.texSubImage2D(this, bWidth, 0, line, format, type);
167                    }
168
169                    // Bottom border
170                    if (bHeight < texHeight) {
171                        Bitmap line = getBorderLine(false, config, texWidth);
172                        canvas.texSubImage2D(this, 0, bHeight, line, format, type);
173                    }
174                }
175            } finally {
176                freeBitmap();
177            }
178            // Update texture state.
179            setAssociatedCanvas(canvas);
180            mState = STATE_LOADED;
181            mContentValid = true;
182        } else {
183            mState = STATE_ERROR;
184            throw new RuntimeException("Texture load fail, no bitmap");
185        }
186    }
187
188    @Override
189    protected boolean onBind(GLCanvas canvas) {
190        updateContent(canvas);
191        return isContentValid();
192    }
193
194    @Override
195    public void recycle() {
196        super.recycle();
197        if (mBitmap != null) freeBitmap();
198    }
199}
200