Java tutorial
/******************************************************************************* * Copyright 2012 Martijn Courteaux <martijn.courteaux@skynet.be> * * 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 net.kubin.rendering; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.nio.IntBuffer; import net.kubin.game.TextureStorage; import net.kubin.math.MathHelper; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; import org.newdawn.slick.opengl.Texture; public class GLFont { // the font that was used to generate character set Font font; // display list base for characters int fontListBase = -1; // texture containing a grid of 100 printable characters int fontTextureHandle = 0; // these will be set by getFontImageSize() and used by buildFont() int fontSize = 0; int textureSize = 0; // temp values for buildFont() int[] charwidths = new int[100]; private Texture texture; /** * Dynamically create a texture mapped character set with the given Font. * Text color will be white on a transparent background. * * @param f * Java Font object */ public GLFont(Font f) { makeFont(f, new float[] { 1, 1, 1, 1 }, new float[] { 0, 0, 0, 0 }); } /** * Create a texture mapped character set with the given Font, Text color and * background color. * * @param f * Java Font object * @param fgColor * foreground (text) color as rgb or rgba values in range 0-1 * @param bgColor * background color as rgb or rgba values in range 0-1 (set alpha * to 0 to make transparent) */ public GLFont(Font f, float[] fgColor, float[] bgColor) { makeFont(f, fgColor, bgColor); } /** * Return the handle to the texture holding the character set. */ public int getTexture() { return fontTextureHandle; } /** * Prepare a texture mapped character set with the given Font, text color * and background color. Characters will be textured onto quads and stored * in display lists. The base display list id is stored in the fontListBase * variable. After makeFont() is run the print() function can be used to * render text in this font. * * @param f * the font to draw characters * @param fgColor * foreground (text) color as rgb or rgba values in range 0-1 * @param bgColor * background color as rgb or rgba values in range 0-1 (set alpha * to 0 to make transparent) * * @see createFontImage() * @see print() */ public void makeFont(Font f, float[] fgColor, float[] bgColor) { int charsetTexture = 0; if ((charsetTexture = makeFontTexture(f, fgColor, bgColor)) > 0) { // create 100 display lists, one for each character // textureSize and fontSize are calculated by createFontImage() buildFont(charsetTexture, textureSize, fontSize); fontTextureHandle = charsetTexture; font = f; } } /** * Return a texture containing a character set with the given Font arranged * in a 10x10 grid of printable characters. * * @param f * the font to draw characters * @param fgColor * foreground (text) color as rgb or rgba values in range 0-1 * @param bgColor * background color as rgb or rgba values in range 0-1 (set alpha * to 0 to make transparent) * @see createFontImage() * @see print() */ public int makeFontTexture(Font f, float[] fgColor, float[] bgColor) { int texture = 0; try { // Create a BufferedImage containing a 10x10 grid of printable // characters BufferedImage image = createFontImage(f, // the font fgColor, // text color bgColor); // background color // make a texture with the buffered image this.texture = TextureStorage.loadTexture("font-" + f.getFontName(), image); texture = this.texture.getTextureID(); } catch (Exception e) { System.out.println("makeChar(): exception " + e); } return texture; } /** * Return a texture containing the given single character with the Courier * font. TO DO: pass Font as a parameter. * * @param onechar * character to draw into texture */ public static int makeCharTexture(Font f, String onechar, float[] fgColor, float[] bgColor) { int texture = 0; try { // Create a BufferedImage with one character BufferedImage image = createCharImage(onechar, f, // the font fgColor, // text bgColor); // background // make a texture from the image Texture tex = TextureStorage.loadTexture("Font-" + f.getFontName() + "-" + onechar, image); texture = tex.getTextureID(); } catch (Exception e) { System.out.println("makeChar(): exception " + e); } return texture; } /** * return a BufferedImage containing the given character drawn with the * given font. Character will be drawn on its baseline, and centered * horizontally in the image. * * @param text * a single character to render * @param font * the font to render with * @param fgColor * foreground (text) color as rgb or rgba values in range 0-1 * @param bgColor * background color as rgb or rgba values in range 0-1 (set alpha * to 0 to make transparent) * @return */ public static BufferedImage createCharImage(String text, Font font, float[] fgColor, float[] bgColor) { Color bg = bgColor == null ? new Color(0, 0, 0, 0) : (bgColor.length == 3 ? new Color(bgColor[0], bgColor[1], bgColor[2], 1) : new Color(bgColor[0], bgColor[1], bgColor[2], bgColor[3])); Color fg = fgColor == null ? new Color(1, 1, 1, 1) : (fgColor.length == 3 ? new Color(fgColor[0], fgColor[1], fgColor[2], 1) : new Color(fgColor[0], fgColor[1], fgColor[2], fgColor[3])); boolean isAntiAliased = true; boolean usesFractionalMetrics = false; // get size of texture image neaded to hold largest character of this // font int maxCharSize = getFontSize(font); int imgSize = MathHelper.getPowerOfTwoBiggerThan(maxCharSize); if (imgSize > 2048) { throw new RuntimeException("GLFont.createCharImage(): texture size will be too big (" + imgSize + ") Make the font size smaller."); } // we'll draw text into this image BufferedImage image = new BufferedImage(imgSize, imgSize, BufferedImage.TYPE_INT_ARGB); Graphics2D g = image.createGraphics(); // Clear image with background color (make transparent if color has // alpha value) if (bg.getAlpha() < 255) { g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, (float) bg.getAlpha() / 255f)); } g.setColor(bg); g.fillRect(0, 0, imgSize, imgSize); // prepare to draw character in foreground color g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); g.setColor(fg); g.setFont(font); g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, isAntiAliased ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, usesFractionalMetrics ? RenderingHints.VALUE_FRACTIONALMETRICS_ON : RenderingHints.VALUE_FRACTIONALMETRICS_OFF); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // place the character (on baseline, centered horizontally) FontMetrics fm = g.getFontMetrics(); int cwidth = fm.charWidth(text.charAt(0)); int height = fm.getHeight(); int ascent = fm.getAscent(); int vborder = (int) ((float) (imgSize - height) / 2f); int hborder = (int) ((float) (imgSize - cwidth) / 2f); g.drawString(text, hborder, vborder + ascent); g.dispose(); return image; } /** * Return a BufferedImage containing 100 printable characters drawn with the * given font. Characters will be arranged in a 10x10 grid. * * @param text * @param font * @param imgSize * a power of two (32 64 256 etc) * @param fgColor * foreground (text) color as rgb or rgba values in range 0-1 * @param bgColor * background color as rgb or rgba values in range 0-1 (set alpha * to 0 to make transparent) * @return */ public BufferedImage createFontImage(Font font, float[] fgColor, float[] bgColor) { Color bg = bgColor == null ? new Color(0, 0, 0, 0) : (bgColor.length == 3 ? new Color(bgColor[0], bgColor[1], bgColor[2], 1) : new Color(bgColor[0], bgColor[1], bgColor[2], bgColor[3])); Color fg = fgColor == null ? new Color(1, 1, 1, 1) : (fgColor.length == 3 ? new Color(fgColor[0], fgColor[1], fgColor[2], 1) : new Color(fgColor[0], fgColor[1], fgColor[2], fgColor[3])); boolean isAntiAliased = false; boolean usesFractionalMetrics = false; // get size of texture image neaded to hold 10x10 character grid fontSize = getFontSize(font); textureSize = MathHelper.getPowerOfTwoBiggerThan(fontSize * 10); System.out.println("GLFont.getFontImageSize(): build font with fontsize=" + fontSize + " gridsize=" + (fontSize * 10) + " texturesize=" + textureSize); if (textureSize > 2048) { throw new RuntimeException("GLFont.createFontImage(): texture size will be too big (" + textureSize + ") Make the font size smaller."); } // create a buffered image to hold charset BufferedImage image = new BufferedImage(textureSize, textureSize, BufferedImage.TYPE_INT_ARGB); Graphics2D g = image.createGraphics(); // Clear image with background color (make transparent if color has // alpha value) if (bg.getAlpha() < 255) { g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, (float) bg.getAlpha() / 255f)); } g.setColor(bg); g.fillRect(0, 0, textureSize, textureSize); // prepare to draw characters in foreground color g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); g.setColor(fg); g.setFont(font); g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, isAntiAliased ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, usesFractionalMetrics ? RenderingHints.VALUE_FRACTIONALMETRICS_ON : RenderingHints.VALUE_FRACTIONALMETRICS_OFF); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // get font measurements FontMetrics fm = g.getFontMetrics(); int ascent = fm.getMaxAscent(); // draw the grid of 100 characters for (int r = 0; r < 10; r++) { for (int c = 0; c < 10; c++) { char ch = (char) (32 + ((r * 10) + c)); g.drawString(String.valueOf(ch), (c * fontSize), (r * fontSize) + ascent); charwidths[(r * 10) + c] = fm.charWidth(ch); } } g.dispose(); return image; } /** * Return the maximum character size of the given Font. This will be the max * of the vertical and horizontal font dimensions, so can be used to create * a square image large enough to hold any character rendered with this * Font. * <P> * Creates a BufferedImage and Graphics2D graphics context to get font sizes * (is there a more efficient way to do this?). * <P> * * @param font * Font object describes the font to render with * @return power-of-two texture size large enough to hold the character set */ public static int getFontSize(Font font) { boolean isAntiAliased = true; boolean usesFractionalMetrics = false; // just a dummy image so we can get a graphics context BufferedImage image = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB); Graphics2D g = image.createGraphics(); // prepare to draw character g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); g.setFont(font); g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, isAntiAliased ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, usesFractionalMetrics ? RenderingHints.VALUE_FRACTIONALMETRICS_ON : RenderingHints.VALUE_FRACTIONALMETRICS_OFF); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // get character measurements FontMetrics fm = g.getFontMetrics(); int ascent = fm.getMaxAscent(); int descent = fm.getMaxDescent(); int advance = fm.charWidth('W'); // width of widest char, more reliable // than getMaxAdvance(); int leading = fm.getLeading(); // calculate size of 10x10 character grid int fontHeight = ascent + descent + (leading / 2); int fontWidth = advance; int maxCharSize = Math.max(fontHeight, fontWidth); return maxCharSize; } /** * Build the character set display list from the given texture. Creates one * quad for each character, with one letter textured onto each quad. Assumes * the texture is a 256x256 image containing every character of the charset * arranged in a 16x16 grid. Each character is 16x16 pixels. Call * destroyFont() to release the display list memory. * * Should be in ORTHO (2D) mode to render text (see setOrtho()). * * Special thanks to NeHe and Giuseppe D'Agata for the "2D Texture Font" * tutorial (http://nehe.gamedev.net). * * @param charSetImage * texture image containing 100 characters in a 10x10 grid * @param fontWidth * how many pixels to allow per character on screen * * @see destroyFont() */ public void buildFont(int fontTxtrHandle, int textureSize, int fontSize) { int unitSize = fontSize; // pixel size of one block in 10x10 grid float usize = (float) unitSize / (float) (textureSize); // UV size of // one block in // grid float chU, chV; // character UV position // Create 100 Display Lists fontListBase = GL11.glGenLists(100); // make a quad for each character in texture for (int i = 0; i < 100; i++) { int x = (i % 10); // column int y = (i / 10); // row // make character UV coordinate // the character V position is tricky because we have to invert the // V coord // (char # 0 is at top of texture image, but V 0 is at bottom) chU = (float) (x * unitSize) / (float) textureSize; chV = (float) (y * unitSize) / (float) textureSize; // chV = (float) (textureSize - (y * unitSize) - unitSize) / (float) // textureSize; GL11.glNewList(fontListBase + i, GL11.GL_COMPILE); // start display // list { GL11.glBegin(GL11.GL_QUADS); // Make A unitSize square quad { GL11.glTexCoord2f(chU, chV); // Texture Coord (Bottom Left) GL11.glVertex2i(0, unitSize); GL11.glTexCoord2f(chU + usize, chV); // Texture Coord // (Bottom Right) GL11.glVertex2i(unitSize, unitSize); GL11.glTexCoord2f(chU + usize, chV + usize); // Texture // Coord // (Top // Right) GL11.glVertex2i(unitSize, 0); GL11.glTexCoord2f(chU, chV + usize); // Texture Coord (Top // Left) GL11.glVertex2i(0, 0); } GL11.glEnd(); GL11.glTranslatef(charwidths[i], 0, 0); // shift right the width // of the character } GL11.glEndList(); // done display list } } /** * Clean up the allocated display lists for the character set. */ public void destroyFont() { if (fontListBase != -1) { GL11.glDeleteLists(fontListBase, 100); fontListBase = -1; } } /** * Render a text string in 2D over the scene, using the character set * created by this GLFont object. * * @see makeFont() */ public void print(int x, int y, String msg) { if (msg != null) { GL11.glPushMatrix(); GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); // enable the charset texture GL11.glEnable(GL11.GL_TEXTURE_2D); texture.bind(); // draw the text GL11.glTranslatef(x, y, 0); // Position The Text (in pixel coords) IntBuffer buffer = BufferUtils.createIntBuffer(msg.length()); for (int i = 0; i < msg.length(); i++) { buffer.put(fontListBase + (msg.charAt(i) - 32)); } buffer.flip(); GL11.glCallLists(buffer); GL11.glPopMatrix(); } } }