/*
This file is part of jpcsp.
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.graphics.capture;
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_32BIT_INDEXED;
import static jpcsp.graphics.GeCommands.TPSM_PIXEL_STORAGE_MODE_4BIT_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.RE.IRenderingEngine.RE_DEPTH_STENCIL;
import static jpcsp.graphics.RE.software.BaseRenderer.depthBufferPixelFormat;
import static jpcsp.memory.ImageReader.colorABGRtoARGB;
import static jpcsp.util.Utilities.alignUp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.Buffer;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.HashMap;
import java.util.zip.CRC32;
import java.util.zip.Deflater;
import javax.imageio.ImageIO;
import org.apache.log4j.Logger;
import jpcsp.graphics.VideoEngine;
import jpcsp.memory.IMemoryReader;
import jpcsp.memory.MemoryReader;
/**
* @author gid15
*
*/
public class CaptureImage {
private static Logger log = VideoEngine.log;
private static final String bmpFileFormat = "bmp";
private static final String pngFileFormat = "png";
private int imageaddr;
private int level;
private Buffer buffer;
private IMemoryReader imageReader;
private int width;
private int height;
private int bufferWidth;
private int bufferStorage;
private boolean compressedImage;
private int compressedImageSize;
private boolean invert;
private boolean overwriteFile;
private String fileNamePrefix;
private String directory = "tmp/";
private String fileFormat = bmpFileFormat;
private String fileName;
private String fileNameSuffix = "";
private static HashMap<Integer, Integer> lastFileIndex = new HashMap<Integer, Integer>();
private static abstract class AbstractCaptureImage {
public abstract void writeHeader(String fileName, String fileFormat, int width, int height, int readWidth) throws IOException;
public abstract void startLine(int y);
public abstract void writePixel(int pixel) throws IOException;
public abstract void writeEnd() throws IOException;
public void writePixel(byte[] pixel) throws IOException {
writePixel(getARGB(pixel));
}
public void endLine() throws IOException {
}
public boolean isInverted() {
return false;
}
}
// See http://en.wikipedia.org/wiki/BMP_file_format
// for detailed information about the BMP file format
private static class CaptureImageBMP extends AbstractCaptureImage {
private int imageRawBytes;
private byte[] completeImageBytes;
private int pixelIndex;
private int width;
private int height;
private String fileName;
@Override
public void writeHeader(String fileName, String fileFormat, int width, int height, int readWidth) throws IOException {
this.fileName = fileName;
this.width = width;
this.height = height;
int rowPad = (4 - ((width * 4) & 3)) & 3;
imageRawBytes = (width * 4) + rowPad;
completeImageBytes = new byte[height * imageRawBytes];
}
@Override
public void startLine(int y) {
pixelIndex = y * imageRawBytes;
}
@Override
public void writePixel(int pixel) {
completeImageBytes[pixelIndex + 0] = (byte) (pixel >> 16); // B
completeImageBytes[pixelIndex + 1] = (byte) (pixel >> 8); // G
completeImageBytes[pixelIndex + 2] = (byte) (pixel ); // R
completeImageBytes[pixelIndex + 3] = (byte) (pixel >> 24); // A
pixelIndex += 4;
}
@Override
public void writeEnd() throws IOException {
byte[] fileHeader = new byte[14];
byte[] dibHeader = new byte[56];
int fileSize = fileHeader.length + dibHeader.length + completeImageBytes.length;
fileHeader[0] = 'B'; // Magic number
fileHeader[1] = 'M'; // Magic number
storeLittleEndianInt(fileHeader, 2, fileSize); // Size of the BMP file
storeLittleEndianInt(fileHeader, 10, fileHeader.length + dibHeader.length); // Offset where the Pixel Array (bitmap data) can be found
storeLittleEndianInt(dibHeader, 0, dibHeader.length); // Number of bytes in the DIB header (from this point)
storeLittleEndianInt(dibHeader, 4, width); // Width of the bitmap in pixels
storeLittleEndianInt(dibHeader, 8, height); // Height of the bitmap in pixels
storeLittleEndianShort(dibHeader, 12, 1); // Number of color planes being used
storeLittleEndianShort(dibHeader, 14, 32); // Number of bits per pixel
storeLittleEndianInt(dibHeader, 16, 0); // BI_BITFIELDS, no Pixel Array compression used
storeLittleEndianInt(dibHeader, 20, completeImageBytes.length); // Size of the raw data in the Pixel Array (including padding)
storeLittleEndianInt(dibHeader, 24, 2835); // Horizontal physical resolution of the image (pixels/meter)
storeLittleEndianInt(dibHeader, 28, 2835); // Vertical physical resolution of the image (pixels/meter)
storeLittleEndianInt(dibHeader, 32, 0); // Number of colors in the palette
storeLittleEndianInt(dibHeader, 36, 0); // 0 means all colors are important
storeLittleEndianInt(dibHeader, 40, 0x00FF0000); // Red channel bit mask in big-endian (valid because BI_BITFIELDS is specified)
storeLittleEndianInt(dibHeader, 44, 0x0000FF00); // Green channel bit mask in big-endian (valid because BI_BITFIELDS is specified)
storeLittleEndianInt(dibHeader, 48, 0x000000FF); // Blue channel bit mask in big-endian (valid because BI_BITFIELDS is specified)
storeLittleEndianInt(dibHeader, 52, 0xFF000000); // Alpha channel bit mask in big-endian
OutputStream outStream = new FileOutputStream(fileName);
outStream.write(fileHeader);
outStream.write(dibHeader);
outStream.write(completeImageBytes);
outStream.close();
}
@Override
public boolean isInverted() {
// The image in the BMP file has always to be upside-down as compared to the PSP image
return true;
}
}
private static class CaptureImageImageIO extends AbstractCaptureImage {
private BufferedImage im;
private int[] lineARGB;
private int x;
private int y;
private int width;
private String fileName;
private String fileFormat;
@Override
public void writeHeader(String fileName, String fileFormat, int width, int height, int readWidth) throws IOException {
this.fileName = fileName;
this.fileFormat = fileFormat;
this.width = width;
// Remark: use TYPE_3BYTE_BGR instead of TYPE_4BYTE_ABGR, it looks like ImageIO
// is not correctly handling images with alpha values. Incorrect png and jpg images
// are created when using an alpha component.
im = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
lineARGB = new int[width];
}
@Override
public void startLine(int y) {
this.y = y;
x = 0;
}
@Override
public void writePixel(int pixel) throws IOException {
lineARGB[x] = colorABGRtoARGB(pixel);
x++;
}
@Override
public void endLine() {
im.setRGB(0, y, width, 1, lineARGB, 0, width);
}
@Override
public void writeEnd() throws IOException {
if (!ImageIO.write(im, fileFormat, new File(fileName))) {
log.error(String.format("Cannot save image in format %s using ImageIO: %s", fileFormat, fileName));
}
}
}
private static class CaptureImagePNG extends AbstractCaptureImage {
private int width;
private int height;
private String fileName;
private byte[] buffer;
private int pixelIndex;
@Override
public void writeHeader(String fileName, String fileFormat, int width, int height, int readWidth) throws IOException {
this.fileName = fileName;
this.width = width;
this.height = height;
// 4 bytes per pixel plus one byte per line for the filter type
buffer = new byte[width * height * 4 + height];
}
@Override
public void startLine(int y) {
pixelIndex = width * y * 4 + y;
// Write the filter type byte at the beginning of each line
buffer[pixelIndex] = 0; // filter type: None
pixelIndex++;
}
@Override
public void writePixel(int pixel) throws IOException {
buffer[pixelIndex + 0] = (byte) (pixel ); // R
buffer[pixelIndex + 1] = (byte) (pixel >> 8); // G
buffer[pixelIndex + 2] = (byte) (pixel >> 16); // B
buffer[pixelIndex + 3] = (byte) (pixel >> 24); // A
pixelIndex += 4;
}
@Override
public void writeEnd() throws IOException {
// See https://en.wikipedia.org/wiki/Portable_Network_Graphics
// for detailed information about the PNG file format
byte[] fileHeader = new byte[8];
fileHeader[0] = (byte) 0x89;
fileHeader[1] = 'P';
fileHeader[2] = 'N';
fileHeader[3] = 'G';
fileHeader[4] = '\r';
fileHeader[5] = '\n';
fileHeader[6] = (byte) 0x1A;
fileHeader[7] = '\n';
byte[] ihdr = new byte[13 + 8 + 4];
storeBigEndianInt(ihdr, 0, 13);
storeChunkType(ihdr, 4, 'I', 'H', 'D', 'R');
storeBigEndianInt(ihdr, 8, width);
storeBigEndianInt(ihdr, 12, height);
ihdr[16] = 8; // bit depth
ihdr[17] = 6; // color type: red, green, blue, alpha
ihdr[18] = 0; // compression method: deflate/inflate
ihdr[19] = 0; // filter method: none
ihdr[20] = 0; // interlace method: no interlace
storeCRC(ihdr, 21);
Deflater deflater = new Deflater();
deflater.setInput(buffer);
deflater.finish();
byte[] data = new byte[buffer.length];
int dataLength = deflater.deflate(data);
byte[] idat = new byte[8 + dataLength + 4];
storeBigEndianInt(idat, 0, dataLength);
storeChunkType(idat, 4, 'I', 'D', 'A', 'T');
System.arraycopy(data, 0, idat, 8, dataLength);
storeCRC(idat, 8 + dataLength);
byte[] iend = new byte[12];
storeBigEndianInt(iend, 0, 0);
storeChunkType(iend, 4, 'I', 'E', 'N', 'D');
storeCRC(iend, 8);
OutputStream outStream = new FileOutputStream(fileName);
outStream.write(fileHeader);
outStream.write(ihdr);
outStream.write(idat);
outStream.write(iend);
outStream.close();
}
}
public CaptureImage(int imageaddr, int level, Buffer buffer, int width, int height, int bufferWidth, int bufferStorage, boolean compressedImage, int compressedImageSize, boolean invert, boolean overwriteFile, String fileNamePrefix) {
this.imageaddr = imageaddr;
this.level = level;
this.buffer = buffer;
this.width = width;
this.height = height;
this.bufferWidth = bufferWidth;
this.bufferStorage = bufferStorage;
this.compressedImage = compressedImage;
this.compressedImageSize = compressedImageSize;
this.invert = invert;
this.overwriteFile = overwriteFile;
this.fileNamePrefix = fileNamePrefix == null ? "Image" : fileNamePrefix;
}
public CaptureImage(int imageaddr, int level, IMemoryReader imageReader, int width, int height, int bufferWidth, boolean invert, boolean overwriteFile, String fileNamePrefix) {
this.imageaddr = imageaddr;
this.level = level;
this.imageReader = imageReader;
this.width = width;
this.height = height;
this.bufferWidth = bufferWidth;
this.bufferStorage = TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888;
this.compressedImage = false;
this.compressedImageSize = 0;
this.invert = invert;
this.overwriteFile = overwriteFile;
this.fileNamePrefix = fileNamePrefix == null ? "Image" : fileNamePrefix;
}
public void setFileFormat(String fileFormat) {
this.fileFormat = fileFormat;
}
public void setDirectory(String directory) {
this.directory = directory;
}
public String getFileName() {
if (fileName == null) {
String levelName = "";
if (level > 0) {
levelName = "_" + level;
}
int scanIndex = 0;
Integer lastIndex = lastFileIndex.get(imageaddr);
if (lastIndex != null) {
scanIndex = lastIndex.intValue() + 1;
}
for (int i = scanIndex; ; i++) {
String id = (i == 0 ? "" : "-" + i);
fileName = String.format("%s%s%08X%s%s%s.%s", directory, fileNamePrefix, imageaddr, fileNameSuffix, levelName, id, fileFormat);
if (overwriteFile) {
break;
}
File file = new File(fileName);
if (!file.exists()) {
lastFileIndex.put(imageaddr, i);
break;
}
}
}
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public boolean fileExists() {
return new File(getFileName()).exists();
}
public void setFileNameSuffix(String fileNameSuffix) {
this.fileNameSuffix = fileNameSuffix;
}
public void write() throws IOException {
if (bufferStorage >= TPSM_PIXEL_STORAGE_MODE_4BIT_INDEXED && bufferStorage <= TPSM_PIXEL_STORAGE_MODE_32BIT_INDEXED && bufferStorage != depthBufferPixelFormat) {
// Writing of indexed images not supported
return;
}
if (compressedImage) {
decompressImage();
}
boolean imageInvert = invert;
int readWidth = Math.min(width, bufferWidth);
byte[] pixelBytes = new byte[4];
byte[] blackPixelBytes = new byte[pixelBytes.length];
// ImageIO doesn't support the bmp file format and
// doesn't properly write PNG files with pixel alpha values
AbstractCaptureImage captureImage;
if (bmpFileFormat.equals(fileFormat)) {
captureImage = new CaptureImageBMP();
} else if (pngFileFormat.equals(fileFormat)) {
captureImage = new CaptureImagePNG();
} else {
captureImage = new CaptureImageImageIO();
}
captureImage.writeHeader(getFileName(), fileFormat, width, height, readWidth);
if (captureImage.isInverted()) {
imageInvert = !imageInvert;
}
boolean imageType32Bit = bufferStorage == TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888 || bufferStorage == RE_DEPTH_STENCIL;
if (imageReader != null) {
for (int y = 0; y < height; y++) {
captureImage.startLine(imageInvert ? (height - y - 1) : y);
for (int x = 0; x < readWidth; x++) {
int pixel = imageReader.readNext();
captureImage.writePixel(pixel);
}
captureImage.endLine();
}
captureImage.writeEnd();
} else if (buffer instanceof IntBuffer && imageType32Bit) {
IntBuffer intBuffer = (IntBuffer) buffer;
for (int y = 0; y < height; y++) {
intBuffer.position((imageInvert ? (height - y - 1) : y) * bufferWidth);
captureImage.startLine(imageInvert ? (height - y - 1) : y);
for (int x = 0; x < readWidth; x++) {
try {
int pixel = intBuffer.get();
captureImage.writePixel(pixel);
} catch (BufferUnderflowException e) {
captureImage.writePixel(blackPixelBytes);
}
}
captureImage.endLine();
}
} else if (buffer instanceof IntBuffer && !imageType32Bit) {
IntBuffer intBuffer = (IntBuffer) buffer;
for (int y = 0; y < height; y++) {
intBuffer.position((imageInvert ? (height - y - 1) : y) * bufferWidth / 2);
captureImage.startLine(imageInvert ? (height - y - 1) : y);
for (int x = 0; x < readWidth; x += 2) {
try {
int twoPixels = intBuffer.get();
getPixelBytes((short) twoPixels, bufferStorage, pixelBytes);
captureImage.writePixel(pixelBytes);
if (x + 1 < readWidth) {
getPixelBytes((short) (twoPixels >>> 16), bufferStorage, pixelBytes);
captureImage.writePixel(pixelBytes);
}
} catch (BufferUnderflowException e) {
captureImage.writePixel(blackPixelBytes);
captureImage.writePixel(blackPixelBytes);
}
}
captureImage.endLine();
}
} else if (buffer instanceof ShortBuffer && !imageType32Bit) {
ShortBuffer shortBuffer = (ShortBuffer) buffer;
for (int y = 0; y < height; y++) {
shortBuffer.position((imageInvert ? (height - y - 1) : y) * bufferWidth);
captureImage.startLine(imageInvert ? (height - y - 1) : y);
for (int x = 0; x < readWidth; x++) {
short pixel = shortBuffer.get();
getPixelBytes(pixel, bufferStorage, pixelBytes);
captureImage.writePixel(pixelBytes);
}
captureImage.endLine();
}
} else if (imageType32Bit) {
for (int y = 0; y < height; y++) {
IMemoryReader memoryReader = MemoryReader.getMemoryReader(imageaddr + (imageInvert ? (height - y - 1) : y) * bufferWidth * 4, bufferWidth * 4, 4);
captureImage.startLine(imageInvert ? (height - y - 1) : y);
for (int x = 0; x < readWidth; x++) {
int pixel = memoryReader.readNext();
captureImage.writePixel(pixel);
}
captureImage.endLine();
}
} else {
for (int y = 0; y < height; y++) {
IMemoryReader memoryReader = MemoryReader.getMemoryReader(imageaddr + (imageInvert ? (height - y - 1) : y) * bufferWidth * 2, bufferWidth * 2, 2);
captureImage.startLine(imageInvert ? (height - y - 1) : y);
for (int x = 0; x < readWidth; x++) {
short pixel = (short) memoryReader.readNext();
getPixelBytes(pixel, bufferStorage, pixelBytes);
captureImage.writePixel(pixelBytes);
}
captureImage.endLine();
}
}
if (buffer != null) {
buffer.rewind();
}
captureImage.writeEnd();
if (log.isDebugEnabled()) {
log.debug(String.format("Saved image to %s", getFileName()));
}
}
private static void storeLittleEndianInt(byte[] buffer, int offset, int value) {
buffer[offset ] = (byte) (value );
buffer[offset + 1] = (byte) (value >> 8);
buffer[offset + 2] = (byte) (value >> 16);
buffer[offset + 3] = (byte) (value >> 24);
}
private static void storeBigEndianInt(byte[] buffer, int offset, int value) {
buffer[offset ] = (byte) (value >> 24);
buffer[offset + 1] = (byte) (value >> 16);
buffer[offset + 2] = (byte) (value >> 8);
buffer[offset + 3] = (byte) (value );
}
private static void storeChunkType(byte[] buffer, int offset, char c1, char c2, char c3, char c4) {
buffer[offset ] = (byte) c1;
buffer[offset + 1] = (byte) c2;
buffer[offset + 2] = (byte) c3;
buffer[offset + 3] = (byte) c4;
}
private static void storeCRC(byte[] buffer, int offset) {
CRC32 crc32 = new CRC32();
crc32.update(buffer, 4, offset - 4);
storeBigEndianInt(buffer, offset, (int) crc32.getValue());
}
private static void storeLittleEndianShort(byte[] buffer, int offset, int value) {
buffer[offset ] = (byte) (value );
buffer[offset + 1] = (byte) (value >> 8);
}
private void getPixelBytes(short pixel, int imageType, byte[] pixelBytes) {
switch (imageType) {
case TPSM_PIXEL_STORAGE_MODE_16BIT_BGR5650:
pixelBytes[0] = (byte) ((pixel >> 8) & 0xF8); // B
pixelBytes[1] = (byte) ((pixel >> 3) & 0xFC); // G
pixelBytes[2] = (byte) ((pixel << 3) & 0xF8); // R
pixelBytes[3] = 0; // A
break;
case TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR5551:
pixelBytes[0] = (byte) ((pixel >> 7) & 0xF8); // B
pixelBytes[1] = (byte) ((pixel >> 2) & 0xF8); // G
pixelBytes[2] = (byte) ((pixel << 3) & 0xF8); // R
pixelBytes[3] = (byte) ((pixel >> 15) != 0 ? 0xFF : 0x00); // A
break;
case TPSM_PIXEL_STORAGE_MODE_16BIT_ABGR4444:
pixelBytes[0] = (byte) ((pixel >> 4) & 0xF0); // B
pixelBytes[1] = (byte) ((pixel ) & 0xF0); // G
pixelBytes[2] = (byte) ((pixel << 4) & 0xF0); // R
pixelBytes[3] = (byte) ((pixel >> 8) & 0xF0); // A
break;
case depthBufferPixelFormat:
// Gray color value based on depth value
pixelBytes[0] = (byte) (pixel >> 8);
pixelBytes[1] = pixelBytes[0];
pixelBytes[2] = pixelBytes[0];
pixelBytes[3] = pixelBytes[0];
break;
default:
// Black pixel
pixelBytes[0] = 0;
pixelBytes[1] = 0;
pixelBytes[2] = 0;
pixelBytes[3] = 0;
break;
}
}
private static int getARGB(byte[] pixelBytes) {
return ((pixelBytes[3] & 0xFF) << 24) |
((pixelBytes[2] & 0xFF) << 16) |
((pixelBytes[1] & 0xFF) << 8) |
((pixelBytes[0] & 0xFF));
}
private void storePixel(IntBuffer buffer, int x, int y, int color) {
buffer.put(y * width + x, color);
}
private int round4(int n) {
return alignUp(n, 3);
}
private int getInt32(Buffer buffer) {
if (buffer instanceof IntBuffer) {
return ((IntBuffer) buffer).get();
} else if (buffer instanceof ShortBuffer) {
ShortBuffer shortBuffer = (ShortBuffer) buffer;
int n0 = shortBuffer.get() & 0xFFFF;
int n1 = shortBuffer.get() & 0xFFFF;
return (n1 << 16) | n0;
} else if (buffer instanceof ByteBuffer) {
return ((ByteBuffer) buffer).getInt();
}
return 0;
}
private void decompressImageDXT(int dxtLevel) {
IntBuffer decompressedBuffer = IntBuffer.allocate(round4(width) * round4(height));
//
// For more information of the S3 Texture compression (DXT), see
// http://en.wikipedia.org/wiki/S3_Texture_Compression
//
int strideX = 0;
int strideY = 0;
int[] colors = new int[4];
int strideSize = (dxtLevel == 1 ? 8 : 16);
int[] alphas = new int[16];
int[] alphasLookup = new int[8];
for (int i = 0; i < compressedImageSize; i += strideSize) {
if (dxtLevel > 1) {
if (dxtLevel <= 3) {
// 64 bits of alpha channel data: four bits for each pixel
int alphaBits = 0;
for (int j = 0; j < 16; j++, alphaBits >>>= 4) {
if ((j % 8) == 0) {
alphaBits = getInt32(buffer);
}
int alpha = alphaBits & 0x0F;
alphas[j] = alpha << 4;
}
} else {
// 64 bits of alpha channel data: two 8 bit alpha values and a 4x4 3 bit lookup table
int bits0 = getInt32(buffer);
int bits1 = getInt32(buffer);
int alpha0 = bits0 & 0xFF;
int alpha1 = (bits0 >> 8) & 0xFF;
alphasLookup[0] = alpha0;
alphasLookup[1] = alpha1;
if (alpha0 > alpha1) {
alphasLookup[2] = (6 * alpha0 + 1 * alpha1) / 7;
alphasLookup[3] = (5 * alpha0 + 2 * alpha1) / 7;
alphasLookup[4] = (4 * alpha0 + 3 * alpha1) / 7;
alphasLookup[5] = (3 * alpha0 + 4 * alpha1) / 7;
alphasLookup[6] = (2 * alpha0 + 5 * alpha1) / 7;
alphasLookup[7] = (1 * alpha0 + 6 * alpha1) / 7;
} else {
alphasLookup[2] = (4 * alpha0 + 1 * alpha1) / 5;
alphasLookup[3] = (3 * alpha0 + 2 * alpha1) / 5;
alphasLookup[4] = (2 * alpha0 + 3 * alpha1) / 5;
alphasLookup[5] = (1 * alpha0 + 4 * alpha1) / 5;
alphasLookup[6] = 0x00;
alphasLookup[7] = 0xFF;
}
int bits = bits0 >> 16;
for (int j = 0; j < 16; j++) {
int lookup;
if (j == 5) {
lookup = (bits & 1) << 2 | (bits1 & 3);
bits = bits1 >>> 2;
} else {
lookup = bits & 7;
bits >>>= 3;
}
alphas[j] = alphasLookup[lookup];
}
}
}
int color = getInt32(buffer);
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;
if (color0 > color1 || dxtLevel > 1) {
r3 = (r0 + r1 * 2) / 3;
g3 = (g0 + g1 * 2) / 3;
b3 = (b0 + b1 * 2) / 3;
} else {
r3 = 0x00;
g3 = 0x00;
b3 = 0x00;
}
colors[0] = ((b0 & 0xFF) << 16) | ((g0 & 0xFF) << 8) | (r0 & 0xFF);
colors[1] = ((b1 & 0xFF) << 16) | ((g1 & 0xFF) << 8) | (r1 & 0xFF);
colors[2] = ((b2 & 0xFF) << 16) | ((g2 & 0xFF) << 8) | (r2 & 0xFF);
colors[3] = ((b3 & 0xFF) << 16) | ((g3 & 0xFF) << 8) | (r3 & 0xFF);
int bits = getInt32(buffer);
for (int y = 0, alphaIndex = 0; y < 4; y++) {
for (int x = 0; x < 4; x++, bits >>>= 2, alphaIndex++) {
int bgr = colors[bits & 3];
int alpha = alphas[alphaIndex] << 24;
storePixel(decompressedBuffer, strideX + x, strideY + y, bgr | alpha);
}
}
strideX += 4;
if (strideX >= width) {
strideX = 0;
strideY += 4;
}
}
buffer.rewind();
compressedImage = false;
buffer = decompressedBuffer;
bufferWidth = width;
bufferStorage = TPSM_PIXEL_STORAGE_MODE_32BIT_ABGR8888;
}
private void decompressImage() {
switch (bufferStorage) {
case TPSM_PIXEL_STORAGE_MODE_DXT1:
decompressImageDXT(1);
break;
case TPSM_PIXEL_STORAGE_MODE_DXT3:
decompressImageDXT(3);
break;
case TPSM_PIXEL_STORAGE_MODE_DXT5:
decompressImageDXT(5);
break;
default:
log.warn(String.format("Unsupported compressed buffer storage %d", bufferStorage));
break;
}
}
}