Toolmingo
Guides8 min read

How to Crop an Image (JavaScript, Python, Go, PHP)

Learn how to crop images programmatically — fixed coordinates, aspect-ratio crop, center crop, and thumbnail generation — with code examples in JavaScript, Python, Go, and PHP.

How to Crop an Image

Cropping is the most fundamental image editing operation — selecting a rectangular region and discarding everything outside it. Whether you are building a profile-photo uploader, a thumbnail pipeline, or a document scanner, you will eventually need to crop images in code. This guide explains the cropping model, common patterns, and working implementations in JavaScript, Python, Go, and PHP.


The Cropping Model

Every crop operation needs four numbers:

Parameter Meaning
x Left edge of the crop region (pixels from the left)
y Top edge of the crop region (pixels from the top)
width Width of the crop region
height Height of the crop region

The output image is width × height pixels. The origin (0, 0) is always the top-left corner of the source image.

Source image (800 × 600)
┌──────────────────────────┐
│                          │
│    ┌──────────┐          │
│    │ crop     │          │
│    │ x=100    │          │
│    │ y=50     │          │
│    │ w=300    │          │
│    │ h=200    │          │
│    └──────────┘          │
│                          │
└──────────────────────────┘

Bounds check: x + width ≤ image_width and y + height ≤ image_height. Exceeding the bounds causes errors or silent clipping depending on the library.


Common Cropping Patterns

1. Fixed-coordinate crop

You know exactly which region you want. Pass x, y, width, height directly.

Use case: cropping a signature region from a scanned form, extracting a logo from a screenshot.

2. Center crop

Take the middle of an image. Given a target size tw × th:

x = (image_width  - tw) / 2
y = (image_height - th) / 2

Use case: generating square thumbnails from landscape or portrait photos.

3. Aspect-ratio crop

Crop to a specific ratio (e.g. 16:9) using as much of the image as possible:

if image_width / image_height > target_ratio:
    # image is wider — constrain by height
    crop_height = image_height
    crop_width  = crop_height * target_ratio
else:
    # image is taller — constrain by width
    crop_width  = image_width
    crop_height = crop_width / target_ratio

x = (image_width  - crop_width)  / 2
y = (image_height - crop_height) / 2

Use case: video thumbnails, social media cards, responsive image variants.

4. Smart / face-aware crop

Detect the subject (face, object) first, then place the crop window around it. This is a separate topic (object detection), but the cropping step is still the same four-number rectangle.


JavaScript

Browser: Canvas API

/**
 * Crop an image using the Canvas API (browser).
 * @param {HTMLImageElement} img - loaded <img> element
 * @param {number} x - left edge
 * @param {number} y - top edge
 * @param {number} width - crop width
 * @param {number} height - crop height
 * @returns {Promise<Blob>} - cropped image as PNG Blob
 */
function cropImage(img, x, y, width, height) {
  const canvas = document.createElement("canvas");
  canvas.width  = width;
  canvas.height = height;

  const ctx = canvas.getContext("2d");
  // drawImage(source, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
  ctx.drawImage(img, x, y, width, height, 0, 0, width, height);

  return new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
}

// Center-crop example
function centerCrop(img, size) {
  const x = Math.floor((img.naturalWidth  - size) / 2);
  const y = Math.floor((img.naturalHeight - size) / 2);
  return cropImage(img, x, y, size, size);
}

Node.js: Sharp

Sharp is the standard server-side image library for Node.js.

import sharp from "sharp";

// Fixed-coordinate crop
await sharp("input.jpg")
  .extract({ left: 100, top: 50, width: 300, height: 200 })
  .toFile("cropped.jpg");

// Center crop to 400×400
async function centerCropFile(inputPath, outputPath, size) {
  const meta = await sharp(inputPath).metadata();
  const left = Math.floor((meta.width  - size) / 2);
  const top  = Math.floor((meta.height - size) / 2);

  await sharp(inputPath)
    .extract({ left, top, width: size, height: size })
    .toFile(outputPath);
}

// Aspect-ratio crop (16:9)
async function aspectRatioCrop(inputPath, outputPath, ratioW = 16, ratioH = 9) {
  const meta   = await sharp(inputPath).metadata();
  const ratio  = ratioW / ratioH;
  let cropW, cropH;

  if (meta.width / meta.height > ratio) {
    cropH = meta.height;
    cropW = Math.floor(cropH * ratio);
  } else {
    cropW = meta.width;
    cropH = Math.floor(cropW / ratio);
  }

  const left = Math.floor((meta.width  - cropW) / 2);
  const top  = Math.floor((meta.height - cropH) / 2);

  await sharp(inputPath)
    .extract({ left, top, width: cropW, height: cropH })
    .toFile(outputPath);
}

Python

The Pillow library uses a (left, upper, right, lower) box tuple — note the difference from width/height.

from PIL import Image

def crop_image(input_path: str, output_path: str,
               x: int, y: int, width: int, height: int) -> None:
    """Crop using top-left corner + dimensions."""
    with Image.open(input_path) as img:
        box = (x, y, x + width, y + height)
        cropped = img.crop(box)
        cropped.save(output_path)

def center_crop(input_path: str, output_path: str, size: int) -> None:
    """Crop a square from the centre of the image."""
    with Image.open(input_path) as img:
        w, h = img.size
        x = (w - size) // 2
        y = (h - size) // 2
        cropped = img.crop((x, y, x + size, y + size))
        cropped.save(output_path)

def aspect_ratio_crop(input_path: str, output_path: str,
                      ratio_w: int = 16, ratio_h: int = 9) -> None:
    """Centre-crop to the given aspect ratio."""
    with Image.open(input_path) as img:
        w, h = img.size
        ratio = ratio_w / ratio_h

        if w / h > ratio:
            crop_h = h
            crop_w = int(crop_h * ratio)
        else:
            crop_w = w
            crop_h = int(crop_w / ratio)

        x = (w - crop_w) // 2
        y = (h - crop_h) // 2
        cropped = img.crop((x, y, x + crop_w, y + crop_h))
        cropped.save(output_path)

Pillow box format:

Parameter Meaning
left x (pixels from left)
upper y (pixels from top)
right x + width
lower y + height

Go

The standard library image package includes a SubImage method on most image types.

package main

import (
    "image"
    "image/jpeg"
    "image/png"
    "os"
    "path/filepath"
    "strings"
)

// CropImage crops src to the given rectangle and saves as PNG or JPEG.
func CropImage(src image.Image, x, y, width, height int, outputPath string) error {
    type subImager interface {
        SubImage(r image.Rectangle) image.Image
    }

    si, ok := src.(subImager)
    if !ok {
        return fmt.Errorf("image does not support SubImage")
    }

    rect    := image.Rect(x, y, x+width, y+height)
    cropped := si.SubImage(rect)

    out, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer out.Close()

    ext := strings.ToLower(filepath.Ext(outputPath))
    if ext == ".jpg" || ext == ".jpeg" {
        return jpeg.Encode(out, cropped, &jpeg.Options{Quality: 90})
    }
    return png.Encode(out, cropped)
}

// CenterCrop crops a square of `size` from the centre.
func CenterCrop(src image.Image, size int, outputPath string) error {
    b := src.Bounds()
    x := (b.Dx() - size) / 2
    y := (b.Dy() - size) / 2
    return CropImage(src, x, y, size, size, outputPath)
}

SubImage returns a view into the same pixel data — it does not copy pixels. To get an independent copy, encode to a buffer and re-decode, or draw onto a new image.RGBA.


PHP

GD (built into PHP) and Imagick (ImageMagick extension) both support cropping.

GD

<?php
/**
 * Crop an image file and save the result.
 */
