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); }