Home | History | Annotate | Download | only in spritetext
      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      */
     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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 {
    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