Skip to content

Conversation

@ju1ius
Copy link
Contributor

@ju1ius ju1ius commented Apr 1, 2024

The current implementation of the dominant_color_rgb_to_hex function validates the RGB color by:

  1. allocating an array of 256 64-bit integers
  2. performing three linear scans of the array (one for each color channel)

Which is not a very performant way to check if a variable is an integer in the range [0..255].

This PR replaces the current implementation by simple type checks and integer comparisons.

@github-actions
Copy link

github-actions bot commented Apr 1, 2024

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: ju1ius <[email protected]>
Co-authored-by: westonruter <[email protected]>
Co-authored-by: felixarntz <[email protected]>

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

Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

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

@ju1ius Thank you for the PR, great catch!

I have one suggestion on how your logic could be further simplified, mostly easier to understand when reading the code.

Comment on lines +175 to +180
if ( ! (
\is_int( $red ) && \is_int( $green ) && \is_int( $blue )
&& $red >= 0 && $red <= 255
&& $green >= 0 && $green <= 255
&& $blue >= 0 && $blue <= 255
) ) {
Copy link
Member

Choose a reason for hiding this comment

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

This could be further simplified by not having the entire clause negated.

Suggested change
if ( ! (
\is_int( $red ) && \is_int( $green ) && \is_int( $blue )
&& $red >= 0 && $red <= 255
&& $green >= 0 && $green <= 255
&& $blue >= 0 && $blue <= 255
) ) {
if (
! is_int( $red ) || $red < 0 || $red > 255
|| ! is_int( $green ) || $green < 0 || $green > 255
|| ! is_int( $blue ) || $blue < 0 || $blue > 255
) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This feels weird to me.

// One would normally write A.
if (!isValid($r, $b, $b)) return null;
else return toHex($r, $g, $b);
// or B.
if (isValid($r, $b, $b)) return toHex($r, $g, $b);
else return null;
// but not C.
if (isInvalid($r, $b, $b)) return null;
else return toHex($r, $g, $b);
// and certainly not D.
if (!isInvalid($r, $b, $b)) return toHex($r, $g, $b);
else return null;

If you really want to remove the negation, the most readable IMO would be option B:

if (
    \is_int($r) && $r >= 0 && $r <= 255
    && \is_int($g) && $g >= 0 && $g <= 255
    && \is_int($b) && $b >= 0 && $b <= 255
) {
    return sprintf('%02x%02x%02x', $r, $g, $b);
}

return null;

Copy link
Member

Choose a reason for hiding this comment

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

I would agree with you if we had a function like isValid to call here, but since we don't have one, I think we shouldn't reason about readability / understandability based on a hypothetical function. Your option B would be okay from a readability perspective, but then it would no longer use an early return, which is a good convention to avoid a lot of nesting of if clauses.

So IMO it makes sense to either go with my suggestion, or introduce a separate function like your isValid that we can call here to simplify readability even more.

Copy link
Member

Choose a reason for hiding this comment

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

@ju1ius Will you be adding a function like dominant_color_is_valid_rgb()?

Copy link
Contributor Author

@ju1ius ju1ius May 5, 2024

Choose a reason for hiding this comment

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

Hi, @westonruter.

The dominant_color_rgb_to_hex function currently has two call sites and in both of them the input is guaranteed to be valid RGB, so the validation steps are useless.

In the Imagick editor case, the ImagickPixel::getColor method guarantees that the r, g and b keys of the returned array are integers in the [0, 255] range.

In the GD editor case, the imagecolorat function returns int|false. The PHP type coercion rules and the following bit operations therefore guarantee that the $r, $g and $b variables are integers in the [0, 255] range.

Therefore my suggestion would be to just inline the sprintf calls, remove the dominant_color_rgb_to_hex function to reduce API surface, and optionally fix the error checking in the GD editor so that a WP_Error is returned instead of "000000".

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for that. In 31fd033 I've added an explicit check to ensure that we only use the imagecolorat() return value if it is an int.

Also, note that in #1203 the use of PHP type hints is introduced so that this code can be simplified to:

	if ( ! (
		$red >= 0 && $red <= 255 && 
		$green >= 0 && $green <= 255 &&
		$blue >= 0 && $blue <= 255
	) ) {

Copy link
Member

Choose a reason for hiding this comment

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

I've applied that simplification with fixing the merge conflict for that PR: 7e85330

@felixarntz felixarntz added [Type] Enhancement A suggestion for improvement of an existing feature [Focus] Images no milestone PRs that do not have a defined milestone for release [Plugin] Image Placeholders Issues for the Image Placeholders plugin (formerly Dominant Color Images) labels Apr 1, 2024
… no-array-alloc

* 'trunk' of https://github.com/WordPress/performance: (277 commits)
  Add comments explaining why PHPStan is ignored
  Fix function stub docs
  Add rememberPossiblyImpureFunctionValues:false to fix it_should_use_the_next_available_hash_for_the_full_size_image_on_multiple_image_edits
  Fix types for arguments passed in tests
  Ignore phpstan errors when testing for incorrect usage
  Bump PHPStan level to 5
  Work around PHPStan not being aware of callback being called
  Improve specificity of assertions
  Remove assertions which are always true
  Eliminate ignoring phpstan error
  Fix offset does not exist on array in test
  Fix variable in empty() always exists and is not falsy
  Use function stub to override filtered return value typing
  Revert "Add typing for attachment metadata added by webp-uploads"
  Revert "Add typing for attachment metadata added by Dominant Color Images"
  Update comment to reference visibility instead of opacity
  Use visibility:hidden instead of opacity:0
  Remove needless isset() check for function key in debug_backtrace() response
  Add treatPhpDocTypesAsCertain:false to fix 54 issues
  Bump PHPStan level to 4
  ...
@westonruter westonruter changed the title fix: avoid needless array allocation in rgb to hex conversion Avoid needless array allocation in rgb to hex conversion May 8, 2024
@westonruter westonruter merged commit b63b25a into WordPress:trunk May 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no milestone PRs that do not have a defined milestone for release [Plugin] Image Placeholders Issues for the Image Placeholders plugin (formerly Dominant Color Images) [Type] Enhancement A suggestion for improvement of an existing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants