/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.gallery3d.glrenderer;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.opengl.GLUtils;

import com.android.gallery3d.common.Utils;

import java.util.HashMap;

import javax.microedition.khronos.opengles.GL11;

// UploadedTextures use a Bitmap for the content of the texture.
//
// Subclasses should implement onGetBitmap() to provide the Bitmap and
// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
// is not needed anymore.
//
// isContentValid() is meaningful only when the isLoaded() returns true.
// It means whether the content needs to be updated.
//
// The user of this class should call recycle() when the texture is not
// needed anymore.
//
// By default an UploadedTexture is opaque (so it can be drawn faster without
// blending). The user or subclass can override it using setOpaque().
public abstract class UploadedTexture extends BasicTexture {

    // To prevent keeping allocation the borders, we store those used borders here.
    // Since the length will be power of two, it won't use too much memory.
    private static HashMap<BorderKey, Bitmap> sBorderLines =
            new HashMap<BorderKey, Bitmap>();
    private static BorderKey sBorderKey = new BorderKey();

    @SuppressWarnings("unused")
    private static final String TAG = "Texture";
    private boolean mContentValid = true;

    // indicate this textures is being uploaded in background
    private boolean mIsUploading = false;
    private boolean mOpaque = true;
    private boolean mThrottled = false;
    private static int sUploadedCount;
    private static final int UPLOAD_LIMIT = 100;

    protected Bitmap mBitmap;
    private int mBorder;

    protected UploadedTexture() {
        this(false);
    }

    protected UploadedTexture(boolean hasBorder) {
        super(null, 0, STATE_UNLOADED);
        if (hasBorder) {
            setBorder(true);
            mBorder = 1;
        }
    }

    protected void setIsUploading(boolean uploading) {
        mIsUploading = uploading;
    }

    public boolean isUploading() {
        return mIsUploading;
    }

    private static class BorderKey implements Cloneable {
        public boolean vertical;
        public Config config;
        public int length;

        @Override
        public int hashCode() {
            int x = config.hashCode() ^ length;
            return vertical ? x : -x;
        }

        @Override
        public boolean equals(Object object) {
            if (!(object instanceof BorderKey)) return false;
            BorderKey o = (BorderKey) object;
            return vertical == o.vertical
                    && config == o.config && length == o.length;
        }

        @Override
        public BorderKey clone() {
            try {
                return (BorderKey) super.clone();
            } catch (CloneNotSupportedException e) {
                throw new AssertionError(e);
            }
        }
    }

    protected void setThrottled(boolean throttled) {
        mThrottled = throttled;
    }

    private static Bitmap getBorderLine(
            boolean vertical, Config config, int length) {
        BorderKey key = sBorderKey;
        key.vertical = vertical;
        key.config = config;
        key.length = length;
        Bitmap bitmap = sBorderLines.get(key);
        if (bitmap == null) {
            bitmap = vertical
                    ? Bitmap.createBitmap(1, length, config)
                    : Bitmap.createBitmap(length, 1, config);
            sBorderLines.put(key.clone(), bitmap);
        }
        return bitmap;
    }

    private Bitmap getBitmap() {
        if (mBitmap == null) {
            mBitmap = onGetBitmap();
            int w = mBitmap.getWidth() + mBorder * 2;
            int h = mBitmap.getHeight() + mBorder * 2;
            if (mWidth == UNSPECIFIED) {
                setSize(w, h);
            }
        }
        return mBitmap;
    }

    private void freeBitmap() {
        Utils.assertTrue(mBitmap != null);
        onFreeBitmap(mBitmap);
        mBitmap = null;
    }

    @Override
    public int getWidth() {
        if (mWidth == UNSPECIFIED) getBitmap();
        return mWidth;
    }

    @Override
    public int getHeight() {
        if (mWidth == UNSPECIFIED) getBitmap();
        return mHeight;
    }

    protected abstract Bitmap onGetBitmap();

    protected abstract void onFreeBitmap(Bitmap bitmap);

    protected void invalidateContent() {
        if (mBitmap != null) freeBitmap();
        mContentValid = false;
        mWidth = UNSPECIFIED;
        mHeight = UNSPECIFIED;
    }

    
Whether the content on GPU is valid.
/** * Whether the content on GPU is valid. */
public boolean isContentValid() { return isLoaded() && mContentValid; }
Updates the content on GPU's memory.
Params:
  • canvas –
/** * Updates the content on GPU's memory. * @param canvas */
public void updateContent(GLCanvas canvas) { if (!isLoaded()) { if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) { return; } uploadToCanvas(canvas); } else if (!mContentValid) { Bitmap bitmap = getBitmap(); int format = GLUtils.getInternalFormat(bitmap); int type = GLUtils.getType(bitmap); canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type); freeBitmap(); mContentValid = true; } } public static void resetUploadLimit() { sUploadedCount = 0; } public static boolean uploadLimitReached() { return sUploadedCount > UPLOAD_LIMIT; } private void uploadToCanvas(GLCanvas canvas) { Bitmap bitmap = getBitmap(); if (bitmap != null) { try { int bWidth = bitmap.getWidth(); int bHeight = bitmap.getHeight(); int width = bWidth + mBorder * 2; int height = bHeight + mBorder * 2; int texWidth = getTextureWidth(); int texHeight = getTextureHeight(); Utils.assertTrue(bWidth <= texWidth && bHeight <= texHeight); // Upload the bitmap to a new texture. mId = canvas.getGLId().generateTexture(); canvas.setTextureParameters(this); if (bWidth == texWidth && bHeight == texHeight) { canvas.initializeTexture(this, bitmap); } else { int format = GLUtils.getInternalFormat(bitmap); int type = GLUtils.getType(bitmap); Config config = bitmap.getConfig(); canvas.initializeTextureSize(this, format, type); canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type); if (mBorder > 0) { // Left border Bitmap line = getBorderLine(true, config, texHeight); canvas.texSubImage2D(this, 0, 0, line, format, type); // Top border line = getBorderLine(false, config, texWidth); canvas.texSubImage2D(this, 0, 0, line, format, type); } // Right border if (mBorder + bWidth < texWidth) { Bitmap line = getBorderLine(true, config, texHeight); canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type); } // Bottom border if (mBorder + bHeight < texHeight) { Bitmap line = getBorderLine(false, config, texWidth); canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type); } } } finally { freeBitmap(); } // Update texture state. setAssociatedCanvas(canvas); mState = STATE_LOADED; mContentValid = true; } else { mState = STATE_ERROR; throw new RuntimeException("Texture load fail, no bitmap"); } } @Override protected boolean onBind(GLCanvas canvas) { updateContent(canvas); return isContentValid(); } @Override protected int getTarget() { return GL11.GL_TEXTURE_2D; } public void setOpaque(boolean isOpaque) { mOpaque = isOpaque; } @Override public boolean isOpaque() { return mOpaque; } @Override public void recycle() { super.recycle(); if (mBitmap != null) freeBitmap(); } }