| /* |
| * Copyright (C) 2007 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 android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Paint; |
| import android.graphics.Rect; |
| import android.graphics.Paint.Style; |
| import android.graphics.drawable.Drawable; |
| import android.opengl.GLUtils; |
| |
| import java.util.ArrayList; |
| |
| import javax.microedition.khronos.opengles.GL10; |
| import javax.microedition.khronos.opengles.GL11; |
| import javax.microedition.khronos.opengles.GL11Ext; |
| |
| /** |
| * 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. |
| * |
| */ |
| public 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<Label> mLabels = new ArrayList<Label>(); |
| |
| private static final int STATE_NEW = 0; |
| private static final int STATE_INITIALIZED = 1; |
| private static final int STATE_ADDING = 2; |
| private static final int STATE_DRAWING = 3; |
| private int mState; |
| } |