2D Graphics Android

/*
 * Copyright (C) 2008 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.example.android.apis.graphics.spritetext;
import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL10Ext;
import javax.microedition.khronos.opengles.GL11;
import javax.microedition.khronos.opengles.GL11Ext;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
/**
 * An OpenGL text label maker.
 * 
 * 
 * OpenGL labels are implemented by creating a Bitmap, drawing all the labels
 * into the Bitmap, converting the Bitmap into an Alpha texture, and drawing
 * portions of the texture using glDrawTexiOES.
 * 
 * The benefits of this approach are that the labels are drawn using the high
 * quality anti-aliased font rasterizer, full character set support, and all the
 * text labels are stored on a single texture, which makes it faster to use.
 * 
 * The drawbacks are that you can only have as many labels as will fit onto one
 * texture, and you have to recreate the whole texture if any label text
 * changes.
 * 
 */
class LabelMaker {
  /**
   * Create a label maker or maximum compatibility with various OpenGL ES
   * implementations, the strike width and height must be powers of two, We
   * want the strike width to be at least as wide as the widest window.
   * 
   * @param fullColor
   *            true if we want a full color backing store (4444), otherwise
   *            we generate a grey L8 backing store.
   * @param strikeWidth
   *            width of strike
   * @param strikeHeight
   *            height of strike
   */
  public LabelMaker(boolean fullColor, int strikeWidth, int strikeHeight) {
    mFullColor = fullColor;
    mStrikeWidth = strikeWidth;
    mStrikeHeight = strikeHeight;
    mTexelWidth = (float) (1.0 / mStrikeWidth);
    mTexelHeight = (float) (1.0 / mStrikeHeight);
    mClearPaint = new Paint();
    mClearPaint.setARGB(0, 0, 0, 0);
    mClearPaint.setStyle(Style.FILL);
    mState = STATE_NEW;
  }
  /**
   * Call to initialize the class. Call whenever the surface has been created.
   * 
   * @param gl
   */
  public void initialize(GL10 gl) {
    mState = STATE_INITIALIZED;
    int[] textures = new int[1];
    gl.glGenTextures(1, textures, 0);
    mTextureID = textures[0];
    gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
    // Use Nearest for performance.
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
        GL10.GL_NEAREST);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
        GL10.GL_NEAREST);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
        GL10.GL_CLAMP_TO_EDGE);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
        GL10.GL_CLAMP_TO_EDGE);
    gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE,
        GL10.GL_REPLACE);
  }
  /**
   * Call when the surface has been destroyed
   */
  public void shutdown(GL10 gl) {
    if (gl != null) {
      if (mState > STATE_NEW) {
        int[] textures = new int[1];
        textures[0] = mTextureID;
        gl.glDeleteTextures(1, textures, 0);
        mState = STATE_NEW;
      }
    }
  }
  /**
   * Call before adding labels. Clears out any existing labels.
   * 
   * @param gl
   */
  public void beginAdding(GL10 gl) {
    checkState(STATE_INITIALIZED, STATE_ADDING);
    mLabels.clear();
    mU = 0;
    mV = 0;
    mLineHeight = 0;
    Bitmap.Config config = mFullColor ? Bitmap.Config.ARGB_4444
        : Bitmap.Config.ALPHA_8;
    mBitmap = Bitmap.createBitmap(mStrikeWidth, mStrikeHeight, config);
    mCanvas = new Canvas(mBitmap);
    mBitmap.eraseColor(0);
  }
  /**
   * Call to add a label
   * 
   * @param gl
   * @param text
   *            the text of the label
   * @param textPaint
   *            the paint of the label
   * @return the id of the label, used to measure and draw the label
   */
  public int add(GL10 gl, String text, Paint textPaint) {
    return add(gl, null, text, textPaint);
  }
  /**
   * Call to add a label
   * 
   * @param gl
   * @param text
   *            the text of the label
   * @param textPaint
   *            the paint of the label
   * @return the id of the label, used to measure and draw the label
   */
  public int add(GL10 gl, Drawable background, String text, Paint textPaint) {
    return add(gl, background, text, textPaint, 0, 0);
  }
  /**
   * Call to add a label
   * 
   * @return the id of the label, used to measure and draw the label
   */
  public int add(GL10 gl, Drawable drawable, int minWidth, int minHeight) {
    return add(gl, drawable, null, null, minWidth, minHeight);
  }
  /**
   * Call to add a label
   * 
   * @param gl
   * @param text
   *            the text of the label
   * @param textPaint
   *            the paint of the label
   * @return the id of the label, used to measure and draw the label
   */
  public int add(GL10 gl, Drawable background, String text, Paint textPaint,
      int minWidth, int minHeight) {
    checkState(STATE_ADDING, STATE_ADDING);
    boolean drawBackground = background != null;
    boolean drawText = (text != null) && (textPaint != null);
    Rect padding = new Rect();
    if (drawBackground) {
      background.getPadding(padding);
      minWidth = Math.max(minWidth, background.getMinimumWidth());
      minHeight = Math.max(minHeight, background.getMinimumHeight());
    }
    int ascent = 0;
    int descent = 0;
    int measuredTextWidth = 0;
    if (drawText) {
      // Paint.ascent is negative, so negate it.
      ascent = (int) Math.ceil(-textPaint.ascent());
      descent = (int) Math.ceil(textPaint.descent());
      measuredTextWidth = (int) Math.ceil(textPaint.measureText(text));
    }
    int textHeight = ascent + descent;
    int textWidth = Math.min(mStrikeWidth, measuredTextWidth);
    int padHeight = padding.top + padding.bottom;
    int padWidth = padding.left + padding.right;
    int height = Math.max(minHeight, textHeight + padHeight);
    int width = Math.max(minWidth, textWidth + padWidth);
    int effectiveTextHeight = height - padHeight;
    int effectiveTextWidth = width - padWidth;
    int centerOffsetHeight = (effectiveTextHeight - textHeight) / 2;
    int centerOffsetWidth = (effectiveTextWidth - textWidth) / 2;
    // Make changes to the local variables, only commit them
    // to the member variables after we've decided not to throw
    // any exceptions.
    int u = mU;
    int v = mV;
    int lineHeight = mLineHeight;
    if (width > mStrikeWidth) {
      width = mStrikeWidth;
    }
    // Is there room for this string on the current line?
    if (u + width > mStrikeWidth) {
      // No room, go to the next line:
      u = 0;
      v += lineHeight;
      lineHeight = 0;
    }
    lineHeight = Math.max(lineHeight, height);
    if (v + lineHeight > mStrikeHeight) {
      throw new IllegalArgumentException("Out of texture space.");
    }
    int u2 = u + width;
    int vBase = v + ascent;
    int v2 = v + height;
    if (drawBackground) {
      background.setBounds(u, v, u + width, v + height);
      background.draw(mCanvas);
    }
    if (drawText) {
      mCanvas.drawText(text, u + padding.left + centerOffsetWidth, vBase
          + padding.top + centerOffsetHeight, textPaint);
    }
    // We know there's enough space, so update the member variables
    mU = u + width;
    mV = v;
    mLineHeight = lineHeight;
    mLabels.add(new Label(width, height, ascent, u, v + height, width,
        -height));
    return mLabels.size() - 1;
  }
  /**
   * Call to end adding labels. Must be called before drawing starts.
   * 
   * @param gl
   */
  public void endAdding(GL10 gl) {
    checkState(STATE_ADDING, STATE_INITIALIZED);
    gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
    GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0);
    // Reclaim storage used by bitmap and canvas.
    mBitmap.recycle();
    mBitmap = null;
    mCanvas = null;
  }
  /**
   * Get the width in pixels of a given label.
   * 
   * @param labelID
   * @return the width in pixels
   */
  public float getWidth(int labelID) {
    return mLabels.get(labelID).width;
  }
  /**
   * Get the height in pixels of a given label.
   * 
   * @param labelID
   * @return the height in pixels
   */
  public float getHeight(int labelID) {
    return mLabels.get(labelID).height;
  }
  /**
   * Get the baseline of a given label. That's how many pixels from the top of
   * the label to the text baseline. (This is equivalent to the negative of
   * the label's paint's ascent.)
   * 
   * @param labelID
   * @return the baseline in pixels.
   */
  public float getBaseline(int labelID) {
    return mLabels.get(labelID).baseline;
  }
  /**
   * Begin drawing labels. Sets the OpenGL state for rapid drawing.
   * 
   * @param gl
   * @param viewWidth
   * @param viewHeight
   */
  public void beginDrawing(GL10 gl, float viewWidth, float viewHeight) {
    checkState(STATE_INITIALIZED, STATE_DRAWING);
    gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID);
    gl.glShadeModel(GL10.GL_FLAT);
    gl.glEnable(GL10.GL_BLEND);
    gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
    gl.glColor4x(0x10000, 0x10000, 0x10000, 0x10000);
    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glPushMatrix();
    gl.glLoadIdentity();
    gl.glOrthof(0.0f, viewWidth, 0.0f, viewHeight, 0.0f, 1.0f);
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glPushMatrix();
    gl.glLoadIdentity();
    // Magic offsets to promote consistent rasterization.
    gl.glTranslatef(0.375f, 0.375f, 0.0f);
  }
  /**
   * Draw a given label at a given x,y position, expressed in pixels, with the
   * lower-left-hand-corner of the view being (0,0).
   * 
   * @param gl
   * @param x
   * @param y
   * @param labelID
   */
  public void draw(GL10 gl, float x, float y, int labelID) {
    checkState(STATE_DRAWING, STATE_DRAWING);
    Label label = mLabels.get(labelID);
    gl.glEnable(GL10.GL_TEXTURE_2D);
    ((GL11) gl).glTexParameteriv(GL10.GL_TEXTURE_2D,
        GL11Ext.GL_TEXTURE_CROP_RECT_OES, label.mCrop, 0);
    ((GL11Ext) gl).glDrawTexiOES((int) x, (int) y, 0, (int) label.width,
        (int) label.height);
  }
  /**
   * Ends the drawing and restores the OpenGL state.
   * 
   * @param gl
   */
  public void endDrawing(GL10 gl) {
    checkState(STATE_DRAWING, STATE_INITIALIZED);
    gl.glDisable(GL10.GL_BLEND);
    gl.glMatrixMode(GL10.GL_PROJECTION);
    gl.glPopMatrix();
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glPopMatrix();
  }
  private void checkState(int oldState, int newState) {
    if (mState != oldState) {
      throw new IllegalArgumentException("Can't call this method now.");
    }
    mState = newState;
  }
  private static class Label {
    public Label(float width, float height, float baseLine, int cropU,
        int cropV, int cropW, int cropH) {
      this.width = width;
      this.height = height;
      this.baseline = baseLine;
      int[] crop = new int[4];
      crop[0] = cropU;
      crop[1] = cropV;
      crop[2] = cropW;
      crop[3] = cropH;
      mCrop = crop;
    }
    public float width;
    public float height;
    public float baseline;
    public int[] mCrop;
  }
  private int mStrikeWidth;
  private int mStrikeHeight;
  private boolean mFullColor;
  private Bitmap mBitmap;
  private Canvas mCanvas;
  private Paint mClearPaint;
  private int mTextureID;
  private float mTexelWidth; // Convert texel to U
  private float mTexelHeight; // Convert texel to V
  private int mU;
  private int mV;
  private int mLineHeight;
  private ArrayList