Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the math behind CSS's background-size:cover

Tags:

css

gd

I'm creating an "image generator" where users can upload an image and add text and/or draw on it. The outputted image is a fixed size (698x450).

On the client side, when the user uploads their image it is set as the background of a div that's 698x450 with background-size:cover. This makes it fill the area nicely.

The final combined image is generated by PHP using GD functions. My question is, how can I get the image to scale in PHP the same way it does in CSS. I want the result of the PHP script to look the same as if the image was set in CSS as it was above. Does anyone know how browsers using background-size:cover calculate how to scale the image appropriately? I want to translate this into PHP.

Thanks

like image 682
antriver Avatar asked Apr 23 '12 17:04

antriver


People also ask

What does background-size Cover do in CSS?

The background-size CSS property sets the size of the element's background image. The image can be left to its natural size, stretched, or constrained to fit the available space.

What does background-size cover mean?

cover = Scale the background image to be as large as possible so that the background area is completely covered by the background image. Some parts of the background image may not be in view within the background positioning area.

Can we specify the background-size in percentage True or false?

As per the W3C Specs: A percentage is relative to the background positioning area. and background positioning area is one of either border-box or padding-box or content-box based on the background-origin property. Here you haven't specified any value explicitly for this and so its default value of padding-box is used.


3 Answers

Here's a logic behind cover calculations.

You have four base values :

imgWidth // your original img width
imgHeight

containerWidth // your container  width (here 698px)
containerHeight

Two ratios derived from these values :

imgRatio = (imgHeight / imgWidth)       // original img ratio
containerRatio = (containerHeight / containerWidth)     // container ratio

You want to find two new values :

finalWidth // the scaled img width
finalHeight

So :

if (containerRatio > imgRatio) 
{
    finalHeight = containerHeight
    finalWidth = (containerHeight / imgRatio)
} 
else 
{
    finalWidth = containerWidth 
    finalHeight = (containerWidth / imgRatio)
}

... and you have the equivalent of a background-size : cover.

like image 94
mddw Avatar answered Oct 02 '22 12:10

mddw


I know this is a very old question, but the answer I wrote is actually cleaner by using max and mins on the ratios between the images instead of each image with itself:

var originalRatios = {
  width: containerWidth / imageNaturalWidth,
  height: containerHeight / imageNaturalHeight
};

// formula for cover:
var coverRatio = Math.max(originalRatios.width, originalRatios.height); 

// result:
var newImageWidth = imageNaturalWidth * coverRatio;
var newImageHeight = imageNaturalHeight * coverRatio;

I like this approach because it is very systematic — maybe it's the wrong word —. What I mean is you can get rid of the if statements and make it work in a more "math formula" kind of way (input = output, if that makes sense):

var ratios = {
  cover: function(wRatio, hRatio) {
    return Math.max(wRatio, hRatio);
  },

  contain: function(wRatio, hRatio) {
    return Math.min(wRatio, hRatio);
  },

  // original size
  "auto": function() {
    return 1;
  },

  // stretch
  "100% 100%": function(wRatio, hRatio) {
    return { width:wRatio, height:hRatio };
  }
};

function getImageSize(options) {
  if(!ratios[options.size]) {
    throw new Error(options.size + " not found in ratios");
  }

  var r = ratios[options.size](
    options.container.width / options.image.width,
    options.container.height / options.image.height
  );

  return {
    width: options.image.width * (r.width || r),
    height: options.image.height * (r.height || r)
  };
}

Usage

const { width, height } = getImageSize({
  container: {width: 100, height: 100},
  image: {width: 200, height: 50},
  size: 'cover' // 'contain' | 'auto' | '100% 100%'
});

Playground

I created a jsbin here if you want to take a look at what I mean with systematic (it also has a scale method that I thought was not needed in this answer but very useful for something other than the usual).

like image 15
pgarciacamou Avatar answered Oct 02 '22 10:10

pgarciacamou


Thanks to mdi for pointing me in the right direction, but that didn't seem quite right. This is the solution that worked for me:

    $imgRatio = $imageHeight / $imageWidth;
    $canvasRatio = $canvasHeight / $canvasWidth;

    if ($canvasRatio > $imgRatio) {
        $finalHeight = $canvasHeight;
        $scale = $finalHeight / $imageHeight;
        $finalWidth = round($imageWidth * $scale , 0);
    } else {
        $finalWidth = $canvasWidth;
        $scale = $finalWidth / $imageWidth;
        $finalHeight = round($imageHeight * $scale , 0);
    }
like image 5
antriver Avatar answered Oct 02 '22 10:10

antriver