From fda68eec83e2dd589054e01c614f98e986dfa9e0 Mon Sep 17 00:00:00 2001 From: Kishor Jena Date: Thu, 5 Jan 2023 17:41:25 +0530 Subject: [PATCH] Support for alpha channel, bug fix This commit adds support for the alpha channel and fixes issues such as ICCP, ALPH chunks, and reading VP8L data. Also fixed code where VP8X flags were being read incorrectly. --- .../webpencoder/webp/muxer/WebpChunk.java | 2 + .../webpencoder/webp/muxer/WebpChunkType.java | 4 +- .../webp/muxer/WebpContainerReader.java | 52 ++++++++++++++++--- .../webp/muxer/WebpContainerWriter.java | 23 +++++++- .../webpencoder/webp/muxer/WebpMuxer.java | 24 +++++++-- 5 files changed, 92 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpChunk.java b/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpChunk.java index b3dabeb..b5b234e 100644 --- a/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpChunk.java +++ b/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpChunk.java @@ -27,6 +27,8 @@ public class WebpChunk { public boolean useAlphaBlending; public boolean disposeToBackgroundColor; + public byte[] alphaData; + public WebpChunk(WebpChunkType t) { type = t; } diff --git a/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpChunkType.java b/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpChunkType.java index 8d1b12b..ead4b9b 100644 --- a/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpChunkType.java +++ b/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpChunkType.java @@ -9,5 +9,7 @@ public enum WebpChunkType { VP8, VP8L, ANIM, - ANMF + ANMF, + ICCP, + ALPH } diff --git a/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpContainerReader.java b/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpContainerReader.java index 3f1fe8a..7119aa8 100644 --- a/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpContainerReader.java +++ b/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpContainerReader.java @@ -1,5 +1,8 @@ package com.n4no.webpencoder.webp.muxer; +import android.util.Log; + +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; @@ -52,6 +55,10 @@ public WebpChunk read() throws IOException { return readAnim(); if (isFourCc(fcc, 'A', 'N', 'M', 'F')) return readAnmf(); + if (isFourCc(fcc, 'I', 'C', 'C', 'P')) + return readIccp(); + if (isFourCc(fcc, 'A', 'L', 'P', 'H')) + return readAlph(); throw new IOException(String.format("Not supported FourCC: %c.%c.%c.%c.", fcc[0], fcc[1], fcc[2], fcc[3])); @@ -63,6 +70,24 @@ public WebpChunk read() throws IOException { return null; } + private WebpChunk readAlph() throws IOException { + int chunkSize = readUInt32(); + WebpChunk chunk = new WebpChunk(WebpChunkType.ALPH); + chunk.alphaData = readPayload(chunkSize); + chunk.hasAlpha = true; + return chunk; + } + + private WebpChunk readIccp() throws IOException { + int chunkSize = readUInt32(); + WebpChunk chunk = new WebpChunk(WebpChunkType.ICCP); + + readPayload(chunkSize); + // no need to store the payload to this chunk as Animated Webp does not require ICCP + + return chunk; + } + private WebpChunk readVp8x() throws IOException { int chunkSize = readUInt32(); if (chunkSize != 10) @@ -74,11 +99,12 @@ private WebpChunk readVp8x() throws IOException { 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); + bs.get(0); // R reserved + chunk.hasAnim = bs.get(1); // A Animation + chunk.hasXmp = bs.get(2); // X XMP + chunk.hasExif = bs.get(3); // E Exif + chunk.hasAlpha = bs.get(4); // L Alpha + chunk.hasIccp = bs.get(5); // I ICCP chunk.width = readUInt24(); chunk.height = readUInt24(); @@ -110,7 +136,10 @@ private WebpChunk readVp8l() throws IOException { WebpChunk chunk = new WebpChunk(WebpChunkType.VP8L); chunk.isLossless = true; - chunk.payload = readPayload(chunkSize); +// chunkSize is not telling the correct size of payload to read. +// chunk.payload = readPayload(chunkSize); +// So reading all bytes remained (Max 1024 bytes) + chunk.payload = readAllBytes(); debug(String.format("VP8L: bytes = %d", chunkSize)); return chunk; @@ -193,6 +222,17 @@ private boolean isFourCc(byte[] h, char a, char b, char c, char d) { return h[0] == a && h[1] == b && h[2] == c && h[3] == d; } + private byte[] readAllBytes() throws IOException { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + int numRead; + while ((numRead = _inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, numRead); + _offset += numRead; + } + return outputStream.toByteArray(); + } + } private void debug(String message) { if (_debug) System.out.println(message); diff --git a/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpContainerWriter.java b/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpContainerWriter.java index f0b88c5..f39ffa1 100644 --- a/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpContainerWriter.java +++ b/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpContainerWriter.java @@ -1,5 +1,7 @@ package com.n4no.webpencoder.webp.muxer; +import android.util.Log; + import com.n4no.webpencoder.webp.muxer.stream.SeekableOutputStream; import java.io.IOException; @@ -87,7 +89,15 @@ private void writeAnim(WebpChunk chunk) throws IOException { private void writeAnmf(WebpChunk chunk) throws IOException { write(new byte[] { 'A', 'N', 'M', 'F' }); - writeUInt32(chunk.payload.length + 24); + + // if ALPH chunk present, get size + int AlphaSize = 0; + if(chunk.alphaData != null){ + AlphaSize = 4 + 4 + chunk.alphaData.length; + // 4 bytes (ALPH header) + 4 bytes (chunk size) + length of Alpha BitStream + } + + writeUInt32(chunk.payload.length + 24 + AlphaSize); writeUInt24(chunk.x); // 3 bytes (3) writeUInt24(chunk.y); // 3 bytes (6) @@ -100,6 +110,11 @@ private void writeAnmf(WebpChunk chunk) throws IOException { bs.set(0, chunk.disposeToBackgroundColor); write(bitSetToBytes(bs, 1)); // 1 byte (16) + // Insert ALPH chunk + if(chunk.alphaData != null){ + writeAlph(chunk.alphaData); + } + if (chunk.isLossless) write(new byte[] { 'V', 'P', '8', 'L' }); // 4 bytes (20) else @@ -108,6 +123,12 @@ private void writeAnmf(WebpChunk chunk) throws IOException { write(chunk.payload); } + private void writeAlph(byte[] alphaData) throws IOException { + write(new byte[] { 'A', 'L', 'P', 'H' }); // 4 + writeUInt32(alphaData.length); // 4 + write(alphaData); // x + } + // private void write(byte[] bytes) throws IOException { diff --git a/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpMuxer.java b/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpMuxer.java index 2a937d2..dffef2c 100644 --- a/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpMuxer.java +++ b/app/src/main/java/com/n4no/webpencoder/webp/muxer/WebpMuxer.java @@ -1,5 +1,7 @@ package com.n4no.webpencoder.webp.muxer; +import android.util.Log; + import java.io.IOException; import java.io.InputStream; @@ -16,6 +18,9 @@ public class WebpMuxer { private int _width; private int _height; + private byte[] _alphaData; + private boolean _hasAlpha; + public WebpMuxer(WebpContainerWriter writer) { _writer = writer; } @@ -42,13 +47,13 @@ public void writeFirstFrameFromWebm(InputStream inputStream) throws IOException WebpChunk chunk = readFirstChunkWithPayload(reader); reader.close(); - writeFrame(chunk.payload, chunk.isLossless); + writeFrame(chunk, chunk.payload, chunk.isLossless); } - public void writeFrame(byte[] payload, boolean isLossless) throws IOException { + public void writeFrame(WebpChunk chunk,byte[] payload, boolean isLossless) throws IOException { if (_isFirstFrame) { _isFirstFrame = false; - writeHeader(); + writeHeader(chunk); } if (hasAnim()) @@ -70,18 +75,25 @@ private boolean hasAnim() { private WebpChunk readFirstChunkWithPayload(WebpContainerReader reader) throws IOException { WebpChunk chunk; while ((chunk = reader.read()) != null) { + + if (chunk.type == WebpChunkType.ALPH) + _alphaData = chunk.alphaData; + + if (chunk.type == WebpChunkType.VP8X) + _hasAlpha = chunk.hasAlpha; + if (chunk.payload != null) return chunk; } throw new IOException("Can not find chunk with payload."); } - private void writeHeader() throws IOException { + private void writeHeader(WebpChunk chunk) throws IOException { _writer.writeHeader(); WebpChunk vp8x = new WebpChunk(WebpChunkType.VP8X); vp8x.hasAnim = hasAnim(); - vp8x.hasAlpha = false; + vp8x.hasAlpha = _hasAlpha; vp8x.hasXmp = false; vp8x.hasExif = false; vp8x.hasIccp = false; @@ -111,6 +123,8 @@ private void writeAnmf(byte[] payload, boolean isLossless) throws IOException { anmf.useAlphaBlending = false; anmf.disposeToBackgroundColor = false; + anmf.alphaData = _alphaData; + _writer.write(anmf); }