/** * The MIT License (MIT) * * Copyright (c) 2014 momokan (http://lwjgfont.chocolapod.net) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package net.chocolapod.lwjgfont.texture; import static org.lwjgl.opengl.GL11.GL_LINEAR; import static org.lwjgl.opengl.GL11.GL_RGBA; import static org.lwjgl.opengl.GL11.GL_RGBA16; import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER; import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER; import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; import static org.lwjgl.opengl.GL11.GL_NEAREST; import static org.lwjgl.opengl.GL11.GL_LINEAR_MIPMAP_LINEAR; import static org.lwjgl.opengl.GL11.glBindTexture; import static org.lwjgl.opengl.GL11.glTexImage2D; import static org.lwjgl.opengl.GL11.glTexParameteri; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferUShort; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.util.HashMap; import java.util.Map; import javax.imageio.ImageIO; import org.lwjgl.BufferUtils; import org.lwjgl.LWJGLException; import org.lwjgl.input.Keyboard; import org.lwjgl.opengl.ContextAttribs; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.DisplayMode; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL13; import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL30; import org.lwjgl.opengl.PixelFormat; public class FontTextureLoader { private static final Map<String, FontTexture> texturesMap = new HashMap<>(); public static FontTexture loadTexture(Class clazz, String imagePath) throws IOException { FontTexture texture = texturesMap.get(imagePath); if (texture == null) { texture = makeTexture(clazz, imagePath); texturesMap.put(imagePath, texture); } return texture; } private static FontTexture makeTexture(Class clazz, String imagePath) throws IOException { BufferedImage srcImage; int srcImageType; srcImage = ImageIO.read(clazz.getResourceAsStream(imagePath)); srcImageType = srcImage.getType(); int target = GL_TEXTURE_2D; // target int dstPixelFormat = GL_RGBA; // dst pixel format int format = GL_UNSIGNED_BYTE; // data type // テクスチャー ID を生成する int textureID = GL11.glGenTextures(); FontTexture texture = new FontTexture(target, textureID); // glTexImage2D() の対象となるテクスチャー ID をバインドする glBindTexture(target, textureID); // All RGB bytes are aligned to each other and each component is 1 byte // GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); int width = srcImage.getWidth(); int height = srcImage.getHeight(); texture.setWidth(width); texture.setHeight(height); texture.setTextureWidth(width); texture.setTextureHeight(height); texture.setAlphaPremultiplied(false); ByteBuffer byteBuffer; Pixel pixel = null; // System.out.println(srcImageType); if (srcImageType == BufferedImage.TYPE_INT_ARGB) { throw new RuntimeException("Unsupported type: " + srcImage.getType()); } else if (srcImageType == BufferedImage.TYPE_3BYTE_BGR) { pixel = new Pixel3ByteBGR(); } else if (srcImageType == BufferedImage.TYPE_4BYTE_ABGR) { // Photoshop の 8bit/チャネル として処理する // これは ABGR の各色について、それぞれが 8bit(1Byte) の画像フォーマットとなる。 pixel = new Pixel4ByteABGR(); } else if (srcImageType == BufferedImage.TYPE_CUSTOM) { // Photoshop の 16bit/チャネル として処理する // これは ABGR の各色について、それぞれが 16bit(2Byte) の画像フォーマットとなる。 // pixel = new Pixel8ByteABGR(); // この辺の設定は、ひとまず // MikMikuStudio/engine/src/core/com/jme3/texture/Image.java // MikMikuStudio/engine/src/jogl2/com/jme3/renderer/jogl/TextureUtil.java // を参考にしてみた // pixel = new Pixel4ByteABGR(); /* dstPixelFormat = GL_RGBA2; dstPixelFormat = GL_RGBA8; */ dstPixelFormat = GL_RGBA16; /* format = GL_UNSIGNED_SHORT; format = GL_SHORT; format = GL_UNSIGNED_SHORT_4_4_4_4; */ // Miku.png (8bit/channel)がこれで動いたので、ひとまず Pixel4ByteABGR で動かす pixel = new Pixel4ByteABGR(); } else { throw new RuntimeException("Unsupported type: " + srcImage.getType()); } byteBuffer = pixel.toBuffer(srcImage); byteBuffer.order(ByteOrder.nativeOrder()); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int b[] = srcImage.getRaster().getPixel(x, y, pixel.getBuffer()); pixel.writeBuffer(byteBuffer); } } byteBuffer.flip(); // 画像の拡大・縮小時の補間方法を設定する glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // バイト配列と色情報のフォーマットからテクスチャーを生成する glTexImage2D(target, 0, dstPixelFormat, // テクスチャ内のカラー要素数 width, height, 0, // テクスチャの境界幅。境界が存在しない場合は 0、存在する場合は 1 GL_RGBA, // ピクセル内の色の順序。 参考 http://wisdom.sakura.ne.jp/system/opengl/gl22.html format, // 各チャネル(色)のデータ型。 参考 http://wisdom.sakura.ne.jp/system/opengl/gl22.html byteBuffer); // ミップマップの自動生成 // GL30.glGenerateMipmap(GL_TEXTURE_2D); byteBuffer.clear(); return texture; } public static void dispose() { for (FontTexture texture: texturesMap.values()) { texture.dispose(); } texturesMap.clear(); } interface Pixel { public ByteBuffer toBuffer(BufferedImage srcImage); public int[] getBuffer(); public void writeBuffer(ByteBuffer byteBuffer); } // 1 ピクセルあたりのビット深度 64 の画像フォーマットの変換用クラス /* static class Pixel8ByteABGR implements Pixel { private int[] buffer = new int[8]; @Override public int[] getBuffer() { return buffer; } @Override public byte[] getRed() { return new byte[] {(byte)buffer[0]}; // return new byte[] {(byte)buffer[0], (byte)buffer[1]}; } @Override public byte[] getGreen() { return new byte[] {(byte)buffer[1]}; // return new byte[] {(byte)buffer[2], (byte)buffer[3]}; } @Override public byte[] getBlue() { return new byte[] {(byte)buffer[2]}; // return new byte[] {(byte)buffer[4], (byte)buffer[5]}; } @Override public byte[] getAlpha() { return new byte[] {(byte)buffer[3]}; // return new byte[] {(byte)buffer[6], (byte)buffer[7]}; } @Override public ByteBuffer toBuffer(BufferedImage srcImage) { DataBufferUShort imageBuffer = (DataBufferUShort)srcImage.getRaster().getDataBuffer(); ByteBuffer byteBuffer = ByteBuffer.allocateDirect(imageBuffer.getSize() * 2); return byteBuffer; } } */ // 1 ピクセルあたりのビット深度 32 の画像フォーマットの変換用クラス static class Pixel4ByteABGR implements Pixel { private int[] buffer = new int[4]; @Override public int[] getBuffer() { return buffer; } @Override public ByteBuffer toBuffer(BufferedImage srcImage) { // 読み込む画像も返還後のテクスチャーも 4 Byte / pixel なので、 // もとのバイト列と同じ大きさのバイト領域を用意する DataBufferByte imageBuffer = (DataBufferByte)srcImage.getRaster().getDataBuffer(); ByteBuffer byteBuffer = ByteBuffer.allocateDirect(imageBuffer.getSize()); return byteBuffer; } @Override public void writeBuffer(ByteBuffer byteBuffer) { byteBuffer.put(new byte[] { (byte)buffer[0], // Red (byte)buffer[1], // Green (byte)buffer[2], // Blue (byte)buffer[3] // Alpha }); } } static class Pixel3ByteBGR implements Pixel { private int[] buffer = new int[3]; @Override public int[] getBuffer() { return buffer; } @Override public ByteBuffer toBuffer(BufferedImage srcImage) { // 読み込む画像は 3 Byte / pixel だが、変換後のテクスチャーはアルファを足して 4 Byte / pixel になるので、 // もとのバイト列の 3/4 倍の長さのバイト領域を用意する DataBufferByte imageBuffer = (DataBufferByte)srcImage.getRaster().getDataBuffer(); ByteBuffer byteBuffer = ByteBuffer.allocateDirect(imageBuffer.getSize() / 3 * 4); return byteBuffer; } @Override public void writeBuffer(ByteBuffer byteBuffer) { byteBuffer.put(new byte[] { (byte)buffer[0], // Red (byte)buffer[1], // Green (byte)buffer[2], // Blue (byte)0xff }); } } }