You have a 1600 × 900 image.
You need to place it into a 1080 × 1350 Instagram-style canvas.
Three different tools give you three different results:
- one crops the left and right edges
- one adds empty space
- one makes everything look unnaturally tall
None of those tools is necessarily broken.
They are applying different image fitting rules.
Frontend developers use these rules constantly through CSS properties such as object-fit, but the underlying math is simple enough that it is worth understanding directly.
Start with aspect ratio
Aspect ratio is:
width / height
For our source:
1600 / 900
= 1.777...
That is approximately:
16:9
For the target:
1080 / 1350
= 0.8
That is:
4:5
So we are trying to fit a wide image into a portrait box.
There is no way to do that while simultaneously:
- showing every source pixel
- filling every target pixel
- preserving the original aspect ratio
You must sacrifice something.
That is the entire reason contain, cover, and stretch exist.
Contain
contain means:
Scale the image until the entire image fits inside the target.
The scale factor is:
containScale =
min(
targetWidth / sourceWidth,
targetHeight / sourceHeight
)
Using our values:
targetWidth / sourceWidth
= 1080 / 1600
= 0.675
targetHeight / sourceHeight
= 1350 / 900
= 1.5
Take the smaller value:
containScale = 0.675
The final image becomes:
1600 × 0.675 = 1080
900 × 0.675 = 607.5
So the rendered image is approximately:
1080 × 608
inside a:
1080 × 1350
canvas.
Result:
image fully visible
+ aspect ratio preserved
+ empty space remains
The vertical empty space is:
1350 - 608
= 742px
If centered:
371px top
371px bottom
Cover
cover means:
Scale the image until the entire target is filled.
The formula is:
coverScale =
max(
targetWidth / sourceWidth,
targetHeight / sourceHeight
)
We already calculated:
0.675
1.5
Take the larger value:
coverScale = 1.5
Now:
1600 × 1.5 = 2400
900 × 1.5 = 1350
The scaled image is:
2400 × 1350
inside a target of:
1080 × 1350
The target is completely filled.
But:
2400 - 1080
= 1320px
must be cropped horizontally.
If centered:
660px cropped from left
660px cropped from right
Result:
target fully filled
+ aspect ratio preserved
+ some source content removed
Stretch
Stretch means:
Ignore the source aspect ratio and force the image to exactly match the target.
So:
1600 × 900
becomes:
1080 × 1350
The horizontal scale is:
1080 / 1600
= 0.675
The vertical scale is:
1350 / 900
= 1.5
Those values are very different.
The result will be visibly distorted.
People become taller.
Circles become ovals.
Logos become malformed.
Stretch is useful in some generated textures or abstract cases, but it is rarely the right default for photography.
The three modes in one table
| Mode | Preserves aspect ratio? | Shows all source content? | Fills target completely? |
|---|---|---|---|
| Contain | Yes | Yes | No |
| Cover | Yes | No | Yes |
| Stretch | No | Yes | Yes |
This table explains most resizing UI.
JavaScript implementation
Let's implement the sizing calculations.
function containSize(
sourceWidth,
sourceHeight,
targetWidth,
targetHeight
) {
const scale = Math.min(
targetWidth / sourceWidth,
targetHeight / sourceHeight
);
return {
width: sourceWidth * scale,
height: sourceHeight * scale,
scale
};
}
For cover:
function coverSize(
sourceWidth,
sourceHeight,
targetWidth,
targetHeight
) {
const scale = Math.max(
targetWidth / sourceWidth,
targetHeight / sourceHeight
);
return {
width: sourceWidth * scale,
height: sourceHeight * scale,
scale
};
}
And stretch:
function stretchSize(
targetWidth,
targetHeight
) {
return {
width: targetWidth,
height: targetHeight
};
}
Calculating the centered crop for cover
If you use cover, you need to decide which part of the oversized result remains visible.
For centered placement:
function getCoverPlacement(
sourceWidth,
sourceHeight,
targetWidth,
targetHeight
) {
const scale = Math.max(
targetWidth / sourceWidth,
targetHeight / sourceHeight
);
const renderedWidth =
sourceWidth * scale;
const renderedHeight =
sourceHeight * scale;
return {
x:
(targetWidth - renderedWidth)
/ 2,
y:
(targetHeight - renderedHeight)
/ 2,
width: renderedWidth,
height: renderedHeight
};
}
Notice that x or y may be negative.
That is not a bug.
It means the oversized image extends outside the target canvas.
Draw the cover result to Canvas
function drawCover(
ctx,
image,
targetWidth,
targetHeight
) {
const scale = Math.max(
targetWidth / image.width,
targetHeight / image.height
);
const width =
image.width * scale;
const height =
image.height * scale;
const x =
(targetWidth - width) / 2;
const y =
(targetHeight - height) / 2;
ctx.drawImage(
image,
x,
y,
width,
height
);
}
This gives you behavior similar to:
object-fit: cover;
object-position: center;
Draw the contain result
function drawContain(
ctx,
image,
targetWidth,
targetHeight
) {
const scale = Math.min(
targetWidth / image.width,
targetHeight / image.height
);
const width =
image.width * scale;
const height =
image.height * scale;
const x =
(targetWidth - width) / 2;
const y =
(targetHeight - height) / 2;
ctx.drawImage(
image,
x,
y,
width,
height
);
}
If you want a padded background:
ctx.fillStyle = "#ffffff";
ctx.fillRect(
0,
0,
targetWidth,
targetHeight
);
drawContain(
ctx,
image,
targetWidth,
targetHeight
);
Crop and cover are related, but not identical UX concepts
From the user's perspective, "cover" often means automatic cropping.
But a good product may allow the user to control the crop anchor.
For example:
center
top
bottom
left
right
face position
manual focal point
If the subject is on the left side of a photo, centered cropping can cut it out.
So production resizing tools often need a focal point:
const focalPoint = {
x: 0.25,
y: 0.5
};
where:
0 = left/top
1 = right/bottom
Then the crop can be biased toward the important subject.
Why social-media resizing is mostly an aspect-ratio problem
Different platforms request different output canvases.
Examples include:
1:1
4:5
9:16
16:9
The annoying part is not just typing a new width and height.
It is deciding:
What should happen to the original composition when its aspect ratio does not match the destination?
That is why a useful social image workflow needs more than a width/height input.
It needs fit behavior.
On BatchSet's Social Media Resizer I expose this directly as modes such as:
Crop
Pad
Stretch
with predefined platform sizes.
The tool currently includes 28 presets across eight platforms, so the workflow becomes:
upload once
→ choose destinations
→ choose fit behavior
→ export
instead of manually creating each canvas.
If you already understand the geometry and just want the outputs:
Try BatchSet's Social Media Image Resizer
What about CSS?
For display-only UI, CSS already solves this elegantly.
.image {
width: 1080px;
height: 1350px;
object-fit: cover;
}
MDN's object-fit documentation explains these behaviors.
But CSS only changes how the image is displayed.
It does not necessarily create a new exported file at that target size.
When users need an actual:
1080 × 1350 PNG
or:
1080 × 1920 JPG
you need rendering and export logic.
Don't resize by changing only one dimension blindly
A common implementation mistake:
canvas.width = 1080;
canvas.height = 1350;
ctx.drawImage(
image,
0,
0,
1080,
1350
);
This is stretch.
If that is not intentional, the output will be distorted.
Always define the desired behavior explicitly.
A reusable fitting function
You can unify the modes:
function fitImage({
sourceWidth,
sourceHeight,
targetWidth,
targetHeight,
mode
}) {
if (mode === "stretch") {
return {
x: 0,
y: 0,
width: targetWidth,
height: targetHeight
};
}
const ratioX =
targetWidth / sourceWidth;
const ratioY =
targetHeight / sourceHeight;
const scale =
mode === "cover"
? Math.max(ratioX, ratioY)
: Math.min(ratioX, ratioY);
const width =
sourceWidth * scale;
const height =
sourceHeight * scale;
return {
x: (targetWidth - width) / 2,
y: (targetHeight - height) / 2,
width,
height
};
}
Now your renderer can work with a single object.
Final takeaway
Image resizing is not just "change width and height."
It is a trade-off between three goals:
preserve aspect ratio
show all source content
fill the entire target
You can reliably achieve two of the three.
That gives you:
- contain
- cover
- stretch
Once you know the formulas, image-resizing behavior stops feeling arbitrary.
And more importantly, you can choose the right mode intentionally instead of discovering it after your user's face gets cropped out of the final export.
Top comments (0)