# Saving the output image as a GIF

Browsers can load GIF images to the canvas but cannot output GIF images.

To work around this browser limiation we can use the [postprocessImageBlob](../../api/image-editor/exports/#postprocessimageblob) property on the [createDefaultImageWriter](../../api/image-editor/exports/#createdefaultimagewriter) function to convert images to different formats.

The third-party library [gif.js](https://github.com/jnordberg/gif.js) can convert JPEGs and PNGs to GIFs, we'll use it in the `postprocessImageBlob` hook.

In the example below gif.js needs to be added using a script tag, see the [gif.js documentation](https://github.com/jnordberg/gif.js) for more information.

* [ ![](/pintura/static/assets/technologies-mono/javascript.svg)JavaScript](#javascript-panel-0)
* [ ![](/pintura/static/assets/technologies-mono/react.svg)React](#react-panel-0)
* [ ![](/pintura/static/assets/technologies-mono/vue.svg)Vue](#vue-panel-0)
* [ ![](/pintura/static/assets/technologies-mono/svelte.svg)Svelte](#svelte-panel-0)
* [ ![](/pintura/static/assets/technologies-mono/angular.svg)Angular](#angular-panel-0)
* [ ![](/pintura/static/assets/technologies-mono/jquery.svg)jQuery](#jquery-panel-0)

* [index.html](#index-html-panel-0)

```html
<!DOCTYPE html>

<head>
    <link rel="stylesheet" href="./pintura.css" />
</head>

<img src="" alt="" />

<style>
    .pintura-editor {
        height: 600px;
    }
</style>

<div id="editor"></div>

<script type="module">
    import { appendDefaultEditor, processImage } from './pintura.js';

    const editor = appendDefaultEditor('#editor', {
        src: 'image.jpeg',
        imageWriter: {
            postprocessImageBlob: ({ blob }) =>
                new Promise((resolve) => {
                    // Load the output blob as an image so we can load it with gif.js
                    const image = new Image();
                    image.onload = () => {
                        // The image has loaded, let's create a GIF
                        const gif = new GIF();
                        gif.addFrame(image);
                        gif.on('finished', (gifBlob) => {
                            // clean up URL to our output image
                            URL.revokeObjectURL(blob);

                            // return the GIF to the editor
                            resolve(gifBlob);
                        });
                        gif.render();
                    };

                    // Convert the blob to a URL so we can load it as an image
                    image.src = URL.createObjectURL(blob);
                }),
        },
    });

    editor.on('process', (imageState) => {
        document.querySelector('img').src = URL.createObjectURL(
            imageState.dest
        );
    });
</script>

```

* [App.jsx](#App-jsx-panel-0)
* [App.css](#App-css-panel-0)

```jsx
import '@pqina/pintura/pintura.css';
import './App.css';
import { useState } from 'react';
import { PinturaEditor } from '@pqina/react-pintura';
import { processImage, getEditorDefaults } from '@pqina/pintura';

const editorDefaults = getEditorDefaults({
    imageWriter: {
        postprocessImageBlob: ({ blob }) =>
            new Promise((resolve) => {
                // Load the output blob as an image so we can load it with gif.js
                const image = new Image();
                image.onload = () => {
                    // The image has loaded, let's create a GIF
                    const gif = new GIF();
                    gif.addFrame(image);
                    gif.on('finished', (gifBlob) => {
                        // clean up URL to our output image
                        URL.revokeObjectURL(blob);

                        // return the GIF to the editor
                        resolve(gifBlob);
                    });
                    gif.render();
                };

                // Convert the blob to a URL so we can load it as an image
                image.src = URL.createObjectURL(blob);
            }),
    },
});

function App() {
    const [editorResult, setEditorResult] = useState(undefined);

    const handleEditorProcess = (imageState) => {
        setEditorResult(URL.createObjectURL(imageState.dest));
    };

    return (
        <div className="App">
            {editorResult && <img alt="" src={editorResult} />}

            <PinturaEditor
                {...editorDefaults}
                src={'image.jpeg'}
                onProcess={handleEditorProcess}
            />
        </div>
    );
}

export default App;

```

```css
.pintura-editor {
    height: 600px;
}

```

* [App.vue](#App-vue-panel-0)

```html
<template>
    <div>
        <img v-if="editorResult" alt="" :src="editorResult" />

        <PinturaEditor
            v-bind="editorDefaults"
            src="image.jpeg"
            v-on:pintura:process="handleEditorProcess($event)"
        />
    </div>
</template>
<script>
import { PinturaEditor } from '@pqina/vue-pintura';
import { processImage, getEditorDefaults } from '@pqina/pintura';

export default {
    name: 'App',

    components: {
        PinturaEditor,
    },

    data() {
        return {
            editorResult: undefined,

            editorDefaults: getEditorDefaults({
                imageWriter: {
                    postprocessImageBlob: ({ blob }) =>
                        new Promise((resolve) => {
                            // Load the output blob as an image so we can load it with gif.js
                            const image = new Image();
                            image.onload = () => {
                                // The image has loaded, let's create a GIF
                                const gif = new GIF();
                                gif.addFrame(image);
                                gif.on('finished', (gifBlob) => {
                                    // clean up URL to our output image
                                    URL.revokeObjectURL(blob);

                                    // return the GIF to the editor
                                    resolve(gifBlob);
                                });
                                gif.render();
                            };

                            // Convert the blob to a URL so we can load it as an image
                            image.src = URL.createObjectURL(blob);
                        }),
                },
            }),
        };
    },

    methods: {
        handleEditorProcess: function (imageState) {
            this.editorResult = URL.createObjectURL(imageState.dest);
        },
    },
};
</script>
<style>
@import '@pqina/pintura/pintura.css';

.pintura-editor {
    height: 600px;
}
</style>

```

* [App.svelte](#App-svelte-panel-0)

```svelte
<script>
    import { PinturaEditor } from '@pqina/svelte-pintura';
    import { processImage, getEditorDefaults } from '@pqina/pintura';
    import '@pqina/pintura/pintura.css';

    let editorResult = undefined;

    let editorDefaults = getEditorDefaults({
        imageWriter: {
            postprocessImageBlob: ({ blob }) =>
                new Promise((resolve) => {
                    // Load the output blob as an image so we can load it with gif.js
                    const image = new Image();
                    image.onload = () => {
                        // The image has loaded, let's create a GIF
                        const gif = new GIF();
                        gif.addFrame(image);
                        gif.on('finished', (gifBlob) => {
                            // clean up URL to our output image
                            URL.revokeObjectURL(blob);

                            // return the GIF to the editor
                            resolve(gifBlob);
                        });
                        gif.render();
                    };

                    // Convert the blob to a URL so we can load it as an image
                    image.src = URL.createObjectURL(blob);
                }),
        },
    });

    const handleEditorProcess = (event) => {
        const imageState = event.detail;

        editorResult = URL.createObjectURL(imageState.dest);
    };
</script>

<div>
    {#if editorResult} <img alt="" src={editorResult} /> {/if}

    <PinturaEditor
        {...editorDefaults}
        src={'image.jpeg'}
        on:process={handleEditorProcess}
    />
</div>

<style>
    div :global(.pintura-editor) {
        height: 600px;
    }
</style>

```

* [app.component.ts](#app-component-ts-panel-0)
* [app.component.html](#app-component-html-panel-0)
* [app.component.css](#app-component-css-panel-0)
* [app.module.ts](#app-module-ts-panel-0)

```javascript
import { Component } from '@angular/core';

import { DomSanitizer } from '@angular/platform-browser';

import { processImage, getEditorDefaults } from '@pqina/pintura';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css'],
})
export class AppComponent {
    constructor(private domSanitizer: DomSanitizer) {}

    editorResult?: string = undefined;

    editorDefaults: any = getEditorDefaults({
        imageWriter: {
            postprocessImageBlob: ({ blob }) =>
                new Promise((resolve) => {
                    // Load the output blob as an image so we can load it with gif.js
                    const image = new Image();
                    image.onload = () => {
                        // The image has loaded, let's create a GIF
                        const gif = new GIF();
                        gif.addFrame(image);
                        gif.on('finished', (gifBlob) => {
                            // clean up URL to our output image
                            URL.revokeObjectURL(blob);

                            // return the GIF to the editor
                            resolve(gifBlob);
                        });
                        gif.render();
                    };

                    // Convert the blob to a URL so we can load it as an image
                    image.src = <string>(
                        this.domSanitizer.bypassSecurityTrustResourceUrl(
                            URL.createObjectURL(blob)
                        )
                    );
                }),
        },
    });

    handleEditorProcess(imageState: any): void {
        this.editorResult = <string>(
            this.domSanitizer.bypassSecurityTrustResourceUrl(
                URL.createObjectURL(imageState.dest)
            )
        );
    }
}

```

```html
<img *ngIf="editorResult" [src]="editorResult" alt="" />

<pintura-editor
    [options]="editorDefaults"
    src="image.jpeg"
    (process)="handleEditorProcess($event)"
></pintura-editor>

```

```css
::ng-deep .pintura-editor {
    height: 600px;
}

```

```javascript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AngularPinturaModule } from '@pqina/angular-pintura';

@NgModule({
    declarations: [AppComponent],
    imports: [BrowserModule, AngularPinturaModule],
    exports: [AppComponent],
    providers: [],
    bootstrap: [AppComponent],
})
export class AppModule {}

```

* [index.html](#index-html-panel-0)

```html
<!DOCTYPE html>
<head>
    <link rel="stylesheet" href="./pintura/pintura.css" />
</head>

<script src="./jquery.js"></script>
<script src="./jquery-pintura/useEditorWithJQuery-iife.js"></script>
<script src="./pintura/pintura-iife.js"></script>

<img src="" alt="" />

<style>
    .pintura-editor {
        height: 600px;
    }
</style>

<div id="editor"></div>

<script>
    useEditorWithJQuery(jQuery, pintura);

    $(function () {
        var { processImage } = $.fn.pintura;

        var editor = $('#editor').pinturaDefault({
            src: 'image.jpeg',
            imageWriter: {
                postprocessImageBlob: ({ blob }) =>
                    new Promise((resolve) => {
                        // Load the output blob as an image so we can load it with gif.js
                        const image = new Image();
                        image.onload = () => {
                            // The image has loaded, let's create a GIF
                            const gif = new GIF();
                            gif.addFrame(image);
                            gif.on('finished', (gifBlob) => {
                                // clean up URL to our output image
                                URL.revokeObjectURL(blob);

                                // return the GIF to the editor
                                resolve(gifBlob);
                            });
                            gif.render();
                        };

                        // Convert the blob to a URL so we can load it as an image
                        image.src = URL.createObjectURL(blob);
                    }),
            },
        });

        editor.on('pintura:process', function (event) {
            const imageState = event.detail;
            $('img').attr('src', URL.createObjectURL(imageState.dest));
        });
    });
</script>

```

## Editing Animated GIFs

We can take this a step further to enable editing animated GIFs. When editing animated GIFs we'll have to access each frame of the GIF, apply changes, and then generate a new GIF.

We'll also have to create a custom image processor.

To get the individual frames of the GIF we'll add [omggif.js](https://github.com/deanm/omggif) to our project.

* [ ![](/pintura/static/assets/technologies-mono/javascript.svg)JavaScript](#javascript-panel-1)
* [ ![](/pintura/static/assets/technologies-mono/react.svg)React](#react-panel-1)
* [ ![](/pintura/static/assets/technologies-mono/vue.svg)Vue](#vue-panel-1)
* [ ![](/pintura/static/assets/technologies-mono/svelte.svg)Svelte](#svelte-panel-1)
* [ ![](/pintura/static/assets/technologies-mono/angular.svg)Angular](#angular-panel-1)
* [ ![](/pintura/static/assets/technologies-mono/jquery.svg)jQuery](#jquery-panel-1)

* [index.html](#index-html-panel-1)

```html
<!DOCTYPE html>

<head>
    <link rel="stylesheet" href="./pintura.css" />
</head>

<img src="" alt="" />

<style>
    .pintura-editor {
        height: 600px;
    }
</style>

<div id="editor"></div>

<script type="module">
    import {
        appendDefaultEditor,
        processDefaultImage,
        createDefaultImageWriter,
    } from './pintura.js';

    const framesFromGIF = (blob) => {
        return new Promise(async (resolve) => {
            const ab = await blob.arrayBuffer();

            // use the GifReader supplied by omggif.js to get the frames
            const gifReader = new GifReader(new Uint8Array(ab));

            const frameCount = gifReader.numFrames();
            const frames = [];

            for (let i = 0; i < frameCount; i++) {
                const currentFrame = gifReader.frameInfo(i);

                const image = new ImageData(
                    currentFrame.width,
                    currentFrame.height
                );

                gifReader.decodeAndBlitFrameRGBA(i, image.data);

                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');
                canvas.width = image.width;
                canvas.height = image.height;
                ctx.putImageData(image, 0, 0);

                frames[i] = {
                    ...currentFrame,
                    canvas,
                };
            }

            resolve(frames);
        });
    };

    const gifFromFrames = (frames) => {
        return new Promise(async (resolve) => {
            // use GIF supplied by gif.js to create animated gif
            const gif = new GIF();

            // done
            gif.on('finished', resolve);

            // add frames
            frames.forEach((frame) => {
                gif.addFrame(frame.imageData, { delay: frame.delay * 10 });
            });

            gif.render();
        });
    };

    const editor = appendDefaultEditor('#editor', {
        src: 'animation.gif',
        imageWriter: [
            // our custom imageWriter has only one step,
            // read the gif and pass it to the custom processor
            [
                async (state, options, onprogress) => {
                    const { src, imageState } = state;

                    // get all the frame data and info from the animated GIF
                    const frames = await framesFromGIF(src);

                    // apply our image transforms to each frame
                    for (const [index, frame] of frames.entries()) {
                        const { dest } = await processDefaultImage(
                            frame.canvas,
                            {
                                imageWriter: createDefaultImageWriter({
                                    format: 'imageData',
                                }),
                                imageState,
                            }
                        );

                        // set frame output image data
                        frame.imageData = dest;
                    }

                    // generate a new animated GIF and set it as output image
                    state.dest = await gifFromFrames(frames);

                    return state;
                },
                'generate-animated-gif',
            ],
        ],
    });

    editor.on('process', (imageState) => {
        document.querySelector('img').src = URL.createObjectURL(
            imageState.dest
        );
    });
</script>

```

* [App.jsx](#App-jsx-panel-1)
* [App.css](#App-css-panel-1)

```jsx
import '@pqina/pintura/pintura.css';
import './App.css';
import { useState } from 'react';
import { PinturaEditor } from '@pqina/react-pintura';
import {
    processDefaultImage,
    getEditorDefaults,
    createDefaultImageWriter,
} from '@pqina/pintura';

const editorDefaults = getEditorDefaults({
    imageWriter: [
        // our custom imageWriter has only one step,
        // read the gif and pass it to the custom processor
        [
            async (state, options, onprogress) => {
                const { src, imageState } = state;

                // get all the frame data and info from the animated GIF
                const frames = await framesFromGIF(src);

                // apply our image transforms to each frame
                for (const [index, frame] of frames.entries()) {
                    const { dest } = await processDefaultImage(frame.canvas, {
                        imageWriter: createDefaultImageWriter({
                            format: 'imageData',
                        }),
                        imageState,
                    });

                    // set frame output image data
                    frame.imageData = dest;
                }

                // generate a new animated GIF and set it as output image
                state.dest = await gifFromFrames(frames);

                return state;
            },
            'generate-animated-gif',
        ],
    ],
});

const framesFromGIF = (blob) => {
    return new Promise(async (resolve) => {
        const ab = await blob.arrayBuffer();

        // use the GifReader supplied by omggif.js to get the frames
        const gifReader = new GifReader(new Uint8Array(ab));

        const frameCount = gifReader.numFrames();
        const frames = [];

        for (let i = 0; i < frameCount; i++) {
            const currentFrame = gifReader.frameInfo(i);

            const image = new ImageData(
                currentFrame.width,
                currentFrame.height
            );

            gifReader.decodeAndBlitFrameRGBA(i, image.data);

            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            canvas.width = image.width;
            canvas.height = image.height;
            ctx.putImageData(image, 0, 0);

            frames[i] = {
                ...currentFrame,
                canvas,
            };
        }

        resolve(frames);
    });
};

const gifFromFrames = (frames) => {
    return new Promise(async (resolve) => {
        // use GIF supplied by gif.js to create animated gif
        const gif = new GIF();

        // done
        gif.on('finished', resolve);

        // add frames
        frames.forEach((frame) => {
            gif.addFrame(frame.imageData, { delay: frame.delay * 10 });
        });

        gif.render();
    });
};

function App() {
    const [editorResult, setEditorResult] = useState(undefined);

    const handleEditorProcess = (imageState) => {
        setEditorResult(URL.createObjectURL(imageState.dest));
    };

    return (
        <div className="App">
            {editorResult && <img alt="" src={editorResult} />}

            <PinturaEditor
                {...editorDefaults}
                src={'animation.gif'}
                onProcess={handleEditorProcess}
            />
        </div>
    );
}

export default App;

```

```css
.pintura-editor {
    height: 600px;
}

```

* [App.vue](#App-vue-panel-1)

```html
<template>
    <div>
        <img v-if="editorResult" alt="" :src="editorResult" />

        <PinturaEditor
            v-bind="editorDefaults"
            src="animation.gif"
            v-on:pintura:process="handleEditorProcess($event)"
        />
    </div>
</template>
<script>
import { PinturaEditor } from '@pqina/vue-pintura';
import {
    processDefaultImage,
    getEditorDefaults,
    createDefaultImageWriter,
} from '@pqina/pintura';

const framesFromGIF = function (blob) {
    return new Promise(async (resolve) => {
        const ab = await blob.arrayBuffer();

        // use the GifReader supplied by omggif.js to get the frames
        const gifReader = new GifReader(new Uint8Array(ab));

        const frameCount = gifReader.numFrames();
        const frames = [];

        for (let i = 0; i < frameCount; i++) {
            const currentFrame = gifReader.frameInfo(i);

            const image = new ImageData(
                currentFrame.width,
                currentFrame.height
            );

            gifReader.decodeAndBlitFrameRGBA(i, image.data);

            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            canvas.width = image.width;
            canvas.height = image.height;
            ctx.putImageData(image, 0, 0);

            frames[i] = {
                ...currentFrame,
                canvas,
            };
        }

        resolve(frames);
    });
};

const gifFromFrames = function (frames) {
    return new Promise(async (resolve) => {
        // use GIF supplied by gif.js to create animated gif
        const gif = new GIF();

        // done
        gif.on('finished', resolve);

        // add frames
        frames.forEach((frame) => {
            gif.addFrame(frame.imageData, { delay: frame.delay * 10 });
        });

        gif.render();
    });
};

export default {
    name: 'App',

    components: {
        PinturaEditor,
    },

    data() {
        return {
            editorResult: undefined,

            editorDefaults: getEditorDefaults({
                imageWriter: [
                    // our custom imageWriter has only one step,
                    // read the gif and pass it to the custom processor
                    [
                        async (state, options, onprogress) => {
                            const { src, imageState } = state;

                            // get all the frame data and info from the animated GIF
                            const frames = await framesFromGIF(src);

                            // apply our image transforms to each frame
                            for (const [index, frame] of frames.entries()) {
                                const { dest } = await processDefaultImage(
                                    frame.canvas,
                                    {
                                        imageWriter: createDefaultImageWriter({
                                            format: 'imageData',
                                        }),
                                        imageState,
                                    }
                                );

                                // set frame output image data
                                frame.imageData = dest;
                            }

                            // generate a new animated GIF and set it as output image
                            state.dest = await gifFromFrames(frames);

                            return state;
                        },
                        'generate-animated-gif',
                    ],
                ],
            }),
        };
    },

    methods: {
        handleEditorProcess: function (imageState) {
            this.editorResult = URL.createObjectURL(imageState.dest);
        },
    },
};
</script>
<style>
@import '@pqina/pintura/pintura.css';

.pintura-editor {
    height: 600px;
}
</style>

```

* [App.svelte](#App-svelte-panel-1)

```svelte
<script>
    import { PinturaEditor } from '@pqina/svelte-pintura';
    import {
        processDefaultImage,
        getEditorDefaults,
        createDefaultImageWriter,
    } from '@pqina/pintura';
    import '@pqina/pintura/pintura.css';

    const framesFromGIF = (blob) => {
        return new Promise(async (resolve) => {
            const ab = await blob.arrayBuffer();

            // use the GifReader supplied by omggif.js to get the frames
            const gifReader = new GifReader(new Uint8Array(ab));

            const frameCount = gifReader.numFrames();
            const frames = [];

            for (let i = 0; i < frameCount; i++) {
                const currentFrame = gifReader.frameInfo(i);

                const image = new ImageData(
                    currentFrame.width,
                    currentFrame.height
                );

                gifReader.decodeAndBlitFrameRGBA(i, image.data);

                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');
                canvas.width = image.width;
                canvas.height = image.height;
                ctx.putImageData(image, 0, 0);

                frames[i] = {
                    ...currentFrame,
                    canvas,
                };
            }

            resolve(frames);
        });
    };

    const gifFromFrames = (frames) => {
        return new Promise(async (resolve) => {
            // use GIF supplied by gif.js to create animated gif
            const gif = new GIF();

            // done
            gif.on('finished', resolve);

            // add frames
            frames.forEach((frame) => {
                gif.addFrame(frame.imageData, { delay: frame.delay * 10 });
            });

            gif.render();
        });
    };

    let editorResult = undefined;

    let editorDefaults = getEditorDefaults({
        imageWriter: [
            // our custom imageWriter has only one step,
            // read the gif and pass it to the custom processor
            [
                async (state, options, onprogress) => {
                    const { src, imageState } = state;

                    // get all the frame data and info from the animated GIF
                    const frames = await framesFromGIF(src);

                    // apply our image transforms to each frame
                    for (const [index, frame] of frames.entries()) {
                        const { dest } = await processDefaultImage(
                            frame.canvas,
                            {
                                imageWriter: createDefaultImageWriter({
                                    format: 'imageData',
                                }),
                                imageState,
                            }
                        );

                        // set frame output image data
                        frame.imageData = dest;
                    }

                    // generate a new animated GIF and set it as output image
                    state.dest = await gifFromFrames(frames);

                    return state;
                },
                'generate-animated-gif',
            ],
        ],
    });

    const handleEditorProcess = (event) => {
        const imageState = event.detail;

        editorResult = URL.createObjectURL(imageState.dest);
    };
</script>

<div>
    {#if editorResult} <img alt="" src={editorResult} /> {/if}

    <PinturaEditor
        {...editorDefaults}
        src={'animation.gif'}
        on:process={handleEditorProcess}
    />
</div>

<style>
    div :global(.pintura-editor) {
        height: 600px;
    }
</style>

```

* [app.component.ts](#app-component-ts-panel-1)
* [app.component.html](#app-component-html-panel-1)
* [app.component.css](#app-component-css-panel-1)
* [app.module.ts](#app-module-ts-panel-1)

```javascript
import { Component } from '@angular/core';

import { DomSanitizer } from '@angular/platform-browser';

import {
    processDefaultImage,
    getEditorDefaults,
    createDefaultImageWriter,
} from '@pqina/pintura';

const framesFromGIF = (blob: Blob): Promise<any[]> => {
    return new Promise(async (resolve) => {
        const ab = await blob.arrayBuffer();

        // use the GifReader supplied by omggif.js to get the frames
        const gifReader = new GifReader(new Uint8Array(ab));

        const frameCount = gifReader.numFrames();
        const frames = [];

        for (let i = 0; i < frameCount; i++) {
            const currentFrame = gifReader.frameInfo(i);

            const image = new ImageData(
                currentFrame.width,
                currentFrame.height
            );

            gifReader.decodeAndBlitFrameRGBA(i, image.data);

            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            canvas.width = image.width;
            canvas.height = image.height;
            ctx.putImageData(image, 0, 0);

            frames[i] = {
                ...currentFrame,
                canvas,
            };
        }

        resolve(frames);
    });
};

const gifFromFrames = (frames: any[]): Promise<Blob> => {
    return new Promise(async (resolve) => {
        // use GIF supplied by gif.js to create animated gif
        const gif = new GIF();

        // done
        gif.on('finished', resolve);

        // add frames
        frames.forEach((frame) => {
            gif.addFrame(frame.imageData, { delay: frame.delay * 10 });
        });

        gif.render();
    });
};

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css'],
})
export class AppComponent {
    constructor(private domSanitizer: DomSanitizer) {}

    editorResult?: string = undefined;

    editorDefaults: any = getEditorDefaults({
        imageWriter: [
            // our custom imageWriter has only one step,
            // read the gif and pass it to the custom processor
            [
                async (state, options, onprogress) => {
                    const { src, imageState } = state;

                    // get all the frame data and info from the animated GIF
                    const frames = await framesFromGIF(src);

                    // apply our image transforms to each frame
                    for (const [index, frame] of frames.entries()) {
                        const { dest } = await processDefaultImage(
                            frame.canvas,
                            {
                                imageWriter: createDefaultImageWriter({
                                    format: 'imageData',
                                }),
                                imageState,
                            }
                        );

                        // set frame output image data
                        frame.imageData = dest;
                    }

                    // generate a new animated GIF and set it as output image
                    state.dest = await gifFromFrames(frames);

                    return state;
                },
                'generate-animated-gif',
            ],
        ],
    });

    handleEditorProcess(imageState: any): void {
        this.editorResult = <string>(
            this.domSanitizer.bypassSecurityTrustResourceUrl(
                URL.createObjectURL(imageState.dest)
            )
        );
    }
}

```

```html
<img *ngIf="editorResult" [src]="editorResult" alt="" />

<pintura-editor
    [options]="editorDefaults"
    src="animation.gif"
    (process)="handleEditorProcess($event)"
></pintura-editor>

```

```css
::ng-deep .pintura-editor {
    height: 600px;
}

```

```javascript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { AngularPinturaModule } from '@pqina/angular-pintura';

@NgModule({
    declarations: [AppComponent],
    imports: [BrowserModule, AngularPinturaModule],
    exports: [AppComponent],
    providers: [],
    bootstrap: [AppComponent],
})
export class AppModule {}

```

* [index.html](#index-html-panel-1)

```html
<!DOCTYPE html>
<head>
    <link rel="stylesheet" href="./pintura/pintura.css" />
</head>

<script src="./jquery.js"></script>
<script src="./jquery-pintura/useEditorWithJQuery-iife.js"></script>
<script src="./pintura/pintura-iife.js"></script>

<img src="" alt="" />

<style>
    .pintura-editor {
        height: 600px;
    }
</style>

<div id="editor"></div>

<script>
    useEditorWithJQuery(jQuery, pintura);

    $(function () {
        var { processDefaultImage, createDefaultImageWriter } = $.fn.pintura;

        const framesFromGIF = (blob) => {
            return new Promise(async (resolve) => {
                const ab = await blob.arrayBuffer();

                // use the GifReader supplied by omggif.js to get the frames
                const gifReader = new GifReader(new Uint8Array(ab));

                const frameCount = gifReader.numFrames();
                const frames = [];

                for (let i = 0; i < frameCount; i++) {
                    const currentFrame = gifReader.frameInfo(i);

                    const image = new ImageData(
                        currentFrame.width,
                        currentFrame.height
                    );

                    gifReader.decodeAndBlitFrameRGBA(i, image.data);

                    const canvas = document.createElement('canvas');
                    const ctx = canvas.getContext('2d');
                    canvas.width = image.width;
                    canvas.height = image.height;
                    ctx.putImageData(image, 0, 0);

                    frames[i] = {
                        ...currentFrame,
                        canvas,
                    };
                }

                resolve(frames);
            });
        };

        const gifFromFrames = (frames) => {
            return new Promise(async (resolve) => {
                // use GIF supplied by gif.js to create animated gif
                const gif = new GIF();

                // done
                gif.on('finished', resolve);

                // add frames
                frames.forEach((frame) => {
                    gif.addFrame(frame.imageData, { delay: frame.delay * 10 });
                });

                gif.render();
            });
        };

        var editor = $('#editor').pinturaDefault({
            src: 'animation.gif',
            imageWriter: [
                // our custom imageWriter has only one step,
                // read the gif and pass it to the custom processor
                [
                    async (state, options, onprogress) => {
                        const { src, imageState } = state;

                        // get all the frame data and info from the animated GIF
                        const frames = await framesFromGIF(src);

                        // apply our image transforms to each frame
                        for (const [index, frame] of frames.entries()) {
                            const { dest } = await processDefaultImage(
                                frame.canvas,
                                {
                                    imageWriter: createDefaultImageWriter({
                                        format: 'imageData',
                                    }),
                                    imageState,
                                }
                            );

                            // set frame output image data
                            frame.imageData = dest;
                        }

                        // generate a new animated GIF and set it as output image
                        state.dest = await gifFromFrames(frames);

                        return state;
                    },
                    'generate-animated-gif',
                ],
            ],
        });

        editor.on('pintura:process', function (event) {
            const imageState = event.detail;
            $('img').attr('src', URL.createObjectURL(imageState.dest));
        });
    });
</script>

```