Feature: CSS Gradient Low Quality Image Placeholders#2550
Conversation
…sed for initial image processing and dominant colours
…never do anything in any test
…ominant_color_get_dominant_color_data. So not redundant to call it afterwards
…e GD tests tun, though not for imagick)
…t stuff coming in other PR. Also, cleaner to just convert in one place for all editors
|
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 If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Codecov Report❌ Patch coverage is 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| --lqip-ca: mod(round(down, calc((var(--lqip) + 524288) / 262144)), 4); | ||
| --lqip-cb: mod(round(down, calc((var(--lqip) + 524288) / 65536)), 4); |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Can be made more specific, unless this causes problems for PHPStan.
| * @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'] ) . '; '; |
There was a problem hiding this comment.
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.
| $style_attribute = '--dominant-color: #' . esc_attr( $image_meta['dominant_color'] ) . '; '; | |
| $style_attribute = '--dominant-color: #' . $image_meta['dominant_color'] . '; '; |
There was a problem hiding this comment.
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'] ); | |||
There was a problem hiding this comment.
| $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; |
There was a problem hiding this comment.
| $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 |
There was a problem hiding this comment.
| * @since 1.3.0 | |
| * @since n.e.x.t |
| } | ||
|
|
||
| if ( count( $values ) < 6 ) { | ||
| return array(); |
There was a problem hiding this comment.
How about returning null in case of an error?
There was a problem hiding this comment.
Or rather, what about returning a WP_Error? There is already a WP_Error being thrown away above.
| * @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 { |
There was a problem hiding this comment.
| * @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 |
There was a problem hiding this comment.
is this right?
| * @since 1.0.0 | |
| * @since n.e.x.t |
| 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}' | ||
| ); | ||
| } |
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.