Back to project page Processing-Android-Eclipse-Demos.
The source code is released under:
MIT License
If you think the Android project Processing-Android-Eclipse-Demos listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ //w w w . ja v a 2 s . c o m /* Part of the Processing project - http://processing.org Copyright (c) 2011-12 Ben Fry and Casey Reas This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.processing.opengl; import java.util.HashMap; import com.processing.core.PApplet; import com.processing.core.PConstants; import com.processing.core.PFont; import com.processing.core.PGraphics; import com.processing.core.PImage; /** * All the infrastructure needed for optimized font rendering * in OpenGL. Basically, this special class is needed because * fonts in Processing are handled by a separate PImage for each * glyph. For performance reasons, all these glyphs should be * stored in a single OpenGL texture (otherwise, rendering a * string of text would involve binding and un-binding several * textures. * PFontTexture manages the correspondence between individual * glyphs and the large OpenGL texture containing them. Also, * in the case that the font size is very large, one single * OpenGL texture might not be enough to store all the glyphs, * so PFontTexture also takes care of spreading a single font * over several textures. * @author Andres Colubri */ class FontTexture implements PConstants { protected PGL pgl; protected boolean is3D; protected int minSize; protected int maxSize; protected int offsetX; protected int offsetY; protected int lineHeight; protected Texture[] textures = null; protected PImage[] images = null; protected int currentTex; protected int lastTex; protected TextureInfo[] glyphTexinfos; protected HashMap<PFont.Glyph, TextureInfo> texinfoMap; public FontTexture(PGraphicsOpenGL pg, PFont font, boolean is3D) { pgl = PGraphicsOpenGL.pgl; this.is3D = is3D; initTexture(pg, font); } protected void allocate() { // Nothing to do here: the font textures will allocate // themselves. } protected void dispose() { for (int i = 0; i < textures.length; i++) { textures[i].dispose(); } } protected void initTexture(PGraphicsOpenGL pg, PFont font) { currentTex = -1; lastTex = -1; int spow = PGL.nextPowerOfTwo(font.getSize()); minSize = PApplet.min(PGraphicsOpenGL.maxTextureSize, PApplet.max(PGL.MIN_FONT_TEX_SIZE, spow)); maxSize = PApplet.min(PGraphicsOpenGL.maxTextureSize, PApplet.max(PGL.MAX_FONT_TEX_SIZE, 2 * spow)); if (maxSize < spow) { PGraphics.showWarning("The font size is too large to be properly " + "displayed with OpenGL"); } addTexture(pg); offsetX = 0; offsetY = 0; lineHeight = 0; texinfoMap = new HashMap<PFont.Glyph, TextureInfo>(); glyphTexinfos = new TextureInfo[font.getGlyphCount()]; addAllGlyphsToTexture(pg, font); } public boolean addTexture(PGraphicsOpenGL pg) { int w, h; boolean resize; w = maxSize; if (-1 < currentTex && textures[currentTex].glHeight < maxSize) { // The height of the current texture is less than the maximum, this // means we can replace it with a larger texture. h = PApplet.min(2 * textures[currentTex].glHeight, maxSize); resize = true; } else { h = minSize; resize = false; } Texture tex; if (is3D) { // Bilinear sampling ensures that the texture doesn't look pixelated // either when it is magnified or minified... tex = new Texture(w, h, new Texture.Parameters(ARGB, Texture.BILINEAR, false)); } else { // ...however, the effect of bilinear sampling is to add some blurriness // to the text in its original size. In 2D, we assume that text will be // shown at its original size, so linear sampling is chosen instead (which // only affects minimized text). tex = new Texture(w, h, new Texture.Parameters(ARGB, Texture.LINEAR, false)); } if (textures == null) { textures = new Texture[1]; textures[0] = tex; images = new PImage[1]; images[0] = pg.wrapTexture(tex); currentTex = 0; } else if (resize) { // Replacing old smaller texture with larger one. // But first we must copy the contents of the older // texture into the new one. Texture tex0 = textures[currentTex]; tex.put(tex0); textures[currentTex] = tex; pg.setCache(images[currentTex], tex); images[currentTex].width = tex.width; images[currentTex].height = tex.height; } else { // Adding new texture to the list. Texture[] tempTex = textures; textures = new Texture[textures.length + 1]; PApplet.arrayCopy(tempTex, textures, tempTex.length); textures[tempTex.length] = tex; currentTex = textures.length - 1; PImage[] tempImg = images; images = new PImage[textures.length]; PApplet.arrayCopy(tempImg, images, tempImg.length); images[tempImg.length] = pg.wrapTexture(tex); } lastTex = currentTex; // Make sure that the current texture is bound. tex.bind(); return resize; } public void begin() { setTexture(0); } public void end() { for (int i = 0; i < textures.length; i++) { pgl.disableTexturing(textures[i].glTarget); } } public void setTexture(int idx) { if (0 <= idx && idx < textures.length) { currentTex = idx; } } public PImage getTexture(int idx) { if (0 <= idx && idx < images.length) { return images[idx]; } return null; } public PImage getCurrentTexture() { return getTexture(currentTex); } // Add all the current glyphs to opengl texture. public void addAllGlyphsToTexture(PGraphicsOpenGL pg, PFont font) { // loop over current glyphs. for (int i = 0; i < font.getGlyphCount(); i++) { addToTexture(pg, i, font.getGlyph(i)); } } public void updateGlyphsTexCoords() { // loop over current glyphs. for (int i = 0; i < glyphTexinfos.length; i++) { TextureInfo tinfo = glyphTexinfos[i]; if (tinfo != null && tinfo.texIndex == currentTex) { tinfo.updateUV(); } } } public TextureInfo getTexInfo(PFont.Glyph glyph) { TextureInfo info = texinfoMap.get(glyph); return info; } public TextureInfo addToTexture(PGraphicsOpenGL pg, PFont.Glyph glyph) { int n = glyphTexinfos.length; if (n == 0) { glyphTexinfos = new TextureInfo[1]; } addToTexture(pg, n, glyph); return glyphTexinfos[n]; } public boolean contextIsOutdated() { boolean outdated = false; for (int i = 0; i < textures.length; i++) { if (textures[i].contextIsOutdated()) { outdated = true; } } if (outdated) { for (int i = 0; i < textures.length; i++) { PGraphicsOpenGL.removeTextureObject(textures[i].glName, textures[i].context); textures[i].glName = 0; } } return outdated; } // Adds this glyph to the opengl texture in PFont. protected void addToTexture(PGraphicsOpenGL pg, int idx, PFont.Glyph glyph) { // We add one pixel to avoid issues when sampling the font texture at // fractional screen positions. I.e.: the pixel on the screen only contains // half of the font rectangle, so it would sample half of the color from the // glyph area in the texture, and the other half from the contiguous pixel. // If the later contains a portion of the neighbor glyph and the former // doesn't, this would result in a shaded pixel when the correct output is // blank. This is a consequence of putting all the glyphs in a common // texture with bilinear sampling. int w = 1 + glyph.width + 1; int h = 1 + glyph.height + 1; // Converting the pixels array from the PImage into a valid RGBA array for // OpenGL. int[] rgba = new int[w * h]; int t = 0; int p = 0; if (PGL.BIG_ENDIAN) { java.util.Arrays.fill(rgba, 0, w, 0xFFFFFF00); // Set the first row to blank pixels. t = w; for (int y = 0; y < glyph.height; y++) { rgba[t++] = 0xFFFFFF00; // Set the leftmost pixel in this row as blank for (int x = 0; x < glyph.width; x++) { rgba[t++] = 0xFFFFFF00 | glyph.image.pixels[p++]; } rgba[t++] = 0xFFFFFF00; // Set the rightmost pixel in this row as blank } java.util.Arrays.fill(rgba, (h - 1) * w, h * w, 0xFFFFFF00); // Set the last row to blank pixels. } else { java.util.Arrays.fill(rgba, 0, w, 0x00FFFFFF); // Set the first row to blank pixels. t = w; for (int y = 0; y < glyph.height; y++) { rgba[t++] = 0x00FFFFFF; // Set the leftmost pixel in this row as blank for (int x = 0; x < glyph.width; x++) { rgba[t++] = (glyph.image.pixels[p++] << 24) | 0x00FFFFFF; } rgba[t++] = 0x00FFFFFF; // Set the rightmost pixel in this row as blank } java.util.Arrays.fill(rgba, (h - 1) * w, h * w, 0x00FFFFFF); // Set the last row to blank pixels. } // Is there room for this glyph in the current line? if (offsetX + w > textures[currentTex].glWidth) { // No room, go to the next line: offsetX = 0; offsetY += lineHeight; lineHeight = 0; } lineHeight = Math.max(lineHeight, h); boolean resized = false; if (offsetY + lineHeight > textures[currentTex].glHeight) { // We run out of space in the current texture, so we add a new texture: resized = addTexture(pg); if (resized) { // Because the current texture has been resized, we need to // update the UV coordinates of all the glyphs associated to it: updateGlyphsTexCoords(); } else { // A new texture has been created. Reseting texture coordinates // and line. offsetX = 0; offsetY = 0; lineHeight = 0; } } TextureInfo tinfo = new TextureInfo(currentTex, offsetX, offsetY, w, h, rgba); offsetX += w; if (idx == glyphTexinfos.length) { TextureInfo[] temp = new TextureInfo[glyphTexinfos.length + 1]; System.arraycopy(glyphTexinfos, 0, temp, 0, glyphTexinfos.length); glyphTexinfos = temp; } glyphTexinfos[idx] = tinfo; texinfoMap.put(glyph, tinfo); } class TextureInfo { int texIndex; int width; int height; int[] crop; float u0, u1; float v0, v1; int[] pixels; TextureInfo(int tidx, int cropX, int cropY, int cropW, int cropH, int[] pix) { texIndex = tidx; crop = new int[4]; // The region of the texture corresponding to the glyph is surrounded by a // 1-pixel wide border to avoid artifacts due to bilinear sampling. This // is why the additions and subtractions to the crop values. crop[0] = cropX + 1; crop[1] = cropY + 1 + cropH - 2; crop[2] = cropW - 2; crop[3] = -cropH + 2; pixels = pix; updateUV(); updateTex(); } void updateUV() { width = textures[texIndex].glWidth; height = textures[texIndex].glHeight; u0 = (float)crop[0] / (float)width; u1 = u0 + (float)crop[2] / (float)width; v0 = (float)(crop[1] + crop[3]) / (float)height; v1 = v0 - (float)crop[3] / (float)height; } void updateTex() { textures[texIndex].setNative(pixels, crop[0] - 1, crop[1] + crop[3] - 1, crop[2] + 2, -crop[3] + 2); } } }