mrkeyoor.com_
Sat 08 Aug 22:00 UTC
npmUtilsupdated 08 Aug 2026

gifwrap

gifwrap is a Node.js library for decoding, editing, and encoding single-frame or animated GIF files. It expands each frame into a Jimp-shaped RGBA Buffer, offers pixel, crop, scale, palette, and quantization helpers, then writes the frames through an omggif-based codec. It can work with filenames or Buffers and includes TypeScript declarations. Despite its name and bitmap compatibility, Jimp is optional and is not installed with the package.

Verdict

gifwrap remains a convenient pure-JavaScript tool for small, controlled Node.js GIF jobs, especially when its RGBA frame model fits existing code. Do not make it the default for untrusted uploads or a new high-volume image service; maintenance and memory behavior point toward sharper alternatives.

API stability4/5Version 0.10.1 exposes a compact set of classes that map directly to frames, bitmaps, a codec, and utilities, and the detailed declarations match most of that surface. The API has not churned, but the package is still below 1.0, mutating quantizers can surprise callers, and the included Gif constructor declaration does not match the source constructor order even though users are told not to construct Gif directly.
Docs4/5The README is unusually complete for a small package: it explains RGBA layout, binary transparency, frame options, loop and color-table behavior, all three quantizers, Jimp copying versus sharing, custom codecs, and each public method. Weak spots are operational limits, modern Jimp compatibility, safe handling of hostile inputs, and examples that mostly use older Promise chaining and CommonJS style.
Maintenance2/5The project is not archived and now lives in the jimp-dev organization, but npm 0.10.1 dates to March 2022 and GitHub reports the last push in December 2022. Open reports cover memory exhaustion, transparency, invalid GIFs, a circular dependency, and a request for a new maintainer. That is a weak maintenance position for code parsing user-controlled media.
Ecosystem3/5gifwrap recorded 3,506,893 downloads in the measured week, includes TypeScript declarations, uses the established omggif codec, and shares Jimp's bitmap shape without forcing Jimp into the dependency tree. Its surrounding ecosystem is still small, Node-only, CommonJS-only, and tied to the limited GIF format rather than a broad current image-processing stack.

Use it if

  • You need to inspect or rewrite animated GIF frames in a Node.js batch job without a native image dependency
  • You want direct RGBA Buffer access and are comfortable implementing pixel operations yourself
  • You need to create a small GIF from generated frames and control delays, looping, offsets, disposal, and palette scope
  • You already use Jimp-shaped bitmap objects and need a narrow bridge to GIF encoding
Skip it if

Setup reality

npm install gifwrap brings image-q and omggif, both JavaScript packages, so there is no compiler, native binary, credential, or config file. The package is CommonJS, runs only in Node.js, and reads or writes local paths with the filesystem. It also accepts encoded Buffers when you want to own I/O. TypeScript declarations are included as index.d.ts even though package.json does not advertise a types field, so most Node TypeScript resolvers find them through the conventional filename. The real setup cost is resource and image policy. A decoded frame is a width by height RGBA Buffer, which means four bytes per pixel for every frame before object overhead; a long or oversized animation can consume much more memory than its compressed file suggests. Put byte, dimension, and frame-count limits around untrusted input, ideally outside the request path. GIF permits no partial transparency, and gifwrap converts alpha 0 to transparent while treating every other alpha value as opaque. The encoder rejects frames needing more than 256 color indexes, so photographic or Jimp-edited frames usually need one of GifUtil's in-place quantizers before encoding. Quantizing all frames together can reduce palette flicker but costs additional CPU and memory. Frame delays are centiseconds, loops defaults to 0 for endless looping, and disposal method matters when frames use offsets or smaller rectangles. GifUtil.write overwrites its target and rejects some suspicious non-GIF suffixes, but your application still owns directory creation, collision handling, atomic replacement, and cleanup. The Jimp bridge shares or copies a bitmap rather than depending on Jimp, and its README examples were written against a much older Jimp constructor API, so verify compatibility with the Jimp major you actually install.

Patterns

Decode a GIF from a fileread-gif-file

const { GifUtil } = require('gifwrap');

const gif = await GifUtil.read('input.gif');
console.log({
  width: gif.width,
  height: gif.height,
  frames: gif.frames.length,
  loops: gif.loops,
});

All decoded frames and their RGBA buffers are retained in memory. Check input size before using this on uploads.

