Animated images

You can upload animated GIFs to Cloudinary, resize and crop them, further transform them, optimize them, convert them to modern video or animated image formats and create new animated GIFs. Animated GIFs can be uploaded to Cloudinary just like any other image format.

Manipulating animated GIFs

Animated GIFs can be manipulated like any other image uploaded to Cloudinary. For example:

  • Original uploaded kitten_fighting animated GIF:

Ruby:
cl_image_tag("kitten_fighting.gif")
PHP:
cl_image_tag("kitten_fighting.gif")
Python:
CloudinaryImage("kitten_fighting.gif").image()
Node.js:
cloudinary.image("kitten_fighting.gif")
Java:
cloudinary.url().imageTag("kitten_fighting.gif");
JS:
cloudinary.imageTag('kitten_fighting.gif').toHtml();
jQuery:
$.cloudinary.image("kitten_fighting.gif")
React:
<Image publicId="kitten_fighting.gif" >

</Image>
Vue.js:
<cld-image publicId="kitten_fighting.gif" >

</cld-image>
Angular:
<cl-image public-id="kitten_fighting.gif" >

</cl-image>
.Net:
cloudinary.Api.UrlImgUp.BuildImageTag("kitten_fighting.gif")
Android:
MediaManager.get().url().generate("kitten_fighting.gif");
iOS:
imageView.cldSetImage(cloudinary.createUrl().generate("kitten_fighting.gif")!, cloudinary: cloudinary)
Originally uploaded kitten_fighting animated GIF

  • Resized to a width of 200 pixels (height is adjusted automatically to keep the aspect ratio):

Ruby:
cl_image_tag("kitten_fighting.gif", :width=>200, :crop=>"scale")
PHP:
cl_image_tag("kitten_fighting.gif", array("width"=>200, "crop"=>"scale"))
Python:
CloudinaryImage("kitten_fighting.gif").image(width=200, crop="scale")
Node.js:
cloudinary.image("kitten_fighting.gif", {width: 200, crop: "scale"})
Java:
cloudinary.url().transformation(new Transformation().width(200).crop("scale")).imageTag("kitten_fighting.gif");
JS:
cloudinary.imageTag('kitten_fighting.gif', {width: 200, crop: "scale"}).toHtml();
jQuery:
$.cloudinary.image("kitten_fighting.gif", {width: 200, crop: "scale"})
React:
<Image publicId="kitten_fighting.gif" >
  <Transformation width="200" crop="scale" />
</Image>
Vue.js:
<cld-image publicId="kitten_fighting.gif" >
  <cld-transformation width="200" crop="scale" />
</cld-image>
Angular:
<cl-image public-id="kitten_fighting.gif" >
  <cl-transformation width="200" crop="scale">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(200).Crop("scale")).BuildImageTag("kitten_fighting.gif")
Android:
MediaManager.get().url().transformation(new Transformation().width(200).crop("scale")).generate("kitten_fighting.gif");
iOS:
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setWidth(200).setCrop("scale")).generate("kitten_fighting.gif")!, cloudinary: cloudinary)
Animated GIF resized to a width of 200 pixels

  • Cropped to a height and width of 200 pixels with rounded corners:

Ruby:
cl_image_tag("kitten_fighting.gif", :width=>200, :height=>200, :radius=>"max", :crop=>"crop")
PHP:
cl_image_tag("kitten_fighting.gif", array("width"=>200, "height"=>200, "radius"=>"max", "crop"=>"crop"))
Python:
CloudinaryImage("kitten_fighting.gif").image(width=200, height=200, radius="max", crop="crop")
Node.js:
cloudinary.image("kitten_fighting.gif", {width: 200, height: 200, radius: "max", crop: "crop"})
Java:
cloudinary.url().transformation(new Transformation().width(200).height(200).radius("max").crop("crop")).imageTag("kitten_fighting.gif");
JS:
cloudinary.imageTag('kitten_fighting.gif', {width: 200, height: 200, radius: "max", crop: "crop"}).toHtml();
jQuery:
$.cloudinary.image("kitten_fighting.gif", {width: 200, height: 200, radius: "max", crop: "crop"})
React:
<Image publicId="kitten_fighting.gif" >
  <Transformation width="200" height="200" radius="max" crop="crop" />
</Image>
Vue.js:
<cld-image publicId="kitten_fighting.gif" >
  <cld-transformation width="200" height="200" radius="max" crop="crop" />
