I've gone through a lot of questions on here and across the Internet in general and haven't had any success yet.
I have a byte array that represents the raw data that should be turned into a PNG image. It has alpha, so my colors are RGBA. As far as I know, the byte array is the raw image data itself, no headers or metadata or whatever else might be assumed by some libraries or methods (this byte array was decoded from DXT5).
How can I turn my raw image data byte array into a PNG image, with alpha?
EDIT: Since I have no idea what I'm doing in this domain, I may be including more code here than is necessary to triage the problem.
Here is the DXT5 decoding stuff:
private void decodeDXT5(ByteBuffer encodedBytes, int position, byte[] decodedBytes, int width, int height, int currentY, int currentX) {
encodedBytes.order(ByteOrder.LITTLE_ENDIAN);
encodedBytes.position(position);
byte alpha0 = encodedBytes.get();
byte alpha1 = encodedBytes.get();
byte[] rgb = new byte[6];
encodedBytes.get(rgb, 0, 6);
byte[] color0 = RGB565_to_RGB888(encodedBytes.getShort());
byte[] color1 = RGB565_to_RGB888(encodedBytes.getShort());
byte[] c = new byte[]{encodedBytes.get(), encodedBytes.get(), encodedBytes.get(), encodedBytes.get()};
byte[] a = new byte[]{
(byte) (0x7 & rgb[0]),
(byte) (0x7 & (rgb[0] >> 3)),
(byte) (0x7 & (((0x1 & rgb[1]) << 2) + (rgb[0] >> 6))),
(byte) (0x7 & (rgb[1] >> 1)),
(byte) (0x7 & (rgb[1] >> 4)),
(byte) (0x7 & (((0x3 & rgb[2]) << 1) + (rgb[1] >> 7))),
(byte) (0x7 & (rgb[2] >> 2)),
(byte) (0x7 & (rgb[2] >> 5)),
(byte) (0x7 & rgb[3]),
(byte) (0x7 & (rgb[3] >> 3)),
(byte) (0x7 & (((0x1 & rgb[4]) << 2) + (rgb[3] >> 6))),
(byte) (0x7 & (rgb[4] >> 1)),
(byte) (0x7 & (rgb[4] >> 4)),
(byte) (0x7 & (((0x3 & rgb[5]) << 1) + (rgb[4] >> 7))),
(byte) (0x7 & (rgb[5] >> 2)),
(byte) (0x7 & (rgb[5] >> 5))
};
for (int i = 0; i < 16; i++) {
int e = Math.floorDiv(i, 4);
decodedBytes[width * 4 * (height - 1 - currentY - e) + 4 * currentX + ((i - (e * 4)) * 4) + 0] = c2Value(3 & c[e], color0[0], color1[0]); //red
decodedBytes[width * 4 * (height - 1 - currentY - e) + 4 * currentX + ((i - (e * 4)) * 4) + 1] = c2Value(3 & c[e], color0[1], color1[1]); //green
decodedBytes[width * 4 * (height - 1 - currentY - e) + 4 * currentX + ((i - (e * 4)) * 4) + 2] = c2Value(3 & c[e], color0[2], color1[2]); //blue
decodedBytes[width * 4 * (height - 1 - currentY - e) + 4 * currentX + ((i - (e * 4)) * 4) + 3] = a2Value(a[i], alpha0, alpha1); //alpha
c[e] = (byte) (c[e] >> 2);
}
}
private byte[] RGB565_to_RGB888(short rgb) {
byte r = (byte) (((rgb & 0xF800) >> 11) * 8);
byte g = (byte) (((rgb & 0x07E0) >> 5) * 4);
byte b = (byte) ((rgb & 0x001F) * 8);
return new byte[]{r, g, b};
}
private byte c2Value(int code, byte color0, byte color1) {
switch (code) {
case 0:
return color0;
case 1:
return color1;
case 2:
case 3:
return (byte) ((color0 + color1 + 1) >> 1);
}
return color0;
}
private byte a2Value(int code, byte alpha0, byte alpha1) {
if (alpha0 > alpha1) {
switch (code) {
case 0:
return alpha0;
case 1:
return alpha1;
case 2:
return (byte) ((6 * alpha0 + 1 * alpha1) / 7);
case 3:
return (byte) ((5 * alpha0 + 2 * alpha1) / 7);
case 4:
return (byte) ((4 * alpha0 + 3 * alpha1) / 7);
case 5:
return (byte) ((3 * alpha0 + 4 * alpha1) / 7);
case 6:
return (byte) ((2 * alpha0 + 5 * alpha1) / 7);
case 7:
return (byte) ((1 * alpha0 + 6 * alpha1) / 7);
default:
LOG.error("a2Value code : " + code);
}
} else {
switch (code) {
case 0:
return alpha0;
case 1:
return alpha1;
case 2:
return (byte) ((4 * alpha0 + 1 * alpha1) / 5);
case 3:
return (byte) ((3 * alpha0 + 2 * alpha1) / 5);
case 4:
return (byte) ((2 * alpha0 + 3 * alpha1) / 5);
case 5:
return (byte) ((1 * alpha0 + 4 * alpha1) / 5);
case 6:
return 0;
case 7:
return (byte) 0xFF; //why, what, WHY???
default:
LOG.error("a2Value code : " + code);
}
}
return alpha0;
}
Which is run from:
ByteBuffer bbIn = ByteBuffer.allocate(imageDataSize);
bbIn.put(imageData, 0, imageDataSize);
bbIn.position(0);
byte[] decodedImage = new byte[height * width * 4];
int currentX = 0;
int currentY = 0;
int position = 0;
while (position < imageData.length) {
if ((currentX == width) && (currentY == height)) {
break;
}
decodeDXT5(bbIn, position, decodedImage, width, height, currentY, currentX);
currentX += 4;
if (currentX + 4 > width) {
currentX = 0;
currentY += 4;
}
position += 16;
}
And the PNG part is here. This is the latest iteration of it from my search efforts, and it's the closest yet. The file size, dimensions, shape, etc, is all there; it's just that the colors are off. There are a lot of artifacts going on; colors that aren't a part of the image. The PNG code seems to look ok (I've tried several different band offset orders, but they all result in the same image). Maybe the DXT5 decoding is wrong? Or maybe the PNG stuff is, in fact, not ok. Like I said, I don't know what I'm doing here as it's outside my domain. As mentioned earlier, this DXT5 decoded image is in the form of RGBA (one byte for each).
DataBuffer dataBuffer = new DataBufferByte(decodedImage, decodedImage.length);
int samplesPerPixel = 4;
int[] bandOffsets = {0, 1, 2, 3};
WritableRaster raster = Raster.createInterleavedRaster(dataBuffer, width, height, samplesPerPixel * width, samplesPerPixel, bandOffsets, null);
ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
BufferedImage image = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
File file = new File("/opt/wildfly/standalone/tmp/temp.png");
ImageIO.write(image, "png", file);
Java's lack of unsigned types to the, uh, rescue...
This was an issue with signed vs unsigned. Java doesn't have unsigned, as we know. This was not actually a PNG issue, but an issue when decompressing from DXT5. When there was math involved, I needed to make sure I was working with the equivalent of an unsigned type (so in grand Java tradition, use the next highest type and mask off the sign bit).