s o m e t h i n g a b o u t m e

how to remove chunks from PNG

From PNG specification: The PNG datastream consists of a PNG signature followed by a sequence of chunks. Each chunk has a chunk type which specifies its function.

There are different types of PNG chunks:

  1. four of them are termed critical chunks: IHDR (first chunk, header), PLTE (for indexed PNG images), IDAT(image data chunks), IEND (last chunk, bottom line)
  2. The remaining 14 chunk types are termed ancillary chunk types, which encoders may generate and decoders may interpret.
    1. Transparency information: tRNS (transparency information).
    2. Colour space information: cHRM, gAMA, iCCP, sBIT, sRGB (colour space information).
    3. Textual information: iTXt, tEXt, zTXt (textual information).
    4. Miscellaneous information: bKGD, hIST, pHYs, sPLT (miscellaneous information).
    5. Time information: tIME (time stamp information).

By the way, each chunk has a 4 character identifier as you see and upper case means it’s required, lower case means it’s optional.

Chunks follow a format that goes like this: 4 bytes that store the length of the data block of the chunk, 4 bytes for the identifier, then the chunk data, then 4 bytes for the CRC. So chunk is [length.id.data.crc] Additionally every chunk type has its position boundaries in PNG data stream.

My issue was to remove bKGD chunk from PNG file (some mobile phones doesn’t like bKGD chunk in PNG file) so we will look closely at bKGD chunk.

bKGD can be placed between IHDR (always first) and first IDAT chunk so we need to find bKGD identifier or identifier of first IDAT chunk and then break the loop. So code is:


public static InputStream removebKGDChunk(InputStream is) {
        try {
            byte[] data = IOUtils.toByteArray(is);

            if (data.length == 0) {
                return new ByteArrayInputStream(data);
            }

            int chunkDataLenght = 0;
            int chunkPosition = 0;
            for (int i = 0; i < data.length; i++) {
                if (data[i + 4] == 'b' && data[i + 5] == 'K'
                    && data[i + 6] == 'G' && data[i + 7] == 'D') {
                    chunkPosition = i;
                    chunkDataLenght = (data[i++] << 4) | (data[i++] << 4)
                                            | (data[i++] << 4) | data[i];
                    break;
                }
                if (data[i + 4] == 'I' && data[i + 5] == 'D'
                    && data[i + 6] == 'A' && data[i + 7] == 'T') {
                    break;
                }
            }
            if (chunkPosition != 0) {
                int fullChunkLenght = MIN_CHUNK_LENGHT + chunkDataLenght;

                // MIN_CHUNK_LENGHT = lengthField + idField + CRC = 12

                System.arraycopy(data, chunkPosition + fullChunkLenght,
                        data, chunkPosition,
                        data.length - chunkPosition - fullChunkLenght);
                return new ByteArrayInputStream(
                        ArrayUtils.subarray(data, 0, data.length - fullChunkLenght));
            }
            return new ByteArrayInputStream(data);
        } catch (IOException e) {
            logger.warn("Cannot remove bKGD chunk ", e);
        } finally {
            IOUtils.closeQuietly(is);
        }
        return null;
    }

I am working with InputStream directly to make this functionality easy to plug/unplug.

Any questions? :)

RSS feed | Trackback URI

1 Comment »

2008-03-11 14:16:04

[…] already have the post about chunk removing in Java. But how to remove chunk from PNG file data stream in […]

 
Name (required)
E-mail (required - never shown publicly)
URI
Subscribe to comments via email
Your Comment (smaller size | larger size)
You may use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> in your comment.