// Copyright 2008 Google Inc. // // 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.google.android.stardroid.renderer.util; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.opengl.GLUtils; import com.google.android.stardroid.util.FixedPoint; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import javax.microedition.khronos.opengles.GL10; public class LabelMaker { private int mStrikeWidth; private int mStrikeHeight; private boolean mFullColor; private Bitmap mBitmap; private Canvas mCanvas; private Resources mRes; private TextureReference mTexture = null; private float mTexelWidth; // Convert texel to U private float mTexelHeight; // Convert texel to V /** * A class which contains data that describes a label and its position in the texture. */ public static class LabelData { public LabelData(String text, int color, int fontSize) { mText = text; mColor = color; mFontSize = fontSize; } // Sets data about the label's position in the texture. public void setTextureData(int widthInPixels, int heightInPixels, int cropU, int cropV, int cropW, int cropH, float texelWidth, float texelHeight) { mWidthInPixels = widthInPixels; mHeightInPixels = heightInPixels; int[] texCoords = new int[8]; // lower left texCoords[0] = FixedPoint.floatToFixedPoint(cropU * texelWidth); texCoords[1] = FixedPoint.floatToFixedPoint(cropV * texelHeight); // upper left texCoords[2] = FixedPoint.floatToFixedPoint(cropU * texelWidth); texCoords[3] = FixedPoint.floatToFixedPoint((cropV + cropH) * texelHeight); // lower right texCoords[4] = FixedPoint.floatToFixedPoint((cropU + cropW) * texelWidth); texCoords[5] = FixedPoint.floatToFixedPoint(cropV * texelHeight); // upper right texCoords[6] = FixedPoint.floatToFixedPoint((cropU + cropW) * texelWidth); texCoords[7] = FixedPoint.floatToFixedPoint((cropV + cropH) * texelHeight); mTexCoords = ByteBuffer.allocateDirect(8*4).order(ByteOrder.nativeOrder()).asIntBuffer(); mTexCoords.put(texCoords); mTexCoords.position(0); mCrop = new int[] { cropU, cropV, cropW, cropH }; } public String getText() { return mText; } public int getColor() { return mColor; } public int getFontSize() { return mFontSize; } public int getWidthInPixels() { return mWidthInPixels; } public int getHeightInPixels() { return mHeightInPixels; } public IntBuffer getTexCoords() { return mTexCoords; } public int[] getCrop() { return mCrop; } private String mText = ""; private int mColor = 0xffffffff; private int mFontSize = 24; private int mWidthInPixels = 0; private int mHeightInPixels = 0; private IntBuffer mTexCoords = null; private int[] mCrop = null; } /** * 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. */ public LabelMaker(boolean fullColor) { mFullColor = fullColor; mStrikeWidth = 512; mStrikeHeight = -1; } /** * Call to initialize the class. Call whenever the surface has been created. * * @param gl */ public TextureReference initialize(GL10 gl, Paint textPaint, LabelData[] labels, Resources res, TextureManager textureManager) { mRes = res; mTexture = textureManager.createTexture(gl); mTexture.bind(gl); 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); int minHeight = addLabelsInternal(gl, textPaint, false, labels); // Round up to the nearest power of two, since textures have to be a power of two in size. int roundedHeight = 1; while (roundedHeight < minHeight) roundedHeight <<= 1; mStrikeHeight = roundedHeight; mTexelWidth = (float) (1.0 / mStrikeWidth); mTexelHeight = (float) (1.0 / mStrikeHeight); beginAdding(gl); addLabelsInternal(gl, textPaint, true, labels); endAdding(gl); return mTexture; } /** * Call when the surface has been destroyed */ public void shutdown(GL10 gl) { if (mTexture != null) { mTexture.delete(gl); } } /** * Call to add a list of labels * * @param gl * @param textPaint the paint of the label * @param labels the array of labels being added * @return the required height */ private int addLabelsInternal(GL10 gl, Paint textPaint, boolean drawToCanvas, LabelData[] labels) { int u = 0; int v = 0; int lineHeight = 0; for (LabelData label : labels) { int ascent = 0; int descent = 0; int measuredTextWidth = 0; int height = 0; int width = 0; // TODO(jpowell): This is a hack to deal with text that's too wide to // fit on the screen. We should really split this up among multiple lines, // but just making the text smaller is much easier. int fontSize = label.getFontSize(); do { textPaint.setColor(0xff000000 | label.getColor()); textPaint.setTextSize(fontSize * mRes.getDisplayMetrics().density); // 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(label.getText())); height = ascent + descent; width = measuredTextWidth; // If it's wider than the screen, try it again with a font size of 1 // smaller. fontSize--; } while (fontSize > 0 && width > mRes.getDisplayMetrics().widthPixels); int nextU; // Is there room for this string on the current line? if (u + width > mStrikeWidth) { // No room, go to the next line: u = 0; nextU = width; v += lineHeight; lineHeight = 0; } else { nextU = u + width; } lineHeight = Math.max(lineHeight, height); if (v + lineHeight > mStrikeHeight && drawToCanvas) { throw new IllegalArgumentException("Out of texture space."); } int vBase = v + ascent; if (drawToCanvas) { mCanvas.drawText(label.getText(), u, vBase, textPaint); label.setTextureData(width, height, u, v + height, width, -height, mTexelWidth, mTexelHeight); } u = nextU; } return v + lineHeight; } private void beginAdding(GL10 gl) { 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); } private void endAdding(GL10 gl) { mTexture.bind(gl); GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0); // Reclaim storage used by bitmap and canvas. mBitmap.recycle(); mBitmap = null; mCanvas = null; } }