Decode bytes without filesystem I/Odecode-gif-buffer

const { GifCodec } = require('gifwrap');

const codec = new GifCodec();
const gif = await codec.decodeGif(encodedBuffer);

The buffer must contain the complete GIF. Catch GifError for malformed encoding failures.

Create a solid-color framecreate-solid-frame

const { GifFrame } = require('gifwrap');

const frame = new GifFrame(160, 90, 0x3366ffff, {
  delayCentisecs: 10,
  disposalMethod: GifFrame.DisposeToBackgroundColor,
});

Packed colors use 0xRRGGBBAA order. Frame delay is measured in hundredths of a second.

Write an animated GIFwrite-animated-gif

const { GifFrame, GifUtil } = require('gifwrap');

const frames = [
  new GifFrame(80, 80, 0xff0000ff, { delayCentisecs: 12 }),
  new GifFrame(80, 80, 0x0000ffff, { delayCentisecs: 12 }),
];

await GifUtil.write('colors.gif', frames, { loops: 0 });

loops: 0 means repeat forever. GifUtil.write overwrites the destination rather than creating an atomic temporary file.

Encode frames to an in-memory Bufferencode-gif-buffer

const { GifCodec } = require('gifwrap');

const codec = new GifCodec();
const encoded = await codec.encodeGif(frames, { loops: 3 });
await uploadObject(encoded.buffer, 'image/gif');

The resolved Gif holds both encoded bytes and frame references, so release it after upload when processing large animations.

Edit pixels with coordinatesedit-pixels

gif.frames.forEach((frame) => {
  frame.scanAllCoords((x, y, index) => {
    if (x < 20) {
      frame.bitmap.data[index] = 255;
      frame.bitmap.data[index + 1] = 0;
      frame.bitmap.data[index + 2] = 0;
      frame.bitmap.data[index + 3] = 255;
    }
  });
});

Each pixel occupies RGBA bytes at index through index + 3. Any alpha other than zero becomes fully opaque in the GIF.

Quantize all frames to a shared color budgetquantize-animation

const { GifUtil } = require('gifwrap');

GifUtil.quantizeWu(gif.frames, 256, 5, {
  ditherAlgorithm: 'FloydSteinberg',
  serpentine: true,
});

await GifUtil.write('quantized.gif', gif.frames, gif);

Quantizers modify the supplied frames in place. Processing all frames together helps keep colors consistent across the animation.

Crop every frame to a rectanglecrop-frame

for (const frame of gif.frames) {
  frame.reframe(10, 20, 120, 80);
}

Offsets describe the new frame relative to the old image. If the requested bounds extend outside it, supply a fill RGBA value.

Pad a frame with transparent pixelspad-frame

frame.reframe(-16, -16,
  frame.bitmap.width + 32,
  frame.bitmap.height + 32,
  0x00000000
);

A negative offset adds space on the left or top. The fill color is required when the new frame exceeds existing bounds.

Scale pixel art by an integer factorscale-pixel-art

for (const frame of gif.frames) {
  frame.scale(3);
}

scale only accepts an integer factor of at least 1 and performs nearest-neighbor-style pixel expansion, not smooth resizing.

Check whether a frame can be encodedinspect-palette

for (const [index, frame] of gif.frames.entries()) {
  const palette = frame.getPalette();
  if (palette.indexCount > 256) {
    throw new Error(`Frame ${index} needs quantization`);
  }
}

Transparency consumes one color index, so a transparent frame can hold at most 255 opaque colors without quantization.

Clone frames before destructive editsclone-before-editing

const { GifUtil } = require('gifwrap');

const editedFrames = GifUtil.cloneFrames(gif.frames);
editedFrames.forEach((frame) => frame.greyscale());

Cloning duplicates pixel buffers and frame options. It protects the source but can nearly double frame memory.

Alternatives

PackageRegistryPick it when
sharpnpmUse it for actively maintained, high-throughput server image processing when a native libvips dependency is acceptable.
gifuct-jsnpmUse it when browser-side GIF parsing and frame decompression matter more than writing modified GIF files.
gif-encoder-2npmUse it for a focused streaming-style GIF encoder when you generate frames and do not need gifwrap's decoder and bitmap helpers.
omggifnpmUse the lower-level codec directly when you want fewer abstractions and can manage indexed pixels and frame details yourself.