javaandroidwebpvp8riff

How to read and write VP8L and ALPH chunks of Webp images


I am trying these classes written in pure java code which used for encode the animated WEBP from list of static images. It is based on Google's Webp container specification. I tried to modify the codes to fix some errors. Now

Problem: If image contains VP8L then encoder does not produce valid animated WEBP image (its blank image with 2.5MB size, Supplying 2 images with size of input images). If I set the quality while compressing WEBP to below 100 and then it read chunks as RIFF-WEBP-VP8X-ICCP-ALPH-VP8 in sequence. It creates an animated images with solid black background. It drops the transparent background data maybe, but it animates through each frame.

Code for writing ANMF chunk:

// ANMF chunk
private void writeAnmf(WebpChunk chunk) throws IOException {

   write(new byte[] { 'A', 'N', 'M', 'F' });
   writeUInt32(chunk.payload.length + 24);

   writeUInt24(chunk.x); // 3 bytes (3)
   writeUInt24(chunk.y); // 3 bytes (6)
   writeUInt24(chunk.width); // 3 bytes (9)
   writeUInt24(chunk.height); // 3 bytes (12)
   writeUInt24(chunk.duration); // 3 bytes (15)

   BitSet bs = new BitSet(6);
   bs.set(1, chunk.useAlphaBlending);
   bs.set(0, chunk.disposeToBackgroundColor);
   write(bitSetToBytes(bs, 1)); // 1 byte (16)


   if (chunk.isLossless) {
      write(new byte[] { 'V', 'P', '8', 'L' }); // 4 bytes (20)
      Log.d(Tag,"writting vp8l ; isLossLess");
   } else {
      Log.d(Tag,"writting vp8 ; Lossy");
      write(new byte[] { 'V', 'P', '8', ' ' });
   }
   writeUInt32(chunk.payload.length-16h); // 4 bytes (24)
   write(chunk.payload);

}
// ANIM chunk
private WebpChunk readVp8x() throws IOException {
        int chunkSize = readUInt32();
        if (chunkSize != 10)
            throw new IOException("Expected 10 bytes for VP8X.");

        WebpChunk chunk = new WebpChunk(WebpChunkType.VP8X);

        byte[] flags = new byte[4];
        read(flags, 4);
        BitSet bs = BitSet.valueOf(flags);

        chunk.hasIccp = bs.get(0);
        chunk.hasAnim = bs.get(1);
        chunk.hasExif = bs.get(2);
        chunk.hasXmp = bs.get(3);
        chunk.hasAlpha = bs.get(4);

        chunk.width = readUInt24();
        chunk.height = readUInt24();

        debug(String.format("VP8X: size = %dx%d", chunk.width, chunk.height));
        return chunk;
    }

Code for reading VP8L chunk:

    private WebpChunk readVp8l() throws IOException {
        Log.d(Tag,"readVp8l() body ");
        int chunkSize = readUInt32();

        WebpChunk chunk = new WebpChunk(WebpChunkType.VP8L);
        chunk.isLossless = true;
        chunk.payload = readPayload(chunkSize);

        debug(String.format("VP8L: bytes = %d", chunkSize));
        return chunk;
    }

function for writing bytes to output stream

    private void write(byte[] bytes, int length) throws IOException {
        _outputStream.write(bytes, length);
        _offset += length;
    }

It read chunks as RIFF[size]WEBP -> VP8X - ICCP - ALPH - VP8L and writing as RIFF[size]WEBP -> VP8X - ANIM - ANMF (for first frame) and then ANMF for rest of frames. what is going wrong with these codes?


Solution

  • Found out the solution by examining more. reading ALPH: We need to store the alpha data (Alpha BitStream) somewhere so that we can use that while writing the frames. writing ANMF: while writing ANMF, after writing ANMF metadata we need to write the ALPH header > BitStream length > BitStream respectively. Also we must add this length to ANMF chunk size. The ALPH chunk must be written before VP8/VP8L chunk.

    private void writeAnmf(WebpChunk chunk) throws IOException {
            write(new byte[] { 'A', 'N', 'M', 'F' });
    
            int alphSize = 0;
            if(chunk.alphaData != null){
                alphSize = 8+chunk.alphaData.length;
            }
    
            writeUInt32(chunk.payload.length + 24 + alphSize);
    
            writeUInt24(chunk.x); // 3 bytes (3)
            writeUInt24(chunk.y); // 3 bytes (6)
            writeUInt24(chunk.width); // 3 bytes (9)
            writeUInt24(chunk.height); // 3 bytes (12)
            writeUInt24(chunk.duration); // 3 bytes (15)
    
            BitSet bs = new BitSet(8);
            bs.set(0, true);
            bs.set(1, chunk.disposeToBackgroundColor);
            write(bitSetToBytes(bs, 1)); // 1 byte (16)
    
            if (chunk.alphaData != null){
                write(new byte[] { 'A', 'L', 'P', 'H' }); // 4
                writeUInt32(chunk.alphaData.length); // 4
                write(chunk.alphaData);
                Log.e("WebpDemo","ALPH ");
            }
    
    
            if (chunk.isLossless)
                write(new byte[] { 'V', 'P', '8', 'L' }); // 4 bytes (20)
            else
                write(new byte[] { 'V', 'P', '8', ' ' });
    
            writeUInt32(chunk.payload.length); // 4 bytes (24)
            write(chunk.payload);
    
    
        }