</cld-image>
Angular:
<cl-image public-id="kitten_fighting.gif" >
  <cl-transformation width="200" height="200" radius="max" crop="crop">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(200).Height(200).Radius("max").Crop("crop")).BuildImageTag("kitten_fighting.gif")
Android:
MediaManager.get().url().transformation(new Transformation().width(200).height(200).radius("max").crop("crop")).generate("kitten_fighting.gif");
iOS:
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setWidth(200).setHeight(200).setRadius("max").setCrop("crop")).generate("kitten_fighting.gif")!, cloudinary: cloudinary)
Animated GIF cropped to 200x200 circle

Deliver a single frame

Use the page parameter (pg in URLs) to deliver only a single frame of an animated GIF. For example, to deliver the second frame in the kitten_fighting GIF file:

Ruby:
cl_image_tag("kitten_fighting.gif", :page=>2)
PHP:
cl_image_tag("kitten_fighting.gif", array("page"=>2))
Python:
CloudinaryImage("kitten_fighting.gif").image(page=2)
Node.js:
cloudinary.image("kitten_fighting.gif", {page: 2})
Java:
cloudinary.url().transformation(new Transformation().page(2)).imageTag("kitten_fighting.gif");
JS:
cloudinary.imageTag('kitten_fighting.gif', {page: 2}).toHtml();
jQuery:
$.cloudinary.image("kitten_fighting.gif", {page: 2})
React:
<Image publicId="kitten_fighting.gif" >
  <Transformation page="2" />
</Image>
Vue.js:
<cld-image publicId="kitten_fighting.gif" >
  <cld-transformation page="2" />
</cld-image>
Angular:
<cl-image public-id="kitten_fighting.gif" >
  <cl-transformation page="2">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Page(2)).BuildImageTag("kitten_fighting.gif")
Android:
MediaManager.get().url().transformation(new Transformation().page(2)).generate("kitten_fighting.gif");
iOS:
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setPage(2)).generate("kitten_fighting.gif")!, cloudinary: cloudinary)
2nd frame only in kitten_fighting GIF

Control the delay between frames

Use the delay parameter (dl in URLs) to control the amount of time (in milliseconds) that passes between displaying the individual frames in an animated GIF. For example, to deliver the kitten_fighting GIF with a delay of 200 milliseconds between frames:

Ruby:
cl_image_tag("kitten_fighting.gif", :delay=>"200")
PHP:
cl_image_tag("kitten_fighting.gif", array("delay"=>"200"))
Python:
CloudinaryImage("kitten_fighting.gif").image(delay="200")
Node.js:
cloudinary.image("kitten_fighting.gif", {delay: "200"})
Java:
cloudinary.url().transformation(new Transformation().delay("200")).imageTag("kitten_fighting.gif");
JS:
cloudinary.imageTag('kitten_fighting.gif', {delay: "200"}).toHtml();
jQuery:
$.cloudinary.image("kitten_fighting.gif", {delay: "200"})
React:
<Image publicId="kitten_fighting.gif" >
  <Transformation delay="200" />
</Image>
Vue.js:
<cld-image publicId="kitten_fighting.gif" >
  <cld-transformation delay="200" />
</cld-image>
Angular:
<cl-image public-id="kitten_fighting.gif" >
  <cl-transformation delay="200">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Delay("200")).BuildImageTag("kitten_fighting.gif")
Android:
MediaManager.get().url().transformation(new Transformation().delay("200")).generate("kitten_fighting.gif");
iOS:
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setDelay("200")).generate("kitten_fighting.gif")!, cloudinary: cloudinary)
kitten_fighting with 200 ms delay between frames

Set a fixed looping value

By default, animated GIFs with a resource_type of image run in an infinite loop. Use the loop effect (e_loop in URLs) to limit the number of loops. The loop parameter value is 0-based. For example, to set your GIF to loop only 3 times:

Ruby:
cl_image_tag("spiral_animated.gif", :effect=>"loop:2")
PHP:
cl_image_tag("spiral_animated.gif", array("effect"=>"loop:2"))
Python:
CloudinaryImage("spiral_animated.gif").image(effect="loop:2")
Node.js:
cloudinary.image("spiral_animated.gif", {effect: "loop:2"})
Java:
cloudinary.url().transformation(new Transformation().effect("loop:2")).imageTag("spiral_animated.gif");
JS:
cloudinary.imageTag('spiral_animated.gif', {effect: "loop:2"}).toHtml();
jQuery:
$.cloudinary.image("spiral_animated.gif", {effect: "loop:2"})
React:
<Image publicId="spiral_animated.gif" >
  <Transformation effect="loop:2" />
