Cloudinary Blog

How To Distort Images Dynamically to Fit your Graphic Design

How To Distort Images Dynamically to Fit your Graphic Design

How to Distort images in the cloud

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.

Ruby:
Copy to clipboard
cl_image_tag("base.jpg", :transformation=>[
  {:width=>700, :height=>200, :crop=>"scale"},
  {: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=>{:font_family=>"roboto", :font_size=>120, :font_weight=>"bold", :text=>"%2B%20%20%20%20%20%20%20%20%3D"}}
  ])
PHP v1:
Copy to clipboard
cl_image_tag("base.jpg", array("transformation"=>array(
  array("width"=>700, "height"=>200, "crop"=>"scale"),
  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"=>array("font_family"=>"roboto", "font_size"=>120, "font_weight"=>"bold", "text"=>"%2B%20%20%20%20%20%20%20%20%3D"))
  )))
PHP v2:
Copy to clipboard
(new ImageTag('base.jpg'))
  ->resize(Resize::scale()->width(700)->height(200))
  ->overlay(
      Overlay::source(Source::image('mobile_phone')
        ->transformation((new ImageTransformation())
          ->resize(Resize::scale()->width(150))))
      ->position((new Position())
        ->gravity(Gravity::compass(Compass::west()))))
    ->overlay(
        Overlay::source(Source::image('mobile_phone')
          ->transformation((new ImageTransformation())
            ->resize(Resize::scale()->width(150))))
        ->position((new Position())
          ->gravity(Gravity::compass(Compass::east()))))
          ->overlay(
              Overlay::source(Source::image('movie_time')
                ->transformation((new ImageTransformation())
                  ->resize(Resize::scale()->width(90))))
              ->position((new Position())
                ->gravity(Gravity::compass(Compass::center()))))
            ->overlay(
                Overlay::source(Source::image('movie_time')
                  ->transformation((new ImageTransformation())
                    ->resize(Resize::scale()->width(100))
                    ->reshape(Reshape::distort([30, 20, 85, 40, 25, 120, -30, 90]))))
                ->position((new Position())
                  ->gravity(Gravity::compass(Compass::east()))))
                  ->overlay(
                      Overlay::source(
                          Source::text('+        =', (new TextStyle('roboto', 120))
                            ->fontWeight(FontWeight::bold())
                      
                    
                      
                    
                      
                      )));
Python:
Copy to clipboard
CloudinaryImage("base.jpg").image(transformation=[
  {'width': 700, 'height': 200, 'crop': "scale"},
  {'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': {'font_family': "roboto", 'font_size': 120, 'font_weight': "bold", 'text': "%2B%20%20%20%20%20%20%20%20%3D"}}
  ])
Node.js:
Copy to clipboard
cloudinary.image("base.jpg", {transformation: [
  {width: 700, height: 200, crop: "scale"},
  {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: {font_family: "roboto", font_size: 120, font_weight: "bold", text: "%2B%20%20%20%20%20%20%20%20%3D"}}
  ]})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation()
  .width(700).height(200).crop("scale").chain()
  .overlay(new Layer().publicId("mobile_phone")).width(150).gravity("west").chain()
  .overlay(new Layer().publicId("mobile_phone")).width(150).gravity("east").chain()
  .overlay(new Layer().publicId("movie_time")).width(90).gravity("center").chain()
  .overlay(new Layer().publicId("movie_time")).width(100).gravity("east").effect("distort:30:20:85:40:25:120:-30:90").chain()
  .overlay(new TextLayer().fontFamily("roboto").fontSize(120).fontWeight("bold").text("%2B%20%20%20%20%20%20%20%20%3D"))).imageTag("base.jpg");
