
Why mobile-only?
Google search favors mobile-ready sites
Online industries change
But...are sites ready?

(Guest post by Grant Kemp)
Over the past few months Inviqa have been working to build an official plugin to simplify the process of adding Cloudinary to the 100,000s of Magento sites around the world. Inviqa is one of Magento’s largest partners in Europe and has been working with customers to integrate Cloudinary and ensure that the plugin can cater to a Magento store’s needs.
At the forefront of the digital eCommerce revolution is Magento, which is the world’s largest eCommerce platform by numbers, and powers the stores of global brands from Nike and Gant to high fashion brands like Christian Louboutin and Paul Smith.
Images are the closest thing that an eCommerce store has to real sales people. It is images that encourage visitors to buy, images that allow visitors to connect with products, and most importantly make the transition from new visitors to customers.
The challenge for a typical Magento administrator is that managing images is hard work. If they make them too big, then the site slows down and creates a bad shopping experience. Make them too small and customers will struggle to see the products and are less likely to add them to their shopping basket.
There is a lot of work that goes into making images. They need to be uploaded, stored, versioned, integrated into Magento, made to work responsively, and then finally shown to the customers. Cloudinary’s focus for Magento is on reducing complexity and working to make image optimization and workflows easier, simpler… and automatic.
By installing the plugin via the Magento Connect store, the whole process of letting Cloudinary take control over the image features on Magento happens automatically.
This first release helps to solve the largest headache for Magento sites which is to make Magento perform faster. All web users enjoy fast websites and we have worked hard to provide Magento developers with a framework to add their own custom functionality.
We also implemented controls within Magento that allow:
For those of you looking to try out the plugin, you can install it in a few minutes via the Magento Connect Store. Take a look at the source code on Cloudinary’s GitHub Repos. Cloudinary has a free plan which is a quick and easy way to try out the features.
If you are looking for new features please feel free to get in touch via our GitHub Repos or visit the feature request forum via the Cloudinary support website.
We look forward to helping Magento merchants take some of the effort out of delivering quality images on their site. Most importantly as shoppers ourselves, we want Magento site visitors to enjoy a faster shopping experience and access amazing quality product images and videos when they choose to buy online from their favourite stores.
We have created a quick video demo that shows how to integrate Cloudinary into Magento in under 3 minutes thanks to the new module.
There are other features that we haven’t had time to fit into this blog post, so we encourage you to try it out for yourself and share any feedback with us. We are eager to keep developing and improving the plugin.
We also welcome GitHub comments, feature requests and pull requests, as we want to continue to make this the most versatile and powerful image and media plugin in the eCommerce market.
About the Author
Grant Kemp works for Inviqa and has collaborated with Cloudinary to bring their technology to Magento. When not at work, Grant blogs about retail innovation on Connectedwindow.com and builds apps on iOS and Android.
Content Optimization and Personalization programs can deliver tremendous ROI to an organization but tend to be very resource intensive, requiring developers to build the code for alternate experiences and creative folks to generate the content. Many of the content optimization/personalization tools out there today (Maxymiser, Optimizely, Adobe Target, Ensighten etc.) have created WYSIWYG (What You See Is What You Get) editors to help relieve the code/development bottleneck but the creative bottleneck stubbornly remains.
Often you’ll have a great personalization or A/B test idea but can’t execute it because the creative team doesn’t have the time or resources to create what’s needed. Anybody on an optimization/testing/personalization team has dealt with this before and it’s been an intractable problem for years.
It’s a bit of a catch 22: the more successful you are in your optimization program, the harder and more resource intensive it is going to be to maintain, especially as you get into personalization which requires more and more content catered to the individual user. How does one scale-up such initiatives going forward without hiring an army of creative folks?
Often there’s already creative content on your website somewhere or images from past campaigns that you can reuse; it’s only the wrong size, needs rounded corners, or just needs specific text overlayed on top of it. Let’s take Williams-Sonoma’s website (a retailer I love) as an example.
If you go to williams-sonoma.com and scroll down on the homepage you’ll see a section titled “Just for You!”. This section is used for displaying personalized content based on your browsing behavior. If this is your first time at Williams-Sonoma and you’ve yet to browse, you’ll see the default content. At the time of writing, it looks like this:
But if I start navigating the site, Williams Sonoma learns what sort of products I’m interested in and will show me relevant content, making my experience better. I navigated around the “Cutlery” section and now on the homepage I’m presented with the following:
This is very cool, now I can see relevant content with less clutter.
But how does this look from Williams Sonoma’s end? Clearly they have to maintain additional content, about a dozen or so different versions for each main product category. This of course puts an additional strain on both the development and creative teams.
This might be why we only see the dozen or so different versions and then only on one spot on one page. If they wanted to expand their personalization initiatives they would have to weigh the benefits against the additional effort, and while the benefits are linear, the effort becomes exponential.
When I was navigating through the cutlery category to build up a category affinity, I was actually looking at the “Rösle Cheese Knives”. But when I returned to the homepage I was shown “Shun Cutlery” content. Same category (cutlery) but still probably irrelevant to me, perhaps not personalized enough. As we discussed, it’s probably impossible to get to this level of personalization (true 1:1) as it’s just too much effort to create all the resources that would be needed as I’d guess Williams-Sonoma has in the area of 1000 products or more.
Can you imagine what the response would be if you went to your creative team and asked them to create 1000+ different images for each spot you’d like to try personalized content (on top of their normal job)? I’m guessing there would be laughter followed by an invitation to get out of their office.
With Cloudinary we can actually create all of these images dynamically, simply by changing the url of the image, applying custom parameters right in the image URL and building an image on the fly. The following images were created with absolutely zero use of image software and built to mirror the creative style of the images currently on williams-sonoma.com:
Here are the details showing how these images were created:
cl_image_tag("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0167/rosle-cheese-knives-c.jpg", :type=>"fetch", :transformation=>[ {:border=>"1px_solid_gray", :width=>320, :height=>400, :crop=>:fill}, {:overlay=>"white-bar", :width=>300, :height=>90, :y=>20, :gravity=>:south}, {:overlay=>"text:Helvetica_14:ROSLE%20CHEESE%20KNIVES%20%3E", :y=>58, :gravity=>:south} ])
cl_image_tag("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0167/rosle-cheese-knives-c.jpg", array("type"=>"fetch", "transformation"=>array( array("border"=>"1px_solid_gray", "width"=>320, "height"=>400, "crop"=>"fill"), array("overlay"=>"white-bar", "width"=>300, "height"=>90, "y"=>20, "gravity"=>"south"), array("overlay"=>"text:Helvetica_14:ROSLE%20CHEESE%20KNIVES%20%3E", "y"=>58, "gravity"=>"south") )))
CloudinaryImage("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0167/rosle-cheese-knives-c.jpg").image(type="fetch", transformation=[ {"border": "1px_solid_gray", "width": 320, "height": 400, "crop": "fill"}, {"overlay": "white-bar", "width": 300, "height": 90, "y": 20, "gravity": "south"}, {"overlay": "text:Helvetica_14:ROSLE%20CHEESE%20KNIVES%20%3E", "y": 58, "gravity": "south"} ])
cloudinary.image("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0167/rosle-cheese-knives-c.jpg", {type: "fetch", transformation: [ {border: "1px_solid_gray", width: 320, height: 400, crop: "fill"}, {overlay: "white-bar", width: 300, height: 90, y: 20, gravity: "south"}, {overlay: "text:Helvetica_14:ROSLE%20CHEESE%20KNIVES%20%3E", y: 58, gravity: "south"} ]})
cloudinary.url().transformation(new Transformation() .border("1px_solid_gray").width(320).height(400).crop("fill").chain() .overlay("white-bar").width(300).height(90).y(20).gravity("south").chain() .overlay("text:Helvetica_14:ROSLE%20CHEESE%20KNIVES%20%3E").y(58).gravity("south")).type("fetch").imageTag("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0167/rosle-cheese-knives-c.jpg")
$.cloudinary.image("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0167/rosle-cheese-knives-c.jpg", {type: "fetch", transformation: [ {border: "1px_solid_gray", width: 320, height: 400, crop: "fill"}, {overlay: "white-bar", width: 300, height: 90, y: 20, gravity: "south"}, {overlay: "text:Helvetica_14:ROSLE%20CHEESE%20KNIVES%20%3E", y: 58, gravity: "south"} ]})
cloudinary.Api.UrlImgUp.Transform(new Transformation() .Border("1px_solid_gray").Width(320).Height(400).Crop("fill").Chain() .Overlay("white-bar").Width(300).Height(90).Y(20).Gravity("south").Chain() .Overlay("text:Helvetica_14:ROSLE%20CHEESE%20KNIVES%20%3E").Y(58).Gravity("south")).Type(fetch).BuildImageTag("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0167/rosle-cheese-knives-c.jpg")
cl_image_tag("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0012/img31c.jpg", :type=>"fetch", :transformation=>[ {:border=>"1px_solid_gray", :width=>320, :height=>400, :crop=>:fill}, {:overlay=>"white-bar", :width=>300, :height=>90, :y=>10, :gravity=>:south}, {:overlay=>"text:Helvetica_14:VITAMIX%20780%20BLENDER%20%3E", :y=>48, :gravity=>:south} ])
cl_image_tag("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0012/img31c.jpg", array("type"=>"fetch", "transformation"=>array( array("border"=>"1px_solid_gray", "width"=>320, "height"=>400, "crop"=>"fill"), array("overlay"=>"white-bar", "width"=>300, "height"=>90, "y"=>10, "gravity"=>"south"), array("overlay"=>"text:Helvetica_14:VITAMIX%20780%20BLENDER%20%3E", "y"=>48, "gravity"=>"south") )))
CloudinaryImage("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0012/img31c.jpg").image(type="fetch", transformation=[ {"border": "1px_solid_gray", "width": 320, "height": 400, "crop": "fill"}, {"overlay": "white-bar", "width": 300, "height": 90, "y": 10, "gravity": "south"}, {"overlay": "text:Helvetica_14:VITAMIX%20780%20BLENDER%20%3E", "y": 48, "gravity": "south"} ])
cloudinary.image("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0012/img31c.jpg", {type: "fetch", transformation: [ {border: "1px_solid_gray", width: 320, height: 400, crop: "fill"}, {overlay: "white-bar", width: 300, height: 90, y: 10, gravity: "south"}, {overlay: "text:Helvetica_14:VITAMIX%20780%20BLENDER%20%3E", y: 48, gravity: "south"} ]})
cloudinary.url().transformation(new Transformation() .border("1px_solid_gray").width(320).height(400).crop("fill").chain() .overlay("white-bar").width(300).height(90).y(10).gravity("south").chain() .overlay("text:Helvetica_14:VITAMIX%20780%20BLENDER%20%3E").y(48).gravity("south")).type("fetch").imageTag("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0012/img31c.jpg")
$.cloudinary.image("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0012/img31c.jpg", {type: "fetch", transformation: [ {border: "1px_solid_gray", width: 320, height: 400, crop: "fill"}, {overlay: "white-bar", width: 300, height: 90, y: 10, gravity: "south"}, {overlay: "text:Helvetica_14:VITAMIX%20780%20BLENDER%20%3E", y: 48, gravity: "south"} ]})
cloudinary.Api.UrlImgUp.Transform(new Transformation() .Border("1px_solid_gray").Width(320).Height(400).Crop("fill").Chain() .Overlay("white-bar").Width(300).Height(90).Y(10).Gravity("south").Chain() .Overlay("text:Helvetica_14:VITAMIX%20780%20BLENDER%20%3E").Y(48).Gravity("south")).Type(fetch).BuildImageTag("http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0012/img31c.jpg")
Let’s take a look at what’s going on here a bit more closely. There are really two key parts to the URL. What the background image should be, denoted by '/http://ab.wsimgs.com/wsimgs/ab/images/dp/wcm/201540/0167/rosle-cheese-knives-c.jpg'
.
And what the text block should say, denoted by 'l_text:Helvetica_14:ROSLE CHEESE KNIVES >'
.
All the other parameters define the size of the image, the text style, and things like that.
You can read up about all of our on the fly image (and video!) transformations in our documentation.
You can play with this yourself, use this widget to build your own images!
Generated Image URL:
Changing only those two parameters in the image url allows you to build an image for ANY product (or banner/hero image/whatever you can dream up) on the fly. The only thing you need to do is track the last product (or page/category/article/etc) your visitor viewed and dynamically replace the values in the image URL. This is simple enough to do with any Optimization tool (Maxymiser, Optimizely, Adobe Target), and even some Tag Management Systems (Ensighten, Adobe DTM).
We’re truly talking about 1:1 personalization here with only a single URL. Also, I don’t even have to go through the work of uploading my images, as Cloudinary can automatically pull in your current images, transform them as you see fit, and place them out across the CDN using our 'fetch' functionality used in the examples above.
The same ideas can be applied to other areas as well: site banners, creative emails, display ads, as well as managing all of your site’s assets (which we do for thousands of customers). There is also a WYSIWYG image editor in the UI:
Cloudinary automatically places all images on a worldwide CDN for extremely fast performance, alongside image quality optimization and dynamic image formatting to reduce bandwidth and speed up your site’s load time.
Using Cloudinary for your A/B Testing and Personalization program can help you reduce your exposure to creative bottlenecks, accelerate the number of tests you can do, and open up new possibilities. With Cloudinary you can dynamically resize images and videos, overlay custom text and other images/videos on top of them, add filters, change colors, create on the fly banners, and much more. In order to test this out for yourself, sign up for a free account here.
It can be quite a challenge to graphically design a website or mobile application that displays images in very precise shapes and orientations. This can take the form of warping 2D pictures to have a 3D perspective, placing images in precise shapes or overlaying images in specific locations within another image, for example: overlaying an image over the screen of a smartphone.
Whether the desire is to display an image with 3D perspective, or fit an image into an irregular shape, such creativity normally comes with the large overhead of tweaking every image that needs to be displayed. If a website hosts a large number of images, or also wants to reshape user uploaded pictures, the challenge can quickly outweigh the reward or even become insurmountable.
cl_image_tag("base.jpg", :transformation=>[ {:width=>700, :height=>200}, {:overlay=>"mobile_phone", :width=>150, :gravity=>:west}, {:overlay=>"mobile_phone", :width=>150, :gravity=>:east}, {:overlay=>"movie_time", :width=>90, :gravity=>:center}, {:overlay=>"movie_time", :width=>100, :gravity=>:east, :effect=>"distort:30:20:85:40:25:120:-30:90"}, {:overlay=>"text:roboto_120_bold:%2B%20%20%20%20%20%20%20%3D"} ])
cl_image_tag("base.jpg", array("transformation"=>array( array("width"=>700, "height"=>200), array("overlay"=>"mobile_phone", "width"=>150, "gravity"=>"west"), array("overlay"=>"mobile_phone", "width"=>150, "gravity"=>"east"), array("overlay"=>"movie_time", "width"=>90, "gravity"=>"center"), array("overlay"=>"movie_time", "width"=>100, "gravity"=>"east", "effect"=>"distort:30:20:85:40:25:120:-30:90"), array("overlay"=>"text:roboto_120_bold:%2B%20%20%20%20%20%20%20%3D") )))
CloudinaryImage("base.jpg").image(transformation=[ {"width": 700, "height": 200}, {"overlay": "mobile_phone", "width": 150, "gravity": "west"}, {"overlay": "mobile_phone", "width": 150, "gravity": "east"}, {"overlay": "movie_time", "width": 90, "gravity": "center"}, {"overlay": "movie_time", "width": 100, "gravity": "east", "effect": "distort:30:20:85:40:25:120:-30:90"}, {"overlay": "text:roboto_120_bold:%2B%20%20%20%20%20%20%20%3D"} ])
cloudinary.image("base.jpg", {transformation: [ {width: 700, height: 200}, {overlay: "mobile_phone", width: 150, gravity: "west"}, {overlay: "mobile_phone", width: 150, gravity: "east"}, {overlay: "movie_time", width: 90, gravity: "center"}, {overlay: "movie_time", width: 100, gravity: "east", effect: "distort:30:20:85:40:25:120:-30:90"}, {overlay: "text:roboto_120_bold:%2B%20%20%20%20%20%20%20%3D"} ]})
cloudinary.url().transformation(new Transformation() .width(700).height(200).chain() .overlay("mobile_phone").width(150).gravity("west").chain() .overlay("mobile_phone").width(150).gravity("east").chain() .overlay("movie_time").width(90).gravity("center").chain() .overlay("movie_time").width(100).gravity("east").effect("distort:30:20:85:40:25:120:-30:90").chain() .overlay("text:roboto_120_bold:%2B%20%20%20%20%20%20%20%3D")).imageTag("base.jpg")
$.cloudinary.image("base.jpg", {transformation: [ {width: 700, height: 200}, {overlay: "mobile_phone", width: 150, gravity: "west"}, {overlay: "mobile_phone", width: 150, gravity: "east"}, {overlay: "movie_time", width: 90, gravity: "center"}, {overlay: "movie_time", width: 100, gravity: "east", effect: "distort:30:20:85:40:25:120:-30:90"}, {overlay: "text:roboto_120_bold:%2B%20%20%20%20%20%20%20%3D"} ]})
cloudinary.Api.UrlImgUp.Transform(new Transformation() .Width(700).Height(200).Chain() .Overlay("mobile_phone").Width(150).Gravity("west").Chain() .Overlay("mobile_phone").Width(150).Gravity("east").Chain() .Overlay("movie_time").Width(90).Gravity("center").Chain() .Overlay("movie_time").Width(100).Gravity("east").Effect("distort:30:20:85:40:25:120:-30:90").Chain() .Overlay("text:roboto_120_bold:%2B%20%20%20%20%20%20%20%3D")).BuildImageTag("base.jpg")
The solution is to use a tool that can dynamically do the transformations for you, based on predefined parameters. This blog post will show you how to accomplish this with two new effects Cloudinary has added to its considerable repertoire of image manipulation features: distort
and shear
.
In order to make it easy to consistently shape your images, Cloudinary has introduced support for a transformation effect
called distort
(e_distort
in image manipulation URLs) that allows you to dynamically customize your images to fit any quadrilateral shape. A quadrilateral (also known as a quadrangle or tetragon) is any polygon with four edges (or sides) and four vertices (or corners). The effect distorts an image by giving each of the four corners of the image new coordinates, and then mapping every pixel in the image in proportion to the new shape of the quadrilateral.
The distort effect parameter accepts 8 values separated by colons (:
), as each of the 4 corners needs to be represented by both an x and a y coordinate. The new coordinates for each of the 4 corners is given in a clockwise direction, starting with the top left corner, and the value of each new coordinate can be one of the following values:
p
appended) representing the percentage from the top left corner (which has the coordinates: 0p,0p).The image below shows an example of calculating the coordinates of the new corners of an image when distorting it to a new shape. The image originally has a width of 300 pixels and a height of 180 pixels, and the new corner coordinates are given in relation to these values:
e_distort:40:25:280:60:260:155:35:165
The following two images show themovie_time
image, and then the image after applying the distort effect calculated above:
cl_image_tag("movie_time.jpg", :width=>300, :height=>180, :crop=>:fill)
cl_image_tag("movie_time.jpg", array("width"=>300, "height"=>180, "crop"=>"fill"))
CloudinaryImage("movie_time.jpg").image(width=300, height=180, crop="fill")
cloudinary.image("movie_time.jpg", {width: 300, height: 180, crop: "fill"})
cloudinary.url().transformation(new Transformation().width(300).height(180).crop("fill")).imageTag("movie_time.jpg")
$.cloudinary.image("movie_time.jpg", {width: 300, height: 180, crop: "fill"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(300).Height(180).Crop("fill")).BuildImageTag("movie_time.jpg")
Applying the same distort effect (calculated above) to this image, using on-the-fly image manipulation URLs:
cl_image_tag("movie_time.jpg", :transformation=>[ {:width=>300, :height=>180, :crop=>:fill}, {:effect=>"distort:40:25:280:60:260:155:35:165"} ])
cl_image_tag("movie_time.jpg", array("transformation"=>array( array("width"=>300, "height"=>180, "crop"=>"fill"), array("effect"=>"distort:40:25:280:60:260:155:35:165") )))
CloudinaryImage("movie_time.jpg").image(transformation=[ {"width": 300, "height": 180, "crop": "fill"}, {"effect": "distort:40:25:280:60:260:155:35:165"} ])
cloudinary.image("movie_time.jpg", {transformation: [ {width: 300, height: 180, crop: "fill"}, {effect: "distort:40:25:280:60:260:155:35:165"} ]})
cloudinary.url().transformation(new Transformation() .width(300).height(180).crop("fill").chain() .effect("distort:40:25:280:60:260:155:35:165")).imageTag("movie_time.jpg")
$.cloudinary.image("movie_time.jpg", {transformation: [ {width: 300, height: 180, crop: "fill"}, {effect: "distort:40:25:280:60:260:155:35:165"} ]})
cloudinary.Api.UrlImgUp.Transform(new Transformation() .Width(300).Height(180).Crop("fill").Chain() .Effect("distort:40:25:280:60:260:155:35:165")).BuildImageTag("movie_time.jpg")
The distort effect is especially useful when used together with the overlay feature to create 3D perspectives. You can manipulate your image overlays (or underlays for that matter) to exactly match the dimensions and perspective of any quadrilateral shape in an image.
The following example demonstrates how an overlay can be distorted to match the 3D perspective of a DVD cover:
cl_image_tag("disc_box.jpg")
cl_image_tag("disc_box.jpg")
CloudinaryImage("disc_box.jpg").image()
cloudinary.image("disc_box.jpg")
cloudinary.url().imageTag("disc_box.jpg")
$.cloudinary.image("disc_box.jpg")
cloudinary.Api.UrlImgUp.BuildImageTag("disc_box.jpg")
An overlay of the movie_time
image can be distorted to match the 3D perspective of the DVD cover, where each of the 4 corners of the overlay is adjusted to coincide with the 4 corners of the DVD cover:
cl_image_tag("disc_box.jpg", :transformation=>[ {:width=>400, :crop=>:scale}, {:overlay=>"movie_time", :width=>300, :effect=>"distort:55:55:195:20:195:350:55:320"} ])
cl_image_tag("disc_box.jpg", array("transformation"=>array( array("width"=>400, "crop"=>"scale"), array("overlay"=>"movie_time", "width"=>300, "effect"=>"distort:55:55:195:20:195:350:55:320") )))
CloudinaryImage("disc_box.jpg").image(transformation=[ {"width": 400, "crop": "scale"}, {"overlay": "movie_time", "width": 300, "effect": "distort:55:55:195:20:195:350:55:320"} ])
cloudinary.image("disc_box.jpg", {transformation: [ {width: 400, crop: "scale"}, {overlay: "movie_time", width: 300, effect: "distort:55:55:195:20:195:350:55:320"} ]})
cloudinary.url().transformation(new Transformation() .width(400).crop("scale").chain() .overlay("movie_time").width(300).effect("distort:55:55:195:20:195:350:55:320")).imageTag("disc_box.jpg")
$.cloudinary.image("disc_box.jpg", {transformation: [ {width: 400, crop: "scale"}, {overlay: "movie_time", width: 300, effect: "distort:55:55:195:20:195:350:55:320"} ]})
cloudinary.Api.UrlImgUp.Transform(new Transformation() .Width(400).Crop("scale").Chain() .Overlay("movie_time").Width(300).Effect("distort:55:55:195:20:195:350:55:320")).BuildImageTag("disc_box.jpg")
Cloudinary has also added support for another transformation effect
called shear
(e_shear
in URLs). The shear effect skews the image along the x-axis and the y-axis according to a specified value in degrees. The parameter accepts two values separated by a colon (:
), the first representing how much to skew the image on the x-axis and the second representing the amount of skew to apply on the y-axis. Negative values are allowed and skew the image in the opposite direction.
For example, to shear the movie_time
image by 40 degrees on the x-axis:
cl_image_tag("movie_time.jpg", :effect=>"shear:40:0")
cl_image_tag("movie_time.jpg", array("effect"=>"shear:40:0"))
CloudinaryImage("movie_time.jpg").image(effect="shear:40:0")
cloudinary.image("movie_time.jpg", {effect: "shear:40:0"})
cloudinary.url().transformation(new Transformation().effect("shear:40:0")).imageTag("movie_time.jpg")
$.cloudinary.image("movie_time.jpg", {effect: "shear:40:0"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().Effect("shear:40:0")).BuildImageTag("movie_time.jpg")
The shear effect can also be useful for transforming overlay images as in the following example of a yellow sports car overlaid on a white t-shirt.
cl_image_tag("yellow_sports_car.png")
cl_image_tag("yellow_sports_car.png")
CloudinaryImage("yellow_sports_car.png").image()
cloudinary.image("yellow_sports_car.png")
cloudinary.url().imageTag("yellow_sports_car.png")
$.cloudinary.image("yellow_sports_car.png")
cloudinary.Api.UrlImgUp.BuildImageTag("yellow_sports_car.png")
The shear effect is added to the overlay to give the impression of the car accelerating forwards:
cl_image_tag("blank_shirt.jpg", :overlay=>"yellow_sports_car", :width=>400, :x=>20, :y=>120, :gravity=>:north, :effect=>"shear:20:0")
cl_image_tag("blank_shirt.jpg", array("overlay"=>"yellow_sports_car", "width"=>400, "x"=>20, "y"=>120, "gravity"=>"north", "effect"=>"shear:20:0"))
CloudinaryImage("blank_shirt.jpg").image(overlay="yellow_sports_car", width=400, x=20, y=120, gravity="north", effect="shear:20:0")
cloudinary.image("blank_shirt.jpg", {overlay: "yellow_sports_car", width: 400, x: 20, y: 120, gravity: "north", effect: "shear:20:0"})
cloudinary.url().transformation(new Transformation().overlay("yellow_sports_car").width(400).x(20).y(120).gravity("north").effect("shear:20:0")).imageTag("blank_shirt.jpg")
$.cloudinary.image("blank_shirt.jpg", {overlay: "yellow_sports_car", width: 400, x: 20, y: 120, gravity: "north", effect: "shear:20:0"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().Overlay("yellow_sports_car").Width(400).X(20).Y(120).Gravity("north").Effect("shear:20:0")).BuildImageTag("blank_shirt.jpg")
You can easily mix and match the distort effect with other image manipulation capabilities supported by Cloudinary, such as animated GIF generation for example. The following example showcases a Ruby script that creates a very simple animated GIF of spinning text consisting of 20 individual frames. The script calculates how to modify the text string for each frame with the distort
effect parameter in order to give the spinning text a 3D perspective. Each frame is then uploaded to Cloudinary, where each individual image (frame) is constructed from:
Each frame is a therefore a combination of the base image together with an overlay of a slightly modified version of the text string.
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, the images are uploaded, and the animated GIF is created, the final file is ready for delivery:
cl_image_tag("spinning_text.gif", :delay=>100, :type=>"multi")
cl_image_tag("spinning_text.gif", array("delay"=>100, "type"=>"multi"))
CloudinaryImage("spinning_text.gif").image(delay=100, type="multi")
cloudinary.image("spinning_text.gif", {delay: 100, type: "multi"})
cloudinary.url().transformation(new Transformation().delay(100)).type("multi").imageTag("spinning_text.gif")
$.cloudinary.image("spinning_text.gif", {delay: 100, type: "multi"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().Delay(100)).Type(multi).BuildImageTag("spinning_text.gif")
Pretty cool use for the distort effect, right?
You can do some pretty cool things with image distortion, and in this post we showed you how Cloudinary can do this easily in the cloud using simple dynamic manipulation parameters and delivery URLs. Distort and shear are the two new Cloudinary effects that are especially useful for the exact positioning of overlays and giving images a 3D perspective.
These features are available for use with all Cloudinary accounts, including the free tier.
Want to give it a spin…? Add a comment below with your own creation using distort, shear and other Cloudinary manipulation capabilities. We’ll pick the coolest ones and send over a bunch of Cloudinary swag!
This is a guest post by Eric Portis – a proud member of (and newsletter-writer for) the Responsive Issues Community Group. The RICG formulated, championed, and standardized the new HTML features presented in the article.
Five years ago, Ethan Marcotte coined the term “responsive web design” and gave it three defining ingredients: fluid grids, flexible media, and media queries.
That second ingredient, “flexible media” turned out to be a bit of a bugbear.
You see, most of the media on the web is bitmap images. Heck, measured by bytes, most of the web is bitmap images. And bitmap images are fixed. They have a fixed height and a fixed width, and while it’s possible to make them visually grow and shrink using width
and max-width
CSS declarations, forcing users on small, low-resolution displays to load enormous images which have been sized to look good on high-resolution displays is a performance disaster. Every time we send a user more pixels than their device can use, we’re asking them to load data which they’ll end up throwing away. We’re sending them waste.
How much waste? Tim Kadlec has estimated that — for users on low-res mobile devices — 72% of image bytes served up by responsive pages are wasted. Responsive pages are sending small-screened users nearly four times as much data as they need.
New markup is here to save the day. We can’t include a single image resource that’ll work for everybody, but by using new responsive image features, we can stuff multiple resources into a single <img>
and mark them up in such a way that browsers will load the most appropriate resource out of the set. We can mark up image elements which have the ability to adapt.
Let’s dive in with an example: a news-y article page with a single, large-ish image up top:
https://ericportis.com/etc/cloudinary/castro-on-the-line/
How can we make this image responsive? The first tool in our belt is an <img>
attribute named srcset
. srcset
allows us to stuff multiple resources into a single <img>
, via a comma separated list:
srcset="large.jpg 1024w, medium.jpg 512w, small.jpg 256w"
We label each resource with a “width descriptor” — those numbers with 'w'
s after them, above. These let the browser know how wide each resource is. If a resource is 1024 pixels wide, stick a 1024w
after it in srcset
.
Browsers will load the resource that best matches the current context. So, if the image is 512px
wide on the layout, users on standard screens will probably get medium.jpg
, and users on HiDPI display will probably get large.jpg
.
Why “probably?” Ultimately, browsers decide which users get which resources. And we, as authors, can’t necessarily predict which sorts of users will get what. This is a good thing! Browsers know a lot more about their context than we do. They have a wealth of information about each user’s device, connection, and preferences and the pick-as-you-please philosophy of srcset
lets them leverage it.
So, let’s add a srcset
attribute to an <img>
:
<img srcset="small.jpg 256w, medium.jpg 512w, large.jpg 1024w" src="medium.jpg" alt="It’s responsive!" />
Unfortunately, that won’t quite cut it. When browsers encounter our srcset
, they’re missing a crucial piece of information: the <img>
’s layout width. In order to know an image’s display dimensions, browsers have to load and parse all of its page’s CSS – something which usually happens long after srcset
-parsing. To cope, we need to give the browser an estimate of the layout width of the image right in our markup, using a new attribute: sizes
.
sizes
takes CSS lengths. If our <img>
had a fixed width, we could simply stick that width in a sizes
and call it a day. For instance – if it was always 640px
across on the layout, our sizes
would look like this:
sizes="640px"
Our example image is a little more complicated. Below its max width of 30em
, it occupies the full width of the viewport: 100vw
. Above that max-width, it will always be 30em
wide (learn about 'em' and 'vw' CSS units)
So our image has two possible sizes. Luckily, just like srcset
, sizes
lets us supply multiple values in a comma-separated list. And it lets us tell the browser which lengths to use, when, using media queries. So to give the browser a complete picture of our <img>
’s dynamic layout width, we can mark it up with the following:
sizes="(min-width: 30em) 30em, 100vw"
That says, if the viewport is at least 30em
wide, this image will be 30em
wide[1]. Otherwise, the image will be 100vw
wide.
Putting all of the pieces together, we arrive at the following markup:
<img srcset="small.jpg 256w, medium.jpg 512w, large.jpg 1024w" sizes="(max-width: 30em) 30em, 100vw" src="medium.jpg" alt="It’s responsive!" />
This gives us a fluid, adaptable image, which will look crisp on large, hi-res displays and minimize the number of wasted image bytes sent to users on low-res devices[2].
But! Now we, as authors, have a new problem: creating and managing three resources instead of just one.
Rendering small, medium, and large versions for our single-image example is a minor inconvenience. But when multiplied by a whole page’s worth of images, this extra work becomes a real burden. Asking poor, mistake-prone humans to render multiple versions of images at specific sizes very carefully and entirely by hand is both cruel and foolish. We need to automate.
Using Cloudinary, we can upload a single, canonical, high-resolution image to the cloud and deliver it in as many different sizes as we want via URL parameters (or Cloudinary’s various SDKs). Controlling our images’ technical characteristics via text and code has enormous benefits:
Using Cloudinary, adding a new image size (or changing an old one) on a site with thousands of images is as easy as changing a few lines in a template file. And content authors only have to worry about creating and uploading the highest-resolution image that they have; the nitty-gritty details of how that image will be variously compressed, sized, and served to users are handled automatically.
So let’s re-visit our example. And this time let’s not simply assume that we have multiple down-sized versions of our image readily at hand — let’s generate those resources with Cloudinary.
After creating a free Cloudinary account I uploaded the full-res (1.2MB!) version of the example image to my Media Library and gave it an easy-to-remember name: on_the_phone
. Once uploaded, I can access it using the following URL.
cl_image_tag("on_the_phone.jpg")
cl_image_tag("on_the_phone.jpg")
CloudinaryImage("on_the_phone.jpg").image()
cloudinary.image("on_the_phone.jpg")
cloudinary.url().imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg")
cloudinary.Api.UrlImgUp.BuildImageTag("on_the_phone.jpg")
Let’s break this URL down:
res.cloudinary.com/
: the base Cloudinary URLeeeps
: my Cloudinary cloud nameimage/upload/
: the path to images uploaded to my media libraryon-the-phone
the name of the image we’d like to access.jpg
: the desired format. We could just as easily request a .png
or .gif
here; even though the original, uploaded image is a JPEG, Cloudinary will do the conversion on the fly.All of the resizing action is going to happen between the upload/
and the image name. This is where image transformations go — the bits that are going to let us control exactly how our image is compressed, shaped, and sized for delivery[3].
The first thing that we’re going to want to do to this image is scale it down. To do that, we’ll use Cloudinary’s width parameter. Stick a w_
and a number into our URL, and Cloudinary will give us our image scaled to that many pixels across.
cl_image_tag("on_the_phone.jpg", :width=>512)
cl_image_tag("on_the_phone.jpg", array("width"=>512))
CloudinaryImage("on_the_phone.jpg").image(width=512)
cloudinary.image("on_the_phone.jpg", {width: 512})
cloudinary.url().transformation(new Transformation().width(512)).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {width: 512})
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(512)).BuildImageTag("on_the_phone.jpg")
This scaled-down, 512-pixel-wide image weighs in at 86KB. How could we slim it down even further?
Cloudinary’s default compression settings are fairly conservative, resulting in heavy images of high quality. We can choose our own tradeoff – sacrificing a little quality for lighter images – using the quality parameter. The quality parameter takes a number between 0 and 100; after a little trial and error, I settled on 70.
cl_image_tag("on_the_phone.jpg", :quality=>70, :width=>512)
cl_image_tag("on_the_phone.jpg", array("quality"=>70, "width"=>512))
CloudinaryImage("on_the_phone.jpg").image(quality=70, width=512)
cloudinary.image("on_the_phone.jpg", {quality: 70, width: 512})
cloudinary.url().transformation(new Transformation().quality(70).width(512)).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {quality: 70, width: 512})
cloudinary.Api.UrlImgUp.Transform(new Transformation().Quality(70).Width(512)).BuildImageTag("on_the_phone.jpg")
This results in an image that’s 46KB — half the size of the unq_
’d version! What else could we do to cut weight? What if tried a different format entirely, like Google’s relatively-new WebP format?
cl_image_tag("on_the_phone.webp", :quality=>70, :width=>512)
cl_image_tag("on_the_phone.webp", array("quality"=>70, "width"=>512))
CloudinaryImage("on_the_phone.webp").image(quality=70, width=512)
cloudinary.image("on_the_phone.webp", {quality: 70, width: 512})
cloudinary.url().transformation(new Transformation().quality(70).width(512)).imageTag("on_the_phone.webp")
$.cloudinary.image("on_the_phone.webp", {quality: 70, width: 512})
cloudinary.Api.UrlImgUp.Transform(new Transformation().Quality(70).Width(512)).BuildImageTag("on_the_phone.webp")
That cuts the size of the image by more than a third again, bringing it down to 36KB. But Here's what that WebP looks like in anything but Chrome:
That clearly won’t do. How can we send new, advanced image formats to browsers that support them, without showing broken image icons to browsers that don’t?
There’s a whole new markup pattern that’s been spec’d specifically to address this problem, but using Cloudinary, we don’t need it. Just stick an f_auto
parameter into an image URL, and Cloudinary will perform some server-side content-negotiation magic to ensure that every browser gets the latest and greatest format that it supports:
cl_image_tag("on_the_phone.jpg", :quality=>70, :width=>512, :fetch_format=>:auto)
cl_image_tag("on_the_phone.jpg", array("quality"=>70, "width"=>512, "fetch_format"=>"auto"))
CloudinaryImage("on_the_phone.jpg").image(quality=70, width=512, fetch_format="auto")
cloudinary.image("on_the_phone.jpg", {quality: 70, width: 512, fetch_format: "auto"})
cloudinary.url().transformation(new Transformation().quality(70).width(512).fetchFormat("auto")).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {quality: 70, width: 512, fetch_format: "auto"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().Quality(70).Width(512).FetchFormat("auto")).BuildImageTag("on_the_phone.jpg")
When accessing this URL, Chrome will get a WebP, Edge and IE 9+ will get a JPEG-XR, and everybody else will get a JPEG.
Okay – now that we know how to use Cloudinary’s image transformations to generate resized (and optimized) versions of our original, let’s stick some Cloudinary URLs into our responsive image markup:
<img sizes="(min-width: 30em) 28em, 100vw" srcset="http://res.cloudinary.com/eeeps/image/upload/f_auto,q_70,w_256/on_the_phone.jpg 256w, http://res.cloudinary.com/eeeps/image/upload/f_auto,q_70,w_512/on_the_phone.jpg 512w, http://res.cloudinary.com/eeeps/image/upload/f_auto,q_70,w_768/on_the_phone.jpg 768w, http://res.cloudinary.com/eeeps/image/upload/f_auto,q_70,w_1024/on_the_phone.jpg 1024w, http://res.cloudinary.com/eeeps/image/upload/f_auto,q_70,w_1280/on_the_phone.jpg 1280w" src="http://res.cloudinary.com/eeeps/image/upload/f_auto,q_70,w_512/on_the_phone.jpg" alt />
That’s the fully-responsive <img>
as it appears on our example page.
This <img>
will adapt right along with our responsive layout, serving a well-optimized version of our resource to users with differing viewport sizes, device resolutions, and browser-format-support capabilities.
Even better, with this markup, we bring the number of image resources that we have to worry about creating and managing back down to one: the high-res original we upload to Cloudinary. And if we ever want to tweak how we’re actually delivering that image to users, it’s as simple as changing a few characters in our HTML.
Responsive images are here, and cloud-based image hosting makes them easy.
Thus concludes part one of a two-part series. Next time, I’ll show you how to pair <picture>
with Cloudinary’s cropping transforms to add another dimension of adaptability to our images: art direction. Stay tuned!
[1]: Particularly sharp readers may notice a slight problem with this: em
are relative, and 30em
in one context might not equal 30em
in another. Within the context of sizes
, an em
is always equal to the default font size (usually 16px
). But within the context of a CSS declaration, the size of an em
is dependent on the font-size
of the selected element. And indeed, our page’s viewport-sized typography means that the two 30em
s will almost never be precisely the same. But you know what? They’re close enough! A precise sizes
value would make our markup harder to read, understand, and maintain – and would only effect which source was chosen in a tiny number of cases. In horseshoes, hand grenades, and sizes
: close counts.
[2]: How do browsers actually use srcset
and sizes
to pick a source? They divide the resource widths (the w
s), by the current layout width (figured out via sizes
) to calculate each resource’s “image density”. So, let’s say our sizes
evaluates to a layout width of 512px
, and we’re working with a srcset="small.jpg 256w, medium.jpg 512w, large.jpg 1024w"
. The image densities would work out to:
small.jpg
: 256w
resource width ÷ 512px
layout width = 0.5x image density
medium.jpg
: 512w
resource width ÷ 512px
layout width = 1x image density
large.jpg
: 1024w
resource width ÷ 512px
layout width = 2x image density
Browsers make their decision based on these image densities. So users on 1x devices will probably get medium.jpg
, users on Hi-DPI displays will most likely load large.jpg
, and perhaps users on slow connections, or who’ve set some sort of preference stating that they suffer from poor eyesight will get small.jpg
. But again, there’s no way for us, as authors, to know for certain which sorts of users will get what. In the browser we trust.
[3]: Cloudinary’s SDK frameworks give you language-specific tools to build these URLs indirectly. Check out the tabs in the examples to see how achieve each transformation using your framework of choice.
As the end of 2015 approaches, we wanted to share a quick summary of Cloudinary’s accomplishment this year and some of our plans for next year. We couldn't possibly do this without including an image manipulation example! That's our hat trick in the title :-)
2015 was a great year for Cloudinary. We more than doubled our numbers, including our team size, offices, customers and revenues. During 2015 we’ve hit another significant milestone, crossing the eight-figures in annual run rate.
In 2015 we also released exciting new features and capabilities that our users have asked us for. Here are a few highlights:
Video management solution - All cloud-based capabilities we had for images are now available for videos as well.
Responsive images - an important challenge for web developers. While we work on multiple new solutions, this year we introduced a Javascript based solution for responsive images.
New Image manipulation capabilities - face-detection based cropping with zoom level control, dynamic image distortion, automatic red eye removal, lossy animated GIF optimization, added support for FLIF - the new lossless image format, image overlay dynamic transformation, image similarity detection using pHash, and many more.
New image processing add-ons - Imagga's automatic image tagging, professional photo background removal and advanced facial attribute detection (more details coming soon).
This year, new capabilities were introduced in guest posts - introduction of Cloudinary's official Magento plugin by Grant Kemp, and responsive images with 'srcset' by Eric Portis.
Many more - interactive cropping and semantic image information in the Media Library UI, a pure Javascript library jQuery-free with new APIs, SEO friendly dynamic delivery URLs, and more.
This year for the first time, we shared some behind-the-scenes technical details and numbers. This post explains Cloudinary's bootstrapped way of organically building a profitable SaaS service. During 2015 we were also honored to add BVP (Bessemer Venture Partners) as a strategic investor. Cloudinary is BVP's 100th investment in cloud companies.
Using Cloudinary you can add overlays on top of underlying images using image manipulation URLs. You can further control the look & feel of the overlays by applying multiple image transformations on them. Furthermore, you can use face detection and even eye detection for placing the overlays exactly above faces or other facial attributes (using the Advanced Facial Attributes Detection add-on). That covers everything needed to dynamically add hats to all the photos on your website or mobile application!
For example, let's take the following images that were uploaded to Cloudinary (thanks to our beloved Orly B.).
The dynamic image manipulation URL below adds the Santa hat exactly above the auto detected eyes while rotating the hat to perfectly match Orly’s face. The hat is dynamically resized and padded in order to fit well as an actual hat.
cl_image_tag("profile_orly_bogler.jpg", :transformation=>[ {:overlay=>"santa_hat", :effect=>"trim"}, {:width=>1.0, :height=>2.45, :crop=>:lpad, :gravity=>:north_east}, {:width=>2.8, :gravity=>:adv_eyes, :flags=>[:region_relative, :layer_apply]} ])
cl_image_tag("profile_orly_bogler.jpg", array("transformation"=>array( array("overlay"=>"santa_hat", "effect"=>"trim"), array("width"=>1.0, "height"=>2.45, "crop"=>"lpad", "gravity"=>"north_east"), array("width"=>2.8, "gravity"=>"adv_eyes", "flags"=>array("region_relative", "layer_apply")) )))
CloudinaryImage("profile_orly_bogler.jpg").image(transformation=[ {"overlay": "santa_hat", "effect": "trim"}, {"width": 1.0, "height": 2.45, "crop": "lpad", "gravity": "north_east"}, {"width": 2.8, "gravity": "adv_eyes", "flags": ["region_relative", "layer_apply"]} ])
cloudinary.image("profile_orly_bogler.jpg", {transformation: [ {overlay: "santa_hat", effect: "trim"}, {width: 1.0, height: 2.45, crop: "lpad", gravity: "north_east"}, {width: 2.8, gravity: "adv_eyes", flags: ["region_relative", "layer_apply"]} ]})
cloudinary.url().transformation(new Transformation() .overlay("santa_hat").effect("trim").chain() .width(1.0).height(2.45).crop("lpad").gravity("north_east").chain() .width(2.8).gravity("adv_eyes").flags("region_relative", "layer_apply")).imageTag("profile_orly_bogler.jpg")
$.cloudinary.image("profile_orly_bogler.jpg", {transformation: [ {overlay: "santa_hat", effect: "trim"}, {width: 1.0, height: 2.45, crop: "lpad", gravity: "north_east"}, {width: 2.8, gravity: "adv_eyes", flags: ["region_relative", "layer_apply"]} ]})
cloudinary.Api.UrlImgUp.Transform(new Transformation() .Overlay("santa_hat").Effect("trim").Chain() .Width(1.0).Height(2.45).Crop("lpad").Gravity("north_east").Chain() .Width(2.8).Gravity("adv_eyes").Flags("region_relative", "layer_apply")).BuildImageTag("profile_orly_bogler.jpg")
An even more powerful capability is to add the same hat overlay to all the faces automatically detected in a photo. You may notice the different dimensions and rotation angles of each hat overlay that are automatically determined according to the detected position of the eyes. The example below uses similar image transformation instructions to add Santa hats to a group of Cloudinary's team taken at the AWS re:Invent conference.
cl_image_tag("cloudinary_team.jpg", :transformation=>[ {:overlay=>"santa_hat", :effect=>"trim"}, {:width=>1.0, :height=>2.29, :crop=>:lpad, :gravity=>:north_east}, {:width=>2.6, :gravity=>:adv_eyes, :flags=>[:region_relative, :layer_apply]} ])
cl_image_tag("cloudinary_team.jpg", array("transformation"=>array( array("overlay"=>"santa_hat", "effect"=>"trim"), array("width"=>1.0, "height"=>2.29, "crop"=>"lpad", "gravity"=>"north_east"), array("width"=>2.6, "gravity"=>"adv_eyes", "flags"=>array("region_relative", "layer_apply")) )))
CloudinaryImage("cloudinary_team.jpg").image(transformation=[ {"overlay": "santa_hat", "effect": "trim"}, {"width": 1.0, "height": 2.29, "crop": "lpad", "gravity": "north_east"}, {"width": 2.6, "gravity": "adv_eyes", "flags": ["region_relative", "layer_apply"]} ])
cloudinary.image("cloudinary_team.jpg", {transformation: [ {overlay: "santa_hat", effect: "trim"}, {width: 1.0, height: 2.29, crop: "lpad", gravity: "north_east"}, {width: 2.6, gravity: "adv_eyes", flags: ["region_relative", "layer_apply"]} ]})
cloudinary.url().transformation(new Transformation() .overlay("santa_hat").effect("trim").chain() .width(1.0).height(2.29).crop("lpad").gravity("north_east").chain() .width(2.6).gravity("adv_eyes").flags("region_relative", "layer_apply")).imageTag("cloudinary_team.jpg")
$.cloudinary.image("cloudinary_team.jpg", {transformation: [ {overlay: "santa_hat", effect: "trim"}, {width: 1.0, height: 2.29, crop: "lpad", gravity: "north_east"}, {width: 2.6, gravity: "adv_eyes", flags: ["region_relative", "layer_apply"]} ]})
cloudinary.Api.UrlImgUp.Transform(new Transformation() .Overlay("santa_hat").Effect("trim").Chain() .Width(1.0).Height(2.29).Crop("lpad").Gravity("north_east").Chain() .Width(2.6).Gravity("adv_eyes").Flags("region_relative", "layer_apply")).BuildImageTag("cloudinary_team.jpg")
As Christmas was last week, I guess a different hat is needed. The example below uses the same technique, specifying the ID of a party_hat
instead of a santa_hat
. This should better fit the upcoming New Year celebration.
cl_image_tag("cloudinary_team.jpg", :transformation=>[ {:overlay=>"party_hat", :effect=>"trim"}, {:width=>1.4, :height=>1.0, :crop=>:scale}, {:width=>1.0, :height=>2.29, :crop=>:lpad, :gravity=>:north_east}, {:width=>3.0, :gravity=>:adv_eyes, :flags=>[:region_relative, :layer_apply]} ])
cl_image_tag("cloudinary_team.jpg", array("transformation"=>array( array("overlay"=>"party_hat", "effect"=>"trim"), array("width"=>1.4, "height"=>1.0, "crop"=>"scale"), array("width"=>1.0, "height"=>2.29, "crop"=>"lpad", "gravity"=>"north_east"), array("width"=>3.0, "gravity"=>"adv_eyes", "flags"=>array("region_relative", "layer_apply")) )))
CloudinaryImage("cloudinary_team.jpg").image(transformation=[ {"overlay": "party_hat", "effect": "trim"}, {"width": 1.4, "height": 1.0, "crop": "scale"}, {"width": 1.0, "height": 2.29, "crop": "lpad", "gravity": "north_east"}, {"width": 3.0, "gravity": "adv_eyes", "flags": ["region_relative", "layer_apply"]} ])
cloudinary.image("cloudinary_team.jpg", {transformation: [ {overlay: "party_hat", effect: "trim"}, {width: 1.4, height: 1.0, crop: "scale"}, {width: 1.0, height: 2.29, crop: "lpad", gravity: "north_east"}, {width: 3.0, gravity: "adv_eyes", flags: ["region_relative", "layer_apply"]} ]})
cloudinary.url().transformation(new Transformation() .overlay("party_hat").effect("trim").chain() .width(1.4).height(1.0).crop("scale").chain() .width(1.0).height(2.29).crop("lpad").gravity("north_east").chain() .width(3.0).gravity("adv_eyes").flags("region_relative", "layer_apply")).imageTag("cloudinary_team.jpg")
$.cloudinary.image("cloudinary_team.jpg", {transformation: [ {overlay: "party_hat", effect: "trim"}, {width: 1.4, height: 1.0, crop: "scale"}, {width: 1.0, height: 2.29, crop: "lpad", gravity: "north_east"}, {width: 3.0, gravity: "adv_eyes", flags: ["region_relative", "layer_apply"]} ]})
cloudinary.Api.UrlImgUp.Transform(new Transformation() .Overlay("party_hat").Effect("trim").Chain() .Width(1.4).Height(1.0).Crop("scale").Chain() .Width(1.0).Height(2.29).Crop("lpad").Gravity("north_east").Chain() .Width(3.0).Gravity("adv_eyes").Flags("region_relative", "layer_apply")).BuildImageTag("cloudinary_team.jpg")
Wouldn’t this be cool to try out on all of your websites’ photos for a single day? :-)
We’re expecting to double our team size again in 2016, so we’ll be able to both tackle all the exciting new features and products on our road-map and continue to offer our customers the best service possible.
In 2016, Cloudinary will continue to help solve the Responsive Images challenges. We’ll take our video management solution to the next level, further enhance Cloudinary's DAM solution and media search capabilities, enhance our online Media Library and strengthen integrations with existing and new development frameworks. We’ll also be adding plenty more image and video manipulation & optimization capabilities.
Ever since we launched Cloudinary over 3.5 years ago, our customers’ needs were always our #1 priority. Keeping this in mind, the majority of the features we’ve added in 2015 were in a response to customer requests, so please keep the suggestions coming!
Happy New Year!
This is a guest post by Kasia Kramnik, Content Marketing Manager at Netguru, a full stack development house and one of Cloudinary's Consulting partners.
Take a look at your website. Are you happy with the way it looks? I bet you are, and that’s really awesome. Keep in mind though, there is one thing you can’t actually see, but you need to experience: the load speed. Sometimes the most important element is invisible to the eye. In this article you’ll find tips on perfecting the invisible as well: loading your site and media with visibly better results.
The reason is simple and it should be crucial to you: your users. They get impatient, they have a low attention span, and they won’t hesitate to leave your website if they have to wait. You may grumble about how hasty your potential customers are, but that’s exactly the challenge: dealing with their tendency to abandon anything that requires patience. One of the online surveys conducted on 2,500 online consumers in the US and UK found that 67% of UK shoppers and 51% in the US admit that a site's slowness is the top reason they’d abandon a cart in an online store.
It’s also worth noting that Google has incorporated site load speed into a list of factors influencing the search ranking position. It seems like there are enough reasons to take a closer look at loading times, but what if the results are far from excellent? How can you speed your page up? Here are a few tips regarding media upload, management, and storage.
The process of load speed optimization starts before the images actually land on your page. When you’re working on your images in any sort of graphics tool, make sure you save them in a format that’s compatible with web publication. This limits the amount of metadata carried on an image and makes it easier to edit the image quality.
What formats are applicable for the web? There’s a variety of options:
Format comparison example:
196 KB in PNG format:
http://res.cloudinary.com/demo/image/upload/w_400/turtles.png
25.2 KB in JPEG format with 80% quality:
http://res.cloudinary.com/demo/image/upload/w_400,q_80/turtles.jpg
20.1 KB in WebP format with 80% quality:
http://res.cloudinary.com/demo/image/upload/w_400,q_80/turtles.webp
Are you delivering your site via HTTPS or considering it? You're not alone - in 2015 the number of sites running on HTTPS almost doubled. Both consumers and web developers are now much more aware of the value of the humble green lock displayed in the address bar. The benefits of using HTTPS extend beyond the customer’s safety, to SEO boosts, and advanced functionalities that are only available when delivering via HTTPS, such as HTTP/2 and WebRTC.
One of the challenges of running a site over HTTPS is that every resource you link to in the site, including images and video, has to be delivered over HTTPS as well. Fortunately, Cloudinary has your back here with a range of options for HTTPS delivery, starting with shared domains, and up to custom SSL certificates for running HTTPS on your own domain. If you already know everything about HTTPS - skip ahead, to HTTPS and Cloudinary.
Secure HTTP (HTTPS), invented in the 90's by Netscape, combines HTTP, the communications protocol, and TLS, an encryption protocol, to provide secure and encrypted communications over the web, and has been a standard for e-commerce and banking sites for over 20 years.
Since 2015 we've seen a big push towards HTTPS in sites that normally wouldn't be considering it due to expense, complexity and performance. Google are now encouraging sites towards HTTPS by announcing that an HTTPS-enabled site will have a better PageRank. Many commercial CDNs and free initiatives such as Let's Encrypt are pushing free SSL programs that make setting up an HTTPS server much easier. Additionally, HTTP/2, the new HTTP standard which offers performance enhancements when a page has many resources, practically requires delivery over HTTPS.
According to HTTP Archive, the percentage of HTTPS sites increased last year by over 70%, from 14% to 24% of all sites:
HTTPS provides two main benefits to internet communications:
Channel encryption prevents a 3rd party from eavesdropping on the communications, or at least makes it very hard and impractical to do at scale.
Server identification prevents someone from impersonating a trusted server, and tricking the client into sending him sensitive information, or presenting the client with information that will mislead him (imagine getting the wrong stock quotes from a site impersonating bloomberg.com). The way server identification is performed, the server presents a certificate issued by a known Certificate Authority, signifying that the owner of the server is indeed the owner of the domain accessed. We'll revisit Server Certificates later in this article.
Browsers notify clients of a secure connection by displaying a green lock in the address bar. If all assets loaded by the site don't use HTTPS, the page is not considered secure. Depending on the browser, it might block the non-secure resources from loading, the address bar lock might appear gray or with a warning sign, or a pop-up warning dialog might appear. So, if you use a CDN or a service such as Cloudinary in your HTTPS site, the resources need to be delivered by HTTPS. The Cloudinary client library takes care of this for you automatically, once configured correctly.
Opening an HTTPS connection requires more time than opening an HTTP connection, as several round trips are needed to authenticate the server certificate and perform the encryption handshake. This issue is amplified on high-latency connections (3G, distant clients, and more), where the multiple round trips can add several seconds to the connection handshake. Some of these latencies can be addressed by using a CDN to deliver images, and by using HTTP/2 to consolidate connections - watch this blog for more info about HTTP/2 in the coming weeks. Some CDN's HTTPS implementations don't perform as well as HTTP, as they have fewer HTTPS-enabled nodes than HTTP-enabled nodes.
There are 4 main methods to deliver your Cloudinary resources: 3 over HTTPS, and one mode in which HTTPS is completely disabled, in order to leverage a wider range of CDN nodes. The security modes are demonstrated below with images of kittens, because we can.
Cloudinary supports image delivery over HTTPS using the standard res.cloudinary.com
address. This is supported on the free tier as well as higher tiers, and works by default when using our Client libraries, which detect the connection type and generate URLs with the https prefix. The connection uses Cloudinary's server certificate which is already stored on our CDN layer.
Example image:
cl_image_tag("meowing_kitten.jpg", :width=>400)
cl_image_tag("meowing_kitten.jpg", array("width"=>400))
CloudinaryImage("meowing_kitten.jpg").image(width=400)
cloudinary.image("meowing_kitten.jpg", {width: 400})
cloudinary.url().transformation(new Transformation().width(400)).imageTag("meowing_kitten.jpg")
$.cloudinary.image("meowing_kitten.jpg", {width: 400})
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(400)).BuildImageTag("meowing_kitten.jpg")
If you are a Cloudinary customer on the Advanced plan or higher, you can enable the "private CDN" feature which allows you to use <cloudname>-res.cloudinary.com
as a hostname to deliver your images. A custom hostname allows you to use specialized CDN-based features such as SEO suffixes, and is required if you choose to put your own CDN in front of Cloudinary, or to use your own domain for HTTP traffic. To use your own domain for HTTPS traffic, see the next section.
Example image:
cl_image_tag("fat_cat.jpg", :width=>500, :use_root_path=>true)
cl_image_tag("fat_cat.jpg", array("width"=>500, "use_root_path"=>true))
CloudinaryImage("fat_cat.jpg").image(width=500, use_root_path=True)
cloudinary.image("fat_cat.jpg", {width: 500, use_root_path: true})
cloudinary.url().transformation(new Transformation().width(500)).useRootPath(true).imageTag("fat_cat.jpg")
$.cloudinary.image("fat_cat.jpg", {width: 500, use_root_path: true})
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(500)).UseRootPath(true).BuildImageTag("fat_cat.jpg")
Customers often want to use their own domain to serve images for the following reasons:
In order for Cloudinary to be able to identify itself securely using the customer's domain, a Server Certificate needs to be generated and signed by a 3rd party. Hosting a server certificate on the CDN is expensive, so Cloudinary now provides a cost-effective solution to serving HTTPS customer domains - SAN Certificates. When you enable SAN, we add your hostname to an existing certificate, already installed on the CDN, which is shared between you, Cloudinary and other customers. This has a significantly lower cost than having your own dedicated server certificate installed on the CDN.
Example image:
https://images.yourdomain.com/my_cat.jpg
(imagine you have a nice kitten image, securely delivered from your own domain).
Cloudinary Customers can serve images on their own domain, or on Cloudinary's domain, without using ssl-enabled CDN nodes at all. This mode is applicable to sites that do not require encryption, and whose audiences are in remote locations with less HTTPS CDN coverage. Customers in Iceland, for example, are often directed to Amsterdam CDN nodes for HTTPS traffic, as the HTTPS-enabled edge in Iceland is overloaded or doesn't exist. Cloudinary customers on the Advanced plan or higher can use their own domain with this setup, and all Cloudinary customers can use the sub-domain cdn.cloudinary.com
instead of the sub-domain res.cloudinary.com
, to utilize the non-HTTPS network.
Example image:
Compare the image loading time of this image with the same image delivered via HTTPS-enabled res.cloudinary.com to see if there's a difference in your area - your results may vary.
When testing from a remote location, the differences can be significant:
remote-server:~ ran$ time curl -s \
http://cdn.cloudinary.com/demo/w_400/hungry_cat.jpg > /dev/null
real 0m0.030s
...
remote-server:~ ran$ time curl -s \
http://res.cloudinary.com/demo/w_400/hungry_cat.jpg > /dev/null
real 0m0.258s
...
HTTPS is in most cases the safest, most secure way to deliver your website, both for you and for your customers, and it should not be complex or expensive. With the options listed above, you can find the method most fitting for you in order to best secure your site's content. To enable SSL modes beyond default HTTPS for your account, please contact us.
The number of different devices available and their potential screen resolutions keep increasing, and to support this wide range of resolutions and devices, responsive website design is now the standard. A website's markup must adapt itself to look perfect on all the different devices and in various resolutions, pixel densities and mobile device orientations. Managing, manipulating and delivering images, is one of the main challenges of responsive design that web developers face.
Implementing a responsive design means building your website where the same images may be displayed in various dimensions. One image for all screen resolutions and different devices is not enough. An image per pixel is too much - so how can you automatically choose the optimal responsive image sizes?
Whether you are using a Javascript based responsive library, the srcset
image attribute, the <picture>
HTML5 element, the modern Client-Hints or other responsive image solutions, they still lack a response to the common need of deciding which image resolutions to select and how many different image versions to include in your responsive website. These are called responsive breakpoints or responsive image breakpoints.
Cloudinary now offers a solution for intelligently finding the optimal responsive image dimensions for each specific image. The responsive breakpoints generation can be done programmatically using a cloud-based API or interactively using a new free open source web tool - the Responsive Breakpoints Generator.
It's a challenge to find the best breakpoints for your images, and to avoid making the mistake of not selecting enough images or selecting too many images, you’ll need to understand the tradeoff between the number of different images, the visual quality and the bandwidth involved.
When a small dimensional reduction significantly reduces the file size of the image, you should definitely create another scaled down image version. On the other hand, if scaling down images by a certain amount doesn't significantly save enough bandwidth, you can deliver bigger images to your users and let the browser handle the resizing.
The challenge of scaling down images is further complicated by the fact that the file size reduction varies for different images. It depends not only on the specific content of the images, but also on the variable sensitivity the image has to the compression algorithms of JPEG, PNG, WebP and other image formats. For some images, a small scale down saves significant file size, while for other images even a more prominent scale down will not significantly affect the file size.
Therefore, you will want to define the file size step where it is worth creating another scaled down image version. Jason Grigsby of Cloud Four called this file size step performance budget in his article about image breakpoints. Cloudinary's analysis verified that different images require a different number of versions in order to balance the bandwidth reduction trade-off according to your performance budget.
Consider the following 2400x1600 JPEG image:
Assume you need to display this image in your responsive website in various width dimensions between 200 and 1000 pixels, and you define the minimum file size step (performance budget) to be about 20KB. As the table below shows, you only need to create and deliver five different versions of this image to fit all the different devices and browsers.
No. | Width | Height | File size | Image |
---|---|---|---|---|
1 | 200 | 133 | 6.9 KB | View image |
2 | 477 | 318 | 27.2 KB | View image |
3 | 681 | 454 | 48.0 KB | View image |
4 | 847 | 565 | 67.6 KB | View image |
5 | 1000 | 667 | 86.9 KB | View image |
Now let's take another JPEG photo:
Trying to find the best breakpoints for this image using the same settings of 200 to 1000 pixels wide and a minimum file size step of about 20KB, results in this image needing nine different versions as the table below shows.
No. | Width | Height | File size | Image |
---|---|---|---|---|
1 | 200 | 133 | 8.7 KB | View image |
2 | 380 | 253 | 27.8 KB | View image |
3 | 514 | 343 | 48.5 KB | View image |
4 | 619 | 413 | 68.3 KB | View image |
5 | 711 | 474 | 87.7 KB | View image |
6 | 804 | 536 | 108.5 KB | View image |
7 | 883 | 589 | 129.3 KB | View image |
8 | 957 | 638 | 148.2 KB | View image |
9 | 1000 | 667 | 160.7 KB | View image |
As shown above, the number of versions required for one image is almost half of the number required for another one. The difference might be even more dramatic for other types of photos. If you multiply this X2 difference by millions of user uploaded images, the result is a huge saving in storage, image processing costs and image management complexity, while still delivering the best looking images and preserving the user experience.
In order to perfectly balance the number of image versions for your responsive website, you need to find the correct breakpoints according to the file size step that you define. How can you do that? You can generate images for all possible width values and only select the ones that reflect a significant enough file size reduction. However this is inefficient and can be expensive.
Analyzing the behavior of the compression mechanisms for various image formats (mainly JPEG, PNG and WebP) resulted in the creation of algorithms to efficiently and intelligently find image breakpoints that match the dimensions and file size saving requirements.
Based on these algorithms we have created a new free public web tool called the Responsive Image Breakpoints Generator.
The Responsive Breakpoints Generator enables you to interactively upload your images and define settings to find the matching image dimensions that fit in your graphic design requirements. As you can see in the screenshot below, you can define the required image width range, the file size step in kilobytes, and a cutoff for the maximum number of images you allow. In addition, you can request that the results include double resolution images for DPR 2.0 displays (e.g., Retina Display).
When you upload an image, the breakpoints are generated according to your settings and are calculated automatically in the cloud. The generated breakpoints are then displayed in a summary table and visually illustrated on your uploaded image. You can also download a zip file containing all the scaled down and optimized images that match the generated breakpoints.
The generator tool also creates an HTML5 image tag that you can copy-paste into your code. The srcset
attribute of the img
tag is set to list the image versions and width values according to the intelligently selected breakpoints. Modern browsers that process the img
tag will then know how to select the correct image version according to the available space of the image in your responsive web layout.
<img sizes="(max-width: 1000px) 100vw, 1000px" srcset="dog_c_scale,w_200.jpg 200w, dog_c_scale,w_508.jpg 508w, dog_c_scale,w_721.jpg 721w, dog_c_scale,w_901.jpg 901w, dog_c_scale,w_1000.jpg 1000w" src="dog_c_scale,w_1000.jpg" alt="A nice dog">
In addition, responsive layouts also involve art direction. The original images may need to be cropped to match a different aspect ratio required by the graphic design, for a mobile device for example. The breakpoints generator tool enables you to select multiple aspect ratios and breakpoints will be generated for each aspect ratio separately, while the original image is cropped to match the required aspect ratio. The downloadable zip file will also contain all the images of all the aspect ratios.
In addition, the generator tool shows an HTML5 'picture' element code sample that combines the different aspect ratios and their breakpoints into a single responsive HTML solution. Below is a sample 'picture' tag which modern browsers, such as Chrome and Firefox already support, while Microsoft's Edge and Apple's Safari have just recently added support to their new official or beta versions. If you want to support older browsers as well, you can use the Picturefill polyfill Javascript library.
<picture> <source media="(max-width: 480px)" sizes="(max-width: 1000px) 100vw, 1000px" srcset="dog_ar_3_4,c_fill__c_scale,w_200.jpg 200w, dog_ar_3_4,c_fill__c_scale,w_386.jpg 386w, dog_ar_3_4,c_fill__c_scale,w_522.jpg 522w, dog_ar_3_4,c_fill__c_scale,w_632.jpg 632w, dog_ar_3_4,c_fill__c_scale,w_739.jpg 739w, dog_ar_3_4,c_fill__c_scale,w_834.jpg 834w, dog_ar_3_4,c_fill__c_scale,w_920.jpg 920w, dog_ar_3_4,c_fill__c_scale,w_1000.jpg 1000w"> <source media="(max-width: 768px)" sizes="(max-width: 1000px) 100vw, 1000px" srcset="dog_ar_16_9,c_fill__c_scale,w_200.jpg 200w, dog_ar_16_9,c_fill__c_scale,w_525.jpg 525w, dog_ar_16_9,c_fill__c_scale,w_746.jpg 746w, dog_ar_16_9,c_fill__c_scale,w_934.jpg 934w, dog_ar_16_9,c_fill__c_scale,w_1000.jpg 1000w"> <img sizes="(max-width: 1000px) 100vw, 1000px" srcset="dog_c_scale,w_200.jpg 200w, dog_c_scale,w_508.jpg 508w, dog_c_scale,w_721.jpg 721w, dog_c_scale,w_901.jpg 901w, dog_c_scale,w_1000.jpg 1000w" src="dog_c_scale,w_1000.jpg" alt="A nice dog"> </picture>
The Responsive Breakpoints Generator is a free web tool. It is open source under the MIT license and is hosted on GitHub, while the actual breakpoints generation algorithms and the image resizing and cropping transformations run in the cloud.
The breakpoints generator web tool introduced above allows you to interactively process your images, which is useful if you have a reasonable amount of statically uploaded images. However, what if your web application includes user-generated content from dynamically uploaded images?
In order to generate breakpoints for user uploaded images, you need to programmatically generate them from your code. For each uploaded image, you need to call an API method to generate the breakpoints, store or cache them on your side, and then build your HTML5 or CSS responsive web code according to these breakpoints.
Cloudinary's API allows you to programmatically request the breakpoints for newly uploaded images or for existing ones. You can specify settings such as the width range, the file size step, and the maximum number of images, and request one or more image transformations to apply on the original image. Such transformations can include aspect-ratio based cropping, face detection based cropping and applying various image effects, filters and optimizations.
You can call the cloud-based API from your development framework code using our open-source SDKs for Ruby on Rails, Node.js, PHP, Python, Java, .Net and other frameworks. For example, the following code generates the breakpoints and the matching images for an uploaded image:
Cloudinary::Uploader.upload("dog.jpg", :responsive_breakpoints => {:create_derived => false, :bytes_step => 20000, :min_width => 200, :max_width => 1000, :max_images => 20}, :public_id => "dog")
\Cloudinary\Uploader::upload("dog.jpg", array( "responsive_breakpoints" => array( array("create_derived" => "false", "bytes_step" => 20000, "min_width" => 200, "max_width" => 1000, "max_images" => 20)), "public_id" => "dog"));
cloudinary.uploader.upload("dog.jpg", responsive_breakpoints = [ {"create_derived": "false", "bytes_step": 20000, "min_width": 200, "max_width": 1000, "max_images": 20}], public_id = "dog")
cloudinary.uploader.v2.upload("dog.jpg", { responsive_breakpoints: [{ create_derived: false, bytes_step: 20000, min_width: 200, max_width: 1000, max_images: 20}], public_id: "dog"}, function(error, result) {console.log(result); });
cloudinary.uploader().upload("dog.jpg", ObjectUtils.asMap( "responsive_breakpoints", new ResponsiveBreakpoint().createDerived("false").bytesStep(20000).minWidth(200).maxWidth(1000).maxImages(20), "public_id", "dog"));
Below you can see a sample of the resulting JSON response:
{ "public_id": "dog", "width": 3000, "height": 2000, "format": "jpg", "bytes": 537666, "url": "http://res.cloudinary.com/demo/image/upload/v1453637947/dog.jpg", "secure_url": "https://res.cloudinary.com/demo/image/upload/v1453637947/dog.jpg", ... "responsive_breakpoints": [ { "breakpoints": [ { "width": 1000, "height": 667, "bytes": 79821, "url": "http://res.cloudinary.com/demo/image/upload/c_scale,w_1000/v1453637947/dog.jpg", "secure_url": "https://res.cloudinary.com/demo/image/upload/c_scale,w_1000/v1453637947/dog.jpg" }, { "width": 891, "height": 594, "bytes": 65666, "url": "http://res.cloudinary.com/demo/image/upload/c_scale,w_891/v1453637947/dog.jpg", "secure_url": "https://res.cloudinary.com/demo/image/upload/c_scale,w_891/v1453637947/dog.jpg" }, { "width": 712, "height": 475, "bytes": 45007, "url": "http://res.cloudinary.com/demo/image/upload/c_scale,w_712/v1453637947/dog.jpg", "secure_url": "https://res.cloudinary.com/demo/image/upload/c_scale,w_712/v1453637947/dog.jpg" }, { "width": 503, "height": 335, "bytes": 25216, "url": "http://res.cloudinary.com/demo/image/upload/c_scale,w_503/v1453637947/dog.jpg", "secure_url": "https://res.cloudinary.com/demo/image/upload/c_scale,w_503/v1453637947/dog.jpg" }, { "width": 200, "height": 133, "bytes": 5712, "url": "http://res.cloudinary.com/demo/image/upload/c_scale,w_200/v1453637947/dog.jpg", "secure_url": "https://res.cloudinary.com/demo/image/upload/c_scale,w_200/v1453637947/dog.jpg" } ] } ] }
You can tell Cloudinary to generate breakpoints for multiple transformed versions of the original image in the same API call. The Node.js code sample below used the explicit
API method to generate breakpoints for an already uploaded image. Breakpoints are requested for a 16:9 face-detection based cropped version of the original image and a 4:3 sharpened image version. The create_derived
flag is enabled so that the derived images don't need to be regenerated when first accessed by your users.
cloudinary.uploader.v2.explicit("dog", { responsive_breakpoints: [ { create_derived: true, bytes_step: 20000, min_width: 200, max_width: 1000, max_images: 20, transformation: { crop: 'fill', aspect_ratio: '16:9', gravity: 'face' }, }, { create_derived: true, bytes_step: 30000, min_width: 350, max_width: 2000, max_images: 18, transformation: { crop: 'fill', width: '4:3', effect: 'sharpen' }, } ] }, function(error, result) { console.log(result); });
Responsive design, and more specifically responsive images, are the 'trending topics' of the web and mobile development world. The lives of web developers aren’t getting any simpler as the number of different devices and potential screen resolutions increase.
Whichever responsive design solution or framework you choose, you still need to generate and deliver multiple versions of each image on your website. The challenge of finding the best fitting resolutions and the responsive breakpoints for each specific image, is common to all approaches and frameworks. It seems that even popular websites can still improve in the selection of the correct image dimensions.
The solution introduced in this post allows web developers to optimize the balance between bandwidth saving and high resolution image delivery for their responsive web sites. Responsive image dimensions are intelligently selected for each specific image and you can use Cloudinary's API to automatically generate breakpoints while uploading new images or for your previously uploaded images. If you don't have a Cloudinary account yet, you can setup one for free.
For the benefits of all web developers, we launched ResponsiveBreakpoints.com. This is a powerful free web tool. It is open sourced and hosted on GitHub. Try it out :-)
This is a guest post by Eric Portis – a proud member of (and newsletter-writer for) the Responsive Issues Community Group. The RICG formulated, championed, and standardized the new HTML features presented in the article.
Previously, we saw how to mark up performant, adaptable <img>
s using srcset
and sizes
in conjunction with Cloudinary’s image transformations.
How far can we push that notion of “adaptability"? Last time, we learned how to offer up an image at a range of different resolutions. This allowed us to send large resources to large screens and small resources to small ones. We used srcset
and sizes
to adapt for performance. But visually, our images remained fixed.
What if we could go a step further and adapt our image’s visual characteristics at breakpoints, just like we adapt our pages’ layouts? The term for this sort of adaptation is art direction. In this article, we’ll create an art-directed front page for our example site:
https://ericportis.com/etc/cloudinary/
The lead article’s preview image on this front page is huge, spanning the entire width of the page. On wide screens, in order to keep it from pushing the rest of the content “below the fold”, we need to crop it. Like this:
The rest of the image previews? They have the opposite problem. Left to simply shrink along with a narrowing viewport, they’d become too small to really make out. So on small screens, we want to “zoom in” on their subjects.
How can we achieve these things in markup? With a new element:
The <picture>
element was added to the HTML specification last year and has been implemented in every major desktop browser except for Safari (Safari support is right around the corner). <picture>
gives us a way to supply alternate, visually distinct versions of an image, and switch them out at breakpoints.
The first thing to know about <picture>
is that it’s a wrapper for <img>
; think of <picture>
as a kind of invisible, magical <span>
which can feed its <img>
alternate sources.
The second thing to know about it is that its markup pattern was adapted from <audio>
and <video>
. So alongside our <img>
, we’ll pack our <picture>
full of <source>
elements.
Each <source>
represents a visually distinct version of the image. We tell the browser which <source>
to use and when, using media
attributes.
<picture> <source media="(min-width: 800px)" sizes="100vw" srcset="cropped-for-wide-screens--large.jpg 1600w, cropped-for-wide-screens--small.jpg 800w" /> <source media="(min-width: 600px)" sizes="100vw" srcset="full-image-for-standard-screens--large.jpg 1200w, full-image-for-standard-screens--small.jpg 600w" /> <img src="zoomed-in-for-small-screens--small.jpg" srcset="zoomed-in-for-small-screens--large.jpg 800w, zoomed-in-for-small-screens--small.jpg 400w" alt /> </picture>
The first <source>
element whose media
attribute matches the current environment wins. The browser picks a resource out of that <source>
’s srcset
/sizes
pair and feeds the picked resource to the <img>
.
Et voila! An image that can change its appearance at breakpoints, as you can see in the examples above.
But dog-gone-it, we’ve done it again — by adding a new dimension of adaptability, we’ve multiplied the number of image resources we need to create and manage, making something that was once simple and static, dynamic and complex.
Cloudinary provides us with tools to manage that complexity.
A few months ago, I sat on on a stage at SmashingConf Freiburg, and Christian Heilmann asked me how, or if, one could automate the process of cropping in on the most important parts of an image. Stumped, I replied, “I don’t know, uh, something something neural networks?”
Right after my talk, Guy Podjarmy whisked me aside and showed me a few of Cloudinary’s auto-cropping features. I was amazed; now I get to show them to you!
First things first: in order to crop using Cloudinary, you need to specify a “crop mode”. We’ll start out by using the fill
crop mode (c_fill
in URLs), which works like background-fit: cover
in CSS. The original image will be stretched or shrunk to cover the entirety of its new box, with any extra bits lopped off.
Let’s say we want to create a 100×100 square crop of our example image. Here’s how we’d do it:
cl_image_tag("on_the_phone.jpg", :width=>100, :height=>100, :crop=>:fill)
cl_image_tag("on_the_phone.jpg", array("width"=>100, "height"=>100, "crop"=>"fill"))
CloudinaryImage("on_the_phone.jpg").image(width=100, height=100, crop="fill")
cloudinary.image("on_the_phone.jpg", {width: 100, height: 100, crop: "fill"})
cloudinary.url().transformation(new Transformation().width(100).height(100).crop("fill")).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {width: 100, height: 100, crop: "fill"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(100).Height(100).Crop("fill")).BuildImageTag("on_the_phone.jpg")
How about a 640×360 version?
cl_image_tag("on_the_phone.jpg", :width=>640, :height=>360, :crop=>:fill)
cl_image_tag("on_the_phone.jpg", array("width"=>640, "height"=>360, "crop"=>"fill"))
CloudinaryImage("on_the_phone.jpg").image(width=640, height=360, crop="fill")
cloudinary.image("on_the_phone.jpg", {width: 640, height: 360, crop: "fill"})
cloudinary.url().transformation(new Transformation().width(640).height(360).crop("fill")).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {width: 640, height: 360, crop: "fill"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(640).Height(360).Crop("fill")).BuildImageTag("on_the_phone.jpg")
In addition to providing heights and widths, Cloudinary lets us supply aspect ratios, which can make our URLs a bit easier to read. This URL returns an image identical to the previous one:
cl_image_tag("on_the_phone.jpg", :aspect_ratio=>"16:9", :width=>640, :crop=>:fill)
cl_image_tag("on_the_phone.jpg", array("aspect_ratio"=>"16:9", "width"=>640, "crop"=>"fill"))
CloudinaryImage("on_the_phone.jpg").image(aspect_ratio="16:9", width=640, crop="fill")
cloudinary.image("on_the_phone.jpg", {aspect_ratio: "16:9", width: 640, crop: "fill"})
cloudinary.url().transformation(new Transformation().aspectRatio("16:9").width(640).crop("fill")).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {aspect_ratio: "16:9", width: 640, crop: "fill"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().AspectRatio("16:9").Width(640).Crop("fill")).BuildImageTag("on_the_phone.jpg")
Ok, let’s try something really wide:
cl_image_tag("on_the_phone.jpg", :aspect_ratio=>"4:1", :width=>640, :crop=>:fill)
cl_image_tag("on_the_phone.jpg", array("aspect_ratio"=>"4:1", "width"=>640, "crop"=>"fill"))
CloudinaryImage("on_the_phone.jpg").image(aspect_ratio="4:1", width=640, crop="fill")
cloudinary.image("on_the_phone.jpg", {aspect_ratio: "4:1", width: 640, crop: "fill"})
cloudinary.url().transformation(new Transformation().aspectRatio("4:1").width(640).crop("fill")).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {aspect_ratio: "4:1", width: 640, crop: "fill"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().AspectRatio("4:1").Width(640).Crop("fill")).BuildImageTag("on_the_phone.jpg")
This crop is… awkward. The president’s head is popping up from the bottom of the frame like a turnip.
By default, Cloudinary crops in on an image’s center. But what if we want to crop in on a different focal point? For that, we need to use Cloudinary’s “gravity” parameter. Our last crop chopped off the president’s body. Let’s aim lower, anchoring our crop to the bottom of the image:
cl_image_tag("on_the_phone.jpg", :aspect_ratio=>"4:1", :width=>640, :crop=>:fill, :gravity=>:south)
cl_image_tag("on_the_phone.jpg", array("aspect_ratio"=>"4:1", "width"=>640, "crop"=>"fill", "gravity"=>"south"))
CloudinaryImage("on_the_phone.jpg").image(aspect_ratio="4:1", width=640, crop="fill", gravity="south")
cloudinary.image("on_the_phone.jpg", {aspect_ratio: "4:1", width: 640, crop: "fill", gravity: "south"})
cloudinary.url().transformation(new Transformation().aspectRatio("4:1").width(640).crop("fill").gravity("south")).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {aspect_ratio: "4:1", width: 640, crop: "fill", gravity: "south"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().AspectRatio("4:1").Width(640).Crop("fill").Gravity("south")).BuildImageTag("on_the_phone.jpg")
Oops! Now we’ve chopped off his head! If only we could center the new, cropped image right on his face…
cl_image_tag("on_the_phone.jpg", :aspect_ratio=>"4:1", :width=>640, :crop=>:fill, :gravity=>:face)
cl_image_tag("on_the_phone.jpg", array("aspect_ratio"=>"4:1", "width"=>640, "crop"=>"fill", "gravity"=>"face"))
CloudinaryImage("on_the_phone.jpg").image(aspect_ratio="4:1", width=640, crop="fill", gravity="face")
cloudinary.image("on_the_phone.jpg", {aspect_ratio: "4:1", width: 640, crop: "fill", gravity: "face"})
cloudinary.url().transformation(new Transformation().aspectRatio("4:1").width(640).crop("fill").gravity("face")).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {aspect_ratio: "4:1", width: 640, crop: "fill", gravity: "face"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().AspectRatio("4:1").Width(640).Crop("fill").Gravity("face")).BuildImageTag("on_the_phone.jpg")
g_face
finds a face in the image and centers the crop on it, ensuring that if there is a person in our photo, they’ll remain front and center.
So! Now we’ve seen how to mark up visually adaptable images using <picture>
and generate alternate crops automatically using Cloudinary. We have everything we need to art direct our example’s giant header image:
<picture> <!-- wide crop --> <source media="(min-width: 600px)" srcset="http://res.cloudinary.com/eeeps/image/upload/c_fill,ar_2:1,g_face,f_auto,q_70,w_600/on_the_phone.jpg 600w, http://res.cloudinary.com/eeeps/image/upload/c_fill,ar_2:1,g_face,f_auto,q_70,w_1200/on_the_phone.jpg 1200w" sizes="100vw" /> <!-- standard crop --> <img srcset="http://res.cloudinary.com/eeeps/image/upload/f_auto,q_70,w_400/on_the_phone.jpg 400w, http://res.cloudinary.com/eeeps/image/upload/f_auto,q_70,w_800/on_the_phone.jpg 800w" src="http://res.cloudinary.com/eeeps/image/upload/f_auto,q_70,w_400/on_the_phone.jpg" alt="President Obama on the phone in the Oval Office" sizes="100vw" /> </picture>
This complex-looking example should now make some sense. We start with an un-cropped <img>
(which includes a srcset
and sizes
so that it’ll look good across resolutions), wrap it in a <picture>
, and give it a <source>
sibling. This <source>
represents the cropped version of our image, and will only send a resource to the <img>
when its media
attribute (min-width: 600px)
matches the current environment.
That chunk of code gets us this:
The hero image in our example is a bit more complex than this, with more breakpoints, more srcset
resources, and a couple of additional Cloudinary tricks which we’ll cover in our next section. View-source-ing it upon completion of the article is left as an exercise to the reader.
Let’s proceed to the thumbnails further down the page. Remember, they have the opposite problem — on small screens, they become too small. On small screens, we want to “zoom in” on their subjects.
In order to do so, we’ll use a new crop mode: c_thumb
. When used with g_faces
, c_thumb
zooms all the way in on a face. Like this:
cl_image_tag("on_the_phone.jpg", :aspect_ratio=>"1:1", :width=>256, :crop=>:thumb, :gravity=>:face)
cl_image_tag("on_the_phone.jpg", array("aspect_ratio"=>"1:1", "width"=>256, "crop"=>"thumb", "gravity"=>"face"))
CloudinaryImage("on_the_phone.jpg").image(aspect_ratio="1:1", width=256, crop="thumb", gravity="face")
cloudinary.image("on_the_phone.jpg", {aspect_ratio: "1:1", width: 256, crop: "thumb", gravity: "face"})
cloudinary.url().transformation(new Transformation().aspectRatio("1:1").width(256).crop("thumb").gravity("face")).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {aspect_ratio: "1:1", width: 256, crop: "thumb", gravity: "face"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().AspectRatio("1:1").Width(256).Crop("thumb").Gravity("face")).BuildImageTag("on_the_phone.jpg")
One gotcha with this technique: it will sometimes create a different crop, depending on the w
specified. c_thumb
will zoom in on the face as tightly as it can at the original image’s full resolution. If we specify a tiny w
, it will happily scale the resulting, fully-zoomed face down:
cl_image_tag("on_the_phone.jpg", :aspect_ratio=>"1:1", :width=>96, :crop=>:thumb, :gravity=>:face)
cl_image_tag("on_the_phone.jpg", array("aspect_ratio"=>"1:1", "width"=>96, "crop"=>"thumb", "gravity"=>"face"))
CloudinaryImage("on_the_phone.jpg").image(aspect_ratio="1:1", width=96, crop="thumb", gravity="face")
cloudinary.image("on_the_phone.jpg", {aspect_ratio: "1:1", width: 96, crop: "thumb", gravity: "face"})
cloudinary.url().transformation(new Transformation().aspectRatio("1:1").width(96).crop("thumb").gravity("face")).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {aspect_ratio: "1:1", width: 96, crop: "thumb", gravity: "face"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().AspectRatio("1:1").Width(96).Crop("thumb").Gravity("face")).BuildImageTag("on_the_phone.jpg")
But, with a large w
, instead of resizing the tightly-cropped-face up, it will pad it with the surrounding image at it’s full resolution:
cl_image_tag("on_the_phone.jpg", :aspect_ratio=>"1:1", :width=>512, :crop=>:thumb, :gravity=>:face)
cl_image_tag("on_the_phone.jpg", array("aspect_ratio"=>"1:1", "width"=>512, "crop"=>"thumb", "gravity"=>"face"))
CloudinaryImage("on_the_phone.jpg").image(aspect_ratio="1:1", width=512, crop="thumb", gravity="face")
cloudinary.image("on_the_phone.jpg", {aspect_ratio: "1:1", width: 512, crop: "thumb", gravity: "face"})
cloudinary.url().transformation(new Transformation().aspectRatio("1:1").width(512).crop("thumb").gravity("face")).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {aspect_ratio: "1:1", width: 512, crop: "thumb", gravity: "face"})
cloudinary.Api.UrlImgUp.Transform(new Transformation().AspectRatio("1:1").Width(512).Crop("thumb").Gravity("face")).BuildImageTag("on_the_phone.jpg")
Put another way: c_thumb
will never upscale your images.
In order to generate consistent crops for arbitrary ranges of w
values, we need to crop first, and resize second. We need to learn a new trick: chained transformations:
cl_image_tag("on_the_phone.jpg", :transformation=>[ {:aspect_ratio=>"1:1", :width=>512, :crop=>:thumb, :gravity=>:face}, {:width=>96} ])
cl_image_tag("on_the_phone.jpg", array("transformation"=>array( array("aspect_ratio"=>"1:1", "width"=>512, "crop"=>"thumb", "gravity"=>"face"), array("width"=>96) )))
CloudinaryImage("on_the_phone.jpg").image(transformation=[ {"aspect_ratio": "1:1", "width": 512, "crop": "thumb", "gravity": "face"}, {"width": 96} ])
cloudinary.image("on_the_phone.jpg", {transformation: [ {aspect_ratio: "1:1", width: 512, crop: "thumb", gravity: "face"}, {width: 96} ]})
cloudinary.url().transformation(new Transformation() .aspectRatio("1:1").width(512).crop("thumb").gravity("face").chain() .width(96)).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {transformation: [ {aspect_ratio: "1:1", width: 512, crop: "thumb", gravity: "face"}, {width: 96} ]})
cloudinary.Api.UrlImgUp.Transform(new Transformation() .AspectRatio("1:1").Width(512).Crop("thumb").Gravity("face").Chain() .Width(96)).BuildImageTag("on_the_phone.jpg")
If we split two sets of transformations with a forward slash, Cloudinary will apply the first set of transformations and treat the resulting image as input to the second set. Neat.
Finally—what if we don’t want such a tight crop? To zoom back out from the subject’s face, we need to learn one more parameter: z
. z
lets us zoom in or out via a multiplier. Values less than one zoom out, and values greater than one zoom in. So, to zoom out so that the cropped face ends up at a quarter of its original, tightly-cropped size, we specify z_0.25
.
cl_image_tag("on_the_phone.jpg", :transformation=>[ {:zoom=>0.25, :aspect_ratio=>"1:1", :width=>512, :crop=>:thumb, :gravity=>:face}, {:width=>96} ])
cl_image_tag("on_the_phone.jpg", array("transformation"=>array( array("zoom"=>0.25, "aspect_ratio"=>"1:1", "width"=>512, "crop"=>"thumb", "gravity"=>"face"), array("width"=>96) )))
CloudinaryImage("on_the_phone.jpg").image(transformation=[ {"zoom": 0.25, "aspect_ratio": "1:1", "width": 512, "crop": "thumb", "gravity": "face"}, {"width": 96} ])
cloudinary.image("on_the_phone.jpg", {transformation: [ {zoom: 0.25, aspect_ratio: "1:1", width: 512, crop: "thumb", gravity: "face"}, {width: 96} ]})
cloudinary.url().transformation(new Transformation() .zoom(0.25).aspectRatio("1:1").width(512).crop("thumb").gravity("face").chain() .width(96)).imageTag("on_the_phone.jpg")
$.cloudinary.image("on_the_phone.jpg", {transformation: [ {zoom: 0.25, aspect_ratio: "1:1", width: 512, crop: "thumb", gravity: "face"}, {width: 96} ]})
cloudinary.Api.UrlImgUp.Transform(new Transformation() .Zoom(0.25).AspectRatio("1:1").Width(512).Crop("thumb").Gravity("face").Chain() .Width(96)).BuildImageTag("on_the_phone.jpg")
And with that, we can smartly zoom our example page’s thumbnails on small screens, using <picture>
, <source>
, and Cloudinary:
<picture> <!-- full image --> <source media="(min-width: 600px)" srcset="http://res.cloudinary.com/eeeps/image/upload/f_auto,q_70,w_150/ronny.jpg 150w, http://res.cloudinary.com/eeeps/image/upload/f_auto,q_70,w_400/ronny.jpg 400w" sizes="calc(8em + 1vw)" /> <!-- zoomed + square-cropped thumb for small screens --> <img srcset="http://res.cloudinary.com/eeeps/image/upload/c_thumb,g_face,ar_1:1,z_0.25,w_180/f_auto,q_70,w_90/ronny.jpg 90w, http://res.cloudinary.com/eeeps/image/upload/c_thumb,g_face,ar_1:1,z_0.25,w_180/f_auto,q_70,w_180/ronny.jpg 180w" sizes="calc(4em + 3vw)" src="http://res.cloudinary.com/eeeps/image/upload/c_thumb,g_face,ar_1:1,z_0.25,w_180/f_auto,q_70,w_90/ronny.jpg" alt="President Obama speaking to Ronny Jackson" /> </picture>
A giant chunk of code like this can be intimidating; the trick is to look at it like a cake – multiple layers, each one building off of the last. Let’s break it down from the bottom.
We start, as always, with an <img>
and some alt
text, describing the image.
For browsers that can display images (and users that can see them), we include an <img src>
on top of it, which contains the mobile-first default version of our image.
Browsers that understand srcset
and sizes
(which, these days, is just about all of them) will use these attributes, instead of the src
, to select a resource to load, giving our image the ability to adapt to fit a range of viewport sizes and display densities.
Finally, we wrap our <img>
in a <picture>
and give it a <source>
sibling, which will, in supporting browsers and on larger screens, allow the image to visually adapt, zooming out at a breakpoint when the viewport is sufficiently large.
Put it all together, and in a modern browser, you get this:
So there you have it – visually-adaptive, art-directed images using <picture>
, <source>
, and Cloudinary. Art direction opens up new frontiers in responsive design; Cloudinary’s on-the-fly face-detection, cropping, resizing, and optimization capabilities make it easy. So: go forth! And mark up performant, adaptable, progressively enhanced images for all.
If you have a web site or mobile application, chances are you need to deliver a lot of media resources, especially images, to your users. How would you know if all your images were delivered correctly to your users and if there were no broken images displayed on your website? Maybe you build image URLs based on a certain naming convention and you end up with URLs that point to non-existing images, which result in HTTP status errors and broken images? Maybe search engines like Google have indexed the URLs of your images that were subsequently deleted or modified, and these URLs now generate errors when accessed?
If you use Cloudinary, then the image URLs also include manipulation and transformation instructions, and so your dynamic code might build invalid URLs (e.g., invalid width, height, etc.) which might result in HTTP 400 errors.
Without a means to identify resource delivery issues you probably don't know exactly what's happening in your system and with your image delivery. Site analytic tools (such as Google Analytics) can't help find these issues and they might get lost in the noise.
To help you identify any image delivery problems, Cloudinary has added an error reporting mechanism. All the requests made to your Cloudinary account via delivery URLs or API calls are processed and any errors generated are displayed in an interactive web page in Cloudinary's Management Console. The error reports can be accessed by clicking the Error report
link on the Reports & Insights page in the Management Console.
The Error Report page lists all the resource delivery errors for the current date (by default), and the number of errors are listed according to the type of error encountered. A graph next to the list of errors visually displays the frequency of each type of error over the last month, and you can retrieve the total number of errors for a specific date by selecting the date from the dropdown box above the graph or by clicking a specific point on the graph itself.
The possible errors reported include:
400 Bad Request
- The server cannot process the request due to something that is perceived to be a request error, e.g., malformed request syntax, invalid image transformation parameters.401 Unauthorized
- Authentication is required and has failed or has not been provided, e.g., the URL should be signed when using add-ons or for certain transformations in Strict Transformations mode, or the image type was restricted in your account's security settings. 404 Not Found
- The requested resource could not be found, e.g., the public_id is invalid. 408 Request Timeout
- The server timed out waiting for the request, e.g., due to a networking error or a slow client.420 Rate limited
- While Cloudinary's plan limits are soft limits, there were either too many concurrent requests for images or the hard quota for add-on usage for your account was exceeded.200 Fallback image on error
- A default image placeholder was delivered as the requested image was not found.Selecting one of the error categories will display a list of errors encountered within that category and the details associated with that error: the reason for the error, the URI of the requested image and the referral web site (who is requesting the resource).
The information contained in the error report is useful for pointing out issues with image delivery, and is displayed in real time with a slight delay of up to 5 minutes. Besides the report's usefulness in identifying and fixing issues with resource delivery on an ongoing basis, the report is especially useful when first integrating with Cloudinary and launching your site or application in production (or launching an updated version thereof), and checking for any issues. Any issues involving the correct building of image URLs, or inadvertently breaking any image deliveries, can be identified, debugged and fixed without waiting for complaints from your users.
The error reporting feature gives you useful information and insight about your image and video delivery, and specific analysis of any errors when delivering your media. The report allows you to locate the problems, debug and analyze the issues and then fix them, and is especially useful when integrating with a new cloud-service like Cloudinary or when launching a new version of your application or website. The error report feature is available for Cloudinary's customers with the Advanced plan or higher.
Cloudinary’s JavaScript library accelerates web development by providing automated image manipulation and management with a few lines of code. The newly released version streamlines the library by providing a much requested jQuery-free core library. At the same time it is fully backward compatible with previous versions. The new library is further enhanced with classes and a chainable API, making the implementation of Cloudinary functionality in your application easier (and more enjoyable!).
Virtually all websites incorporate images and videos in their web pages. With the proliferation of web platforms, devices and rising user expectations, handling media is an increasingly complex task. Services such as Cloudinary alleviate the pain by relieving the developer from having to manually manipulate images, respond to device and layout constraints, and manage storage and availability concerns.
A typical mistake made by novice developers is to provide full size images on the web page. This guaranties that the page will look great on the highest resolution, but at a large cost to download and response times - not to mention bandwidth costs.
The easiest solution - using image manipulation software to create a smaller resolution version of each image - is quickly revealed to be an exponentially difficult task. For one thing, you have to code your web page to display the right image file. Furthermore, you have to keep track of all files related to the same source image. And any change in layout may require rescaling and re-cropping.
Rinse and repeat for each and every image.
Cloudinary’s JavaScript library essentially eliminates this nightmare.
Cloudinary’s strength is largely due to its ability to transform images using a simply coded URL. For example, adding c_scale,w_500
to the URL will scale the image to a width of - you guessed it - 500 pixels. The image is automatically scaled and cached on a CDN, ready to be served.
Cloudinary’s JavaScript API provides the functionality required to programmatically declare the required transformations and generate Cloudinary resource URLs. For example, the aforementioned transformation can be expressed using the following configuration object: {crop: “scale”, width: 500}
.
Creating the imageURL (which will also generate a new image) is simple and intuitive:
cl.url( "elephants.jpg", {crop: "scale", width: 500});
You can also generate the entire image tag:
cl.image( "elephants.jpg", {crop: "scale", width: 500}); // <img src="http://res.cloudinary.com/demo/image/upload/c_scale,w_500/elephants.jpg" width="500">
For more artistic results:
var artistic = Transformation.new() .crop("scale") .width(500) .radius("max") .effect("oil_paint:50"); cl.image("elephants.jpg", artistic); // <img src="http://res.cloudinary.com/demo/image/upload/c_scale,e_oil_paint:50,r_max,w_500/elephants.jpg" >
cl.image("horses.jpg", artistic); // <img src="http://res.cloudinary.com/demo/image/upload/c_scale,e_oil_paint:50,r_max,w_500/horses.jpg" >
See what I mean?
In addition, the SDK provides the functionality required to upload images from the browser directly to the cloud, and apply responsive behaviour to the displayed images. For more information on the available SDKs and media transformation options, see Cloudinary’s documentation.
The Cloudinary JavaScript library functionality is represented using several classes.
The main class, Cloudinary, provides an API for:
In addition, other classes encapsulate specific behavior:
For further information on each class, see the API reference.
After providing basic configuration values, the API is ready to be used.
Here are a few examples:
var cl = cloudinary.Cloudinary.new( { cloud_name: "demo"}); var d = document.getElementById("my_div"); d.appendChild( cl.imageTag("sample", {crop: "scale", width: 200}).toDOM()); var i = document.getElementById(“my_image”); i.src = cl.url( "sample", {crop: "scale", width: 200, angle: 30});
When we first wrote our JavaScript library, it was designed as a jQuery plugin. Virtually all JavaScript developers are familiar with jQuery: it is one of the most popular JavaScript libraries and has a large following. It is designed by and large for client side HTML/DOM manipulation, but includes many useful general purpose functions. jQuery takes the hassle away from many of the woes of web development and has served developers well. The jQuery library also provides browser compatibility by handling special cases and ensuring that the JavaScript code will run on virtually all clients.
However with the recent rise in the number of JavaScript libraries, frameworks and, well, tastes, we have also received many requests to create a jQuery-less library.
The decisions facing one in the task of redesigning a JavaScript library are numerous and daunting. Backward compatibility vs. a clean break? native JavaScript vs. CoffeeScript / TypeScript? lodash, underscore, or implement your own solutions? Npm, Bower or both? Grunt, Gulp, npm? RequireJS, CommonJS, webpack, browserify? - well, you get the point.
Too many choices
Needless to say each choice you make will provoke praise from one camp and battle cries from the other…
Backward compatibility is obviously a big issue. Our philosophy is to let our customers enjoy new features with minimal code changes - if any. The nature of our service also requires that our libraries support a wider range of platforms and browser versions than normally needed. To this end, the new library was designed to be a drop-in replacement to the existing library. This decision has put some constraints on the design of the library - but we were happy (and proud) to have accomplished it in full.
Old version:
$.cloudinary.url(“my_image”, {width: 100});
New version:
$.cloudinary.url(“my_image”, {width: 100});
Backward compatibility, anyone? the new library is a drop-in replacement.
The cloudinary_js library is provided in plain javascript 5. This is important to ensure it will run smoothly regardless of the browser the end user is using. During the development phase, it is often easier to code in a higher function variation of the language.
Early on we made the decision to write the new code in CoffeeScript. While ES2015 (the artist formally known as ES6) was already rolling out, maintaining backward compatibility meant that we would have to “compile” the code anyway. CoffeeScript has its own shortcomings but its familiarity to Rails developers, and the fact the our NodeJS library was written in CoffeeScript, made it a comfortable choice. Besides, comprehensions are cool!
CoffeeScript and ES2015 both allow the creation of classes and objects. Syntax may vary (as does the implementation of class inheritance) but the result is similar. In fact both are mainly syntactic-sugar to a functionality that already existed in “native” JavaScript.
The new library groups functionality into classes. One of the neat benefits is the ability to chain function calls, making the code more descriptive.
The following code, for example, configures the Cloudinary API to use the cloud “demo”.
It then creates an image tag for the “sample” image, scaled to a width of 150px, rotated by 15 degrees, and with the “sepia" effect applied.
var cl = cloudinary.Cloudinary.new({cloud_name: "demo"}); var tag = cl.imageTag("sample").transformation() .width(150) .crop("scale") .angle(15) .effect('sepia');
Since the code is object oriented (rather than global as in the previous version), it is easy to utilize multiple clouds and accounts in the same application.
The following code creates two image tags drawn from two different clouds (note that while the public ID of the image is the same these could be two completely different images as they are defined in separate clouds).
var someCloud = cloudinary.Cloudinary.new( {cloud_name: "someCloud", secure: true}); var otherCloud = cloudinary.Cloudinary.new( {cloud_name: "otherCloud"}); someCloud.imageTag("sample.jpg"); // <img src="https://res.cloudinary.com/someCloud/image/upload/sample.jpg"> otherCloud.imageTag("sample.jpg"); // <img src="http://res.cloudinary.com/otherCloud/image/upload/sample.jpg">
The JavaScript language has limitations, be it array manipulation or determining an “empty” value. Some of these limitations were addressed in ES2015, but as a JavaScript developer you always have to be on your toes making sure that the feature you are using will be supported in all foreseeable environments.
Libraries such as lodash take the edge off by providing an implementation of features when the runtime environment does not support them. Because jQuery support was still required for the Cloudinary jQuery plugin, and because we anticipated frowns on the use of lodash, we designed the library so that lodash is not called directly but through the “util” interface. This allows us to switch between jQuery and lodash when needed. It also allows for the future utilization of a different library, or for a keen programmer to implement “native” code and make the library become fully standalone.
We also created a special shrinkwrapped version of the library which includes a subset of the lodash functions that are required by Cloudinary. The resulting single file is about half the size of a full sourced lodash + Cloudinary.
Uploading a file using JavaScript is basically not a difficult task, but it gets complex quickly and deeply so. To avoid this we have relied in the past on Blueimp’s excellent jQuery-file-upload library. For both backward compatibility and the fact that this library does what it does well, we decided to keep using it. As much as this library is useful however, you can still utilize other upload libraries or write your own code to upload files from the core library. See this example of a pure JavaScript upload to Cloudinary.
There are two common ways to manage dependant libraries in JavaScript: npm and bower. Originally, bower handled client side libraries while npm, as its name suggests (Node Package Manager) was managing server side libraries. Today npm is used to manage client side libraries too. In fact jQuery, which used to have its own repository, moved its plugins to npm.
In order to serve both the core library and the jQuery variant we had to create 3 new github repositories. The main reason was that bower relies directly on the github release information, which means you cannot serve two packages from the same repository.
The main repository which includes the source code, issues and pull requests for the Cloudinary JavaScript library is located at cloudinary_js. In order to support existing websites that relied on the previous version of this library, it includes a backward compatible distribution format.
The new JavaScript API is provided in 3 distribution packages:
Github Repository |
Package name |
Description |
cloudinary-core |
Core Cloudinary Library. Use this if you do not intend to use jQuery. |
|
cloudinary-jquery |
Core Library + jQuery plugin |
|
cloudinary-jquery-file-upload |
Core Library + jQuery plugin + Blueimp File Upload adapter |
The same package names are used in both bower and NPM.
Each package has it's own API documentation site:
This API reference is generated directly from the code and complements the documentation on our main website.
The management of images and other media resources is an important part of modern web and mobile development. The Cloudinary JavaScript library significantly reduces the workload by automating the manipulation and delivery of the resources.
The new version of the library includes 3 distributions: the core library, the jQuery plugin, and the File Upload plugin.
The library also introduces a new API that allows the developer to chain function calls and use multiple accounts in the same application.
Head over and give it a try at cloudinary_js!
We’re looking forward to hearing your impressions!