</Image>
Vue.js:
<cld-image publicId="spiral_animated.gif" >
  <cld-transformation effect="loop:2" />
</cld-image>
Angular:
<cl-image public-id="spiral_animated.gif" >
  <cl-transformation effect="loop:2">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Effect("loop:2")).BuildImageTag("spiral_animated.gif")
Android:
MediaManager.get().url().transformation(new Transformation().effect("loop:2")).generate("spiral_animated.gif");
iOS:
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setEffect("loop:2")).generate("spiral_animated.gif")!, cloudinary: cloudinary)
loop animated gif 3 times

Converting an animated GIF to video

To deliver an animated GIF as a video file, simply change the file extension to either the webm or the mp4 video format. The file is automatically converted to a video format and codec that best fits web and mobile browsers, and the default quality settings represent an optimized trade off between visual quality and file size. See the article on Reduce size of animated GIFs, automatically convert to WebM and MP4

For example, to deliver the kitten_fighting GIF as an MP4 video, reducing the file size by 95%:

Ruby:
cl_video_tag("kitten_fighting", :resource_type=>"image")
PHP:
cl_video_tag("kitten_fighting", array("resource_type"=>"image"))
Python:
CloudinaryImage("kitten_fighting").video()
Node.js:
cloudinary.video("kitten_fighting", {resource_type: "image"})
Java:
cloudinary.url().resourceType("image").videoTag("kitten_fighting");
JS:
cloudinary.imageTag('kitten_fighting').toHtml();
jQuery:
$.cloudinary.video("kitten_fighting", {resource_type: "image"})
React:
<Image publicId="kitten_fighting" resourceType="image">

</Image>
Vue.js:
<cld-image publicId="kitten_fighting" resourceType="image">

</cld-image>
Angular:
<cl-image public-id="kitten_fighting" resource-type="image">

</cl-image>
.Net:
cloudinary.Api.UrlImgUp.BuildVideoTag("kitten_fighting")
Android:
MediaManager.get().url().generate("kitten_fighting.mp4");
iOS:
imageView.cldSetImage(cloudinary.createUrl().generate("kitten_fighting.mp4")!, cloudinary: cloudinary)

The video file can also be delivered using Cloudinary's video tags and further manipulated like any other video file. See the video manipulation and delivery documentation for more details.

Tip
You can also deliver video files as animated GIFs and animated WebPs. For details, see the Creating animated GIF and animated WebP video documentation.

Convert a fetched animated GIF to video

When working with fetched remote images, you must specify the fetched image URL exactly as it is in the remote location, including file extension. Therefore, if you want to convert a fetched animated GIF to MP4, use the fetch_format (f_) parameter to convert the format to MP4.

For example:

Ruby:
cl_video_tag("https://orig00.deviantart.net/1928/f/2013/002/e/5/rubix_by_retsamys-d5q4qb6", :width=>300, :crop=>"scale", :format=>"mp4", :type=>"fetch", :resource_type=>"image")
PHP:
cl_video_tag("https://orig00.deviantart.net/1928/f/2013/002/e/5/rubix_by_retsamys-d5q4qb6", array("width"=>300, "crop"=>"scale", "format"=>"mp4", "type"=>"fetch", "resource_type"=>"image"))
Python:
CloudinaryImage("https://orig00.deviantart.net/1928/f/2013/002/e/5/rubix_by_retsamys-d5q4qb6").video(width=300, crop="scale", format="mp4", type="fetch")
Node.js:
cloudinary.video("https://orig00.deviantart.net/1928/f/2013/002/e/5/rubix_by_retsamys-d5q4qb6", {width: 300, crop: "scale", format: "mp4", type: "fetch", resource_type: "image"})
Java:
cloudinary.url().transformation(new Transformation().width(300).crop("scale")).format("mp4").type("fetch").resourceType("image").videoTag("https://orig00.deviantart.net/1928/f/2013/002/e/5/rubix_by_retsamys-d5q4qb6");
JS:
cloudinary.imageTag('https://orig00.deviantart.net/1928/f/2013/002/e/5/rubix_by_retsamys-d5q4qb6', {width: 300, crop: "scale", format: "mp4", type: "fetch"}).toHtml();
jQuery:
$.cloudinary.video("https://orig00.deviantart.net/1928/f/2013/002/e/5/rubix_by_retsamys-d5q4qb6", {width: 300, crop: "scale", format: "mp4", type: "fetch", resource_type: "image"})
React:
<Image publicId="https://orig00.deviantart.net/1928/f/2013/002/e/5/rubix_by_retsamys-d5q4qb6" format="mp4" type="fetch" resourceType="image">
  <Transformation width="300" crop="scale" />