JS:
Copy to clipboard
cloudinary.imageTag('base.jpg', {transformation: [
  {width: 700, height: 200, crop: "scale"},
  {overlay: new cloudinary.Layer().publicId("mobile_phone"), width: 150, gravity: "west"},
  {overlay: new cloudinary.Layer().publicId("mobile_phone"), width: 150, gravity: "east"},
  {overlay: new cloudinary.Layer().publicId("movie_time"), width: 90, gravity: "center"},
  {overlay: new cloudinary.Layer().publicId("movie_time"), width: 100, gravity: "east", effect: "distort:30:20:85:40:25:120:-30:90"},
  {overlay: new cloudinary.TextLayer().fontFamily("roboto").fontSize(120).fontWeight("bold").text("%2B%20%20%20%20%20%20%20%20%3D")}
  ]}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("base.jpg", {transformation: [
  {width: 700, height: 200, crop: "scale"},
  {overlay: new cloudinary.Layer().publicId("mobile_phone"), width: 150, gravity: "west"},
  {overlay: new cloudinary.Layer().publicId("mobile_phone"), width: 150, gravity: "east"},
  {overlay: new cloudinary.Layer().publicId("movie_time"), width: 90, gravity: "center"},
  {overlay: new cloudinary.Layer().publicId("movie_time"), width: 100, gravity: "east", effect: "distort:30:20:85:40:25:120:-30:90"},
  {overlay: new cloudinary.TextLayer().fontFamily("roboto").fontSize(120).fontWeight("bold").text("%2B%20%20%20%20%20%20%20%20%3D")}
  ]})
React:
Copy to clipboard
<Image publicId="base.jpg" >
  <Transformation width="700" height="200" crop="scale" />
  <Transformation overlay="mobile_phone" width="150" gravity="west" />
  <Transformation overlay="mobile_phone" width="150" gravity="east" />
  <Transformation overlay="movie_time" width="90" gravity="center" />
  <Transformation overlay="movie_time" width="100" gravity="east" effect="distort:30:20:85:40:25:120:-30:90" />
  <Transformation overlay={{fontFamily: "roboto", fontSize: 120, fontWeight: "bold", text: "%2B%20%20%20%20%20%20%20%20%3D"}} />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="base.jpg" >
  <cld-transformation width="700" height="200" crop="scale" />
  <cld-transformation :overlay="mobile_phone" width="150" gravity="west" />
  <cld-transformation :overlay="mobile_phone" width="150" gravity="east" />
  <cld-transformation :overlay="movie_time" width="90" gravity="center" />
  <cld-transformation :overlay="movie_time" width="100" gravity="east" effect="distort:30:20:85:40:25:120:-30:90" />
  <cld-transformation :overlay="{fontFamily: 'roboto', fontSize: 120, fontWeight: 'bold', text: '%2B%20%20%20%20%20%20%20%20%3D'}" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="base.jpg" >
  <cl-transformation width="700" height="200" crop="scale">
  </cl-transformation>
  <cl-transformation overlay="mobile_phone" width="150" gravity="west">
  </cl-transformation>
  <cl-transformation overlay="mobile_phone" width="150" gravity="east">
  </cl-transformation>
  <cl-transformation overlay="movie_time" width="90" gravity="center">
  </cl-transformation>
  <cl-transformation overlay="movie_time" width="100" gravity="east" effect="distort:30:20:85:40:25:120:-30:90">
  </cl-transformation>
  <cl-transformation overlay="text:roboto_120_bold:%2B%20%20%20%20%20%20%20%20%3D">
  </cl-transformation>
</cl-image>
.NET:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation()
  .Width(700).Height(200).Crop("scale").Chain()
  .Overlay(new Layer().PublicId("mobile_phone")).Width(150).Gravity("west").Chain()
  .Overlay(new Layer().PublicId("mobile_phone")).Width(150).Gravity("east").Chain()
  .Overlay(new Layer().PublicId("movie_time")).Width(90).Gravity("center").Chain()
  .Overlay(new Layer().PublicId("movie_time")).Width(100).Gravity("east").Effect("distort:30:20:85:40:25:120:-30:90").Chain()
  .Overlay(new TextLayer().FontFamily("roboto").FontSize(120).FontWeight("bold").Text("%2B%20%20%20%20%20%20%20%20%3D"))).BuildImageTag("base.jpg")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation()
  .width(700).height(200).crop("scale").chain()
  .overlay(new Layer().publicId("mobile_phone")).width(150).gravity("west").chain()
  .overlay(new Layer().publicId("mobile_phone")).width(150).gravity("east").chain()
  .overlay(new Layer().publicId("movie_time")).width(90).gravity("center").chain()
  .overlay(new Layer().publicId("movie_time")).width(100).gravity("east").effect("distort:30:20:85:40:25:120:-30:90").chain()
  .overlay(new TextLayer().fontFamily("roboto").fontSize(120).fontWeight("bold").text("%2B%20%20%20%20%20%20%20%20%3D"))).generate("base.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation()
  .setWidth(700).setHeight(200).setCrop("scale").chain()
  .setOverlay("mobile_phone").setWidth(150).setGravity("west").chain()
  .setOverlay("mobile_phone").setWidth(150).setGravity("east").chain()
  .setOverlay("movie_time").setWidth(90).setGravity("center").chain()
  .setOverlay("movie_time").setWidth(100).setGravity("east").setEffect("distort:30:20:85:40:25:120:-30:90").chain()
  .setOverlay("text:roboto_120_bold:%2B%20%20%20%20%20%20%20%20%3D")).generate("base.jpg")!, cloudinary: cloudinary)
