Build an animated GIF89a written from the specification and read back before download, so the frame count, the delays, the loop count and the palette size are reported from the finished file rather than from the request.
Initializing in your browser…
Convert a video clip to an animated GIF with a palette built from your own frames, four dithering modes each labelled with its measured size cost, a loop count that really reaches the file, and frame rates marked for whether a GIF can hold them exactly.
Caption a picture with text that is measured, wrapped and kept inside the frame. The preview canvas is the exported file, pixel for pixel.
Arrange photos in one of twelve templates whose every cell rectangle, gap, margin and radius is stated in the output's own pixels before anything is drawn.
Four 120 by 90 images, each asked to show for 100 ms, looping forever.
Input
frame1..4.png · 100 ms each · loop forever
What the file actually contains
through ffmpeg written here frames 5 4 delays (ms) 120 80 120 80 40 100 100 100 100 loop 0 0 64 colours asked 256 in the file 64 in the file time to produce never finished about 1 second
The concat demuxer needs the last file repeated to give it a duration, so four images become five frames, and it resamples the timing onto a constant frame rate, so a run of 100 ms frames is stored as 120, 80, 120, 80 and 40. Driven in a browser, the WebAssembly build did not return at all. A GIF is a container with an exact layout, so writing it directly makes the frame count the frame count and the delays the delays, and every figure above is read back out of the finished bytes.
Build an animated GIF from images, with the container written from the GIF89a specification and read back before you download it. The frame count, the delays, the loop count and the palette size are all reported from the finished file, and the colour setting actually reaches the encoder.
Frames in the order you choose, at the timing you choose, with the file confirming both.
Skipping unchanged pixels is worth the most here, because most of the screen does not move.
Drop the width and the colour count and watch the size, which is measured rather than estimated.
The panel reports the frame count, delays, loop count and palette read back out of the finished bytes.
Four images at 100 ms each: the file holds four frames at exactly 100 ms, looping forever, and the page reports all of it read back from the bytes. Through ffmpeg the same four images gave five frames at 120, 80, 120, 80 and 40 ms.
Drop to 32 colours and leave the unchanged-pixel skip on. The panel reports the palette size, the colours actually in the output, and how many pixels were skipped.
A GIF is a container with an exact layout, published in "Graphics Interchange Format Version 89a" (CompuServe, 1990), and everything a GIF tool claims is something the file either says or does not: how many frames it holds, the delay before each one, how many times it loops and how many colours are in its palette. This page writes that container directly and then READS IT BACK before handing it over, so all four of those are reported from the finished bytes rather than from the request.
The tool this replaces went through ffmpeg.wasm. Driven on four 120 by 90 frames it never returned at all: the progress bar reached 45 percent, which is the line immediately before the encoder is called, and stayed there for the whole 195 seconds it was watched. The same command run with a native ffmpeg does complete, and even then it does not produce what was asked for. The concat demuxer needs the last file repeated to give it a duration, so FOUR images became FIVE frames. And it resamples the timing onto a constant frame rate, so a run of four 100 ms frames came out of the file as 120, 80, 120, 80 and 40 ms. Writing the container directly means four images are four frames and 100 ms is 100 ms, both confirmed by an independent reader.
The colour setting is now real. In the old tool the number of colours appeared in exactly one place, a size estimate, while the encoder was hardcoded to 256; asking for 64 produced a 256-colour GIF. Here the palette is built from a census of every frame at once, by frequency, Heckbert median cut (SIGGRAPH 1982) or k-means in CIE Lab, and the page COUNTS the colours in its own output afterwards. Asking for 32, 64, 128 and 256 gives four different files, and the count in each is at most what was asked for. A GIF palette is a power of two from 2 to 256, which the page states rather than leaving to be discovered.
The delay is stored in hundredths of a second (section 23, the Graphic Control Extension), so 40, 50, 100, 200, 500 and 1000 ms are exact and 55 ms is not. Ask for 55 and the page says it stored 60 and that the difference is 5 ms, with the reason. The loop count lives in the NETSCAPE2.0 Application Extension and holds the number of ADDITIONAL plays, which is the field tools get backwards: the option that used to be called "Once" wrote a loop count of 1, which makes the animation play TWICE. The options are now labelled by what the file will do.
Two size controls are offered with their cost measured rather than described. Dithering is Floyd and Steinberg (1976), the 7/16, 3/16, 5/16, 1/16 kernel; it hides banding in a gradient and makes the file larger, because noise does not compress, which on a test photograph took 100.7 KB to 151.2 KB. Skipping unchanged pixels leaves anything identical to the frame before transparent so the frame below shows through; on the same pair it skipped 122,552 of 345,600 pixels and took 118.5 KB down to 100.7 KB. How "nearest colour" is measured is also a choice: CIE 1976 is the Euclidean distance in Lab and is what most encoders use, CIEDE2000 is the better model and about ten times slower, and the page names which one produced the file and how long it took.
Everything runs in this browser with no encoder to download, so a four-frame animation is ready in about a second rather than after a twelve megabyte WebAssembly fetch that may never finish.
Because a video encoder needs the last frame repeated to give it a duration. Tools built on ffmpeg's concat demuxer add that repeat, so four images become five frames. Writing the GIF container directly means the frame count is the number of images, and the page reports what the file actually holds.
Because a GIF stores the delay in hundredths of a second, so it can hold 50 or 60 and nothing in between. The page says what it stored and by how much it differs from what you asked for. Every multiple of ten is exact.
The number of ADDITIONAL plays, in the NETSCAPE2.0 Application Extension. Zero means forever, which is why it is the usual setting. A loop count of 1 makes the animation play twice, not once, which is why the option here is labelled "Play twice".
Because the palette index is one byte, and the table size field in the Logical Screen Descriptor is three bits holding log2(size) minus one, so the size is a power of two up to 256. Anything you ask for is rounded up to the next power of two, and the page counts what really ended up in the file.
Only if you can see banding in a gradient. It works by scattering the error into neighbouring pixels, which is noise, and noise is exactly what LZW cannot compress: on a test photograph at 64 colours it took the file from 100.7 KB to 151.2 KB.
A pixel identical to the one in the frame before is written as transparent, so what is already on screen shows through and the encoder has a long run to compress. On a two frame photographic test it skipped 122,552 of 345,600 pixels and took the file from 118.5 KB to 100.7 KB. It never changes what you see.
Images are decoded, edited, and exported entirely inside this browser tab. No originals, exports, or metadata are uploaded.