{"slug":"imagemagick-image-cropping","meta":{"title":"ImageMagick Image Cropping \u0026 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"]},"body":"# ImageMagick Image Cropping \u0026 Transparency\n\n\u003e ⚠️ **Cookbook example — not live code.** (KEEP THIS CALLOUT.) Every code block below\n\u003e is an **example snippet**, **not part of the llamapress.ai codebase**, and **not\n\u003e running on this server**. This is a reference recipe for a **Leo instance (an AI coding\n\u003e agent) to implement in its own app** — read it to understand the pattern, then recreate\n\u003e it there.\n\nA user uploads a logo or icon that arrives with a big white background, off-center\nplacement, or a wildly non-square aspect ratio. This recipe turns that raw upload into a\nclean asset — whitespace trimmed, background transparent, centered in a square canvas\nwith optional breathing room — using nothing but the `convert` / `identify` CLI, then\nswaps it into the view with `image_tag`.\n\n\u003e **When to use:** a real image file (logo, icon, product shot) needs cleanup before it\n\u003e looks right in the UI. **When not to:** you need per-user runtime processing at scale —\n\u003e that's Active Storage variants (`image.variant(...)`), not one-off shell commands.\n\n---\n\n## The 80/20 in one breath\n\n1. `identify` the original to learn its dimensions and aspect ratio.\n2. One `convert` pass: `-transparent white` (with fuzz) + `-trim +repage` → trimmed,\n   transparent PNG.\n3. Read the trimmed width/height, take the larger side, and `-extent` onto a\n   transparent square canvas (add 25–50% padding if the design needs breathing room).\n4. Verify transparency with `identify -format '%[opaque]'` — expect `False`.\n5. Drop the file in `app/assets/images/` and reference it with `image_tag`.\n\n---\n\n## Layer 1 — Inspect the original\n\n```bash\n# run inside the Rails container (e.g. docker compose exec llamapress bash)\nidentify /rails/app/assets/images/\u003cfilename\u003e\n```\n\nThis returns dimensions, resolution, and color space. Note the aspect ratio — a\nwide/horizontal logo behaves differently from a tall/vertical one when you square it up.\n\n## Layer 2 — Trim whitespace \u0026 make the background transparent\n\n```bash\n# one pass: background → transparent, then auto-crop to the content\nconvert \u003cinput\u003e.png \\\n  -fuzz 15% -transparent white \\\n  -bordercolor none -border 1x1 \\\n  -fuzz 20% -trim +repage \\\n  \u003coutput\u003e-trimmed.png\n```\n\n**Why each flag:**\n\n- `-fuzz 15% -transparent white` — matches near-white pixels (not just pure `#FFFFFF`),\n  removing the background fringe\n- `-bordercolor none -border 1x1` — adds a 1px transparent border so `-trim` works even\n  if the artwork touches an edge\n- `-fuzz 20% -trim +repage` — auto-crops the canvas to the content's bounding box;\n  `+repage` resets the virtual canvas metadata so later operations see the new size\n\n## Layer 3 — Build square variants\n\nRead the trimmed dimensions, then extend the canvas (not the image) to a square:\n\n```bash\nW=$(identify -format '%w' logo-trimmed.png)\nH=$(identify -format '%h' logo-trimmed.png)\nif [ \"$W\" -gt \"$H\" ]; then SIZE=$W; else SIZE=$H; fi\n\n# Tight square — centered, no extra padding\nconvert logo-trimmed.png -background none -gravity center \\\n  -extent ${SIZE}x${SIZE} logo-square.png\n\n# Padded square — 25% breathing room\nPAD=$((SIZE + SIZE/4))\nconvert logo-trimmed.png -background none -gravity center \\\n  -extent ${PAD}x${PAD} logo-padded.png\n\n# Wide padded square — 50% breathing room\nWPAD=$((SIZE + SIZE/2))\nconvert logo-trimmed.png -background none -gravity center \\\n  -extent ${WPAD}x${WPAD} logo-widepad.png\n```\n\nThen verify the result actually has transparency:\n\n```bash\nidentify -format '%[channels] - opaque=%[opaque]' logo-padded.png\n# expected: srgba - opaque=False   (alpha channel present, not fully opaque)\n```\n\n## Layer 4 — Swap into the view\n\n```erb\n\u003c%# app/views/pages/home.html.erb (or wherever the placeholder lives) %\u003e\n\u003c%= image_tag \"logo-padded.png\", alt: \"Company Name\", class: \"w-48 h-48 object-contain\" %\u003e\n```\n\nNo path prefix is needed for files in `app/assets/images/`. Remove any old inline SVG,\nemoji, or gradient `div` that was standing in as a placeholder before the real image\nexisted.\n\n---\n\n## Gotchas (the hard-won stuff)\n\n- **Use shell arithmetic (`$((...))`) for the padding math, not `bc`** — `bc` is often\n  not installed in the container, and the failure is a silent empty variable.\n- **`-extent` needs `-background none`**, or the new canvas area fills with white and\n  you've undone the transparency you just created.\n- **The 1px transparent border before `-trim` is not optional** — if the artwork touches\n  the image edge, `-trim` has no uniform border to detect and either crops nothing or\n  crops wrong.\n- **Always `+repage` after `-trim`.** Without it the virtual canvas keeps the original\n  offset/size, and later `-extent`/`-gravity` operations position the image bizarrely.\n- **Tune the fuzz per image.** `15–20%` handles typical JPEG-artifact fringe around a\n  white background; drop it if the logo itself contains light grays that are getting\n  eaten, raise it if a halo of off-white pixels survives.\n- **ImageMagick 7 renames `convert` to `magick`.** If `convert` is missing, try\n  `magick` (and `magick identify`). Check with `command -v convert magick`.\n- **Run the commands where the asset lives.** On a Leo box the Rails app is inside the\n  `llamapress` container (`/rails/app/assets/images/`) — a host-side path won't be the\n  same file.\n- **New assets may need a recompile in production-style setups.** In development the\n  asset pipeline picks the file up automatically; if the image 404s, clobber assets and\n  restart the web container.\n\n---\n\n## Files this pattern touches\n\n```\napp/assets/images/\u003clogo\u003e-trimmed.png     (generated)\napp/assets/images/\u003clogo\u003e-square.png      (generated)\napp/assets/images/\u003clogo\u003e-padded.png      (generated)\napp/views/\u003cwherever the image renders\u003e.html.erb\n```\n\n## How to adapt to your schema\n\n1. Swap `white` in `-transparent white` for whatever the actual background color is\n   (`'#f5f5f5'`, etc.) — `identify -format '%[pixel:p{0,0}]' input.png` reads the\n   top-left pixel if you're unsure.\n2. Pick the variant the layout needs: tight square for avatars/favicons, 25% padding\n   for cards, 50% for hero placements where the logo shouldn't touch the container edge.\n3. For non-square targets (e.g. a 3:1 navbar strip), pass the exact geometry to\n   `-extent` (`-extent 900x300`) instead of computing a square.\n4. If the image is user-uploaded via Active Storage rather than a static asset, apply\n   the same flags through a variant\n   (`image.variant(fuzz: '15%', transparent: 'white', trim: true)`) instead of shelling\n   out.\n\n## Common flags reference\n\n| Flag | Purpose |\n|------|---------|\n| `-fuzz N%` | Tolerance for matching similar colors (0% = exact, 100% = everything) |\n| `-transparent color` | Make all pixels matching `color` transparent |\n| `-trim` | Auto-crop to remove uniform border |\n| `+repage` | Reset virtual canvas after trim |\n| `-background none` | Fill extra canvas area with transparency |\n| `-gravity center` | Anchor position for extent/resize |\n| `-extent WxH` | Resize the canvas (not the image) |\n| `-strip` | Remove metadata/EXIF |\n| `-quality N` | Compression level (0–100) |\n"}