smartphone with distorted overlay

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.

Webinar
How to Optimize for Page Load Speed

Transforming images using the distort effect

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:

  • An integer representing the number of pixels from the top left corner (which has the coordinates: 0,0).
  • A string (an integer with a 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

Distorting an image by redefining the corner coordinates

The following two images show themovie_time image, and then the image after applying the distort effect calculated above:

Ruby:
Copy to clipboard
cl_image_tag("movie_time.jpg", :width=>300, :height=>180, :crop=>"fill")
PHP v1:
Copy to clipboard
cl_image_tag("movie_time.jpg", array("width"=>300, "height"=>180, "crop"=>"fill"))
PHP v2:
Copy to clipboard
(new ImageTag('movie_time.jpg'))
  ->resize(Resize::fill()->width(300)->height(180));
Python:
Copy to clipboard
CloudinaryImage("movie_time.jpg").image(width=300, height=180, crop="fill")
Node.js:
Copy to clipboard
cloudinary.image("movie_time.jpg", {width: 300, height: 180, crop: "fill"})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation().width(300).height(180).crop("fill")).imageTag("movie_time.jpg");
JS:
Copy to clipboard
cloudinary.imageTag('movie_time.jpg', {width: 300, height: 180, crop: "fill"}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("movie_time.jpg", {width: 300, height: 180, crop: "fill"})
React:
Copy to clipboard
<Image publicId="movie_time.jpg" >
  <Transformation width="300" height="180" crop="fill" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="movie_time.jpg" >
  <cld-transformation width="300" height="180" crop="fill" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="movie_time.jpg" >
  <cl-transformation width="300" height="180" crop="fill">
  </cl-transformation>
</cl-image>
.NET:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation().Width(300).Height(180).Crop("fill")).BuildImageTag("movie_time.jpg")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation().width(300).height(180).crop("fill")).generate("movie_time.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setWidth(300).setHeight(180).setCrop("fill")).generate("movie_time.jpg")!, cloudinary: cloudinary)
movie_time image

Applying the same distort effect (calculated above) to this image, using on-the-fly image manipulation URLs:

Ruby:
Copy to clipboard
cl_image_tag("movie_time.jpg", :transformation=>[
  {:width=>300, :height=>180, :crop=>"fill"},
  {:effect=>"distort:40:25:280:60:260:155:35:165"}
  ])
PHP v1:
Copy to clipboard
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")
  )))
PHP v2:
Copy to clipboard
(new ImageTag('movie_time.jpg'))
  ->resize(Resize::fill()->width(300)->height(180))
  ->reshape(Reshape::distort([40, 25, 280, 60, 260, 155, 35, 165]));
Python:
Copy to clipboard
CloudinaryImage("movie_time.jpg").image(transformation=[
  {'width': 300, 'height': 180, 'crop': "fill"},
  {'effect': "distort:40:25:280:60:260:155:35:165"}
  ])
Node.js:
Copy to clipboard
cloudinary.image("movie_time.jpg", {transformation: [
  {width: 300, height: 180, crop: "fill"},
  {effect: "distort:40:25:280:60:260:155:35:165"}
  ]})
