/* Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp 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 General Public License for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.memory; import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444; import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551; import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650; import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888; import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED; import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_8BIT_INDEXED; import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_16BIT_INDEXED; import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_32BIT_INDEXED; import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_DXT1; import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_DXT3; import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_DXT5; import static jpcsp.graphics.GeCommands.CMODE_FORMAT_16BIT_BGR5650; import static jpcsp.graphics.GeCommands.CMODE_FORMAT_16BIT_ABGR5551; import static jpcsp.graphics.GeCommands.CMODE_FORMAT_16BIT_ABGR4444; import static jpcsp.graphics.GeCommands.CMODE_FORMAT_32BIT_ABGR8888; import jpcsp.graphics.GeCommands; import jpcsp.graphics.RE.IRenderingEngine; /** * @author gid15 * */ public class ImageReader { /** * Return an Image Reader implementing the IMemoryReader interface. * The image is read from memory and the following formats are supported: * - TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650 * - TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551 * - TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444 * - TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888 * - TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED (with clut) * - TPSM_PIXEL_STORAGE_MODE_8BIT_INDEXED (with clut) * - TPSM_PIXEL_STORAGE_MODE_16BIT_INDEXED (with clut) * - TPSM_PIXEL_STORAGE_MODE_32BIT_INDEXED (with clut) * - TPSM_PIXEL_STORAGE_MODE_DXT1 * - TPSM_PIXEL_STORAGE_MODE_DXT3 * - TPSM_PIXEL_STORAGE_MODE_DXT5 * - swizzled or not * * A call the IMemoryReader.readNext() will return the next pixel color when * reading from the top left pixel to bottom right pixel. * * The pixel color is always returned in the format GU_COLOR_8888 (ABGR). * * When the bufferWidth is larger than the image width, the extra pixels * are automatically skipped and not returned by readNext(). * When the bufferWidth is smaller than the image width, a maximum of bufferWidth * pixels is returned by readNext(). * The total number of pixels returned by readNext() is * height * Math.min(width, bufferWidth) * * @param address the address of the top left pixel of the image * @param width the width (in pixels) of the image * @param height the height (in pixels) of the image * @param bufferWidth the maximum number of pixels stored in memory for each row * @param pixelFormat the format of the pixel in memory. * The following formats are supported: * TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650 * TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551 * TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444 * TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888 * TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED * TPSM_PIXEL_STORAGE_MODE_8BIT_INDEXED * TPSM_PIXEL_STORAGE_MODE_16BIT_INDEXED * TPSM_PIXEL_STORAGE_MODE_32BIT_INDEXED * TPSM_PIXEL_STORAGE_MODE_DXT1 * TPSM_PIXEL_STORAGE_MODE_DXT3 * TPSM_PIXEL_STORAGE_MODE_DXT5 * @param swizzle false if the image is stored sequentially in memory. * true if the image is stored swizzled in memory. * @param clutAddr the address of the first clut element * @param clutMode the format of the clut entries * CMODE_FORMAT_16BIT_BGR5650 * CMODE_FORMAT_16BIT_ABGR5551 * CMODE_FORMAT_16BIT_ABGR4444 * CMODE_FORMAT_32BIT_ABGR8888 * @param clutNumBlocks the number of clut blocks * @param clutStart the clut start index * @param clutShift the clut index shift * @param clutMask the clut index mask * @param clut32 a pre-read clut * @param clut16 a pre-read clut * @return the Image Reader implementing the IMemoryReader interface */ public static IMemoryReader getImageReader(int address, int width, int height, int bufferWidth, int pixelFormat, boolean swizzle, int clutAddr, int clutMode, int clutNumBlocks, int clutStart, int clutShift, int clutMask, int[] clut32, short[] clut16) { // // Step 1: read from memory as 32-bit values // int byteSize = getImageByteSize(width, height, bufferWidth, pixelFormat); IMemoryReader imageReader = MemoryReader.getMemoryReader(address, byteSize, 4); // // Step 2: unswizzle the memory if applicable // if (swizzle) { imageReader = new SwizzleDecoder(imageReader, bufferWidth, getBytesPerPixel(pixelFormat)); } // // Step 3: split the 32-bit values to smaller values based on the pixel format // Step 4: convert the pixel for 32-bit values in format 8888 ABGR // boolean hasClut = false; boolean isCompressed = false; switch (pixelFormat) { case TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444: imageReader = new Value32to16Decoder(imageReader); imageReader = new PixelFormat4444Decoder(imageReader); break; case TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551: imageReader = new Value32to16Decoder(imageReader); imageReader = new PixelFormat5551Decoder(imageReader); break; case TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650: imageReader = new Value32to16Decoder(imageReader); imageReader = new PixelFormat565Decoder(imageReader); break; case TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888: break; case TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED: imageReader = new Value32to4Decoder(imageReader); imageReader = getClutDecoder(imageReader, 4, clutAddr, clutMode, clutNumBlocks, clutStart, clutShift, clutMask, clut32, clut16); hasClut = true; break; case TPSM_PIXEL_STORAGE_MODE_8BIT_INDEXED: imageReader = new Value32to8Decoder(imageReader); imageReader = getClutDecoder(imageReader, 8, clutAddr, clutMode, clutNumBlocks, clutStart, clutShift, clutMask, clut32, clut16); hasClut = true; break; case TPSM_PIXEL_STORAGE_MODE_16BIT_INDEXED: imageReader = new Value32to16Decoder(imageReader); imageReader = getClutDecoder(imageReader, 16, clutAddr, clutMode, clutNumBlocks, clutStart, clutShift, clutMask, clut32, clut16); hasClut = true; break; case TPSM_PIXEL_STORAGE_MODE_32BIT_INDEXED: imageReader = getClutDecoder(imageReader, 32, clutAddr, clutMode, clutNumBlocks, clutStart, clutShift, clutMask, clut32, clut16); hasClut = true; break; case TPSM_PIXEL_STORAGE_MODE_DXT1: imageReader = new DXT1Decoder(imageReader, width, height, bufferWidth); isCompressed = true; break; case TPSM_PIXEL_STORAGE_MODE_DXT3: imageReader = new DXT3Decoder(imageReader, width, height, bufferWidth); isCompressed = true; break; case TPSM_PIXEL_STORAGE_MODE_DXT5: imageReader = new DXT5Decoder(imageReader, width, height, bufferWidth); isCompressed = true; break; } // // Step 5: convert the values produced by the clut to 32-bit values in 8888 ABGR format // if (hasClut) { switch (clutMode) { case CMODE_FORMAT_16BIT_BGR5650: imageReader = new PixelFormat565Decoder(imageReader); break; case CMODE_FORMAT_16BIT_ABGR5551: imageReader = new PixelFormat5551Decoder(imageReader); break; case CMODE_FORMAT_16BIT_ABGR4444: imageReader = new PixelFormat4444Decoder(imageReader); break; case CMODE_FORMAT_32BIT_ABGR8888: break; } } // // Step 6: remove the extra row pixels if bufferWidth is larger than width // if (!isCompressed && bufferWidth > width) { imageReader = new MemoryImageDecoder(imageReader, width, bufferWidth); } return imageReader; } private static boolean isSimpleClutMask(int indexBits, int clutMask) { // clutMask 0xFF means no masking if (clutMask == 0xFF) { return true; } // For TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED, a clut mask 0x.F also means no masking if (indexBits == 4 && (clutMask & 0xF) == 0xF) { return true; } return false; } private static IMemoryReader getClutDecoder(IMemoryReader imageReader, int indexBits, int clutAddr, int clutMode, int clutNumBlocks, int clutStart, int clutShift, int clutMask, int[] clut32, short[] clut16) { if (clutStart == 0 && clutShift == 0 && isSimpleClutMask(indexBits, clutMask)) { if (clutMode == CMODE_FORMAT_32BIT_ABGR8888 && clut32 != null) { imageReader = new SimpleClutDecoder(imageReader, clut32, clutMode, indexBits); } else if (clut16 != null) { imageReader = new SimpleClutDecoder(imageReader, clut16, clutMode, indexBits); } else { imageReader = new SimpleClutDecoder(imageReader, clutAddr, clutMode, clutNumBlocks, indexBits); } } else { if (clutMode == CMODE_FORMAT_32BIT_ABGR8888 && clut32 != null) { imageReader = new ClutDecoder(imageReader, clut32, clutMode, clutStart, clutShift, clutMask); } else if (clut16 != null) { imageReader = new ClutDecoder(imageReader, clut16, clutMode, clutStart, clutShift, clutMask); } else { imageReader = new ClutDecoder(imageReader, clutAddr, clutMode, clutNumBlocks, clutStart, clutShift, clutMask); } } return imageReader; } /** * The ImageReader classes are based on a decoder concept, receiving * a IMemoryReader as input and delivering the transformed output also * through the IMemoryReader interface. * * This is the base class for all the decoders. */ private static abstract class ImageDecoder implements IMemoryReader { protected IMemoryReader memoryReader; public ImageDecoder(IMemoryReader memoryReader) { this.memoryReader = memoryReader; } @Override public int getCurrentAddress() { return memoryReader.getCurrentAddress(); } } /** * Decoder: * - input: 32-bit values * - output: 16-bit values (one 32-bit value is producing 2 16-bit values) */ private static final class Value32to16Decoder extends ImageDecoder { private int index; private int value; public Value32to16Decoder(IMemoryReader memoryReader) { super(memoryReader); index = 0; } @Override public int readNext() { if (index == 0) { value = memoryReader.readNext(); index = 1; return (value & 0xFFFF); } index = 0; return (value >>> 16); } @Override public void skip(int n) { if (n > 0) { int previousIndex = index; index += n; if (index > 1) { int skip = index / 2; if (previousIndex > 0) { skip--; } memoryReader.skip(skip); index = index % 2; if (index > 0) { value = memoryReader.readNext(); } } } } } /** * Decoder: * - input: 32-bit values * - output: 8-bit values (one 32-bit value is producing 4 8-bit values) */ private static final class Value32to8Decoder extends ImageDecoder { private int index; private int value; public Value32to8Decoder(IMemoryReader memoryReader) { super(memoryReader); index = 4; } @Override public int readNext() { if (index == 4) { index = 0; value = memoryReader.readNext(); } int n = value & 0xFF; value >>= 8; index++; return n; } @Override public void skip(int n) { if (n > 0) { int previousIndex = index; index += n; if (index > 4) { int skip = index / 4; if (previousIndex > 0) { skip--; } memoryReader.skip(skip); index = index % 4; if (index > 0) { value = memoryReader.readNext(); value >>= index * 8; } else { index = 4; } } else if (index < 4) { value >>= n * 8; } } } } /** * Decoder: * - input: 32-bit values * - output: 4-bit values (one 32-bit value is producing 8 4-bit values) */ private static final class Value32to4Decoder extends ImageDecoder { private int index; private int value; public Value32to4Decoder(IMemoryReader memoryReader) { super(memoryReader); index = 8; } @Override public int readNext() { if (index == 8) { index = 0; value = memoryReader.readNext(); } int n = value & 0xF; value >>= 4; index++; return n; } @Override public void skip(int n) { if (n > 0) { int previousIndex = index; index += n; if (index > 8) { int skip = index / 8; if (previousIndex > 0) { skip--; } memoryReader.skip(skip); index = index % 8; if (index > 0) { value = memoryReader.readNext(); value >>= index * 4; } else { index = 8; } } else if (index < 8) { value >>= n * 4; } } } } /** * Decoder: * - input: 16-bit color values in 5551 ABGR format * - output: 32-bit color values in 8888 ABGR format */ private static final class PixelFormat5551Decoder extends ImageDecoder { public PixelFormat5551Decoder(IMemoryReader memoryReader) { super(memoryReader); } @Override public int readNext() { return color5551to8888(memoryReader.readNext()); } @Override public void skip(int n) { memoryReader.skip(n); } } /** * Decoder: * - input: 16-bit color values in 565 BGR format * - output: 32-bit color values in 8888 ABGR format */ private static final class PixelFormat565Decoder extends ImageDecoder { public PixelFormat565Decoder(IMemoryReader memoryReader) { super(memoryReader); } @Override public int readNext() { return color565to8888(memoryReader.readNext()); } @Override public void skip(int n) { memoryReader.skip(n); } } /** * Decoder: * - input: 16-bit color values in 4444 ABGR format * - output: 32-bit color values in 8888 ABGR format */ private static final class PixelFormat4444Decoder extends ImageDecoder { public PixelFormat4444Decoder(IMemoryReader memoryReader) { super(memoryReader); } @Override public int readNext() { return color4444to8888(memoryReader.readNext()); } @Override public void skip(int n) { memoryReader.skip(n); } } /** * Decoder: * - input: 32-bit values forming a swizzled image * - output: 32-bit values corresponding to the unswizzled image */ private static final class SwizzleDecoder extends ImageDecoder { private int[] buffer; private int index; private int maxIndex; private int rowWidth; private int pitch; private int bxc; public SwizzleDecoder(IMemoryReader memoryReader, int bufferWidth, int bytesPerPixel) { super(memoryReader); rowWidth = (bytesPerPixel > 0) ? (bufferWidth * bytesPerPixel) : (bufferWidth / 2); pitch = rowWidth / 4; bxc = rowWidth / 16; // Swizzle buffer providing space for 8 pixel rows buffer = new int[pitch * 8]; maxIndex = buffer.length; index = maxIndex; } private void reload() { // Reload the swizzle buffer with the next 8 pixel rows int xdest = 0; if (rowWidth >= 16) { for (int bx = 0; bx < bxc; bx++) { int dest = xdest; for (int n = 0; n < 8; n++) { buffer[dest ] = memoryReader.readNext(); buffer[dest + 1] = memoryReader.readNext(); buffer[dest + 2] = memoryReader.readNext(); buffer[dest + 3] = memoryReader.readNext(); dest += pitch; } xdest += 4; } } else if (rowWidth == 8) { for (int n = 0; n < 8; n++, xdest += 2) { buffer[xdest] = memoryReader.readNext(); buffer[xdest + 1] = memoryReader.readNext(); memoryReader.skip(2); } } else if (rowWidth == 4) { for (int n = 0; n < 8; n++, xdest++) { buffer[xdest] = memoryReader.readNext(); memoryReader.skip(3); } } else if (rowWidth == 2) { for (int n = 0; n < 4; n++, xdest++) { int n1 = memoryReader.readNext() & 0xFFFF; memoryReader.skip(3); int n2 = memoryReader.readNext() & 0xFFFF; memoryReader.skip(3); buffer[xdest] = n1 | (n2 << 16); } } else if (rowWidth == 1) { for (int n = 0; n < 2; n++, xdest++) { int n1 = memoryReader.readNext() & 0xFF; memoryReader.skip(3); int n2 = memoryReader.readNext() & 0xFF; memoryReader.skip(3); int n3 = memoryReader.readNext() & 0xFF; memoryReader.skip(3); int n4 = memoryReader.readNext() & 0xFF; memoryReader.skip(3); buffer[xdest] = n1 | (n2 << 8) | (n3 << 16) | (n4 << 24); } } } private int getBufferSkipLength() { return bxc > 0 ? bxc * 32 : 32; } @Override public int readNext() { if (index >= maxIndex) { reload(); index = 0; } return buffer[index++]; } @Override public void skip(int n) { if (n > 0) { int previousIndex = index; index += n; if (index > maxIndex) { int skipBlocks = index / maxIndex; if (previousIndex > 0) { skipBlocks--; } if (skipBlocks > 0) { memoryReader.skip(skipBlocks * getBufferSkipLength()); } index = index % maxIndex; if (index > 0) { reload(); } else { index = maxIndex; } } } } } /** * Decoder: * - input: image with size bufferWidth * height * - output: image with size Math.min(bufferWidth, width) * height */ private static final class MemoryImageDecoder extends ImageDecoder { private int minWidth; private int skipWidth; private int x; public MemoryImageDecoder(IMemoryReader memoryReader, int width, int bufferWidth) { super(memoryReader); minWidth = Math.min(width, bufferWidth); skipWidth = Math.max(0, bufferWidth - width); x = 0; } @Override public int readNext() { if (x >= minWidth) { memoryReader.skip(skipWidth); x = 0; } x++; return memoryReader.readNext(); } @Override public void skip(int n) { x += n; if (x >= minWidth) { int lines = x / minWidth; n += lines * skipWidth; x -= lines * minWidth; } memoryReader.skip(n); } } /** * Decoder for image with clut (color lookup table): * - input: image in format * TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED * TPSM_PIXEL_STORAGE_MODE_8BIT_INDEXED * TPSM_PIXEL_STORAGE_MODE_16BIT_INDEXED * TPSM_PIXEL_STORAGE_MODE_32BIT_INDEXED * - output: image in format * TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650 (when CMODE_FORMAT_16BIT_BGR5650) * TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551 (when CMODE_FORMAT_16BIT_ABGR5551) * TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444 (when CMODE_FORMAT_16BIT_ABGR4444) * TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888 (when CMODE_FORMAT_32BIT_ABGR8888) */ private static class ClutDecoder extends ImageDecoder { protected int[] clut; protected int clutAddr; protected int clutNumBlocks; protected int clutStart; protected int clutShift; protected int clutMask; protected int clutEntrySize; public ClutDecoder(IMemoryReader memoryReader, int clutAddr, int clutMode, int clutNumBlocks, int clutStart, int clutShift, int clutMask) { super(memoryReader); this.clutAddr = clutAddr; this.clutNumBlocks = clutNumBlocks; this.clutStart = clutStart; this.clutShift = clutShift; this.clutMask = clutMask; clutEntrySize = (clutMode == GeCommands.CMODE_FORMAT_32BIT_ABGR8888 ? 4 : 2); readClut(); } public ClutDecoder(IMemoryReader memoryReader, int[] clut, int clutMode, int clutStart, int clutShift, int clutMask) { super(memoryReader); this.clutAddr = 0; this.clutNumBlocks = -1; this.clutStart = clutStart; this.clutShift = clutShift; this.clutMask = clutMask; this.clut = new int[getMaxClutEntries()]; System.arraycopy(clut, 0, this.clut, 0, this.clut.length); } public ClutDecoder(IMemoryReader memoryReader, short[] clut, int clutMode, int clutStart, int clutShift, int clutMask) { super(memoryReader); this.clutAddr = 0; this.clutNumBlocks = -1; this.clutStart = clutStart; this.clutShift = clutShift; this.clutMask = clutMask; this.clut = new int[getMaxClutEntries()]; for (int i = 0; i < this.clut.length; i++) { this.clut[i] = clut[i] & 0xFFFF; } } protected int getMaxClutEntries() { return Integer.highestOneBit(getClutIndex(0xFFFFFFFF)) << 1; } protected int getClutAddr() { return clutAddr + (clutStart << 4) * clutEntrySize; } protected void readClut() { int clutNumEntries = clutNumBlocks * 32 / clutEntrySize; clut = new int[clutNumEntries]; int clutOffset = clutStart << 4; IMemoryReader clutReader = MemoryReader.getMemoryReader(getClutAddr(), (clutNumEntries - clutStart) * clutEntrySize, clutEntrySize); for (int i = clutOffset; i < clutNumEntries; i++) { clut[i] = clutReader.readNext(); } } protected int getClutIndex(int index) { return ((index >> clutShift) & clutMask) | (clutStart << 4); } @Override public int readNext() { int index = memoryReader.readNext(); return clut[getClutIndex(index)]; } @Override public void skip(int n) { memoryReader.skip(n); } } /** * Decoder for image with clut (color lookup table): * - input: image in format * TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED * TPSM_PIXEL_STORAGE_MODE_8BIT_INDEXED * TPSM_PIXEL_STORAGE_MODE_16BIT_INDEXED * TPSM_PIXEL_STORAGE_MODE_32BIT_INDEXED * - output: image in format * TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650 (when CMODE_FORMAT_16BIT_BGR5650) * TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551 (when CMODE_FORMAT_16BIT_ABGR5551) * TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444 (when CMODE_FORMAT_16BIT_ABGR4444) * TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888 (when CMODE_FORMAT_32BIT_ABGR8888) * * Only "simple" cluts (the most common case) are supported * by this specialized class: * clutStart = 0 * clutShift = 0 * clutMask = 0xFF * or 0xF for TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED */ private static final class SimpleClutDecoder extends ClutDecoder { public SimpleClutDecoder(IMemoryReader memoryReader, int clutMode, int clutAddr, int clutNumBlocks, int indexBits) { super(memoryReader, clutMode, clutAddr, clutNumBlocks, 0, 0, getClutMask(indexBits)); } public SimpleClutDecoder(IMemoryReader memoryReader, int[] clut, int clutMode, int indexBits) { super(memoryReader, clut, clutMode, 0, 0, getClutMask(indexBits)); } public SimpleClutDecoder(IMemoryReader memoryReader, short[] clut, int clutMode, int indexBits) { super(memoryReader, clut, clutMode, 0, 0, getClutMask(indexBits)); } private static int getClutMask(int indexBits) { return indexBits == 4 ? 0xF : 0xFF; } @Override protected int getMaxClutEntries() { return clutMask == 0xF ? 16 : 256; } @Override protected int getClutIndex(int index) { return index; } @Override public int readNext() { int index = memoryReader.readNext(); return clut[index]; } @Override public void skip(int n) { memoryReader.skip(n); } } /** * Base class for the DXT-compressed decoders */ private static abstract class DXTDecoder extends ImageDecoder { protected final int width; protected final int bufferWidthSkip; protected final int dxtLevel; protected final int[] buffer; protected int index; protected final int maxIndex; protected final int[] colors = new int[4]; public DXTDecoder(IMemoryReader memoryReader, int width, int height, int bufferWidth, int dxtLevel, int compressionRatio) { super(memoryReader); this.bufferWidthSkip = Math.max(0, getBufferWidthSkip(width, bufferWidth)); width = Math.min(width, bufferWidth); this.width = width; this.dxtLevel = dxtLevel; //compressedImageSize = round4(width) * round4(height) * 4 / compressionRatio; buffer = new int[round4(width) << 2]; // DXT images are compressed in blocks of 4 rows maxIndex = width << 2; index = maxIndex; } protected void reload() { // Reload buffer for (int strideX = 0; strideX < width; strideX += 4) { // PSP DXT1 hardware format reverses the colors and the per-pixel // bits, and encodes the color in RGB 565 format // // PSP DXT3 format reverses the alpha and color parts of each block, // and reverses the color and per-pixel terms in the color part. // // PSP DXT5 format reverses the alpha and color parts of each block, // and reverses the color and per-pixel terms in the color part. In // the alpha part, the 2 reference alpha values are swapped with the // alpha interpolation values. int bits = memoryReader.readNext(); int color = memoryReader.readNext(); readAlpha(); int color0 = (color >> 0) & 0xFFFF; int color1 = (color >> 16) & 0xFFFF; int r0 = (color0 >> 8) & 0xF8; int g0 = (color0 >> 3) & 0xFC; int b0 = (color0 << 3) & 0xF8; int r1 = (color1 >> 8) & 0xF8; int g1 = (color1 >> 3) & 0xFC; int b1 = (color1 << 3) & 0xF8; int r2, g2, b2; if (color0 > color1 || dxtLevel > 1) { r2 = (r0 * 2 + r1) / 3; g2 = (g0 * 2 + g1) / 3; b2 = (b0 * 2 + b1) / 3; } else { r2 = (r0 + r1) / 2; g2 = (g0 + g1) / 2; b2 = (b0 + b1) / 2; } int r3, g3, b3; boolean color3transparent; if (color0 > color1 || dxtLevel > 1) { r3 = (r0 + r1 * 2) / 3; g3 = (g0 + g1 * 2) / 3; b3 = (b0 + b1 * 2) / 3; color3transparent = false; } else { // Transparent black r3 = 0x00; g3 = 0x00; b3 = 0x00; color3transparent = true; } colors[0] = (b0 << 16) | (g0 << 8) | (r0); colors[1] = (b1 << 16) | (g1 << 8) | (r1); colors[2] = (b2 << 16) | (g2 << 8) | (r2); colors[3] = (b3 << 16) | (g3 << 8) | (r3); storePixels(strideX, bits, color3transparent); } memoryReader.skip(bufferWidthSkip); } @Override public int readNext() { if (index >= maxIndex) { reload(); index = 0; } return buffer[index++]; } protected int getSkipLength() { return (width / 4) * (2 + getAlphaSkipLength()) + bufferWidthSkip; } @Override public void skip(int n) { index += n; if (index > maxIndex) { int skipBlocks = (index / maxIndex) - 1; if (skipBlocks > 0) { memoryReader.skip(skipBlocks * getSkipLength()); } index = index % maxIndex; if (index > 0) { reload(); } else { index = maxIndex; } } } protected abstract void storePixels(int strideX, int bits, boolean color3transparent); protected abstract void readAlpha(); protected abstract int getAlphaSkipLength(); protected abstract int getBufferWidthSkip(int width, int bufferWidth); } /** * Decoder for an image compressed with DXT1 * (http://en.wikipedia.org/wiki/S3_Texture_Compression) * - input: image in format * TPSM_PIXEL_STORAGE_MODE_DXT1 * - output: image in format * TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888 */ private static final class DXT1Decoder extends DXTDecoder { public DXT1Decoder(IMemoryReader memoryReader, int width, int height, int bufferWidth) { super(memoryReader, width, height, bufferWidth, 1, 8); } @Override protected void storePixels(int strideX, int bits, boolean color3transparent) { colors[0] |= 0xFF000000; colors[1] |= 0xFF000000; colors[2] |= 0xFF000000; if (!color3transparent) { colors[3] |= 0xFF000000; } for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++, bits >>>= 2) { buffer[y * width + x + strideX] = colors[bits & 3]; } } } @Override protected void readAlpha() { // No alpha } @Override protected int getAlphaSkipLength() { // No alpha return 0; } @Override protected int getBufferWidthSkip(int width, int bufferWidth) { return (bufferWidth - width) >> 1; } } /** * Decoder for an image compressed with DXT3 * (http://en.wikipedia.org/wiki/S3_Texture_Compression) * - input: image in format * TPSM_PIXEL_STORAGE_MODE_DXT3 * - output: image in format * TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888 */ private static final class DXT3Decoder extends DXTDecoder { private long alpha; public DXT3Decoder(IMemoryReader memoryReader, int width, int height, int bufferWidth) { super(memoryReader, width, height, bufferWidth, 3, 4); } @Override protected void storePixels(int strideX, int bits, boolean color3transparent) { for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++, bits >>>= 2, alpha >>>= 4) { int pixelAlpha = (((int) alpha) & 0xF); pixelAlpha = pixelAlpha | (pixelAlpha << 4); buffer[y * width + x + strideX] = colors[bits & 3] | (pixelAlpha << 24); } } } @Override protected void readAlpha() { alpha = memoryReader.readNext() & 0x00000000FFFFFFFFL; alpha |= (((long) memoryReader.readNext()) << 32); } @Override protected int getAlphaSkipLength() { return 2; } @Override protected int getBufferWidthSkip(int width, int bufferWidth) { return bufferWidth - width; } } /** * Decoder for an image compressed with DXT5 * (http://en.wikipedia.org/wiki/S3_Texture_Compression) * - input: image in format * TPSM_PIXEL_STORAGE_MODE_DXT5 * - output: image in format * TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888 */ private static final class DXT5Decoder extends DXTDecoder { private int[] alpha = new int[8]; private long alphaLookup; public DXT5Decoder(IMemoryReader memoryReader, int width, int height, int bufferWidth) { super(memoryReader, width, height, bufferWidth, 5, 4); } @Override protected void storePixels(int strideX, int bits, boolean color3transparent) { for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++, bits >>>= 2, alphaLookup >>>= 3) { int alphaPixel = alpha[((int) alphaLookup) & 7]; buffer[y * width + x + strideX] = colors[bits & 3] | (alphaPixel << 24); } } } @Override protected void readAlpha() { alphaLookup = memoryReader.readNext(); int value = memoryReader.readNext(); alphaLookup |= ((long) (value & 0xFFFF)) << 32; value >>>= 16; int alpha0 = value & 0xFF; int alpha1 = value >> 8; alpha[0] = alpha0; alpha[1] = alpha1; if (alpha0 > alpha1) { alpha[2] = (6 * alpha0 + alpha1) / 7; alpha[3] = (5 * alpha0 + 2 * alpha1) / 7; alpha[4] = (4 * alpha0 + 3 * alpha1) / 7; alpha[5] = (3 * alpha0 + 4 * alpha1) / 7; alpha[6] = (2 * alpha0 + 5 * alpha1) / 7; alpha[7] = ( alpha0 + 6 * alpha1) / 7; } else { alpha[2] = (4 * alpha0 + alpha1) / 5; alpha[3] = (3 * alpha0 + 2 * alpha1) / 5; alpha[4] = (2 * alpha0 + 3 * alpha1) / 5; alpha[5] = ( alpha0 + 4 * alpha1) / 5; alpha[6] = 0x00; alpha[7] = 0xFF; } } @Override protected int getAlphaSkipLength() { return 2; } @Override protected int getBufferWidthSkip(int width, int bufferWidth) { return bufferWidth - width; } } /** * Convert a 4444 color in ABGR format (GU_COLOR_4444) * to a 8888 color in ABGR format (GU_COLOR_8888). * * 4444 format: AAAABBBBGGGGRRRR * 3210321032103210 * transformed into * 8888 format: AAAAAAAABBBBBBBBGGGGGGGGRRRRRRRR * 32103210321032103210321032103210 * * @param color4444 4444 color in ABGR format (GU_COLOR_4444) * @return 8888 color in ABGR format (GU_COLOR_8888) */ public static int color4444to8888(int color4444) { return ((color4444 ) & 0x0000000F) | ((color4444 << 4) & 0x00000FF0) | ((color4444 << 8) & 0x000FF000) | ((color4444 << 12) & 0x0FF00000) | ((color4444 << 16) & 0xF0000000); } /** * Convert a 5551 color in ABGR format (GU_COLOR_5551) * to a 8888 color in ABGR format (GU_COLOR_8888). * * 5551 format: ABBBBBGGGGGRRRRR * 432104321043210 * transformed into * 8888 format: AAAAAAAABBBBBBBBGGGGGGGGRRRRRRRR * 432104324321043243210432 * * @param color5551 5551 color in ABGR format (GU_COLOR_5551) * @return 8888 color in ABGR format (GU_COLOR_8888) */ public static int color5551to8888(int color5551) { return ((color5551 << 3) & 0x000000F8) | ((color5551 >> 2) & 0x00000007) | ((color5551 << 6) & 0x0000F800) | ((color5551 << 1) & 0x00000700) | ((color5551 << 9) & 0x00F80000) | ((color5551 << 4) & 0x00070000) | ((color5551 >> 15) * 0xFF000000); } /** * Convert a 565 color in BGR format (GU_COLOR_5650) * to a 8888 color in ABGR format (GU_COLOR_8888). * * 5650 format: BBBBBGGGGGGRRRRR * 4321054321043210 * transformed into * 8888 format: 11111111BBBBBBBBGGGGGGGGRRRRRRRR * 432104325432105443210432 * * @param color565 565 color in BGR format (GU_COLOR_5650) * @return 8888 color in ABGR format (GU_COLOR_8888) */ public static int color565to8888(int color565) { return ((color565 << 3) & 0x0000F8) | ((color565 >> 2) & 0x000007) | ((color565 << 5) & 0x00FC00) | ((color565 >> 1) & 0x000300) | ((color565 << 8) & 0xF80000) | ((color565 << 3) & 0x070000) | 0xFF000000; } /** * Convert a 8888 color from ABGR (PSP format) to ARGB (Java/Swing format). * ABGR: AAAAAAAABBBBBBBBGGGGGGGGRRRRRRRR * transformed into * ARGB: AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB * * @param colorABGR 8888 color in ABGR format * @return 8888 color in ARGB format */ public static int colorABGRtoARGB(int colorABGR) { return ((colorABGR & 0xFF00FF00) ) | ((colorABGR & 0x000000FF) << 16) | ((colorABGR & 0x00FF0000) >> 16); } /** * Convert a 8888 color from ARGB (Java/Swing format) to ABGR (PSP format). * ARGB: AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB * transformed into * ABGR: AAAAAAAABBBBBBBBGGGGGGGGRRRRRRRR * * @param colorABGR 8888 color in ABGR format * @return 8888 color in ARGB format */ public static int colorARGBtoABGR(int colorARGB) { return ((colorARGB & 0xFF00FF00) ) | ((colorARGB & 0x000000FF) << 16) | ((colorARGB & 0x00FF0000) >> 16); } /** * Return the number of bytes required by a pixel in a specific format. * * @param pixelFormat the format of the pixel (TPSM_PIXEL_STORAGE_MODE_xxx) * @return the number of bytes required by one pixel. * 0 when a pixel requires a half-byte (i.e. 2 pixels per byte). */ private static int getBytesPerPixel(int pixelFormat) { return IRenderingEngine.sizeOfTextureType[pixelFormat]; } /** * Return the image size in bytes when stored in Memory. * * @param width the image width * @param height the image height * @param bufferWidth the image bufferWidth * @param pixelFormat the image pixelFormat * @return number of bytes required to store the image in Memory */ public static int getImageByteSize(int width, int height, int bufferWidth, int pixelFormat) { // Special cases: switch (pixelFormat) { case TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED: return height * bufferWidth / 2; case TPSM_PIXEL_STORAGE_MODE_DXT1: return round4(height) * round4(bufferWidth) / 2; case TPSM_PIXEL_STORAGE_MODE_DXT3: case TPSM_PIXEL_STORAGE_MODE_DXT5: return round4(height) * round4(bufferWidth); } // Common case: return height * bufferWidth * getBytesPerPixel(pixelFormat); } /** * Round the value the next multiple of 4. * E.g.: value == 0: return 0 * value == 1, 2, 3, 4: return 4 * value == 5, 6, 7, 8: return 8 * * @param n the value * @return the value rounded up to the next multiple of 4. */ public static int round4(int n) { return (n + 3) & ~3; } }