Skip to content

Awards badge: auto-pad non-square uploads to a square (#1410)#1411

Merged
jonfroehlich merged 1 commit into
masterfrom
1408-awards-badge-crop
Jun 28, 2026
Merged

Awards badge: auto-pad non-square uploads to a square (#1410)#1411
jonfroehlich merged 1 commit into
masterfrom
1408-awards-badge-crop

Conversation

@jonfroehlich

@jonfroehlich jonfroehlich commented Jun 28, 2026

Copy link
Copy Markdown
Member

What

Adds an option to pad a non-square Award badge upload to a centered square instead of cropping it — eliminating the editor's manual "pad it in an external tool before uploading" step.

The badge is cropped to a 1:1 square on the public Awards page, so a non-square logo otherwise gets content chopped off. Now a "Pad badge to a square (don't crop)" checkbox (default on) pads the upload with white margins (JPEG) or transparent margins (PNG/WebP), keeping the whole image centered.

Note: this branch also carries the earlier square-cropping commit (#1408, already closed) that this builds on, since that work isn't on master yet. The diff vs master therefore includes both.

How / why

The cropper's preview is client-side, so naive server-side padding would make the preview lie. Resolved with:

  1. Server-side Pillow padding — small and robust vs. a fiddly browser canvas + file-swap dance; works regardless of browser.
  2. A checkbox — the one bit of signal needed, since the cropper otherwise always forces a square box. No model/schema change (non-model form field).
  3. A CSS-only honest preview — when padding, the interactive cropper is hidden and an object-fit: contain letterbox preview mirrors exactly what the server saves.

When padding applies, a full-image crop box is stored so the existing crop_corners render path is a no-op.

Image format & compression notes

Padding always re-encodes the image — you can't insert margin pixels into a compressed JPEG/WebP stream (DCT/compressed blocks, not addressable pixels), so Pillow decodes to a bitmap, we paste centered, and the whole image is encoded again.

  • JPEG: re-encoded at fixed quality=92. We don't reuse the source's quantization tables — Pillow's quality="keep" only works on an unmodified image, and pasting onto a larger canvas modifies it. One extra, visually negligible generation of compression for the flat logos badges usually are.
  • WebP: saved lossless=True so a lossless source isn't silently degraded (Pillow's WebP default is lossy q80).
  • PNG/GIF: lossless anyway.
  • Already-square uploads: skipped entirely (return None) — original bytes never touched.

Scope

Award badge first. pad_image_to_square() is field-agnostic, so the same treatment can later extend to Person.image / Sponsor.icon.

Files

  • website/utils/fileutils.pypad_image_to_square()
  • website/admin/award_admin.py — checkbox, save_model wiring, Media
  • website/static/website/js/pad_to_square.js + css/pad_to_square.css — toggle + preview
  • website/tests/test_pad_image_to_square.py — 8 tests

Testing

python manage.py test website.tests.test_pad_image_to_square --settings=makeabilitylab.settings_test → 8 passing: padding logic (white/transparent margins, centering, WebP lossless, square no-op), save_model wiring, and an end-to-end admin add-page render. Maintainer verified the toggle/preview behavior in the local admin.

Screenshot

image

Closes #1410.

🤖 Generated with Claude Code

The Awards badge is cropped to a 1:1 square on the public page; for a
non-square logo that chops off content. Add a "Pad badge to a square
(don't crop)" option (default on) that pads the upload to a centered
square -- white margins for JPEG, transparent for PNG/WebP -- instead of
cropping. This replaces the editor's manual pre-upload padding step.

- pad_image_to_square() in fileutils.py: Pillow padding, EXIF-aware,
  format-preserving. Padding always re-encodes (you can't insert margin
  pixels into a compressed JPEG/WebP stream): JPEG at fixed quality=92,
  WebP saved lossless so a lossless source isn't degraded, PNG/GIF are
  lossless anyway, and already-square uploads return None (untouched).
  Returns a full-image crop box so crop_corners is a no-op on the result.
- AwardAdminForm checkbox + AwardAdmin.save_model wiring; pads only a
  freshly uploaded, non-square badge. No model/schema change.
- pad_to_square.js/.css: hide the cropper and show an honest
  object-fit:contain preview while padding; toggled via a pad-mode class
  on the form, so no load-order dependency on ml_cropper.js.
- Tests: padding logic (white/transparent/centering/webp/square no-op),
  save_model wiring, and the admin add-page render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jonfroehlich jonfroehlich merged commit a192020 into master Jun 28, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto-pad non-square badge uploads to a square (instead of cropping)

1 participant