Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 18, 2025

Problem

Images uploaded in HEIC and JXL formats were displaying rotated 90 degrees clockwise from their original orientation. This occurred because the orientation metadata embedded by phones and cameras was not being properly read during thumbnail creation, causing images to display in their raw sensor orientation rather than the intended viewing orientation.

Root Cause

In the thumbnail creation process (CreateThumbnail function):

  1. HEIC images: Used the generic imagemeta.Decode() function which doesn't properly handle HEIF/HEIC format metadata, failing to extract the correct orientation value
  2. JXL images: Didn't attempt to read orientation metadata at all - orientation was hardcoded to 1 (no rotation)

Solution

This PR makes minimal, surgical changes to properly extract and apply orientation metadata for both formats:

HEIC Images

  • Changed from imagemeta.Decode() to imagemeta.DecodeHeif() - the format-specific decoder designed for HEIF/HEIC metadata extraction
  • This ensures the orientation tag is correctly read from HEIC files

JXL Images

  • Added orientation metadata extraction using imagemeta.Decode() with graceful fallback
  • If metadata extraction fails (JXL has limited library support), defaults to orientation 1
  • This handles both JXL files with embedded EXIF and those without

Error Message Improvements

  • Fixed misleading error messages that incorrectly reported "avif image" errors for HEIC and JXL decoding failures

Technical Details

The extracted orientation values are passed to the existing ApplyOrientation() function in utils/image.go, which correctly handles all 8 EXIF orientation transformations during thumbnail generation. This ensures images display in their intended orientation regardless of how the camera sensor captured them.

Testing

  • ✅ All existing tests pass
  • ✅ No linting issues
  • ✅ CodeQL security scan: 0 vulnerabilities

Fixes #1067

Co-authored-by: tankerkiller125 [email protected]

Original prompt

This section details on the original issue you should resolve

<issue_title>Uploaded HEIC images are rotated 90 degrees</issue_title>
<issue_description>### First Check

  • This is not a feature request
  • I added a very descriptive title to this issue.
  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the documentation, with the integrated search.
  • I already read the docs and didn't find an answer.
  • I can replicate the issue inside the Demo install.

Homebox Version

Version: v0.21.0 Build: 27e9eb2

What is the issue you are experiencing?

When using HEIC images for items, the image will display in the intereface 90 degrees clockwise from their original orientation.

How can the maintainer reproduce the issue?

Upload an HEIC image as a Item/Asset image.

Deployment

Unraid

OS Architechture

x86_64 (AMD, Intel)

Deployment Details

No response</issue_description>

<agent_instructions>The HEIC images and JXL images are rotated due to the metadata that phones and other devices set not being read, and accounted for during the creation of the thumbnail. Read the metadata from these formats and use the existing logic to transform as needed for the thumbnails.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@tankerkiller125 HEIC and JXL files are two formats that we currently can't read the metadata from to rotate the thumbnail appropriately.

The actual image is rotated, metadata causes it to get flipped to the correct orientation on devices. Because the thumbnailing service can't read the metadata on these formats it can't perform the rotation, nor apply the metadata to the thumbnail.</comment_new>

Fixes #983


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed thumbnail orientation for HEIC and JPEG XL images to display correctly based on actual image metadata rather than assuming default orientation.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 18, 2025

Walkthrough

The PR refines thumbnail generation for HEIC and JPEG XL formats by extracting actual image orientation from metadata instead of using hardcoded defaults. Orientation-aware metadata decoding is now applied to both formats before thumbnail creation.

Changes

Cohort / File(s) Summary
HEIC and JPEG XL Thumbnail Orientation Handling
backend/internal/data/repo/repo_item_attachments.go
Updated error messages for HEIC and JXL decoding operations. Added metadata-based orientation extraction for both formats using DecodeHeif (HEIC) and JXL metadata decoding, with orientation defaulting to 1 on decode failure. Resolved orientation is now passed to thumbnail generation instead of assuming orientation 1.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

The changes follow a consistent pattern applied to two image formats with straightforward metadata extraction and error handling improvements. Single-file scope with homogeneous modifications across HEIC and JXL code paths.

Possibly related PRs

Suggested labels

🕷️ bug, go

Security recommendations

  • Input validation: Ensure metadata decoding handles malformed or truncated image data gracefully to prevent crashes or unexpected behavior
  • Library versions: Verify that the HEIC/HEIF and JXL decoding libraries are kept up-to-date to address potential security vulnerabilities in image format parsing
  • Resource limits: Consider adding safeguards against resource exhaustion attacks through oversized or specially-crafted image metadata

Poem

🎨 Metadata whispers secrets true,
Orientation's dance, refined anew,
HEIC and JXL now understand,
Their angle's grace, precisely planned,
No more assumptions, just the facts! 📐

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The description provides a thorough overview with separate sections for the problem, root cause, solution details, technical implementation, and testing outcomes, but it omits the mandatory “What type of PR is this?” section and neither fills in nor removes the “Special notes for your reviewer” section from the template. Please add a “## What type of PR is this?” section with the appropriate category (e.g., bug) and either complete or remove the “Special notes for your reviewer” section to fully comply with the repository’s PR description template.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly and concisely summarizes the primary change—fixing HEIC and JXL image rotation by properly reading orientation metadata—and uses specific terminology without extraneous details or noise.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch copilot/fix-rotated-heic-images

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI changed the title [WIP] Fix issue with uploaded HEIC images being rotated 90 degrees Fix HEIC and JXL image rotation by properly reading orientation metadata Oct 18, 2025
Copilot AI requested a review from tankerkiller125 October 18, 2025 03:24
Copilot finished work on behalf of tankerkiller125 October 18, 2025 03:24
@tankerkiller125
Copy link
Contributor

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai bot added go Pull requests that update Go code 🕷️ bug Something isn't working labels Oct 22, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
backend/internal/data/repo/repo_item_attachments.go (2)

