Skip to content

fix(send): set Width/Height on image messages so clients keep the aspect ratio - #212

Open
mediam4kers wants to merge 2 commits into
evolution-foundation:mainfrom
mediam4kers:fix/image-dimensions
Open

mediam4kers wants to merge 2 commits into
evolution-foundation:mainfrom
mediam4kers:fix/image-dimensions

Conversation

@mediam4kers

@mediam4kers mediam4kers commented Oct 6, 2026 •

Copy link
Copy Markdown

Problem

ImageMessage is sent without Width/Height. WhatsApp clients use these fields to size the chat bubble before the media is downloaded. When they are missing, the iOS app shows every image in a square bubble and center-crops it.

We sent 1:1, 4:5, 3:4, 2:3 and 9:16 test images through /send/media (v0.7.2) to a group. On iPhone all five rendered in the same square, and the 4:5 image lost about 10% at the top and 10% at the bottom. The same image sent from the official app renders at its real ratio.

Fix

  • Reading the size. imageDimensions(fileData) reads the pixel size with image.DecodeConfig, so only the header is parsed, not the whole image.
  • Where it is set. Width/Height are set on every ImageMessage built from uploaded bytes:
    • /send/media by file and by URL, regular and newsletter;
    • carousel image headers;
    • reply-button image headers;
    • image status.
  • EXIF orientation. Phone cameras often store a JPEG sideways and mark it with EXIF orientation 5-8. Clients display it rotated, so for those orientations the dimensions are swapped to the displayed size.
    • The JPEG preview from makeJPEGThumbnail is rotated or mirrored the same way (orientations 2-8), so it no longer previews sideways.
    • The orientation is read from the APP1 EXIF segment with a small parser, with no new dependency.
  • Fallback. When the size cannot be read, both fields stay nil and the message is sent exactly as before.

Testing

  • Unit tests for:
    • PNG landscape and JPEG portrait;
    • invalid data;
    • EXIF orientation reading (with and without EXIF, and a PNG);
    • dimensions swapped for orientation 6 and 8 and not for 1 and 3;
    • a portrait preview for a sideways JPEG;
    • the 90° rotation of orientRGBA.
  • gofmt -l is clean and go test ./pkg/sendMessage/service/ passes. Both were run in a Docker build from tag 0.7.2 with this branch applied.
  • In production. The first commit (dimensions) runs on our production instance. /send/media responses now include imageMessage.width/height, for example 1080x1350 and 1080x1920.

🤖 Generated with Claude Code

…ect ratio

ImageMessage was sent without Width/Height. WhatsApp clients size the chat
bubble from these fields before the media is downloaded; without them the iOS
app falls back to a square bubble and center-crops every portrait or landscape
image (a 1080x1350 image loses ~10% at the top and bottom).

Read the pixel size with image.DecodeConfig (no full decode) and set it on every
ImageMessage built from an uploaded file: /send/media (file and URL, regular and
newsletter), carousel image headers and image status. When the size cannot be
read the fields stay nil and the message is sent as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

The PR fixes client-side image aspect-ratio rendering by reading image dimensions from file headers and attaching them to every uploaded ImageMessage path, while retaining backward-compatible behavior when dimensions cannot be determined.

Sequence diagram for attaching image dimensions to media messages

sequenceDiagram
    participant Client
    participant SendService
    participant ImageHeader
    participant MediaUpload
    participant WhatsApp

    Client->>SendService: sendMediaFileWithRetry() or sendMediaUrlWithRetry()
    SendService->>ImageHeader: imageDimensions(fileData)
    ImageHeader-->>SendService: width, height or nil, nil
    SendService->>MediaUpload: upload image data
    SendService->>WhatsApp: Send ImageMessage with Width and Height
    WhatsApp-->>Client: Image bubble sized to image ratio
Loading

File-Level Changes

Change Details Files
Adds header-only image dimension detection with graceful fallback for unreadable data.
  • Uses image.DecodeConfig to extract pixel width and height without fully decoding images.
  • Returns nil dimensions on invalid or unsupported image data, preserving prior send behavior.
  • Adds unit coverage for PNG landscape, JPEG portrait, and invalid input.
pkg/sendMessage/service/send_service.go
pkg/sendMessage/service/dimensions_test.go
Propagates detected dimensions into all supported uploaded image message variants.
  • Sets Width and Height for regular and newsletter media sent by file or URL.
  • Sets dimensions on carousel image headers.
  • Sets dimensions on image status messages.
pkg/sendMessage/service/send_service.go

Possibly linked issues

  • #GOWS/send: The PR directly fixes the issue by populating Width/Height for uploaded and URL-sourced image messages.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="pkg/sendMessage/service/send_service.go" line_range="1046" />
<code_context>
 			// particular). On failure jpegThumb is nil and the message is sent
 			// without a preview rather than failing the request.
 			jpegThumb := makeJPEGThumbnail(fileData, 72)
+			imgWidth, imgHeight := imageDimensions(fileData)
 			if isNewsletter {
 				// Newsletter: SEM MediaKey e FileEncSHA256
</code_context>
<issue_to_address>
**Button images use square sizing**

When a button message sends a non-square image URL, `SendButton` builds the uploaded button `ImageMessage` without calling `imageDimensions` or setting `Width` and `Height`, so clients fall back to square sizing and crop the image to the wrong aspect ratio.

Call `imageDimensions` on the downloaded image and set the returned `Width` and `Height` on the button image message.

Also at `pkg/sendMessage/service/send_service.go:1057-1058`, `pkg/sendMessage/service/send_service.go:1351`, `pkg/sendMessage/service/send_service.go:2147-2148`.
</issue_to_address>

### Comment 2
<location path="pkg/sendMessage/service/send_service.go" line_range="2148" />
<code_context>
+// center-crops portrait and landscape images. It returns nil pointers when the
+// size cannot be read, so the message is still sent (just without dimensions).
+func imageDimensions(fileData []byte) (width, height *uint32) {
+	cfg, _, err := image.DecodeConfig(bytes.NewReader(fileData))
+	if err != nil || cfg.Width < 1 || cfg.Height < 1 {
+		return nil, nil
</code_context>
<issue_to_address>
**Rotated JPEGs get wrong dimensions**

When an uploaded JPEG has EXIF orientation 5, 6, 7, or 8, `image.DecodeConfig` returns the encoded dimensions, and `imageDimensions` copies them unchanged, so the outgoing `Width` and `Height` have the wrong aspect ratio and clients size the bubble incorrectly.

Read the JPEG orientation and swap the dimensions for orientations 5 through 8 before returning them.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 2 findings to address first, and the change adds image dimensions to outbound message metadata, so an incorrect or unsupported dimension could be retained in already-sent messages and cause clients to render the image with the wrong aspect ratio. Reverting stops future messages from using the values, but does not change messages that were already sent; the impact is bounded to presentation and can generally be corrected by resending the image.

Blocking findings: pkg/sendMessage/service/send_service.go:1046, pkg/sendMessage/service/send_service.go:2148


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread pkg/sendMessage/service/send_service.go
Comment thread pkg/sendMessage/service/send_service.go Outdated
…set dimensions on buttons header image

Review follow-up. Phone cameras often store a JPEG sideways with an EXIF
orientation of 5-8 and clients display it rotated, so Width/Height must be the
displayed size: imageDimensions now swaps them for those orientations. The JPEG
preview built by makeJPEGThumbnail is also rotated/mirrored the same way
(orientations 2-8), otherwise it previews sideways. The orientation is read from
the APP1 EXIF segment with a small parser (no new dependency).

The image header of reply-button messages was the one remaining ImageMessage
without Width/Height; it now gets them too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant