Feature Description
The Image Placeholders plugin (formerly Dominant Color Images) extracts the dominant color of an image by resizing it down to a single 1x1 pixel and reading that pixel's color. This is done in both the Imagick and GD editors:
A community member (@MarcinDudekDev) pointed out in this comment that both Imagick and GD average pixels in gamma-encoded sRGB space, which skews the resulting color darker and less saturated than what the eye actually perceives. This is the classic "gamma error in picture scaling" problem.
Quick illustration: a 50/50 black/white checkerboard averaged in gamma space gives #808080. Averaged in linear light (what you'd see squinting at the image from a distance) it's #BCBCBC - about 20 L* lighter. Real photos land at 4-9 L* off, worst on high contrast - skies with dark foregrounds, foliage, speculars.
Side-by-side demo with sample photos (you can drop in your own test images): https://marcindudekdev.github.io/img2gradient/gamma-demo.html
Proposed Fix
The fix is cheap for this use case (a single 1x1 average):
- Imagick: transform to linear colorspace before the resize and back to sRGB after, i.e. the
transformImageColorspace( Imagick::COLORSPACE_RGB ) → resize 1x1 → transformImageColorspace( Imagick::COLORSPACE_SRGB ) recipe.
- GD: GD has no colorspace support, but since the current implementation can iterate pixels anyway, the fix is to decode each pixel through a 256-entry sRGB→linear LUT, average in linear light, then re-encode to sRGB. A few extra lines, negligible cost. (Note: this would replace the current
imagecopyresampled() 1x1 downscale, which itself averages in gamma space.)
- libvips (for reference):
vipsthumbnail has a --linear flag for exactly this, off by default.
The reporter ran into this while building a gradient placeholder generator (MarcinDudekDev/img2gradient) and noted the linear-light fix was visibly better on photos immediately.
Considerations
- The change would shift extracted placeholder colors lighter/more saturated, so existing stored
dominant_color post meta would not match newly computed values. Regeneration would be needed for consistency, though placeholder colors are not critical so a gradual migration is fine.
- Worth adding test coverage with a high-contrast fixture (e.g. a black/white checkerboard) where the gamma vs. linear difference is largest and easy to assert on.
Reported via community feedback from @MarcinDudekDev.
Feature Description
The Image Placeholders plugin (formerly Dominant Color Images) extracts the dominant color of an image by resizing it down to a single 1x1 pixel and reading that pixel's color. This is done in both the Imagick and GD editors:
Dominant_Color_Image_Editor_Imagick::get_dominant_color()(class-dominant-color-image-editor-imagick.php#L38) usesImagick::resizeImage( 1, 1, ... ).Dominant_Color_Image_Editor_GD::get_dominant_color()(class-dominant-color-image-editor-gd.php#L46) usesimagecopyresampled()to downscale to 1x1.A community member (@MarcinDudekDev) pointed out in this comment that both Imagick and GD average pixels in gamma-encoded sRGB space, which skews the resulting color darker and less saturated than what the eye actually perceives. This is the classic "gamma error in picture scaling" problem.
Side-by-side demo with sample photos (you can drop in your own test images): https://marcindudekdev.github.io/img2gradient/gamma-demo.html
Proposed Fix
The fix is cheap for this use case (a single 1x1 average):
transformImageColorspace( Imagick::COLORSPACE_RGB )→resize 1x1→transformImageColorspace( Imagick::COLORSPACE_SRGB )recipe.imagecopyresampled()1x1 downscale, which itself averages in gamma space.)vipsthumbnailhas a--linearflag for exactly this, off by default.The reporter ran into this while building a gradient placeholder generator (MarcinDudekDev/img2gradient) and noted the linear-light fix was visibly better on photos immediately.
Considerations
dominant_colorpost meta would not match newly computed values. Regeneration would be needed for consistency, though placeholder colors are not critical so a gradual migration is fine.Reported via community feedback from @MarcinDudekDev.