# gifwrap

A Jimp-compatible library for working with GIFs 

## Overview

`gifwrap` is a minimalist library for working with GIFs in Javascript, supporting both single- and multi-frame GIFs. It reads GIFs into an internal representation that's easy to work with and allows for making GIFs from scratch. The frame class is structured to make it easy to move images between [`Jimp`](https://github.com/oliver-moran/jimp) and `gifwrap` for more sophisticated image manipulation in `Jimp`, but the module has no dependency on `Jimp`.

The library uses Dean McNamee's [`omggif`](https://github.com/deanm/omggif) GIF encoder/decoder by default, but it employs an abstraction that allows using other encoders and decoders as well, once suitably wrapped.

At present, the module only works in Node.js. Includes Typescript typings.

## Installation

```
npm install gifwrap --save
```

or

```
yarn add gifwrap
```

## Usage

You can work with either GIF files or GIF encodings, and you can create GIFs from scratch.

The GifFrame class represents a single image frame, and the library largely represents a GIF as an array of GifFrame instances. For example, here is how you create a GIF from scratch:

```js
const { GifFrame, GifUtil, GifCodec } = require('gifwrap');
const width = 200, height = 100;
const frames = [];

let frame = new GifFrame(width, height, { delayCentisecs: 10 });
// modify the pixels at frame.bitmap.data
frames.push(frame);
frame = new GifFrame(width, height, { delayCentisecs: 15 });
// modify the pixels at frame.bitmap.data
frames.push(frame);
// add more frames as desired...

// to write to a file...
GifUtil.write("my-creation.gif", frames, { loops: 3 }).then(gif => {
    console.log("written");
});

// to get the byte encoding without writing to a file...
const codec = new GifCodec();
codec.encodeGif(frames, { loops: 3 }).then(gif => {
    // byte encoding is now in gif.buffer
});

```

Images are represented within a GifFrame exactly as they are in a `Jimp` image. In particular, each GifFrame instance has a `bitmap` property having the following structure:

* `frame.bitmap.width` - Width of image in pixels
* `frame.bitmap.height` - Height of image in pixels
* `frame.bitmap.data` - A Node.js Buffer that can be accessed like an array of bytes. Every 4 adjacent bytes represents the RGBA values of a single pixel. These 4 bytes correspond to red, green, blue, and alpha, in that order. Each pixel begins at an index that is a multiple of 4.

GIFs do not support partial transparency, so within `frame.bitmap.data`, pixels having alpha value 0x00 are treated as transparent and pixels of non-zero alpha value are treated as opaque. The encoder ignores the RGB values of transparent pixels.

`gifwrap` also provides utilities for reading GIF files and for parsing raw encodings:

```js
const { GifUtil } = require('gifwrap');
GifUtil.read("fancy.gif").then(inputGif => {

    inputGif.frames.foreach(frame => {

        const buf = frame.bitmap.data;
        frame.scanAllCoords((x, y, bi) => {

            // Halve all grays on right half of image.

            if (x > inputGif.width / 2) {
                const r = buf[bi];
                const g = buf[bi + 1];
                const b = buf[bi + 2];
                const a = buf[bi + 3];
                if (r === g && r === b && a === 0xFF) {
                    buf[bi] /= 2;
                    buf[bi + 1] /= 2;
                    buf[bi + 2] /= 2;
                }
            }
        });
    });

    // Pass inputGif to write() to preserve the original GIF's specs.
    return GifUtil.write("modified.gif", inputGif.frames, inputGif).then(outputGif => {
        console.log("modified");
    });
});
```

```js
const { GifUtil, GifCodec } = require('gifwrap');
const codec = new GifCodec();
const byteEncodingBuffer = getByteEncodingForSomeGif();

codec.decodeGif(byteEncodingBuffer).then(sourceGif => {

    const edgeLength = Math.min(sourceGif.width, sourceGif.height);
    sourceGif.frames.forEach(frame => {

        // Make each frame a centered square of size edgeLength x edgeLength.
        // Note that frames may vary in size and that reframe() works even if
        // the frame's image is smaller than the square. Should this happen,
        // the space surrounding the original image will be transparent.

        const xOffset = (frame.bitmap.width - edgeLength)/2;
        const yOffset = (frame.bitmap.height - edgeLength)/2;
        frame.reframe(xOffset, yOffset, edgeLength, edgeLength);
    });

    // The encoder determines GIF size from the frames, not the provided spec (sourceGif).
    return GifUtil.write("modified.gif", sourceGif.frames, sourceGif).then(outputGif => {
        console.log("modified");
    });
});
```

Notice that both encoding and decoding yields a GIF object. This is an instance of class Gif, and it provides information about the GIF, such as its size and how many times it loops. Notice also that you never call the Gif constructor to create a GIF. Instead, GIFs are created by providing a GifFrame array and a specification of GIF options. That specification is a subset of the properties of a Gif, so you can pass a previously-loaded Gif as a specification when writing or encoding. The encoder only uses the properties that can't be inferred from the frames -- namely, how many times the GIF loops and how to attempt to package the color tables within the encoding.

## Leveraging Jimp

This module was originally written as a wrapper around Jimp images -- hence its name -- and then with frames as subclasses of Jimp images. Neither approach worked out well. The final approach requires just a tad of legwork to use `gifwrap` images within Jimp.

Both Jimp images and GifFrame instances share the `bitmap` property. By transferring this property back and forth between Jimp images and GifFrame instances, an image can be moved back and forth between the two libraries.

You can construct a GifFrame from a Jimp image as follows:

```js
const { BitmapImage, GifFrame } = require('gifwrap');
const Jimp = require('jimp');
const j = new Jimp(200, 100, 0xFFFFFFFF);

// create a frame clone of a Jip bitmap
const fCopied = new GifFrame(new BitmapImage(j));

// create a frame that shares a bitmap with Jimp (one way)
const fShared1 = new GifFrame(j);

// create a frame that shares a bitmap with Jimp (another way)
const fShared2 = new GifFrame(1, 1, 0); // any GifFrame
fShared2.bitmap = j.bitmap;
```

And you can construct a Jimp instance from a GifFrame image as follows:

```js
const { BitmapImage, GifFrame } = require('gifwrap');
const Jimp = require('jimp');
const frame = new GifFrame(200, 100, 0xFFFFFFFF);

// create a Jimp containing a clone of the frame bitmap
jimpCopied = GifUtil.copyAsJimp(Jimp, frame);

// create a Jimp that shares a bitmap with the frame
jimpShared = GifUtil.shareAsJimp(Jimp, frame);
```

## Encoders and Decoders

`gifwrap` provides a default GIF encoder/decoder, but it is architected to be able to work with other encoders and decoders. The encoder and decoder may even be separate implementations. Encoders and decoders have varying capabilities, performance measures, and levels of reliability.

GifCodec is the default implementation, and it's both an encoder and a decoder. It's an adapter that wraps the [`omggif`](https://github.com/deanm/omggif) module. `omggif` appears to support a broad variety of GIFs, although it cannot produce an interlaced encoding (which there is little need for anyway). Although `omggif` doesn't include a test suite at present, `gifwrap`'s test suite happens to test it reasonably well by virtue of using `omggif` underneath.

An encoder need only implement GifCodec's [`encodeGif()`](#GifCodec+encodeGif) method, and a decoder need only implement its [`decodeGif()`](#GifCodec+decodeGif) method. See the descriptions of those methods for the requirement details. Although GifCodec is stateless, so that instances an be reused across multiple encodings and decodings, third party encoders and decoders need not be. However, applications that use the library with stateful encoders will need to be aware of the need to create new instances.

To use a third-party encoder or decoder with the GifUtil `write()` and `read()` functions, just pass an instance of the encoder or decoder as the last parameter to `write()` or `read()`, respectively. For example:

```js
const { GifUtil } = require('gifwrap');
const SnazzyDecoder = require('gifwrap-snazzy-decoder');
const AwesomeEncoder = require('gifwrap-awesome-encoder');

GifUtil.read("fancy.gif", new SnazzyDecoder()).then(gif =>

    /*...*/

    return GifUtil.write("modified.gif", gif.frames, gif, new AwesomeEncoder()).then(newGif => {
        console.log("modified");
    });
});
```

## API Reference

The [Typescript typings](https://github.com/jtlapp/gifwrap/blob/master/index.d.ts) provide an exact specification of the API and also serve as a cheat sheet. The classes and namespaces follow:

* **gifwrap**

    * [.**Gif**](#new_Gif_new)
    * [.**BitmapImage**](#BitmapImage)
    * [.**GifFrame**](#GifFrame)
    * [.**GifUtil**](#GifUtil)
    * [.**GifCodec**](#GifCodec)
    * [.**GifError**](#new_GifError_new)

{{#class name="Gif"}}
{{>body~}}
{{>member-index~}}
{{/class}}
{{#class name="BitmapImage"}}
{{>body~}}
{{>member-index~}}
{{/class}}
{{#class name="GifFrame"}}
{{>body~}}
{{>member-index~}}
{{/class}}
{{#namespace name="GifUtil"}}
{{>body~}}
{{>member-index~}}
{{/namespace}}
{{#class name="GifCodec"}}
{{>body~}}
{{>member-index~}}
{{/class}}

{{#class name="Gif"}}
{{>members~}}
{{/class}}
{{#class name="BitmapImage"}}
{{>members~}}
{{/class}}
{{#class name="GifFrame"}}
{{>members~}}
{{/class}}
{{#namespace name="GifUtil"}}
{{>members~}}
{{/namespace}}
{{#class name="GifCodec"}}
{{>members~}}
{{/class}}
{{#class name="GifError"}}
{{>body~}}
{{>members~}}
{{/class}}

## LICENSE

MIT License

Copyright © 2017 Joseph T. Lapp

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