function cropImage(
    string $inputPath,
    string $outputPath,
    int $x,
    int $y,
    int $width,
    int $height
): void {
    $src = imagecreatefromstring(file_get_contents($inputPath));
    if ($src === false) {
        throw new RuntimeException("Cannot open image: $inputPath");
    }

    $dst = imagecrop($src, ['x' => $x, 'y' => $y, 'width' => $width, 'height' => $height]);
    if ($dst === false) {
        imagedestroy($src);
        throw new RuntimeException("Crop failed — check bounds.");
    }

    $ext = strtolower(pathinfo($outputPath, PATHINFO_EXTENSION));
    match ($ext) {
        'jpg', 'jpeg' => imagejpeg($dst, $outputPath, 90),
        'png'         => imagepng($dst, $outputPath),
        'webp'        => imagewebp($dst, $outputPath, 90),
        default       => throw new RuntimeException("Unsupported format: $ext"),
    };

    imagedestroy($src);
    imagedestroy($dst);
}

// Center crop to square
function centerCrop(string $inputPath, string $outputPath, int $size): void {
    $src = imagecreatefromstring(file_get_contents($inputPath));
    $x   = (imagesx($src) - $size) / 2;
    $y   = (imagesy($src) - $size) / 2;
    cropImage($inputPath, $outputPath, (int)$x, (int)$y, $size, $size);
    imagedestroy($src);
}

Imagick

<?php
$img = new Imagick('input.jpg');
$img->cropImage(300, 200, 100, 50);   // width, height, x, y
$img->writeImage('cropped.jpg');
$img->destroy();

Note: Imagick parameter order is cropImage(width, height, x, y) — the opposite of x, y, width, height. Easy to mix up.


Quick Reference

Pattern Formula
Fixed crop x, y, width, height — direct
Center square x = (W−s)/2, y = (H−s)/2
16:9 from landscape cropH = H, cropW = H × 16/9, centre
16:9 from portrait cropW = W, cropH = W × 9/16, centre
4:3 thumbnail cropH = H or cropW = W (whichever fits), centre
Safe bounds check x+width ≤ W and y+height ≤ H

Common Pitfalls

1. Off-by-one at the right/bottom edge

If the crop region ends exactly at the image border, some libraries are exclusive (right = x + width) and some are inclusive. Always check the docs.

2. Pillow box is (left, upper, right, lower), not (x, y, w, h)

The most common Python mistake. right = x + width, lower = y + height.

3. Imagick parameter order is reversed

cropImage(width, height, x, y) — not (x, y, width, height).

4. Cropping a JPEG twice degrades quality

Each JPEG encode/decode cycle loses information. Decode → crop → encode once. Never crop by re-saving a JPEG without purpose.

5. Go SubImage shares memory

The returned image.Image from SubImage is a view, not a copy. Modifying the original will change the cropped view. For a true copy, draw into a new image.RGBA.


FAQ

Can I crop without losing image quality? Yes — if you save as PNG (lossless) or use JPEG lossless crop tools (e.g. jpegtran -crop). Standard JPEG encode after crop is lossy by definition.

How do I crop a circle instead of a rectangle? Crop a square first, then apply a circular mask (alpha channel). The "crop" is still rectangular; the circle is a transparency mask on top.

What's the difference between crop and resize? Crop removes pixels outside a region — the remaining pixels are unchanged. Resize keeps all content but scales the number of pixels up or down. You often crop first, then resize.

How do I crop to a percentage of the image? Multiply percentages by pixel dimensions: x = pct_x * width / 100, then use those pixel values.

Does cropping change the file size? Yes. A smaller crop region = fewer pixels = smaller file (all else equal). But the final size also depends on image complexity and format.

What resolution should I target for profile photos? 400×400 px is a common minimum. Most platforms accept up to 2000×2000 px and resize internally. A 400–800 px square PNG or JPEG at 85% quality is the safe default.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools