I am using webp encoder from this. The authur abandoned the project so no help from there. It had some issue like reading FourcC ICCP and ALPH. I added codes for them and now It can encode bitmaps to animated webp.
But the problem is some of animated webp files are throwing error Not supported FourC
while encoding.
Here How I use the encoder
WebpBitmapEncoder encoder = new WebpBitmapEncoder(destination);
encoder.setLoops(0); // 0 = infinity.
for(Bitmap bitmap:bitmap_array) {
Bitmap bitmap2 = scalePreserveRatio(bitmap,512,512);
// Bitmap comp_bitmap = compress(new_bitmap);
encoder.setDuration(80);
encoder.writeFrame(bitmap2, 80);
}
encoder.close();
decoding:
val drawable = GlideApp.with(context).load(source_file).skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.submit().get() as WebpDrawable
drawable.constantState
val state = drawable.constantState as Drawable.ConstantState
val frameLoader: Field = state::class.java.getDeclaredField("frameLoader")
frameLoader.isAccessible = true
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
val webpFrameLoader = frameLoader.get(state) as WebpFrameLoader
val webpDecoder: Field = webpFrameLoader.javaClass.getDeclaredField("webpDecoder")
webpDecoder.isAccessible = true
val standardGifDecoder = webpDecoder.get(webpFrameLoader) as GifDecoder
Log.d("ReactNative", "got ${standardGifDecoder.frameCount} frames:")
for (i in 0 until standardGifDecoder.frameCount) {
val delay = standardGifDecoder.nextDelay
val bitmap = standardGifDecoder.nextFrame as Bitmap
// bitmap
standardGifDecoder.advance()
}
Resizing..
public static Bitmap scalePreserveRatio(Bitmap imageToScale, int destinationWidth,
int destinationHeight) {
// NOTE
if (destinationHeight > 0 && destinationWidth > 0 && imageToScale != null) {
int width = imageToScale.getWidth();
int height = imageToScale.getHeight();
//Calculate the max changing amount and decide which dimension to use
float widthRatio = (float) destinationWidth / (float) width;
float heightRatio = (float) destinationHeight / (float) height;
//Use the ratio that will fit the image into the desired sizes
int finalWidth = (int)Math.floor(width * widthRatio);
int finalHeight = (int)Math.floor(height * widthRatio);
if (finalWidth > destinationWidth || finalHeight > destinationHeight) {
finalWidth = (int)Math.floor(width * heightRatio);
finalHeight = (int)Math.floor(height * heightRatio);
}
//Scale given bitmap to fit into the desired area
imageToScale = Bitmap.createScaledBitmap(imageToScale, finalWidth, finalHeight, true);
//Created a bitmap with desired sizes
Bitmap scaledImage = Bitmap.createBitmap(destinationWidth, destinationHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(scaledImage);
//Draw background color
Paint paint = new Paint();
paint.setColor(Color.TRANSPARENT);
paint.setStyle(Paint.Style.FILL);
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);
//Calculate the ratios and decide which part will have empty areas (width or height)
float ratioBitmap = (float)finalWidth / (float)finalHeight;
float destinationRatio = (float) destinationWidth / (float) destinationHeight;
float left = ratioBitmap >= destinationRatio ? 0 : (float)(destinationWidth - finalWidth) / 2;
float top = ratioBitmap < destinationRatio ? 0: (float)(destinationHeight - finalHeight) / 2;
canvas.drawBitmap(imageToScale, left, top, null);
return scaledImage;
} else {
return imageToScale;
}
}
And the location of the error in encoder is read()
from where errro is being thrown :
package com.n4no.webpencoder.webp.muxer;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.BitSet;
/**
* @author Bartlomiej Tadych, b4rtaz
*/
public class WebpContainerReader {
private final InputStream _inputStream;
private final boolean _debug;
private int _fileSize;
private int _offset;
public WebpContainerReader(InputStream inputStream, boolean debug) {
_inputStream = inputStream;
_debug = debug;
}
public void close() throws IOException {
}
public void readHeader() throws IOException {
byte[] fcc = new byte[4];
read(fcc, 4);
if (!isFourCc(fcc, 'R', 'I', 'F', 'F'))
throw new IOException("Expected RIFF file.");
_fileSize = readUInt32() + 8 - 1;
read(fcc, 4);
if (!isFourCc(fcc, 'W', 'E', 'B', 'P'))
throw new IOException("Expected Webp file.");
}
public WebpChunk read() throws IOException {
byte[] fcc = new byte[4];
if (read(fcc, 4) > 0) {
if (isFourCc(fcc, 'V', 'P', '8', ' '))
return readVp8();
if (isFourCc(fcc, 'V', 'P', '8', 'L'))
return readVp8l();
if (isFourCc(fcc, 'V', 'P', '8', 'X'))
return readVp8x();
if (isFourCc(fcc, 'A', 'N', 'I', 'M'))
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]));
}
if (_fileSize != _offset)
throw new IOException(String.format("Header has wrong file size: %d, expected: %d",
_fileSize, _offset));
return null;
}
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;
}
private byte[] readPayload(int bytes) throws IOException {
byte[] payload = new byte[bytes];
if (read(payload, bytes) != bytes)
throw new IOException("Can not read all bytes.");
return payload;
}
private WebpChunk readVp8() throws IOException {
int chunkSize = readUInt32();
WebpChunk chunk = new WebpChunk(WebpChunkType.VP8);
chunk.isLossless = false;
chunk.payload = readPayload(chunkSize);
debug(String.format("VP8: bytes = %d", chunkSize));
return chunk;
}
private WebpChunk readVp8l() throws IOException {
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;
}
private WebpChunk readAnim() throws IOException {
int chunkSize = readUInt32();
if (chunkSize != 6)
throw new IOException("Expected 6 bytes for ANIM.");
WebpChunk chunk = new WebpChunk(WebpChunkType.ANIM);
chunk.background = readUInt32();
chunk.loops = readUInt16();
debug(String.format("ANIM: loops = %d", chunk.loops));
return chunk;
}
private WebpChunk readAnmf() throws IOException {
int chunkSize = readUInt32();
WebpChunk chunk = new WebpChunk(WebpChunkType.ANMF);
chunk.x = readUInt24();
chunk.y = readUInt24();
chunk.width = readUInt24();
chunk.height = readUInt24();
chunk.duration = readUInt24();
byte[] flags = new byte[1];
read(flags, 1);
BitSet bs = BitSet.valueOf(flags);
chunk.useAlphaBlending = bs.get(1);
chunk.disposeToBackgroundColor = bs.get(0);
byte[] cch = new byte[4];
read(cch, 4);
if (isFourCc(cch, 'V', 'P', '8', 'L'))
chunk.isLossless = true;
else if (isFourCc(cch, 'V', 'P', '8', ' '))
chunk.isLossless = false;
else
throw new IOException("Not supported ANMF payload.");
readUInt32(); // Payload size.
int payloadSize = chunkSize - 24;
chunk.payload = readPayload(payloadSize);
debug(String.format("ANMF: size = %dx%d, offset = %dx%d, duration = %d, bytes = %d",
chunk.width, chunk.height, chunk.x, chunk.y, chunk.duration, payloadSize));
return chunk;
}
private WebpChunk readIccp() throws IOException {
int chunkSize = readUInt32();
WebpChunk chunk = new WebpChunk(WebpChunkType.VP8);
chunk.isLossless = false;
readPayload(chunkSize);
chunk.payload = null;
debug(String.format("iccp: bytes = %d", chunkSize));
return chunk;
}
private WebpChunk readAlph() throws IOException {
int chunkSize = readUInt32();
WebpChunk chunk = new WebpChunk(WebpChunkType.VP8);
chunk.isLossless = false;
readPayload(chunkSize);
chunk.payload = null;
debug(String.format("alph: bytes = %d", chunkSize));
return chunk;
}
//
private final int read(byte[] buffer, int bytes) throws IOException {
int count = _inputStream.read(buffer, 0, bytes);
_offset += count;
return count;
}
private final int readUint(int bytes) throws IOException {
byte[] b = new byte[] { 0, 0, 0, 0 };
read(b, bytes);
return ByteBuffer.wrap(b, 0, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
}
private final int readUInt32() throws IOException {
return readUint(4);
}
private final int readUInt24() throws IOException {
return readUint(3);
}
private final int readUInt16() throws IOException {
return readUint(2);
}
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 void debug(String message) {
if (_debug)
System.out.println(message);
}
}
rest of related files are here on sandbox. https://codesandbox.io/s/polished-water-hl32ud?file=/app/muxer/WebpMuxer.java
without using scalePreserveRatio()
All webp files gets encoded without fail. The scaling function is the culprit here.
I want to resize and encode these animated webp files. How would I fix this exception.