</Image>
Vue.js:
<cld-image publicId="https://orig00.deviantart.net/1928/f/2013/002/e/5/rubix_by_retsamys-d5q4qb6" format="mp4" type="fetch" resourceType="image">
  <cld-transformation width="300" crop="scale" />
</cld-image>
Angular:
<cl-image public-id="https://orig00.deviantart.net/1928/f/2013/002/e/5/rubix_by_retsamys-d5q4qb6" format="mp4" type="fetch" resource-type="image">
  <cl-transformation width="300" crop="scale">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(300).Crop("scale")).Format("mp4").Type("fetch").BuildVideoTag("https://orig00.deviantart.net/1928/f/2013/002/e/5/rubix_by_retsamys-d5q4qb6")
Android:
MediaManager.get().url().transformation(new Transformation().width(300).crop("scale")).format("mp4").type("fetch").generate("https://orig00.deviantart.net/1928/f/2013/002/e/5/rubix_by_retsamys-d5q4qb6.gif");
iOS:
imageView.cldSetImage(cloudinary.createUrl().setFormat("mp4").setType( "fetch").setTransformation(CLDTransformation().setWidth(300).setCrop("scale")).generate("https://orig00.deviantart.net/1928/f/2013/002/e/5/rubix_by_retsamys-d5q4qb6.gif")!, cloudinary: cloudinary)

Converting an animated image to animated WebP or PNG

There are a quite a few advantages to delivering animated files in the WebP or PNG format instead of the GIF format, including:

  • WebP supports 24-bit RGB color with an 8-bit alpha channel, compared to GIF's 8-bit color and 1-bit alpha.
  • WebP supports both lossy and lossless compression (a single animation can combine lossy and lossless frames), well-suited to animated images created from real-world videos. GIF only supports lossless compression.
  • WebP requires fewer bytes than GIF, ranging from 64% smaller for Animated GIF converted to lossy WebP, to 19% smaller for lossless WebP.
  • Animated PNG supports 24-bit images and 8-bit transparency, which are not available for GIFs

To deliver an animated WebP or PNG instead of an animated GIF, change the file extension from .gif to .webp or .png and set the flag parameter to awebp or apng (fl_awebp or fl_apng in URLs). For example, delivering the animated gif called kitten_fighting as an animated WebP:

Ruby:
cl_image_tag("kitten_fighting.webp", :flags=>"awebp")
PHP:
cl_image_tag("kitten_fighting.webp", array("flags"=>"awebp"))
Python:
CloudinaryImage("kitten_fighting.webp").image(flags="awebp")
Node.js:
cloudinary.image("kitten_fighting.webp", {flags: "awebp"})
Java:
cloudinary.url().transformation(new Transformation().flags("awebp")).imageTag("kitten_fighting.webp");
JS:
cloudinary.imageTag('kitten_fighting.webp', {flags: "awebp"}).toHtml();
jQuery:
$.cloudinary.image("kitten_fighting.webp", {flags: "awebp"})
React:
<Image publicId="kitten_fighting.webp" >
  <Transformation flags="awebp" />
</Image>
Vue.js:
<cld-image publicId="kitten_fighting.webp" >
  <cld-transformation flags="awebp" />
</cld-image>
Angular:
<cl-image public-id="kitten_fighting.webp" >
  <cl-transformation flags="awebp">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Flags("awebp")).BuildImageTag("kitten_fighting.webp")
Android:
MediaManager.get().url().transformation(new Transformation().flags("awebp")).generate("kitten_fighting.webp");
iOS:
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setFlags("awebp")).generate("kitten_fighting.webp")!, cloudinary: cloudinary)
kitten_fighting gif delivered in webp format

