1 /* 2 * Copyright (C) 2007 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.example.android.apis.graphics.spritetext; 18 19 import android.graphics.Bitmap; 20 import android.graphics.Canvas; 21 import android.graphics.Paint; 22 import android.graphics.Rect; 23 import android.graphics.Paint.Style; 24 import android.graphics.drawable.Drawable; 25 import android.opengl.GLUtils; 26 27 import java.util.ArrayList; 28 29 import javax.microedition.khronos.opengles.GL10; 30 import javax.microedition.khronos.opengles.GL11; 31 import javax.microedition.khronos.opengles.GL11Ext; 32 33 /** 34 * An OpenGL text label maker. 35 * 36 * 37 * OpenGL labels are implemented by creating a Bitmap, drawing all the labels 38 * into the Bitmap, converting the Bitmap into an Alpha texture, and drawing 39 * portions of the texture using glDrawTexiOES. 40 * 41 * The benefits of this approach are that the labels are drawn using the high 42 * quality anti-aliased font rasterizer, full character set support, and all the 43 * text labels are stored on a single texture, which makes it faster to use. 44 * 45 * The drawbacks are that you can only have as many labels as will fit onto one 46 * texture, and you have to recreate the whole texture if any label text 47 * changes. 48 * 49 */ 50 public class LabelMaker { 51 /** 52 * Create a label maker 53 * or maximum compatibility with various OpenGL ES implementations, 54 * the strike width and height must be powers of two, 55 * We want the strike width to be at least as wide as the widest window. 56 * 57 * @param fullColor true if we want a full color backing store (4444), 58 * otherwise we generate a grey L8 backing store. 59 * @param strikeWidth width of strike 60 * @param strikeHeight height of strike 61 */ LabelMaker(boolean fullColor, int strikeWidth, int strikeHeight)62 public LabelMaker(boolean fullColor, int strikeWidth, int strikeHeight) { 63 mFullColor = fullColor; 64 mStrikeWidth = strikeWidth; 65 mStrikeHeight = strikeHeight; 66 mTexelWidth = (float) (1.0 / mStrikeWidth); 67 mTexelHeight = (float) (1.0 / mStrikeHeight); 68 mClearPaint = new Paint(); 69 mClearPaint.setARGB(0, 0, 0, 0); 70 mClearPaint.setStyle(Style.FILL); 71 mState = STATE_NEW; 72 } 73 74 /** 75 * Call to initialize the class. 76 * Call whenever the surface has been created. 77 * 78 * @param gl 79 */ initialize(GL10 gl)80 public void initialize(GL10 gl) { 81 mState = STATE_INITIALIZED; 82 int[] textures = new int[1]; 83 gl.glGenTextures(1, textures, 0); 84 mTextureID = textures[0]; 85 gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID); 86 87 // Use Nearest for performance. 88 gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, 89 GL10.GL_NEAREST); 90 gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, 91 GL10.GL_NEAREST); 92 93 gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, 94 GL10.GL_CLAMP_TO_EDGE); 95 gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, 96 GL10.GL_CLAMP_TO_EDGE); 97 98 gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, 99 GL10.GL_REPLACE); 100 } 101 102 /** 103 * Call when the surface has been destroyed 104 */ shutdown(GL10 gl)105 public void shutdown(GL10 gl) { 106 if ( gl != null) { 107 if (mState > STATE_NEW) { 108 int[] textures = new int[1]; 109 textures[0] = mTextureID; 110 gl.glDeleteTextures(1, textures, 0); 111 mState = STATE_NEW; 112 } 113 } 114 } 115 116 /** 117 * Call before adding labels. Clears out any existing labels. 118 * 119 * @param gl 120 */ beginAdding(GL10 gl)121 public void beginAdding(GL10 gl) { 122 checkState(STATE_INITIALIZED, STATE_ADDING); 123 mLabels.clear(); 124 mU = 0; 125 mV = 0; 126 mLineHeight = 0; 127 Bitmap.Config config = mFullColor ? 128 Bitmap.Config.ARGB_4444 : Bitmap.Config.ALPHA_8; 129 mBitmap = Bitmap.createBitmap(mStrikeWidth, mStrikeHeight, config); 130 mCanvas = new Canvas(mBitmap); 131 mBitmap.eraseColor(0); 132 } 133 134 /** 135 * Call to add a label 136 * 137 * @param gl 138 * @param text the text of the label 139 * @param textPaint the paint of the label 140 * @return the id of the label, used to measure and draw the label 141 */ add(GL10 gl, String text, Paint textPaint)142 public int add(GL10 gl, String text, Paint textPaint) { 143 return add(gl, null, text, textPaint); 144 } 145 146 /** 147 * Call to add a label 148 * 149 * @param gl 150 * @param text the text of the label 151 * @param textPaint the paint of the label 152 * @return the id of the label, used to measure and draw the label 153 */ add(GL10 gl, Drawable background, String text, Paint textPaint)154 public int add(GL10 gl, Drawable background, String text, Paint textPaint) { 155 return add(gl, background, text, textPaint, 0, 0); 156 } 157 158 /** 159 * Call to add a label 160 * @return the id of the label, used to measure and draw the label 161 */ add(GL10 gl, Drawable drawable, int minWidth, int minHeight)162 public int add(GL10 gl, Drawable drawable, int minWidth, int minHeight) { 163 return add(gl, drawable, null, null, minWidth, minHeight); 164 } 165 166 /** 167 * Call to add a label 168 * 169 * @param gl 170 * @param text the text of the label 171 * @param textPaint the paint of the label 172 * @return the id of the label, used to measure and draw the label 173 */ add(GL10 gl, Drawable background, String text, Paint textPaint, int minWidth, int minHeight)174 public int add(GL10 gl, Drawable background, String text, Paint textPaint, 175 int minWidth, int minHeight) { 176 checkState(STATE_ADDING, STATE_ADDING); 177 boolean drawBackground = background != null; 178 boolean drawText = (text != null) && (textPaint != null); 179 180 Rect padding = new Rect(); 181 if (drawBackground) { 182 background.getPadding(padding); 183 minWidth = Math.max(minWidth, background.getMinimumWidth()); 184 minHeight = Math.max(minHeight, background.getMinimumHeight()); 185 } 186 187 int ascent = 0; 188 int descent = 0; 189 int measuredTextWidth = 0; 190 if (drawText) { 191 // Paint.ascent is negative, so negate it. 192 ascent = (int) Math.ceil(-textPaint.ascent()); 193 descent = (int) Math.ceil(textPaint.descent()); 194 measuredTextWidth = (int) Math.ceil(textPaint.measureText(text)); 195 } 196 int textHeight = ascent + descent; 197 int textWidth = Math.min(mStrikeWidth,measuredTextWidth); 198 199 int padHeight = padding.top + padding.bottom; 200 int padWidth = padding.left + padding.right; 201 int height = Math.max(minHeight, textHeight + padHeight); 202 int width = Math.max(minWidth, textWidth + padWidth); 203 int effectiveTextHeight = height - padHeight; 204 int effectiveTextWidth = width - padWidth; 205 206 int centerOffsetHeight = (effectiveTextHeight - textHeight) / 2; 207 int centerOffsetWidth = (effectiveTextWidth - textWidth) / 2; 208 209 // Make changes to the local variables, only commit them 210 // to the member variables after we've decided not to throw 211 // any exceptions. 212 213 int u = mU; 214 int v = mV; 215 int lineHeight = mLineHeight; 216 217 if (width > mStrikeWidth) { 218 width = mStrikeWidth; 219 } 220 221 // Is there room for this string on the current line? 222 if (u + width > mStrikeWidth) { 223 // No room, go to the next line: 224 u = 0; 225 v += lineHeight; 226 lineHeight = 0; 227 } 228 lineHeight = Math.max(lineHeight, height); 229 if (v + lineHeight > mStrikeHeight) { 230 throw new IllegalArgumentException("Out of texture space."); 231 } 232 233 int u2 = u + width; 234 int vBase = v + ascent; 235 int v2 = v + height; 236 237 if (drawBackground) { 238 background.setBounds(u, v, u + width, v + height); 239 background.draw(mCanvas); 240 } 241 242 if (drawText) { 243 mCanvas.drawText(text, 244 u + padding.left + centerOffsetWidth, 245 vBase + padding.top + centerOffsetHeight, 246 textPaint); 247 } 248 249 // We know there's enough space, so update the member variables 250 mU = u + width; 251 mV = v; 252 mLineHeight = lineHeight; 253 mLabels.add(new Label(width, height, ascent, 254 u, v + height, width, -height)); 255 return mLabels.size() - 1; 256 } 257 258 /** 259 * Call to end adding labels. Must be called before drawing starts. 260 * 261 * @param gl 262 */ endAdding(GL10 gl)263 public void endAdding(GL10 gl) { 264 checkState(STATE_ADDING, STATE_INITIALIZED); 265 gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID); 266 GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0); 267 // Reclaim storage used by bitmap and canvas. 268 mBitmap.recycle(); 269 mBitmap = null; 270 mCanvas = null; 271 } 272 273 /** 274 * Get the width in pixels of a given label. 275 * 276 * @param labelID 277 * @return the width in pixels 278 */ getWidth(int labelID)279 public float getWidth(int labelID) { 280 return mLabels.get(labelID).width; 281 } 282 283 /** 284 * Get the height in pixels of a given label. 285 * 286 * @param labelID 287 * @return the height in pixels 288 */ getHeight(int labelID)289 public float getHeight(int labelID) { 290 return mLabels.get(labelID).height; 291 } 292 293 /** 294 * Get the baseline of a given label. That's how many pixels from the top of 295 * the label to the text baseline. (This is equivalent to the negative of 296 * the label's paint's ascent.) 297 * 298 * @param labelID 299 * @return the baseline in pixels. 300 */ getBaseline(int labelID)301 public float getBaseline(int labelID) { 302 return mLabels.get(labelID).baseline; 303 } 304 305 /** 306 * Begin drawing labels. Sets the OpenGL state for rapid drawing. 307 * 308 * @param gl 309 * @param viewWidth 310 * @param viewHeight 311 */ beginDrawing(GL10 gl, float viewWidth, float viewHeight)312 public void beginDrawing(GL10 gl, float viewWidth, float viewHeight) { 313 checkState(STATE_INITIALIZED, STATE_DRAWING); 314 gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureID); 315 gl.glShadeModel(GL10.GL_FLAT); 316 gl.glEnable(GL10.GL_BLEND); 317 gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); 318 gl.glColor4x(0x10000, 0x10000, 0x10000, 0x10000); 319 gl.glMatrixMode(GL10.GL_PROJECTION); 320 gl.glPushMatrix(); 321 gl.glLoadIdentity(); 322 gl.glOrthof(0.0f, viewWidth, 0.0f, viewHeight, 0.0f, 1.0f); 323 gl.glMatrixMode(GL10.GL_MODELVIEW); 324 gl.glPushMatrix(); 325 gl.glLoadIdentity(); 326 // Magic offsets to promote consistent rasterization. 327 gl.glTranslatef(0.375f, 0.375f, 0.0f); 328 } 329 330 /** 331 * Draw a given label at a given x,y position, expressed in pixels, with the 332 * lower-left-hand-corner of the view being (0,0). 333 * 334 * @param gl 335 * @param x 336 * @param y 337 * @param labelID 338 */ draw(GL10 gl, float x, float y, int labelID)339 public void draw(GL10 gl, float x, float y, int labelID) { 340 checkState(STATE_DRAWING, STATE_DRAWING); 341 Label label = mLabels.get(labelID); 342 gl.glEnable(GL10.GL_TEXTURE_2D); 343 ((GL11)gl).glTexParameteriv(GL10.GL_TEXTURE_2D, 344 GL11Ext.GL_TEXTURE_CROP_RECT_OES, label.mCrop, 0); 345 ((GL11Ext)gl).glDrawTexiOES((int) x, (int) y, 0, 346 (int) label.width, (int) label.height); 347 } 348 349 /** 350 * Ends the drawing and restores the OpenGL state. 351 * 352 * @param gl 353 */ endDrawing(GL10 gl)354 public void endDrawing(GL10 gl) { 355 checkState(STATE_DRAWING, STATE_INITIALIZED); 356 gl.glDisable(GL10.GL_BLEND); 357 gl.glMatrixMode(GL10.GL_PROJECTION); 358 gl.glPopMatrix(); 359 gl.glMatrixMode(GL10.GL_MODELVIEW); 360 gl.glPopMatrix(); 361 } 362 checkState(int oldState, int newState)363 private void checkState(int oldState, int newState) { 364 if (mState != oldState) { 365 throw new IllegalArgumentException("Can't call this method now."); 366 } 367 mState = newState; 368 } 369 370 private static class Label { Label(float width, float height, float baseLine, int cropU, int cropV, int cropW, int cropH)371 public Label(float width, float height, float baseLine, 372 int cropU, int cropV, int cropW, int cropH) { 373 this.width = width; 374 this.height = height; 375 this.baseline = baseLine; 376 int[] crop = new int[4]; 377 crop[0] = cropU; 378 crop[1] = cropV; 379 crop[2] = cropW; 380 crop[3] = cropH; 381 mCrop = crop; 382 } 383 384 public float width; 385 public float height; 386 public float baseline; 387 public int[] mCrop; 388 } 389 390 private int mStrikeWidth; 391 private int mStrikeHeight; 392 private boolean mFullColor; 393 private Bitmap mBitmap; 394 private Canvas mCanvas; 395 private Paint mClearPaint; 396 397 private int mTextureID; 398 399 private float mTexelWidth; // Convert texel to U 400 private float mTexelHeight; // Convert texel to V 401 private int mU; 402 private int mV; 403 private int mLineHeight; 404 private ArrayList<Label> mLabels = new ArrayList<Label>(); 405 406 private static final int STATE_NEW = 0; 407 private static final int STATE_INITIALIZED = 1; 408 private static final int STATE_ADDING = 2; 409 private static final int STATE_DRAWING = 3; 410 private int mState; 411 } 412