Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable transparency for duotone #34130

Merged
merged 14 commits into from
Oct 29, 2021
92 changes: 81 additions & 11 deletions lib/block-supports/duotone.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,25 @@ function gutenberg_tinycolor_bound01( $n, $max ) {
return ( $n % $max ) / (float) $max;
}

/**
* Direct port of tinycolor's boundAlpha function to maintain consistency with
* how tinycolor works.
*
* @see https://github.com/bgrins/TinyColor
*
* @param mixed $n Number of unknown type.
* @return float Value in the range [0,1].
*/
function gutenberg_tinycolor_bound_alpha( $n ) {
if ( is_numeric( $n ) ) {
$n = (float) $n;
if ( $n >= 0 && $n <= 1 ) {
return $n;
}
}
return 1;
}

/**
* Round and convert values of an RGB object.
*
Expand Down Expand Up @@ -116,8 +135,7 @@ function gutenberg_tinycolor_hsl_to_rgb( $hsl_color ) {

/**
* Parses hex, hsl, and rgb CSS strings using the same regex as tinycolor v1.4.2
* used in the JavaScript. Only colors output from react-color are implemented
* and the alpha value is ignored as it is not used in duotone.
* used in the JavaScript. Only colors output from react-color are implemented.
*
* @see https://github.com/bgrins/TinyColor
* @see https://github.com/casesandberg/react-color/
Expand All @@ -138,90 +156,137 @@ function gutenberg_tinycolor_string_to_rgb( $color_str ) {

$rgb_regexp = '/^rgb' . $permissive_match3 . '$/';
if ( preg_match( $rgb_regexp, $color_str, $match ) ) {
return gutenberg_tinycolor_rgb_to_rgb(
$rgb = gutenberg_tinycolor_rgb_to_rgb(
array(
'r' => $match[1],
'g' => $match[2],
'b' => $match[3],
)
);

$rgb['a'] = 1;

return $rgb;
}

$rgba_regexp = '/^rgba' . $permissive_match4 . '$/';
if ( preg_match( $rgba_regexp, $color_str, $match ) ) {
return gutenberg_tinycolor_rgb_to_rgb(
$rgb = gutenberg_tinycolor_rgb_to_rgb(
array(
'r' => $match[1],
'g' => $match[2],
'b' => $match[3],
)
);

$rgb['a'] = gutenberg_tinycolor_bound_alpha( $match[4] );

return $rgb;
}

$hsl_regexp = '/^hsl' . $permissive_match3 . '$/';
if ( preg_match( $hsl_regexp, $color_str, $match ) ) {
return gutenberg_tinycolor_hsl_to_rgb(
$rgb = gutenberg_tinycolor_hsl_to_rgb(
array(
'h' => $match[1],
's' => $match[2],
'l' => $match[3],
)
);

$rgb['a'] = 1;

return $rgb;
}

$hsla_regexp = '/^hsla' . $permissive_match4 . '$/';
if ( preg_match( $hsla_regexp, $color_str, $match ) ) {
return gutenberg_tinycolor_hsl_to_rgb(
$rgb = gutenberg_tinycolor_hsl_to_rgb(
array(
'h' => $match[1],
's' => $match[2],
'l' => $match[3],
)
);

$rgb['a'] = gutenberg_tinycolor_bound_alpha( $match[4] );

return $rgb;
}

$hex8_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/';
if ( preg_match( $hex8_regexp, $color_str, $match ) ) {
return gutenberg_tinycolor_rgb_to_rgb(
$rgb = gutenberg_tinycolor_rgb_to_rgb(
array(
'r' => base_convert( $match[1], 16, 10 ),
'g' => base_convert( $match[2], 16, 10 ),
'b' => base_convert( $match[3], 16, 10 ),
)
);

$rgb['a'] = gutenberg_tinycolor_bound_alpha(
base_convert( $match[4], 16, 10 ) / 255
);

return $rgb;
}

$hex6_regexp = '/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/';
if ( preg_match( $hex6_regexp, $color_str, $match ) ) {
return gutenberg_tinycolor_rgb_to_rgb(
$rgb = gutenberg_tinycolor_rgb_to_rgb(
array(
'r' => base_convert( $match[1], 16, 10 ),
'g' => base_convert( $match[2], 16, 10 ),
'b' => base_convert( $match[3], 16, 10 ),
)
);

$rgb['a'] = 1;

return $rgb;
}

$hex4_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/';
if ( preg_match( $hex4_regexp, $color_str, $match ) ) {
return gutenberg_tinycolor_rgb_to_rgb(
$rgb = gutenberg_tinycolor_rgb_to_rgb(
array(
'r' => base_convert( $match[1] . $match[1], 16, 10 ),
'g' => base_convert( $match[2] . $match[2], 16, 10 ),
'b' => base_convert( $match[3] . $match[3], 16, 10 ),
)
);

$rgb['a'] = gutenberg_tinycolor_bound_alpha(
base_convert( $match[4] . $match[4], 16, 10 ) / 255
);

return $rgb;
}

$hex3_regexp = '/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/';
if ( preg_match( $hex3_regexp, $color_str, $match ) ) {
return gutenberg_tinycolor_rgb_to_rgb(
$rgb = gutenberg_tinycolor_rgb_to_rgb(
array(
'r' => base_convert( $match[1] . $match[1], 16, 10 ),
'g' => base_convert( $match[2] . $match[2], 16, 10 ),
'b' => base_convert( $match[3] . $match[3], 16, 10 ),
)
);

$rgb['a'] = 1;

return $rgb;
}

// The JS color picker considers the string "transparent" to be a hex value,
// so we need to handle it here as a special case.
if ( 'transparent' === $color_str ) {
return array(
'r' => 0,
'g' => 0,
'b' => 0,
'a' => 0,
);
}
}

Expand Down Expand Up @@ -266,13 +331,15 @@ function gutenberg_render_duotone_filter_preset( $preset ) {
'r' => array(),
'g' => array(),
'b' => array(),
'a' => array(),
);
foreach ( $duotone_colors as $color_str ) {
$color = gutenberg_tinycolor_string_to_rgb( $color_str );

$duotone_values['r'][] = $color['r'] / 255;
$duotone_values['g'][] = $color['g'] / 255;
$duotone_values['b'][] = $color['b'] / 255;
$duotone_values['a'][] = $color['a'];
}

ob_start();
Expand All @@ -291,19 +358,22 @@ function gutenberg_render_duotone_filter_preset( $preset ) {
<defs>
<filter id="<?php echo esc_attr( $filter_id ); ?>">
<feColorMatrix
color-interpolation-filters="sRGB"
type="matrix"
values="
.299 .587 .114 0 0
.299 .587 .114 0 0
.299 .587 .114 0 0
0 0 0 1 0
.299 .587 .114 0 0
"
/>
<feComponentTransfer color-interpolation-filters="sRGB" >
<feFuncR type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['r'] ) ); ?>" />
<feFuncG type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['g'] ) ); ?>" />
<feFuncB type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['b'] ) ); ?>" />
<feFuncA type="table" tableValues="<?php echo esc_attr( implode( ' ', $duotone_values['a'] ) ); ?>" />
</feComponentTransfer>
<feComposite in2="SourceGraphic" operator="in" />
</filter>
</defs>
</svg>
Expand Down
28 changes: 21 additions & 7 deletions packages/block-editor/src/hooks/duotone.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@ extend( [ namesPlugin ] );
* @return {Object} R, G, and B values.
*/
export function getValuesFromColors( colors = [] ) {
const values = { r: [], g: [], b: [] };
const values = { r: [], g: [], b: [], a: [] };

colors.forEach( ( color ) => {
const rgbColor = colord( color ).toRgb();
values.r.push( rgbColor.r / 255 );
values.g.push( rgbColor.g / 255 );
values.b.push( rgbColor.b / 255 );
values.a.push( rgbColor.a );
} );

return values;
Expand All @@ -55,6 +56,7 @@ export function getValuesFromColors( colors = [] ) {
* @property {number[]} r Red values.
* @property {number[]} g Green values.
* @property {number[]} b Blue values.
* @property {number[]} a Alpha values.
*/

/**
Expand All @@ -63,7 +65,7 @@ export function getValuesFromColors( colors = [] ) {
* @param {Object} props Duotone props.
* @param {string} props.selector Selector to apply the filter to.
* @param {string} props.id Unique id for this duotone filter.
* @param {Values} props.values R, G, and B values to filter with.
* @param {Values} props.values R, G, B, and A values to filter with.
*
* @return {WPElement} Duotone element.
*/
Expand Down Expand Up @@ -93,13 +95,16 @@ ${ selector } {
<defs>
<filter id={ id }>
<feColorMatrix
// Use sRGB instead of linearRGB so transparency looks correct.
colorInterpolationFilters="sRGB"
type="matrix"
// Use perceptual brightness to convert to grayscale.
// prettier-ignore
values=".299 .587 .114 0 0
.299 .587 .114 0 0
.299 .587 .114 0 0
0 0 0 1 0"
values="
.299 .587 .114 0 0
.299 .587 .114 0 0
.299 .587 .114 0 0
.299 .587 .114 0 0
"
/>
<feComponentTransfer
// Use sRGB instead of linearRGB to be consistent with how CSS gradients work.
Expand All @@ -117,7 +122,16 @@ ${ selector } {
type="table"
tableValues={ values.b.join( ' ' ) }
/>
<feFuncA
type="table"
tableValues={ values.a.join( ' ' ) }
/>
</feComponentTransfer>
<feComposite
// Re-mask the image with the original transparency since the feColorMatrix above loses that information.
in2="SourceGraphic"
operator="in"
/>
</filter>
</defs>
</SVG>
Expand Down
4 changes: 4 additions & 0 deletions packages/components/src/color-list-picker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function ColorOption( {
value,
colors,
disableCustomColors,
enableAlpha,
onChange,
} ) {
const [ isOpen, setIsOpen ] = useState( false );
Expand All @@ -34,6 +35,7 @@ function ColorOption( {
clearable={ false }
onChange={ onChange }
disableCustomColors={ disableCustomColors }
enableAlpha={ enableAlpha }
/>
) }
</>
Expand All @@ -45,6 +47,7 @@ function ColorListPicker( {
labels,
value = [],
disableCustomColors,
enableAlpha,
onChange,
} ) {
return (
Expand All @@ -56,6 +59,7 @@ function ColorListPicker( {
value={ value[ index ] }
colors={ colors }
disableCustomColors={ disableCustomColors }
enableAlpha={ enableAlpha }
onChange={ ( newColor ) => {
const newColors = value.slice();
newColors[ index ] = newColor;
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/color-palette/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default function ColorPalette( {
className,
colors,
disableCustomColors = false,
enableAlpha,
onChange,
value,
} ) {
Expand Down Expand Up @@ -73,6 +74,7 @@ export default function ColorPalette( {
<ColorPicker
color={ value }
onChange={ ( color ) => onChange( color ) }
enableAlpha={ enableAlpha }
/>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export default function CustomDuotoneBar( { value, onChange } ) {
return (
<CustomGradientBar
disableInserter
disableAlpha
background={ background }
hasGradient={ hasGradient }
value={ controlPoints }
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/duotone-picker/duotone-picker.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ function DuotonePicker( {
colors={ colorPalette }
value={ value }
disableCustomColors={ disableCustomColors }
enableAlpha
onChange={ ( newColors ) => {
if ( ! newColors[ 0 ] ) {
newColors[ 0 ] = defaultDark;
Expand Down