The disadvantage of the WebP format is that whereas animated GIFs are universally supported, WebP is only supported by the Chrome, Android and Opera web browsers. To offset this problem you can automatically detect the user's browser and deliver the correct supported format by setting the fetch_format parameter to auto (or f_auto in URLs). For example, to deliver kitten_fighting in the WebP format to supported browsers, and in the GIF format to the rest:

Ruby:
cl_image_tag("kitten_fighting.gif", :fetch_format=>:auto)
PHP:
cl_image_tag("kitten_fighting.gif", array("fetch_format"=>"auto"))
Python:
CloudinaryImage("kitten_fighting.gif").image(fetch_format="auto")
Node.js:
cloudinary.image("kitten_fighting.gif", {fetch_format: "auto"})
Java:
cloudinary.url().transformation(new Transformation().fetchFormat("auto")).imageTag("kitten_fighting.gif");
JS:
cloudinary.imageTag('kitten_fighting.gif', {fetchFormat: "auto"}).toHtml();
jQuery:
$.cloudinary.image("kitten_fighting.gif", {fetch_format: "auto"})
React:
<Image publicId="kitten_fighting.gif" >
  <Transformation fetchFormat="auto" />
</Image>
Vue.js:
<cld-image publicId="kitten_fighting.gif" >
  <cld-transformation fetchFormat="auto" />
</cld-image>
Angular:
<cl-image public-id="kitten_fighting.gif" >
  <cl-transformation fetch-format="auto">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().FetchFormat("auto")).BuildImageTag("kitten_fighting.gif")
Android:
MediaManager.get().url().transformation(new Transformation().fetchFormat("auto")).generate("kitten_fighting.gif");
iOS:
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setFetchFormat("auto")).generate("kitten_fighting.gif")!, cloudinary: cloudinary)
kitten_fighting gif delivered in webp format to Chrome, or in GIF to other browsers

Notes

  • Although animated PNGs are supported only in some browsers and versions, animated PNGs are backward compatible, so all browsers can show the first frame of an animated PNG, even if they don't directly support animated PNGs.
  • Single-frame automated WebP is not supported. Therefore, if you supply a .gif file that is defined as an animated GIF, but has only one frame, then f_auto will always deliver it as a .gif and not as an automated .WebP, even in browsers where automated WebP is supported.

Applying lossy GIF compression

The compression algorithms used in GIFs are lossless, and there is no loss of data when compressing this palette-based format. The "lossiness" comes in when the GIF is first filtered or altered so that the image can then compress more efficiently. The loss of data occurs in this filtering phase by increasing redundant patterns along scan lines to subsequently improve the actual compression and decrease the file size.

To automatically use lossy compression when delivering your animated GIF files, set the flag parameter to lossy (fl_lossy in URLs). For example, the kitten_fighting animated GIF has a file size of 6.3 MB without lossy compression, and a file size of 2.5 MB with lossy compression. The file still looks good and is now 40% of the original size:

Ruby:
cl_image_tag("kitten_fighting.gif", :flags=>"lossy")
PHP:
cl_image_tag("kitten_fighting.gif", array("flags"=>"lossy"))
Python:
CloudinaryImage("kitten_fighting.gif").image(flags="lossy")
Node.js:
cloudinary.image("kitten_fighting.gif", {flags: "lossy"})
Java:
cloudinary.url().transformation(new Transformation().flags("lossy")).imageTag("kitten_fighting.gif");
JS:
cloudinary.imageTag('kitten_fighting.gif', {flags: "lossy"}).toHtml();
jQuery:
$.cloudinary.image("kitten_fighting.gif", {flags: "lossy"})
React:
<Image publicId="kitten_fighting.gif" >
  <Transformation flags="lossy" />
</Image>
Vue.js:
<cld-image publicId="kitten_fighting.gif" >
  <cld-transformation flags="lossy" />
</cld-image>
Angular:
<cl-image public-id="kitten_fighting.gif" >
  <cl-transformation flags="lossy">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Flags("lossy")).BuildImageTag("kitten_fighting.gif")
Android:
MediaManager.get().url().transformation(new Transformation().flags("lossy")).generate("kitten_fighting.gif");
iOS:
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setFlags("lossy")).generate("kitten_fighting.gif")!, cloudinary: cloudinary)
kitten_fighting with lossy compression

To control the level of lossy compression in the resulting animated GIF add the quality parameter (q in URLs), which has a default value of 80 (applied in the example above). For example, enabling lossy compression for the kitten_fighting GIF and also setting the quality parameter to 50 results in a file size of 2.1 MB - 33% of the original file size.

Ruby:
cl_image_tag("kitten_fighting.gif", :flags=>"lossy", :quality=>50)
PHP:
cl_image_tag("kitten_fighting.gif", array("flags"=>"lossy", "quality"=>50))
Python:
CloudinaryImage("kitten_fighting.gif").image(flags="lossy", quality=50)
Node.js:
cloudinary.image("kitten_fighting.gif", {flags: "lossy", quality: 50})
Java:
cloudinary.url().transformation(new Transformation().flags("lossy").quality(50)).imageTag("kitten_fighting.gif");
JS:
cloudinary.imageTag('kitten_fighting.gif', {flags: "lossy", quality: 50}).toHtml();
jQuery:
$.cloudinary.image("kitten_fighting.gif", {flags: "lossy", quality: 50})
React:
<Image publicId="kitten_fighting.gif" >
  <Transformation flags="lossy" quality="50" />
</Image>
Vue.js:
<cld-image publicId="kitten_fighting.gif" >
  <cld-transformation flags="lossy" quality="50" />
</cld-image>
Angular:
<cl-image public-id="kitten_fighting.gif" >
  <cl-transformation flags="lossy" quality="50">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Flags("lossy").Quality(50)).BuildImageTag("kitten_fighting.gif")
Android:
MediaManager.get().url().transformation(new Transformation().flags("lossy").quality(50)).generate("kitten_fighting.gif");
iOS:
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setFlags("lossy").setQuality(50)).generate("kitten_fighting.gif")!, cloudinary: cloudinary)
kitten_fighting with quality set to 50

Creating animated images

You can create a single animated image (GIF, PNG or WebP) from multiple image assets, where each asset is used as a single frame of the resulting animated image.

Animated images can be created from a maximum of 500 frames (individual images), except in the following cases where the maximum is 100 frames:

  • If the processing is done in a synchronous mode (i.e., without the async parameter set to true).
  • If there is a underlay or overlay added to the image (l_ or u_).
  • If any transformation parameters are added that are not on the following list: background, flags, crop, width, height, x, y, gravity, quality, angle, page, and dpr.
  • If the angle parameter is added with a value that is anything but exif or auto.
  • If the crop parameter is added with a value that is anything but scale, limit, fit, fill, thumb or crop.

If the limit is exceeded, only the first 500 (or 100) images will be included.

Step 1: Upload all the images to be included in the animated image. Make sure that you include:

  • An appropriate public ID when uploading each of the files; when they are merged into a single animated image, they are sorted alphabetically by their public IDs.
  • An identical tag for all images. The tag must be unique to these images only; the animated image creation process finds all images with the same tag and merges them into a single file.

Step 2: Use the multi method of the upload API to create the animated image. If the images to be merged are not all the same size, you can add transformation parameters to the URL to crop them accordingly (using one of the crop modes plus width or height, etc). For example, to create an animated GIF from all images with the tag arrow_animation:

Ruby:
Cloudinary::Uploader.multi('arrow_animation')
PHP:
\Cloudinary\Uploader::multi('arrow_animation');
Python:
cloudinary.uploader.multi('arrow_animation')
Node.js:
cloudinary.v2.uploader.multi('arrow_animation',
    function(error,result) {console.log(result); });
Java:
cloudinary.uploader().multi('arrow_animation', ObjectUtils.emptyMap());
cURL:
curl https://api.cloudinary.com/v1_1/demo/image/multi -X POST --data 'tag=arrow_animation&timestamp=173719931&api_key=436464676&signature=a788d68f86a6f868af'

Step 3: To deliver the animated image, use the Cloudinary image delivery URL with type set to multi. For example, to deliver an animated GIF created from all images with the tag arrow_animation:

Ruby:
cl_image_tag("arrow_animation.gif", :type=>"multi")
PHP:
cl_image_tag("arrow_animation.gif", array("type"=>"multi"))
Python:
CloudinaryImage("arrow_animation.gif").image(type="multi")
Node.js:
cloudinary.image("arrow_animation.gif", {type: "multi"})
Java:
cloudinary.url().type("multi").imageTag("arrow_animation.gif");
JS:
cloudinary.imageTag('arrow_animation.gif', {type: "multi"}).toHtml();
jQuery:
$.cloudinary.image("arrow_animation.gif", {type: "multi"})
React:
<Image publicId="arrow_animation.gif" type="multi">

