Skip to content

JPEG export: JFIF APP0 marker causes split color rendering in Apple Photos (flat preview, saturated zoom) #21739

Description

@Mahai17

Describe the bug

JPEG files exported from darktable show inconsistent color rendering in Apple Photos on iPhone, iPad, and macOS:

  • Thumbnail / list-view preview: image appears flat and desaturated (colors roughly 20–30% less saturated than intended)
  • Zoomed / full-resolution view: image appears correctly saturated

The same image exported as TIFF renders correctly in both views. The behavior is reproducible across multiple Apple devices (iOS 17 / iPadOS 17 / macOS 14).

Root cause (forensically confirmed)

darktable's JPEG exporter sets in_color_space = JCS_RGB and calls jpeg_set_defaults(), which causes libjpeg to automatically write a JFIF APP0 marker (FF E0) into every exported JPEG.

Apple Photos uses two separate render paths:

  1. Thumbnail/preview path: sees JFIF → treats YCbCr data as "JFIF-managed" → skips ICC color management → sRGB pixel values are displayed on a Display P3 screen without sRGB→P3 conversion → image appears flat/desaturated
  2. Full-resolution zoom path: performs a complete decode, reads the ICC APP2 profile, converts sRGB → Display P3 correctly → image appears correctly saturated

Binary analysis of the exported file confirms:

Marker sequence (darktable JPEG):
  0x0000  SOI
  0x0002  APP0  JFIF v1.01       ← triggers Apple's JFIF path
  0x000E  APP1  EXIF (22 KB)
  0x5968  APP1  XMP  (36 KB)
  0xEA95  APP2  ICC_PROFILE      ← arrives ~60 KB into the file
  ...     DQT, SOF0, DHT, SOS

The ICC APP2 segment ends up unusually late because darktable first writes the JPEG via libjpeg (ICC is written early), then exiv2 rewrites the entire file to inject EXIF/XMP, pushing ICC after the large EXIF block (src/imageio/format/jpeg.c:288).

Key evidence:

  • darktable JPEG and a GIMP re-export of the same image both have JFIF → both show identical split behavior
  • TIFF export (no JFIF) renders correctly in both Apple Photos views ✅
  • Removing only the JFIF marker with ExifTool (exiftool -JFIF:all= file.jpg) makes both views render correctly
  • The ICC profile content is not the cause — the Windows sRGB v2 profile produces the same flat preview
  • Exif.Photo.ColorSpace = 1 (sRGB) is present and correct but does not affect the behavior

Steps to reproduce

  1. Open any RAW file in darktable
  2. Export as JPEG with sRGB output profile (default settings)
  3. Transfer the JPEG to an iPhone/iPad (iOS 16+) or view in Apple Photos on macOS 13+
  4. Observe: thumbnail/list view appears flat and desaturated
  5. Tap/zoom into the image: colors become correctly saturated

Expected behavior

darktable should produce JPEG files that render with consistent color in both thumbnail and full-resolution views in Apple Photos, matching the appearance of TIFF exports.

Proposed fix

In src/imageio/format/jpeg.c, suppress the JFIF header after jpeg_set_defaults():

jpeg_set_defaults(&(jpg->cinfo));
jpg->cinfo.write_JFIF_header = FALSE;  // ICC APP2 provides all color information

write_JFIF_header is a public field of jpeg_compress_struct (libjpeg/libjpeg-turbo API). Setting it FALSE after jpeg_set_defaults() and before jpeg_start_compress() suppresses the APP0 marker. All colorimetric information is already in the ICC APP2 segment. Resolution information is already written to Exif.Image.XResolution/YResolution by the exiv2 pass (src/common/exif.cc:3062–3064), so no metadata is lost.

Risk: Minimal. All modern decoders (browsers, Photoshop, Lightroom, Apple, Android) use EXIF for resolution. Only pre-2000 JFIF-only decoders would be affected.

Workaround (until patch is available)

Strip the JFIF marker with ExifTool after export:

exiftool -JFIF:all= -overwrite_original exported.jpg

Evidence files

Three files from the same source RAW, tested on multiple Apple devices:

File Apple Photos preview Apple Photos zoom
darktable JPEG (sRGB) flat / desaturated correctly saturated
GIMP re-export of same JPEG flat / desaturated correctly saturated
darktable TIFF (sRGB) correctly saturated ✅ correctly saturated ✅

darktable version

5.6.0 (commit cfcb13c)

Source locations

  • src/imageio/format/jpeg.c:202–203in_color_space = JCS_RGB + jpeg_set_defaults() triggers JFIF
  • src/imageio/format/jpeg.c:255jpeg_start_compress(&cinfo, TRUE) — TRUE = write all markers
  • src/imageio/format/jpeg.c:288 — exiv2 rewrites file after compression (causes late ICC position)
  • src/common/colorspaces.c:1485 — sRGB v2 output profile registration

Additional context

  • Reproducible across multiple iPhones, iPads, and MacBooks
  • PNG exports have a related but separate issue: the cICP chunk written by src/imageio/format/png.c causes Apple Photos to render flat in both preview and zoom (separate issue)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions