Cloudinary Blog

Lossy compression for optimizing animated GIFs

How to optimize animated GIFs with lossy compression

Animated GIFs keep getting more and more popular, but they are generally very big files with slow loading times and high bandwidth costs, while the format itself is quite old and not optimized for modern video clips. As developers, you need to allow users to upload their animated GIF files, but you also need to deliver them optimized, which can be a complex, time consuming process.

One option is to convert animated GIFs to videos, another great feature available with Cloudinary which saves on file size and bandwidth (see this blog post for more details), but videos are still not as easy to embed in sites and apps compared to regular image tags, and some browsers and mobile devices still do not support auto playing video files. Another option is to convert animated GIFs to animated WebP files, but this format, introduced by Google and supported on Chrome, is unfortunately not supported by most of the other mobile devices and browsers. Likewise, GIF conversion tools seem to either create files that are too large and of high quality, or smaller files with a bad visual quality.

Another great solution is to perform GIF optimization using a lossy compression technique rather than the lossless nature of the GIF format, allowing you to optimize uploaded animated GIFs to be delivered as smaller files while still looking good. Lossy compression is actually a misnomer for GIFs as the compression algorithms used in GIFs are lossless, and there is no loss of data when compressing this palette-based format (although converting to GIF from other image formats does result in a loss of data due to the 8bit GIF limitation of 256 colors).

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.

Applying lossy GIF compression

The lossy compression feature is available using Cloudinary's on-the-fly dynamic manipulation URLs, with no need to install any software or to use any computational power on your side because the image manipulation takes place in the cloud. To leverage this capability, and tell Cloudinary to automatically use lossy compression, all you need to do is set the flag parameter to lossy (fl_lossy in URLs).

For example, the following animated GIF named kitten_fighting uploaded to Cloudinary has a file size of 6.3 MB.

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

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

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

</cl-image>
.NET:
Copy to clipboard
cloudinary.Api.UrlImgUp.BuildImageTag("kitten_fighting.gif")
Android:
Copy to clipboard
MediaManager.get().url().generate("kitten_fighting.gif");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().generate("kitten_fighting.gif")!, cloudinary: cloudinary)
original non-optimized animated GIF

Enabling the lossy flag, which means adding the fl_lossy parameter to the delivery URL, optimizes the animated GIF to a file size of 2.5 MB. The file still looks good and is now 40% of the original size.

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

You can further control the level of lossy compression in the resulting animated GIF by also adding the quality parameter (q in URLs), which has a default value of 80. 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, which means we saved almost 70% of the original file size.

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

Further animated GIF manipulations with lossy compression

The lossy compression feature can be mixed with any of Cloudinary's rich set of image manipulation capabilities to match any graphic design, any dimensions, different devices, different browsers, responsive layouts and more. Lossy compression can also optimize a generated image, so instead of optimizing the original large animated GIF, you can optimize each manipulated or cropped version you would like to display.

For example, the following code generates and delivers a version of the uploaded kitten_fighting animated GIF as follows: Crops the animated GIF to a width of 50% and a height of 80%. Adds another uploaded png image named cloudinary_icon as an overlay. The overlay is resized to a width of 40 pixels, positioned 5 pixels from the top right corner of the animated GIF and is made 40% semi transparent. Lossy compression is applied with a quantity value of 50%.

The size of the delivered file is 765 KB compared to 1.9 MB without using lossy compression (a reduction of 60% in file size).

Ruby:
Copy to clipboard
cl_image_tag("kitten_fighting.gif", :transformation=>[
  {:width=>0.5, :height=>0.8, :crop=>"crop"},
  {:overlay=>"cloudinary_icon", :width=>40, :gravity=>"north_east", :opacity=>40, :x=>5, :y=>5, :crop=>"scale"},
  {:flags=>"lossy", :quality=>50}
  ])
PHP v1:
Copy to clipboard
cl_image_tag("kitten_fighting.gif", array("transformation"=>array(
  array("width"=>"0.5", "height"=>"0.8", "crop"=>"crop"),
  array("overlay"=>"cloudinary_icon", "width"=>40, "gravity"=>"north_east", "opacity"=>40, "x"=>5, "y"=>5, "crop"=>"scale"),
  array("flags"=>"lossy", "quality"=>50)
  )))
PHP v2:
Copy to clipboard
(new ImageTag('kitten_fighting.gif'))
  ->resize(Resize::crop()->width(0.5)->height(0.8))
  ->overlay(
      Overlay::source(Source::image('cloudinary_icon')
        ->transformation((new ImageTransformation())
          ->resize(Resize::scale()->width(40))
          ->adjust(Adjust::opacity(40))))
      ->position((new Position())
        ->gravity(Gravity::compass(Compass::northEast()))
        ->offsetX(5)->offsetY(5)))
    ->delivery(Delivery::format(Format::gif())
      ->lossy())
    ->delivery(Delivery::quality(50));
Python:
Copy to clipboard
CloudinaryImage("kitten_fighting.gif").image(transformation=[
  {'width': "0.5", 'height': "0.8", 'crop': "crop"},
  {'overlay': "cloudinary_icon", 'width': 40, 'gravity': "north_east", 'opacity': 40, 'x': 5, 'y': 5, 'crop': "scale"},
  {'flags': "lossy", 'quality': 50}
  ])
Node.js:
Copy to clipboard
cloudinary.image("kitten_fighting.gif", {transformation: [
  {width: "0.5", height: "0.8", crop: "crop"},
  {overlay: "cloudinary_icon", width: 40, gravity: "north_east", opacity: 40, x: 5, y: 5, crop: "scale"},
  {flags: "lossy", quality: 50}
  ]})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation()
  .width(0.5).height(0.8).crop("crop").chain()
  .overlay(new Layer().publicId("cloudinary_icon")).width(40).gravity("north_east").opacity(40).x(5).y(5).crop("scale").chain()
  .flags("lossy").quality(50)).imageTag("kitten_fighting.gif");
JS:
Copy to clipboard
cloudinary.imageTag('kitten_fighting.gif', {transformation: [
  {width: "0.5", height: "0.8", crop: "crop"},
  {overlay: new cloudinary.Layer().publicId("cloudinary_icon"), width: 40, gravity: "north_east", opacity: 40, x: 5, y: 5, crop: "scale"},
  {flags: "lossy", quality: 50}
  ]}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("kitten_fighting.gif", {transformation: [
  {width: "0.5", height: "0.8", crop: "crop"},
  {overlay: new cloudinary.Layer().publicId("cloudinary_icon"), width: 40, gravity: "north_east", opacity: 40, x: 5, y: 5, crop: "scale"},
  {flags: "lossy", quality: 50}
  ]})
React:
Copy to clipboard
<Image publicId="kitten_fighting.gif" >
  <Transformation width="0.5" height="0.8" crop="crop" />
  <Transformation overlay="cloudinary_icon" width="40" gravity="north_east" opacity="40" x="5" y="5" crop="scale" />
  <Transformation flags="lossy" quality="50" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="kitten_fighting.gif" >
  <cld-transformation width="0.5" height="0.8" crop="crop" />
  <cld-transformation :overlay="cloudinary_icon" width="40" gravity="north_east" opacity="40" x="5" y="5" crop="scale" />
  <cld-transformation flags="lossy" quality="50" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="kitten_fighting.gif" >
  <cl-transformation width="0.5" height="0.8" crop="crop">
  </cl-transformation>
  <cl-transformation overlay="cloudinary_icon" width="40" gravity="north_east" opacity="40" x="5" y="5" crop="scale">
  </cl-transformation>
  <cl-transformation flags="lossy" quality="50">
  </cl-transformation>
</cl-image>
.NET:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation()
  .Width(0.5).Height(0.8).Crop("crop").Chain()
  .Overlay(new Layer().PublicId("cloudinary_icon")).Width(40).Gravity("north_east").Opacity(40).X(5).Y(5).Crop("scale").Chain()
  .Flags("lossy").Quality(50)).BuildImageTag("kitten_fighting.gif")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation()
  .width(0.5).height(0.8).crop("crop").chain()
  .overlay(new Layer().publicId("cloudinary_icon")).width(40).gravity("north_east").opacity(40).x(5).y(5).crop("scale").chain()
  .flags("lossy").quality(50)).generate("kitten_fighting.gif");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation()
  .setWidth(0.5).setHeight(0.8).setCrop("crop").chain()
  .setOverlay("cloudinary_icon").setWidth(40).setGravity("north_east").setOpacity(40).setX(5).setY(5).setCrop("scale").chain()
  .setFlags("lossy").setQuality(50)).generate("kitten_fighting.gif")!, cloudinary: cloudinary)
Animated GIF resized, with overlay added and 50% lossy GIF compression

Summary

Lossy compression for animated GIFs allows you to benefit from both worlds: support animated GIFs, enjoy their simplicity, and still deliver smaller files that look good. Improve your user's experience, save on bandwidth, and all that with zero effort in developing your web sites and apps.

If you need much smaller files and you are ready to embed video files, Cloudinary can auto convert GIFs to videos. Likewise, if you allow your users to upload video files and you want to display animated GIFs instead, you can use Cloudinary to dynamically convert videos to GIFs.

The lossy compression feature for animated GIFs is available to all our free and paid plans. If you don't have a Cloudinary account, you are welcome to sign up to our free account and try it out.

Recent Blog Posts

Generate Waveform Images from Audio with Cloudinary

This is a reposting of an article written by David Walsh. Check out his blog HERE!
I've been working a lot with visualizations lately, which is a far cry from your normal webpage element interaction coding; you need advanced geometry knowledge, render and performance knowledge, and much more. It's been a great learning experience but it can be challenging and isn't always an interest of all web developers. That's why we use apps and services specializing in complex tasks like Cloudinary: we need it done quickly and by a tool written by an expert.

Read more
Make All Images on Your Website Responsive in 3 Easy Steps

Images are crucial to website performance, but most still don't implement responsive images. It’s not just about fitting an image on the screen, but also making the the image size relatively rational to the device. The srcset and sizes options, which are your best hit are hard to implement. Cloudinary provides an easier way, which we will discuss in this article.

Read more

The Future of Audio and Video on the Web

By Prosper Otemuyiwa
The Future of Audio and Video on the Web

Web sites and platforms are becoming increasingly media-rich. Today, approximately 62 percent of internet traffic is made up of images, with audio and video constituting a growing percentage of the bytes.

Read more

Embed Images in Email Campaigns at Scale

By Sourav Kundu
Embed Images in Email Campaigns at Scale

tl;dr

Cloudinary is a powerful image hosting solution for email marketing campaigns of any size. With features such as advanced image optimization and on-the-fly image transformation, backed by a global CDN, Cloudinary provides the base for a seamless user experience in your email campaigns leading to increased conversion and performance.

Read more
Build the Back-End For Your Own Instagram-style App with Cloudinary

Github Repo

Managing media files (processing, storage and manipulation) is one of the biggest challenges we encounter as practical developers. These challenges include:

A great service called Cloudinary can help us overcome many of these challenges. Together with Cloudinary, let's work on solutions to these challenges and hopefully have a simpler mental model towards media management.

Read more

Build A Miniflix in 10 Minutes

By Prosper Otemuyiwa
Build A Miniflix in 10 Minutes

Developers are constantly faced with challenges of building complex products every single day. And there are constraints on the time needed to build out the features of these products.

Engineering and Product managers want to beat deadlines for projects daily. CEOs want to roll out new products as fast as possible. Entrepreneurs need their MVPs like yesterday. With this in mind, what should developers do?

Read more