</Image>
Vue.js:
<cld-image publicId="arrow_animation.gif" type="multi">

</cld-image>
Angular:
<cl-image public-id="arrow_animation.gif" type="multi">

</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Type("multi").BuildImageTag("arrow_animation.gif")
Android:
MediaManager.get().url().type("multi").generate("arrow_animation.gif");
iOS:
imageView.cldSetImage(cloudinary.createUrl().setType( "multi").generate("arrow_animation.gif")!, cloudinary: cloudinary)
arrow_animation.gif created from all images with the arrow_animation tag

The following example showcases a method to create a very simple animated gif of revolving text consisting of 20 individual frames. A script is executed to upload the individual images to Cloudinary, where each individual image (frame) is constructed from:

  • A previously uploaded blank image used as a base image.
  • A text string overlaid over the base image.

Each frame is a combination of the base image together with an overlay of a slightly modified version of the text string. The text is modified for each frame with the distort effect parameter to change its perspective.

coordinates = {}
(0..10).each do |frame|
  x_offset = frame * 10
  y_back   = 10*(frame < 5 ? -frame : frame - 10)
  y_front  = y_back*2

  front    = [ x_offset, y_front, 
               100 - x_offset, -y_back,
               100 - x_offset, 100+y_back,
               x_offset, 100 - y_front ]
            .map { |i| "#{i}p" }.join(":")

  back     = [ x_offset, -y_back, 
               100 - x_offset, y_back*2,
               100 - x_offset, 100 - y_back*2,
               x_offset, 100 + y_back ]
            .map { |i| "#{i}p" }.join(":")

  coordinates[frame]      = front
  coordinates[20 - frame] = back
end

(0..19).each do |frame|
  x_offset = frame < 10 ? frame*10 : 200 - frame*10
  myurl    = Cloudinary::Utils.cloudinary_url(
    "base.png",
    :transformation => [
      { :width  => 510, :height => 300, :crop => "scale",
        :background => "white" },
      { :overlay => "text:roboto_150_bold:Spinning text", 
        :color => "#0071BA", :width => 500, :height => 100 },
      { :effect => "distort:#{coordinates[frame]}" },
      { :crop => "crop", gravity: "center", 
        :width => ((500*(100-2*x_offset)/100.0).abs.to_i), 
        :height => 300 },
      { :flags => "layer_apply" }])

  Cloudinary::Uploader.upload(
     myurl,
     :public_id => "spinning_text_#{'%02d' % frame}",
     :tags      => "spinning_text"
  ) if x_offset != 50
end

Cloudinary::Uploader.multi("spinning_text", :delay => 100)

After the script is run and the images are uploaded, the following URL delivers the animated GIF:

Ruby:
cl_image_tag("spinning_text.gif", :delay=>"100", :type=>"multi")
PHP:
cl_image_tag("spinning_text.gif", array("delay"=>"100", "type"=>"multi"))
Python:
CloudinaryImage("spinning_text.gif").image(delay="100", type="multi")
Node.js:
cloudinary.image("spinning_text.gif", {delay: "100", type: "multi"})
Java:
cloudinary.url().transformation(new Transformation().delay("100")).type("multi").imageTag("spinning_text.gif");
JS:
cloudinary.imageTag('spinning_text.gif', {delay: "100", type: "multi"}).toHtml();
jQuery:
$.cloudinary.image("spinning_text.gif", {delay: "100", type: "multi"})
React:
<Image publicId="spinning_text.gif" type="multi">
  <Transformation delay="100" />
</Image>
Vue.js:
<cld-image publicId="spinning_text.gif" type="multi">
  <cld-transformation delay="100" />
</cld-image>
Angular:
<cl-image public-id="spinning_text.gif" type="multi">
  <cl-transformation delay="100">
  </cl-transformation>
</cl-image>
.Net:
cloudinary.Api.UrlImgUp.Transform(new Transformation().Delay("100")).Type("multi").BuildImageTag("spinning_text.gif")
Android:
MediaManager.get().url().transformation(new Transformation().delay("100")).type("multi").generate("spinning_text.gif");
iOS:
imageView.cldSetImage(cloudinary.createUrl().setType( "multi").setTransformation(CLDTransformation().setDelay("100")).generate("spinning_text.gif")!, cloudinary: cloudinary)
animated_logo.gif created from all images with the spinning_text tag