Skip to content

Feature: CSS Gradient Low Quality Image Placeholders#2550

Open
nickchomey wants to merge 14 commits into
WordPress:trunkfrom
nickchomey:feature/css-gradient-lqip
Open

Feature: CSS Gradient Low Quality Image Placeholders#2550
nickchomey wants to merge 14 commits into
WordPress:trunkfrom
nickchomey:feature/css-gradient-lqip

Conversation

@nickchomey

@nickchomey nickchomey commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #2519 and #2535

Note:

This is stacked on/includes #2524, which isn't totally necessary but I think made improvements to the test functions. I can rebase this without any of that if desired.

Relevant technical choices

Implemented the CSS Gradient-based LQIP mechanism described in #2519. Extensive testing was done with various ways to generate the dominant color (the original used ColorThief, which has a php equivalent). On rare occasions, the fancier methods produced somewhat better gradients, but at great processing cost. Ultimately I found that the 1x1 pixel was sufficiently-good - especially given that it is already implemented.

I also changed the GD and Imagick editors to extract the color in linear rgb, as per #2535. Updated some tests to reflect the different results. They all pass for me.

Here's a video that shows the gradients using the previous colorspace (left) and the new colorspace (right), generated by imagick. Generally no difference, but its somewhat better in a few cases.

Kooha-2026-06-25-15-22-14.mp4

Nothing was done to accomodate any sort of transition. To use this, all images would need to be reprocessed. Both the dominant color hex and lqip values are generated and stored in postmeta.

Use of AI Tools

I used Deepseek V4 Flash via Github Copilot to assist with this. I reviewed and iterated on everything.

@github-actions github-actions Bot added the [Plugin] Image Placeholders Issues for the Image Placeholders plugin (formerly Dominant Color Images) label Jun 25, 2026
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: nickchomey <nickchomey@git.wordpress.org>
Co-authored-by: adamsilverstein <adamsilverstein@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.20818% with 64 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.39%. Comparing base (98437a5) to head (3d68d73).

Files with missing lines Patch % Lines
...or-images/class-dominant-color-image-editor-gd.php 57.28% 44 Missing ⚠️
plugins/dominant-color-images/hooks.php 58.33% 10 Missing ⚠️
plugins/dominant-color-images/helper.php 76.00% 6 Missing ⚠️
...ages/class-dominant-color-image-editor-imagick.php 89.18% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##            trunk    #2550      +/-   ##
==========================================
+ Coverage   70.10%   70.39%   +0.29%     
==========================================
  Files          91       92       +1     
  Lines        7823     8049     +226     
==========================================
+ Hits         5484     5666     +182     
- Misses       2339     2383      +44     
Flag Coverage Δ
multisite 70.39% <76.20%> (+0.29%) ⬆️
single 36.74% <69.51%> (+1.22%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment on lines +13 to +14
--lqip-ca: mod(round(down, calc((var(--lqip) + 524288) / 262144)), 4);
--lqip-cb: mod(round(down, calc((var(--lqip) + 524288) / 65536)), 4);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor: the indentation in this file is not consistently using tabs.

* @since 1.0.0
*
* @return string|WP_Error Dominant hex color string, or an error on failure.
* @return array{r: int, g: int, b: int}|WP_Error RGB values (0-255), or WP_Error on failure.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can be made more specific, unless this causes problems for PHPStan.

Suggested change
* @return array{r: int, g: int, b: int}|WP_Error RGB values (0-255), or WP_Error on failure.
* @return array{r: int<0, 255>, g: int<0, 255>, b: int<0, 255>}|WP_Error RGB values (0-255), or WP_Error on failure.

$processor->set_attribute( 'data-dominant-color', $image_meta['dominant_color'] );

$style_attribute = '--dominant-color: #' . $image_meta['dominant_color'] . '; ';
$style_attribute = '--dominant-color: #' . esc_attr( $image_meta['dominant_color'] ) . '; ';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is esc_attr() needed? I don't see why it was needed above either because wp_get_attachment_image() automatically applies esc_attr() on the values. So we can get rid of the esc_attr() calls above too.

Suggested change
$style_attribute = '--dominant-color: #' . esc_attr( $image_meta['dominant_color'] ) . '; ';
$style_attribute = '--dominant-color: #' . $image_meta['dominant_color'] . '; ';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

And in the context here, the escaping is handled by the HTML Tag Processor.

@@ -78,7 +82,14 @@ function dominant_color_update_attachment_image_attributes( $attr, WP_Post $atta
if ( isset( $image_meta['dominant_color'] ) && is_string( $image_meta['dominant_color'] ) && '' !== $image_meta['dominant_color'] ) {
$attr['data-dominant-color'] = esc_attr( $image_meta['dominant_color'] );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
$attr['data-dominant-color'] = esc_attr( $image_meta['dominant_color'] );
$attr['data-dominant-color'] = $image_meta['dominant_color'];

$attr['data-dominant-color'] = esc_attr( $image_meta['dominant_color'] );
$style_attribute = isset( $attr['style'] ) && is_string( $attr['style'] ) ? $attr['style'] : '';
$attr['style'] = '--dominant-color: #' . esc_attr( $image_meta['dominant_color'] ) . ';' . $style_attribute;
$style_attribute = '--dominant-color: #' . esc_attr( $image_meta['dominant_color'] ) . ';' . $style_attribute;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
$style_attribute = '--dominant-color: #' . esc_attr( $image_meta['dominant_color'] ) . ';' . $style_attribute;
$style_attribute = '--dominant-color: #' . $image_meta['dominant_color'] . ';' . $style_attribute;

* exactly 6 pixels. Each cell's raw RGB values are returned for use
* in LQIP generation.
*
* @since 1.3.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
* @since 1.3.0
* @since n.e.x.t

}

if ( count( $values ) < 6 ) {
return array();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How about returning null in case of an error?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Or rather, what about returning a WP_Error? There is already a WP_Error being thrown away above.

Comment on lines +193 to +195
* @return array<int, array{r: int, g: int, b: int}> 6 grid cells as ['r'=>R, 'g'=>G, 'b'=>B].
*/
public function get_lqip_grid_values(): array {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
* @return array<int, array{r: int, g: int, b: int}> 6 grid cells as ['r'=>R, 'g'=>G, 'b'=>B].
*/
public function get_lqip_grid_values(): array {
* @return array<int, array{r: int, g: int, b: int}>
null 6 grid cells as ['r'=>R, 'g'=>G, 'b'=>B], or null on error.
*/
public function get_lqip_grid_values(): ?array {

/**
* Retrieves attachment metadata for an image attachment.
*
* @since 1.0.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

is this right?

Suggested change
* @since 1.0.0
* @since n.e.x.t

Comment on lines +219 to +224
if ( defined( 'DOMINANT_COLOR_LQIP_HOVER' ) && DOMINANT_COLOR_LQIP_HOVER ) {
wp_add_inline_style(
'dominant-color-lqip',
'[style*="--lqip:"]:hover,.force-lqip{object-position:999px 999px}'
);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this just for testing?

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

Labels

[Plugin] Image Placeholders Issues for the Image Placeholders plugin (formerly Dominant Color Images)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CSS Gradient Image Placeholders (ie CSS-only Blur/Thumbhash)

2 participants