---
title: ImageMagick Image Cropping & Transparency
slug: imagemagick-image-cropping
category: Media
summary: Trim whitespace, make backgrounds transparent, and build centered/padded square variants of an uploaded logo with ImageMagick — then swap the result into a Rails view.
tags: [imagemagick, images, assets, logo, cli]
status: stable
visibility: public
source_project: llamapress.ai
layers: [view]
---

# ImageMagick Image Cropping & Transparency

> ⚠️ **Cookbook example — not live code.** (KEEP THIS CALLOUT.) Every code block below
> is an **example snippet**, **not part of the llamapress.ai codebase**, and **not
> running on this server**. This is a reference recipe for a **Leo instance (an AI coding
> agent) to implement in its own app** — read it to understand the pattern, then recreate
> it there.

A user uploads a logo or icon that arrives with a big white background, off-center
placement, or a wildly non-square aspect ratio. This recipe turns that raw upload into a
clean asset — whitespace trimmed, background transparent, centered in a square canvas
with optional breathing room — using nothing but the `convert` / `identify` CLI, then
swaps it into the view with `image_tag`.

> **When to use:** a real image file (logo, icon, product shot) needs cleanup before it
> looks right in the UI. **When not to:** you need per-user runtime processing at scale —
> that's Active Storage variants (`image.variant(...)`), not one-off shell commands.

---

## The 80/20 in one breath

1. `identify` the original to learn its dimensions and aspect ratio.
2. One `convert` pass: `-transparent white` (with fuzz) + `-trim +repage` → trimmed,
   transparent PNG.
3. Read the trimmed width/height, take the larger side, and `-extent` onto a
   transparent square canvas (add 25–50% padding if the design needs breathing room).
4. Verify transparency with `identify -format '%[opaque]'` — expect `False`.
5. Drop the file in `app/assets/images/` and reference it with `image_tag`.

---

## Layer 1 — Inspect the original

```bash
# run inside the Rails container (e.g. docker compose exec llamapress bash)
identify /rails/app/assets/images/<filename>
```

This returns dimensions, resolution, and color space. Note the aspect ratio — a
wide/horizontal logo behaves differently from a tall/vertical one when you square it up.

## Layer 2 — Trim whitespace & make the background transparent

```bash
# one pass: background → transparent, then auto-crop to the content
convert <input>.png \
  -fuzz 15% -transparent white \
  -bordercolor none -border 1x1 \
  -fuzz 20% -trim +repage \
  <output>-trimmed.png
```

**Why each flag:**

- `-fuzz 15% -transparent white` — matches near-white pixels (not just pure `#FFFFFF`),
  removing the background fringe
- `-bordercolor none -border 1x1` — adds a 1px transparent border so `-trim` works even
  if the artwork touches an edge
- `-fuzz 20% -trim +repage` — auto-crops the canvas to the content's bounding box;
  `+repage` resets the virtual canvas metadata so later operations see the new size

## Layer 3 — Build square variants

Read the trimmed dimensions, then extend the canvas (not the image) to a square:

```bash
W=$(identify -format '%w' logo-trimmed.png)
H=$(identify -format '%h' logo-trimmed.png)
if [ "$W" -gt "$H" ]; then SIZE=$W; else SIZE=$H; fi

# Tight square — centered, no extra padding
convert logo-trimmed.png -background none -gravity center \
  -extent ${SIZE}x${SIZE} logo-square.png

# Padded square — 25% breathing room
PAD=$((SIZE + SIZE/4))
convert logo-trimmed.png -background none -gravity center \
  -extent ${PAD}x${PAD} logo-padded.png

# Wide padded square — 50% breathing room
WPAD=$((SIZE + SIZE/2))
convert logo-trimmed.png -background none -gravity center \
  -extent ${WPAD}x${WPAD} logo-widepad.png
```

Then verify the result actually has transparency:

```bash
identify -format '%[channels] - opaque=%[opaque]' logo-padded.png
# expected: srgba - opaque=False   (alpha channel present, not fully opaque)
```

## Layer 4 — Swap into the view

```erb
<%# app/views/pages/home.html.erb (or wherever the placeholder lives) %>
<%= image_tag "logo-padded.png", alt: "Company Name", class: "w-48 h-48 object-contain" %>
```

No path prefix is needed for files in `app/assets/images/`. Remove any old inline SVG,
emoji, or gradient `div` that was standing in as a placeholder before the real image
existed.

---

## Gotchas (the hard-won stuff)

- **Use shell arithmetic (`$((...))`) for the padding math, not `bc`** — `bc` is often
  not installed in the container, and the failure is a silent empty variable.
- **`-extent` needs `-background none`**, or the new canvas area fills with white and
  you've undone the transparency you just created.
- **The 1px transparent border before `-trim` is not optional** — if the artwork touches
  the image edge, `-trim` has no uniform border to detect and either crops nothing or
  crops wrong.
- **Always `+repage` after `-trim`.** Without it the virtual canvas keeps the original
  offset/size, and later `-extent`/`-gravity` operations position the image bizarrely.
- **Tune the fuzz per image.** `15–20%` handles typical JPEG-artifact fringe around a
  white background; drop it if the logo itself contains light grays that are getting
  eaten, raise it if a halo of off-white pixels survives.
- **ImageMagick 7 renames `convert` to `magick`.** If `convert` is missing, try
  `magick` (and `magick identify`). Check with `command -v convert magick`.
- **Run the commands where the asset lives.** On a Leo box the Rails app is inside the
  `llamapress` container (`/rails/app/assets/images/`) — a host-side path won't be the
  same file.
- **New assets may need a recompile in production-style setups.** In development the
  asset pipeline picks the file up automatically; if the image 404s, clobber assets and
  restart the web container.

---

## Files this pattern touches

```
app/assets/images/<logo>-trimmed.png     (generated)
app/assets/images/<logo>-square.png      (generated)
app/assets/images/<logo>-padded.png      (generated)
app/views/<wherever the image renders>.html.erb
```

## How to adapt to your schema

1. Swap `white` in `-transparent white` for whatever the actual background color is
   (`'#f5f5f5'`, etc.) — `identify -format '%[pixel:p{0,0}]' input.png` reads the
   top-left pixel if you're unsure.
2. Pick the variant the layout needs: tight square for avatars/favicons, 25% padding
   for cards, 50% for hero placements where the logo shouldn't touch the container edge.
3. For non-square targets (e.g. a 3:1 navbar strip), pass the exact geometry to
   `-extent` (`-extent 900x300`) instead of computing a square.
4. If the image is user-uploaded via Active Storage rather than a static asset, apply
   the same flags through a variant
   (`image.variant(fuzz: '15%', transparent: 'white', trim: true)`) instead of shelling
   out.

## Common flags reference

| Flag | Purpose |
|------|---------|
| `-fuzz N%` | Tolerance for matching similar colors (0% = exact, 100% = everything) |
| `-transparent color` | Make all pixels matching `color` transparent |
| `-trim` | Auto-crop to remove uniform border |
| `+repage` | Reset virtual canvas after trim |
| `-background none` | Fill extra canvas area with transparency |
| `-gravity center` | Anchor position for extent/resize |
| `-extent WxH` | Resize the canvas (not the image) |
| `-strip` | Remove metadata/EXIF |
| `-quality N` | Compression level (0–100) |
