javascriptnode.jsimagegifsharp

How to composite a static background image and gif with sharp nodejs package


It seems that no matter how much you look online for compositing a still background image (like png or jpeg) with a gif there are no complete solutions. Overlaying a gif on a still background seems to be something just not supported with sharp or at least in most cases it isn't. How can we composite a still background image with a gif by overlaying to create a new gif?
Thanks


Solution

  • Overview

    The answer to this problem is quite nuanced as there are many different short comings and sub-tasks that must be solved.
    In some cases you are going to need to resize your image to allow the gif to neatly overlay the gif.

    Process

    The process to create an animated gif by overlaying a gif on a still background requires a few steps.

    You will want to make sure when you import the gif(s), that you have the animated option set to true (as it is false by default).

    First ensure the size of the gif and input image are compatible (as according to the docs the overlay must be the same size or smaller than the input image). Therefore any resizing, cropping or positioning should be taken into consideration. You will need to have the images processed so that they are the same size (if they aren't already) or when compositing you will have to define your positioning.

    Secondly you will then have to extend the static input image so that that it creates a film roll (or as some might call it a toilet roll). Repeating the static image vertically.

    Finally you can composite the extended static image and gif(s) together, convert it to a gif format and save as a .gif file.

    Resulting code:

    async function overlayGif(background, gifOverlay) { //takes two input paths.
        const overlay = await sharp(gifOverlay, {animated: true}); //make sure animated is true.
        const metadata = await overlay.metadata(); //We'll use the gif metadata to normalize the background.
        const backgroundImg = await sharp(background).resize(metadata.width, metadata.pageHeight); //We are resizing here to normalize background with gif.
        const imgRoll = backgroundImg.extend({bottom: metadata.pageHeight*(metadata.pages-1), extendWith: 'repeat'}); //Must extend to repeat how ever many pages (frames) are in the gif.
        const result = await imgRoll.composite([
            { input: await overlay.toBuffer(), gravity: 'center', animated: true}
        ]).gif(
            {progressive: metadata.isProgressive, delay: metadata.delay, loop: metadata.loop}
            //Just copying the metadata from the gif to the output format (not sure this is necessary).
        );
        const resInfo = await result.toFile("output.gif"); //Write the result to a gif file.
        return result;
    }
    

    Known Limitations

    If you are using the code above you will notice some problems with the result of the composition metadata... Primarily the limitation is that the resulting metadata format is not gif, it's whatever format the background image was (png, jpeg etc)... So you can't really use any of it's metadata features (and even if you use the force property to force the format to be gif, the metadata remains whatever the background image format was), though it does appear to be a gif.