0

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);
User51610
  • 453
  • 5
  • 18
  • https://stackoverflow.com/questions/36407866/converting-byte-array-to-png ... I think this should work with the alpha as well but not sure. – hamena314 Dec 18 '17 at 16:32
  • Since that person is reading data from an existing PNG file, wouldn't that byte array contain other stuff in it as well, and not just the raw image data? I honestly don't know as I've never looked in depth into image formats. – User51610 Dec 18 '17 at 16:37
  • There seems to be something missing from my byte array that causes a call to `ImageIO.read()` with a `ByteArrayInputStream` to return null. The other way, creating a `BufferedImage` directly doesn't work either, as the color type constants there don't seem to have my format, which is 4 bytes, in the order of R, G, B, A. – User51610 Dec 18 '17 at 17:10
  • Best to show your code. :) – hamena314 Dec 18 '17 at 17:25
  • Well there's nothing to really show. I have a byte array that is comprised of raw image data. No header or meta information. Each color is a byte, with a byte for alpha, in the order of RGBA. – User51610 Dec 18 '17 at 18:03
  • I'd say the code for creating the `BufferedImage` and writing the PNG is good, if the order is in fact RGBA (which I'm not sure of). With a full MCVE and test input data, it should be trivial to verify, or fix. You probably also want to show what the PNG should look like, if that's not obvious. – Harald K Dec 19 '17 at 17:05
  • Ha, it looks like you commented and I answered at the same time. Issue was signed vs unsigned when decompressing from DXT5! I fixed that and it works perfectly now. – User51610 Dec 19 '17 at 17:08

1 Answers1

0

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).

User51610
  • 453
  • 5
  • 18