image scaling
aspect ratio
screen fitting
algorithm
mathematics

math/algorithm Fit image to screen retain aspect ratio

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Fitting an image to a screen while retaining its aspect ratio requires calculating a single scale factor that makes the image as large as possible without exceeding either the screen width or height. The algorithm computes two candidate scale factors (one for width, one for height) and picks the smaller one. This is known as "contain" or "fit" mode. The opposite — filling the screen and potentially cropping — picks the larger scale factor ("cover" mode). Both modes are used everywhere: CSS object-fit, image viewers, game rendering, and responsive design.

The Core Algorithm

Given an image of size (imgW, imgH) and a container of size (containerW, containerH):

 
1scaleX = containerW / imgW
2scaleY = containerH / imgH
3
4// "Contain" (fit entire image, may have letterboxing)
5scale = min(scaleX, scaleY)
6
7// "Cover" (fill container, may crop)
8scale = max(scaleX, scaleY)
9
10newWidth  = imgW * scale
11newHeight = imgH * scale

The "contain" scale ensures both dimensions fit within the container. The "cover" scale ensures no empty space, but the image may extend beyond the container.

Python Implementation

python
1def fit_contain(img_w, img_h, container_w, container_h):
2    """Scale image to fit inside container (letterbox)."""
3    scale = min(container_w / img_w, container_h / img_h)
4    new_w = round(img_w * scale)
5    new_h = round(img_h * scale)
6    return new_w, new_h
7
8def fit_cover(img_w, img_h, container_w, container_h):
9    """Scale image to fill container (may crop)."""
10    scale = max(container_w / img_w, container_h / img_h)
11    new_w = round(img_w * scale)
12    new_h = round(img_h * scale)
13    return new_w, new_h
14
15# Example: 1920x1080 image on a 800x600 screen
16print(fit_contain(1920, 1080, 800, 600))  # (800, 450) — fits width, letterbox top/bottom
17print(fit_cover(1920, 1080, 800, 600))    # (1067, 600) — fills height, crops sides
18
19# Example: 600x800 portrait image on a 1024x768 screen
20print(fit_contain(600, 800, 1024, 768))   # (576, 768) — fits height, pillarbox sides
21print(fit_cover(600, 800, 1024, 768))     # (1024, 1365) — fills width, crops top/bottom

Centering the Scaled Image

After scaling, center the image in the container by calculating the offset:

python
1def fit_and_center(img_w, img_h, container_w, container_h):
2    new_w, new_h = fit_contain(img_w, img_h, container_w, container_h)
3    offset_x = (container_w - new_w) / 2
4    offset_y = (container_h - new_h) / 2
5    return new_w, new_h, offset_x, offset_y
6
7w, h, x, y = fit_and_center(1920, 1080, 800, 600)
8print(f"Size: {w}x{h}, Position: ({x}, {y})")
9# Size: 800x450, Position: (0.0, 75.0)

The offsets create equal-sized letterbox bars (horizontal) or pillarbox bars (vertical).

JavaScript / Canvas Implementation

javascript
1function fitContain(imgW, imgH, containerW, containerH) {
2    const scale = Math.min(containerW / imgW, containerH / imgH);
3    return {
4        width: Math.round(imgW * scale),
5        height: Math.round(imgH * scale),
6        x: Math.round((containerW - imgW * scale) / 2),
7        y: Math.round((containerH - imgH * scale) / 2)
8    };
9}
10
11// Draw on canvas
12const canvas = document.getElementById('canvas');
13const ctx = canvas.getContext('2d');
14const img = new Image();
15img.onload = () => {
16    const { width, height, x, y } = fitContain(
17        img.width, img.height, canvas.width, canvas.height
18    );
19    ctx.clearRect(0, 0, canvas.width, canvas.height);
20    ctx.drawImage(img, x, y, width, height);
21};
22img.src = 'photo.jpg';

CSS object-fit

CSS handles this natively with the object-fit property:

css
1/* Contain — fit entirely, letterbox if needed */
2img.contain {
3    width: 100%;
4    height: 100%;
5    object-fit: contain;
6}
7
8/* Cover — fill container, crop if needed */
9img.cover {
10    width: 100%;
11    height: 100%;
12    object-fit: cover;
13}
14
15/* Scale down — like contain, but never scale up */
16img.scale-down {
17    width: 100%;
18    height: 100%;
19    object-fit: scale-down;
20}

object-fit: contain is exactly the min(scaleX, scaleY) algorithm. object-fit: cover uses max(scaleX, scaleY).

Swift / iOS Implementation

swift
1func fitContain(imageSize: CGSize, containerSize: CGSize) -> CGRect {
2    let scaleX = containerSize.width / imageSize.width
3    let scaleY = containerSize.height / imageSize.height
4    let scale = min(scaleX, scaleY)
5
6    let newWidth = imageSize.width * scale
7    let newHeight = imageSize.height * scale
8    let x = (containerSize.width - newWidth) / 2
9    let y = (containerSize.height - newHeight) / 2
10
11    return CGRect(x: x, y: y, width: newWidth, height: newHeight)
12}
13
14// UIImageView does this automatically:
15let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 400, height: 300))
16imageView.contentMode = .scaleAspectFit   // Contain
17// imageView.contentMode = .scaleAspectFill  // Cover
18imageView.clipsToBounds = true  // Clip overflow for cover mode

Never Scale Up (Optional Constraint)

Sometimes you want to fit the image but never scale it larger than its original size:

python
1def fit_scale_down(img_w, img_h, container_w, container_h):
2    """Like contain, but never upscale."""
3    scale = min(container_w / img_w, container_h / img_h, 1.0)
4    new_w = round(img_w * scale)
5    new_h = round(img_h * scale)
6    return new_w, new_h
7
8# Small image in a large container — stays original size
9print(fit_scale_down(200, 150, 800, 600))  # (200, 150)
10
11# Large image in a small container — scales down
12print(fit_scale_down(1920, 1080, 800, 600))  # (800, 450)

Adding 1.0 as a third candidate for min() caps the scale factor at 100%.

Common Pitfalls

  • Dividing integers and getting zero: In languages with integer division (C, Java, Go), containerW / imgW truncates to zero if the container is smaller than the image. Cast to float first: (float)containerW / imgW.
  • Not rounding the final dimensions: Floating-point multiplication can produce values like 799.9999. Without rounding, the image may be 1 pixel too small, leaving a visible gap. Always round to the nearest integer.
  • Forgetting to clip in cover mode: Cover mode produces dimensions larger than the container. Without clipping (overflow: hidden in CSS, clipsToBounds = true in UIKit), the image overflows its container visually.
  • Confusing contain and cover: "Contain" fits the entire image (may have bars). "Cover" fills the entire container (may crop). Mixing them up results in either unexpected cropping or unexpected letterboxing.
  • Ignoring device pixel ratio for retina displays: On high-DPI screens, the physical pixel count is 2x or 3x the logical size. Scale calculations should use logical dimensions for layout but may need physical dimensions for selecting the appropriate image resolution.

Summary

  • Contain (fit): scale = min(containerW/imgW, containerH/imgH) — image fits entirely inside container
  • Cover (fill): scale = max(containerW/imgW, containerH/imgH) — image fills container, may crop
  • Center the scaled image with offset = (container - scaled) / 2
  • CSS object-fit: contain and object-fit: cover implement these algorithms natively
  • iOS UIImageView.contentMode uses .scaleAspectFit (contain) and .scaleAspectFill (cover)
  • Add min(scale, 1.0) to prevent upscaling small images beyond their native resolution

Course illustration
Course illustration

All Rights Reserved.