sksamuel / scrimage

JVM - Java, Kotlin, Scala image processing library
https://sksamuel.github.io/scrimage
Apache License 2.0
1.07k stars 140 forks source link

Animated GIF not Compressed #256

Closed justwiebe closed 1 year ago

justwiebe commented 1 year ago

I am starting with a GIF that is 628kb, but after scaling each frame down 10%, it's now 2.8MB. It looks like each frame now has full pixels

InputStream input = Files.newInputStream(file.toPath());
File output = new File("...");

AnimatedGif gif = AnimatedGifReader.read(ImageSource.of(input));
StreamingGifWriter writer = new StreamingGifWriter(gif.getDelay(0), gif.getLoopCount() == 0);
try (StreamingGifWriter.GifStream stream = writer.prepareStream(output, gif.getFrame(0).getType())) {
    for (ImmutableImage frame : gif.getFrames()) {
        stream.writeFrame(frame.scale(.9));
    }
} catch (final Exception e) {
    log.error(":(", e);
}

Test GIF: k

Test Output: k

justwiebe commented 1 year ago

For reference on what I mean about the frame, here is the first few frames on the original: image

And here is the first few frames on the output: image

justwiebe commented 1 year ago

I was able to fix this by manually removing pixels. It would be great to make this an option in the writer though.

StreamingGifWriter writer = new StreamingGifWriter(gif.getDelay(0), gif.getLoopCount() == 0);
try (StreamingGifWriter.GifStream stream = writer.prepareStream(output, gif.getFrame(0).getType())) {
    BufferedImage last = null;
    for (ImmutableImage frame : gif.getFrames()) {
        BufferedImage image = frame.scale(scale, ScaleMethod.Lanczos3).toNewBufferedImage(BufferedImage.TYPE_INT_ARGB);
        if (last != null) {
            DataBuffer iBuf = image.getRaster().getDataBuffer();
            DataBuffer lBuf = last.getRaster().getDataBuffer();
            last = copy(image);
            for (int i = 0; i < iBuf.getSize(); i++) {
                if (iBuf.getElem(i) == lBuf.getElem(i)) {
                    iBuf.setElem(i, 0);
                }
            }
        } else {
            last = image;
        }
        stream.writeFrame(ImmutableImage.fromAwt(image));
    }
} 

I also would like to further optimize it by cropping the frame to fit the actual pixels and then setting the top/left metadata on the stream, but there's no option for setting that metadata

sksamuel commented 1 year ago

Thanks, I've adapted your work and added withCompression(true) option to the StreamingGifWriter. Will include this in 4.0.33 which I will release shortly.

justwiebe commented 1 year ago

@sksamuel Thanks! One note, is it looks like that option will be ignored if you call writeFrame with any options

sksamuel commented 1 year ago

I can do a follow up release.

On Mon, 16 Jan 2023 at 11:17, BakoviDagi @.***> wrote:

@sksamuel https://github.com/sksamuel Thanks! One note, is it looks like that option will be ignored if you call writeFrame with any options

— Reply to this email directly, view it on GitHub https://github.com/sksamuel/scrimage/issues/256#issuecomment-1384350309, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAFVSGWYVBBQ7Q2I2X26I3TWSV7AZANCNFSM6AAAAAAT2USABY . You are receiving this because you were mentioned.Message ID: @.***>