Java:
Copy to clipboard
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");
JS:
Copy to clipboard
cloudinary.imageTag('movie_time.jpg', {transformation: [
  {width: 300, height: 180, crop: "fill"},
  {effect: "distort:40:25:280:60:260:155:35:165"}
  ]}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("movie_time.jpg", {transformation: [
  {width: 300, height: 180, crop: "fill"},
  {effect: "distort:40:25:280:60:260:155:35:165"}
  ]})
React:
Copy to clipboard
<Image publicId="movie_time.jpg" >
  <Transformation width="300" height="180" crop="fill" />
  <Transformation effect="distort:40:25:280:60:260:155:35:165" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="movie_time.jpg" >
  <cld-transformation width="300" height="180" crop="fill" />
  <cld-transformation effect="distort:40:25:280:60:260:155:35:165" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="movie_time.jpg" >
  <cl-transformation width="300" height="180" crop="fill">
  </cl-transformation>
  <cl-transformation effect="distort:40:25:280:60:260:155:35:165">
  </cl-transformation>
</cl-image>
.NET:
Copy to clipboard
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")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation()
  .width(300).height(180).crop("fill").chain()
  .effect("distort:40:25:280:60:260:155:35:165")).generate("movie_time.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation()
  .setWidth(300).setHeight(180).setCrop("fill").chain()
  .setEffect("distort:40:25:280:60:260:155:35:165")).generate("movie_time.jpg")!, cloudinary: cloudinary)
movie_time image with distort effect e_distort:40:25:280:60:260:155:35:165

Overlays with 3D perspective

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:

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

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

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

</cl-image>
.NET:
Copy to clipboard
cloudinary.Api.UrlImgUp.BuildImageTag("disc_box.jpg")
Android:
Copy to clipboard
MediaManager.get().url().generate("disc_box.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().generate("disc_box.jpg")!, cloudinary: cloudinary)
DVD cover image

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:

Ruby:
Copy to clipboard
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"}
  ])
PHP v1:
Copy to clipboard
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")
  )))
PHP v2:
Copy to clipboard
(new ImageTag('disc_box.jpg'))
  ->resize(Resize::scale()->width(400))
  ->overlay(
      Overlay::source(Source::image('movie_time')
        ->transformation((new ImageTransformation())
          ->resize(Resize::scale()->width(300))
          ->reshape(Reshape::distort([55, 55, 195, 20, 195, 350, 55, 320]))
  )));
Python:
Copy to clipboard
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"}
  ])
Node.js:
Copy to clipboard
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"}
  ]})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation()
  .width(400).crop("scale").chain()
  .overlay(new Layer().publicId("movie_time")).width(300).effect("distort:55:55:195:20:195:350:55:320")).imageTag("disc_box.jpg");
JS:
Copy to clipboard
cloudinary.imageTag('disc_box.jpg', {transformation: [
  {width: 400, crop: "scale"},
  {overlay: new cloudinary.Layer().publicId("movie_time"), width: 300, effect: "distort:55:55:195:20:195:350:55:320"}
  ]}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("disc_box.jpg", {transformation: [
  {width: 400, crop: "scale"},
  {overlay: new cloudinary.Layer().publicId("movie_time"), width: 300, effect: "distort:55:55:195:20:195:350:55:320"}
  ]})
React:
Copy to clipboard
<Image publicId="disc_box.jpg" >
  <Transformation width="400" crop="scale" />
  <Transformation overlay="movie_time" width="300" effect="distort:55:55:195:20:195:350:55:320" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="disc_box.jpg" >
  <cld-transformation width="400" crop="scale" />
  <cld-transformation :overlay="movie_time" width="300" effect="distort:55:55:195:20:195:350:55:320" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="disc_box.jpg" >
  <cl-transformation width="400" crop="scale">
  </cl-transformation>
  <cl-transformation overlay="movie_time" width="300" effect="distort:55:55:195:20:195:350:55:320">
  </cl-transformation>
</cl-image>
.NET:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation()
  .Width(400).Crop("scale").Chain()
  .Overlay(new Layer().PublicId("movie_time")).Width(300).Effect("distort:55:55:195:20:195:350:55:320")).BuildImageTag("disc_box.jpg")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation()
  .width(400).crop("scale").chain()
  .overlay(new Layer().publicId("movie_time")).width(300).effect("distort:55:55:195:20:195:350:55:320")).generate("disc_box.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation()
  .setWidth(400).setCrop("scale").chain()
  .setOverlay("movie_time").setWidth(300).setEffect("distort:55:55:195:20:195:350:55:320")).generate("disc_box.jpg")!, cloudinary: cloudinary)
DVD cover with movie_time cover in correct perspective