445-447: Bug: lost error on Tx creation

Returning nil on Tx failure reports success to caller.

Apply:

- if err != nil {
-   return nil
- }
+ if err != nil {
+   return err
+ }

261-266: Add deferred topic.Shutdown(ctx) after each pubsub.OpenTopic in repo_item_attachments.go

  • In backend/internal/data/repo/repo_item_attachments.go at line 261, insert defer topic.Shutdown(ctx) immediately after the OpenTopic call.
  • At line 727, do the same for the second OpenTopic.

No other OpenTopic without shutdown was found. Closing topics promptly prevents resource leaks and potential Denial-of-Service scenarios.

🧹 Nitpick comments (6)
backend/internal/data/repo/repo_item_attachments.go (6)

629-651: HEIC: fall back on missing/bad metadata and clamp orientation

Don’t fail thumbnailing if HEIC metadata is unreadable. Default to 1 and clamp to [1..8]; log at debug instead of error. This aligns with your JXL path and avoids unnecessary rollbacks.

Apply:

- log.Debug().Msg("reading original file orientation")
- imageMeta, err := imagemeta.DecodeHeif(bytes.NewReader(contentBytes))
- if err != nil {
-   log.Err(err).Msg("failed to decode heic file metadata")
-   err := tx.Rollback()
-   if err != nil {
-     return err
-   }
-   return err
- }
- orientation := uint16(imageMeta.Orientation)
+ log.Debug().Msg("reading original file orientation")
+ orientation := uint16(1) // default
+ imageMeta, err := imagemeta.DecodeHeif(bytes.NewReader(contentBytes))
+ if err != nil {
+   log.Debug().Err(err).Msg("unable to decode heic metadata; defaulting orientation to 1")
+ } else {
+   if o := uint16(imageMeta.Orientation); o >= 1 && o <= 8 {
+     orientation = o
+   } else {
+     log.Debug().Uint16("orientation", uint16(imageMeta.Orientation)).Msg("invalid heic orientation; defaulting to 1")
+   }
+ }

660-679: JXL: good fallback; also clamp orientation to [1..8]

Prevents invalid values from corrupting rotation.

Apply:

- orientation := uint16(1) // Default orientation
+ orientation := uint16(1) // Default orientation
  imageMeta, err := imagemeta.Decode(bytes.NewReader(contentBytes))
  if err != nil {
    log.Debug().Msg("unable to decode jxl metadata, using default orientation")
  } else {
-   orientation = uint16(imageMeta.Orientation)
+   if o := uint16(imageMeta.Orientation); o >= 1 && o <= 8 {
+     orientation = o
+   } else {
+     log.Debug().Uint16("orientation", uint16(imageMeta.Orientation)).Msg("invalid jxl orientation; defaulting to 1")
+   }
  }

533-542: Make extension-based content-type overrides case-insensitive

Uppercase extensions (e.g., .HEIC, .JXL) currently won’t match.

Apply:

-if contentType == "application/octet-stream" {
-  switch {
-  case strings.HasSuffix(title, ".heic") || strings.HasSuffix(title, ".heif"):
-    contentType = "image/heic"
-  case strings.HasSuffix(title, ".avif"):
-    contentType = "image/avif"
-  case strings.HasSuffix(title, ".jxl"):
-    contentType = "image/jxl"
-  }
-}
+if contentType == "application/octet-stream" {
+  switch strings.ToLower(filepath.Ext(title)) {
+  case ".heic", ".heif":
+    contentType = "image/heic"
+  case ".avif":
+    contentType = "image/avif"
+  case ".jxl":
+    contentType = "image/jxl"
+  }
+}

461-463: Use typed enum for attachment type “thumbnail”

Avoid string drift; be consistent with other usages of attachment.Type*.

Apply:

- att := tx.Attachment.Create().
+ att := tx.Attachment.Create().
    SetID(uuid.New()).
    SetTitle(fmt.Sprintf("%s-thumb", title)).
-   SetType("thumbnail")
+   SetType(attachment.TypeThumbnail)

And:

- attachment.TypeNEQ("thumbnail"),
+ attachment.TypeNEQ(attachment.TypeThumbnail),

Also applies to: 716-717


688-688: Typo in error string

“thumnails” → “thumbnails”.

Apply:

- return fmt.Errorf("file type %s is not supported for thumbnail creation or document thumnails disabled", title)
+ return fmt.Errorf("file type %s is not supported for thumbnail creation or document thumbnails disabled", title)

441-710: Security hardening: cap decoded pixels and unify metadata-fallback behavior

  • Add a maximum pixel count (e.g., 100MP) to mitigate decode-bombs; validate bounds.Dx()*bounds.Dy() before scaling.
  • Prefer graceful default-orientation fallback for all formats (JPEG/PNG/GIF/WebP) when EXIF read fails, mirroring JXL.
  • Consider context deadlines for long decodes; respect ctx.Done() to abort.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 397a1c6 and aae830c.

⛔ Files ignored due to path filters (1)
  • .scaffold/go.sum is excluded by !**/*.sum
📒 Files selected for processing (1)
  • backend/internal/data/repo/repo_item_attachments.go (2 hunks)
🔇 Additional comments (1)
backend/internal/data/repo/repo_item_attachments.go (1)

632-632: LGTM: corrected error messages

Nice cleanup: error messages now correctly reference HEIC and JPEG XL.

Also applies to: 663-663

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🕷️ bug Something isn't working go Pull requests that update Go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Uploaded HEIC images are rotated 90 degrees

2 participants