Manipulating images with the shear effect

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:

Ruby:
Copy to clipboard
cl_image_tag("movie_time.jpg", :effect=>"shear:40:0")
PHP v1:
Copy to clipboard
cl_image_tag("movie_time.jpg", array("effect"=>"shear:40:0"))
PHP v2:
Copy to clipboard
(new ImageTag('movie_time.jpg'))
  ->reshape(Reshape::shear(40, 0));
Python:
Copy to clipboard
CloudinaryImage("movie_time.jpg").image(effect="shear:40:0")
Node.js:
Copy to clipboard
cloudinary.image("movie_time.jpg", {effect: "shear:40:0"})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation().effect("shear:40:0")).imageTag("movie_time.jpg");
JS:
Copy to clipboard
cloudinary.imageTag('movie_time.jpg', {effect: "shear:40:0"}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("movie_time.jpg", {effect: "shear:40:0"})
React:
Copy to clipboard
<Image publicId="movie_time.jpg" >
  <Transformation effect="shear:40:0" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="movie_time.jpg" >
  <cld-transformation effect="shear:40:0" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="movie_time.jpg" >
  <cl-transformation effect="shear:40:0">
  </cl-transformation>
</cl-image>
.NET:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation().Effect("shear:40:0")).BuildImageTag("movie_time.jpg")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation().effect("shear:40:0")).generate("movie_time.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setEffect("shear:40:0")).generate("movie_time.jpg")!, cloudinary: cloudinary)
movie_time.jpg with 40 degree shearing on the x-axis

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.

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

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

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

</cl-image>
.NET:
Copy to clipboard
cloudinary.Api.UrlImgUp.BuildImageTag("yellow_sports_car.png")
Android:
Copy to clipboard
MediaManager.get().url().generate("yellow_sports_car.png");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().generate("yellow_sports_car.png")!, cloudinary: cloudinary)
yellow sports car

The shear effect is added to the overlay to give the impression of the car accelerating forwards:

Ruby:
Copy to clipboard
cl_image_tag("blank_shirt.jpg", :overlay=>"yellow_sports_car", :gravity=>"north", :width=>400, :x=>20, :y=>120, :effect=>"shear:20:0")
PHP v1:
Copy to clipboard
cl_image_tag("blank_shirt.jpg", array("overlay"=>"yellow_sports_car", "gravity"=>"north", "width"=>400, "x"=>20, "y"=>120, "effect"=>"shear:20:0"))
PHP v2:
Copy to clipboard
(new ImageTag('blank_shirt.jpg'))
  ->overlay(
      Overlay::source(Source::image('yellow_sports_car')
        ->transformation((new ImageTransformation())
          ->resize(Resize::scale()->width(400))
          ->reshape(Reshape::shear(20, 0))))
      ->position((new Position())
        ->gravity(Gravity::compass(Compass::north()))
        ->offsetX(20)->offsetY(120)
  ));
Python:
Copy to clipboard
CloudinaryImage("blank_shirt.jpg").image(overlay="yellow_sports_car", gravity="north", width=400, x=20, y=120, effect="shear:20:0")
Node.js:
Copy to clipboard
cloudinary.image("blank_shirt.jpg", {overlay: "yellow_sports_car", gravity: "north", width: 400, x: 20, y: 120, effect: "shear:20:0"})
Java:
Copy to clipboard
cloudinary.url().transformation(new Transformation().overlay(new Layer().publicId("yellow_sports_car")).gravity("north").width(400).x(20).y(120).effect("shear:20:0")).imageTag("blank_shirt.jpg");
JS:
Copy to clipboard
cloudinary.imageTag('blank_shirt.jpg', {overlay: new cloudinary.Layer().publicId("yellow_sports_car"), gravity: "north", width: 400, x: 20, y: 120, effect: "shear:20:0"}).toHtml();
jQuery:
Copy to clipboard
$.cloudinary.image("blank_shirt.jpg", {overlay: new cloudinary.Layer().publicId("yellow_sports_car"), gravity: "north", width: 400, x: 20, y: 120, effect: "shear:20:0"})
React:
Copy to clipboard
<Image publicId="blank_shirt.jpg" >
  <Transformation overlay="yellow_sports_car" gravity="north" width="400" x="20" y="120" effect="shear:20:0" />
</Image>
Vue.js:
Copy to clipboard
<cld-image publicId="blank_shirt.jpg" >
  <cld-transformation :overlay="yellow_sports_car" gravity="north" width="400" x="20" y="120" effect="shear:20:0" />
</cld-image>
Angular:
Copy to clipboard
<cl-image public-id="blank_shirt.jpg" >
  <cl-transformation overlay="yellow_sports_car" gravity="north" width="400" x="20" y="120" effect="shear:20:0">
  </cl-transformation>
</cl-image>
.NET:
Copy to clipboard
cloudinary.Api.UrlImgUp.Transform(new Transformation().Overlay(new Layer().PublicId("yellow_sports_car")).Gravity("north").Width(400).X(20).Y(120).Effect("shear:20:0")).BuildImageTag("blank_shirt.jpg")
Android:
Copy to clipboard
MediaManager.get().url().transformation(new Transformation().overlay(new Layer().publicId("yellow_sports_car")).gravity("north").width(400).x(20).y(120).effect("shear:20:0")).generate("blank_shirt.jpg");
iOS:
Copy to clipboard
imageView.cldSetImage(cloudinary.createUrl().setTransformation(CLDTransformation().setOverlay("yellow_sports_car").setGravity("north").setWidth(400).setX(20).setY(120).setEffect("shear:20:0")).generate("blank_shirt.jpg")!, cloudinary: cloudinary)
t-shirt with car 'sheared' by 20 degrees

Animated GIF example with the distort effect

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:

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

Each frame is a therefore a combination of the base image together with an overlay of a slightly modified version of the text string.

Copy to clipboard
  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:

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

Pretty cool use for the distort effect, right?

Summary

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!

Recent Blog Posts

Automate the Staging Process of Videos for Social Media

Rich and engaging media helps build customer engagement and trust but can be time consuming to stage. Developers save a tremendous amount of time by preparing videos for social media with Cloudinary. That’s because Cloudinary’s interface, widgets, and application programming interface (API) transform raw media into polished content, optimizing footage and enabling effortless customization and publishing.

Read more

Top Five Web-Video Formats of 2021

By William Imoh
The Five Most Popular Web-Video Formats and Streaming Protocols

Over the past 15 years, the video industry has undergone a significant change in video formats on the web. In particular, in the early 2010s, the 3GP format, which the 3rd Generation Partnership Project (3GPP) created for 3G-enabled mobile devices, went nearly extinct. The advancement of mobile devices and cellular networks has brought about the need for pioneers to build better formats for a faster user experience.

Read more
Cloudinary Introduces Integration With SAP Commerce Cloud

We’re excited to announce Cloudinary’s integration with SAP Commerce Cloud, through which the latter’s customers can significantly boost the visual media experience on their website or app.

SAP Commerce Cloud powers some of the largest e-commerce sites (B2C, B2B, and B2B2C businesses), complete with building blocks like storefront design and order management. Reinforced with Cloudinary’s laser-sharp focus on optimizing, managing, and delivering images and videos, the new extension will enable SAP Commerce Cloud customers to create unique and engaging visual experiences effortlessly.

Read more
Personalizing Video Email for Marketing Campaigns With Cloudinary

As critical as it is to engage with shoppers in order to succeed in e-commerce, old-style, boring emails are far from being effective. In fact, they tend to be annoying because no one likes to read formulaic, generic messages that are sent en masse. For better results, rethink your email marketing campaigns and try out creative strategies.

Read more
Muted Videos and Subtitles

The bane of our existence is the lack of efficient ways for tackling the plethora of recurring tasks in our lives. One of those tasks is surfing the internet. We consume a lot of web content daily, of which a large percentage are images and videos. We’re constantly quickly scrolling through 30-second videos or checking out pictures of cute items we’d like to buy in our free time.

Read more

Building a Roommate-Matching App With Cloudinary and Jamstack

By Marcelo Ricardo de Oliveira
Building a Roommate-Matching App With Cloudinary and Jamstack

Roommate matching can be a pain—especially during the COVID pandemic when people don't want to meet in person. Matching apps like Flatmates, Roomster, and roommates.com are helpful, and if you're in the roommate-matching space, you know that great video is essential for those seeking roommates. Fortunately, Cloudinary can help.

Read more