[change] Image: improve tintColor style implementation

The `tintColor` style is now implemented by passing an inline SVG color matrix
as the CSS `filter`. This avoids the need to render SVG elements to the DOM and
avoids the need to manage unique IDs.

Still no support in Safari; lacks support for inline SVG filters.

Close #1914
This commit is contained in:
Nicolas Gallagher
2021-09-30 12:34:00 -07:00
parent 081a128db3
commit 7017d662ac
3 changed files with 37 additions and 62 deletions
+11
View File
@@ -98,6 +98,17 @@ export default function ImagePage() {
<Image resizeMode="stretch" source={resizesource} style={styles.resizeMode} />
</View>
</View>
<Divider />
<View style={styles.row}>
<View style={styles.column}>
<Text style={styles.text}>Tint color</Text>
<Image source={dataBase64Svg} style={[styles.image, { tintColor: 'red' }]} />
</View>
<View style={styles.column}>
<Text style={styles.text}>Tint color</Text>
<Image source={dataSvg} style={[styles.image, { tintColor: 'blue' }]} />
</View>
</View>
</Example>
);
}
@@ -329,25 +329,8 @@ exports[`components/Image prop "style" removes other unsupported View styles 1`]
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="filter: url(#tint-57);"
style="filter: url('data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\"><filter id=\\"tint\\"><feColorMatrix type=\\"matrix\\" values=\\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1.00 0\\" /></filter></svg>#tint');"
/>
<svg
style="position: absolute; height: 0px; visibility: hidden; width: 0px;"
>
<defs>
<filter
id="tint-57"
>
<feflood
flood-color="blue"
/>
<fecomposite
in2="SourceAlpha"
operator="atop"
/>
</filter>
</defs>
</svg>
</div>
`;
@@ -378,7 +361,7 @@ exports[`components/Image prop "style" supports "tintcolor" property (convert to
>
<div
class="css-view-1dbjc4n r-backgroundColor-1niwhzg r-backgroundPosition-vvn4in r-backgroundRepeat-u6sd8q r-backgroundSize-4gszlv r-bottom-1p0dtai r-height-1pi2tsx r-left-1d2f490 r-position-u8s1d r-right-zchlnj r-top-ipm5af r-width-13qz1uu r-zIndex-1wyyakw"
style="background-image: url(https://google.com/favicon.ico); filter: url(#tint-56);"
style="background-image: url(https://google.com/favicon.ico); filter: url('data:image/svg+xml,<svg xmlns=\\"http://www.w3.org/2000/svg\\"><filter id=\\"tint\\"><feColorMatrix type=\\"matrix\\" values=\\"0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1.00 0\\" /></filter></svg>#tint');"
/>
<img
alt=""
@@ -386,23 +369,6 @@ exports[`components/Image prop "style" supports "tintcolor" property (convert to
draggable="false"
src="https://google.com/favicon.ico"
/>
<svg
style="position: absolute; height: 0px; visibility: hidden; width: 0px;"
>
<defs>
<filter
id="tint-56"
>
<feflood
flood-color="red"
/>
<fecomposite
in2="SourceAlpha"
operator="atop"
/>
</filter>
</defs>
</svg>
</div>
`;
+24 -26
View File
@@ -20,6 +20,7 @@ import PixelRatio from '../PixelRatio';
import StyleSheet from '../StyleSheet';
import TextAncestorContext from '../Text/TextAncestorContext';
import View from '../View';
import processColor from '../processColor';
export type { ImageProps };
@@ -28,23 +29,9 @@ const LOADED = 'LOADED';
const LOADING = 'LOADING';
const IDLE = 'IDLE';
let _filterId = 0;
const svgDataUriPattern = /^(data:image\/svg\+xml;utf8,)(.*)/;
function createTintColorSVG(tintColor, id) {
return tintColor && id != null ? (
<svg style={{ position: 'absolute', height: 0, visibility: 'hidden', width: 0 }}>
<defs>
<filter id={`tint-${id}`} suppressHydrationWarning={true}>
<feFlood floodColor={`${tintColor}`} key={tintColor} />
<feComposite in2="SourceAlpha" operator="atop" />
</filter>
</defs>
</svg>
) : null;
}
function getFlatStyle(style, blurRadius, filterId) {
function getFlatStyle(style, blurRadius) {
const flatStyle = { ...StyleSheet.flatten(style) };
const { filter, resizeMode, shadowOffset, tintColor } = flatStyle;
@@ -65,8 +52,26 @@ function getFlatStyle(style, blurRadius, filterId) {
filters.push(`drop-shadow(${shadowString})`);
}
}
if (tintColor && filterId != null) {
filters.push(`url(#tint-${filterId})`);
if (tintColor) {
const colorInt = processColor(tintColor);
if (colorInt != null) {
const r = (colorInt >> 16) & 255;
const g = (colorInt >> 8) & 255;
const b = colorInt & 255;
const a = ((colorInt >> 24) & 255) / 255;
const alpha = a.toFixed(2);
const matrix = `0 0 0 0 ${r / 255} 0 0 0 0 ${g / 255} 0 0 0 0 ${b / 255} 0 0 0 ${alpha} 0`;
// NOTE: Safari doesn't support inline SVG filters (reported 2012)
// https://bugs.webkit.org/show_bug.cgi?id=104169
const svgFilter =
"url('data:image/svg+xml," +
'<svg xmlns="http://www.w3.org/2000/svg">' +
'<filter id="tint">' +
`<feColorMatrix type="matrix" values="${matrix}" />` +
'</filter>' +
"</svg>#tint')";
filters.push(svgFilter);
}
}
if (filters.length > 0) {
@@ -85,7 +90,7 @@ function getFlatStyle(style, blurRadius, filterId) {
delete flatStyle.overlayColor;
delete flatStyle.resizeMode;
return [flatStyle, resizeMode, _filter, tintColor];
return [flatStyle, resizeMode, _filter];
}
function resolveAssetDimensions(source) {
@@ -182,14 +187,9 @@ const Image: React.AbstractComponent<ImageProps, React.ElementRef<typeof View>>
const [layout, updateLayout] = React.useState({});
const hasTextAncestor = React.useContext(TextAncestorContext);
const hiddenImageRef = React.useRef(null);
const filterRef = React.useRef(_filterId++);
const requestRef = React.useRef(null);
const shouldDisplaySource = state === LOADED || (state === LOADING && defaultSource == null);
const [flatStyle, _resizeMode, filter, tintColor] = getFlatStyle(
style,
blurRadius,
filterRef.current
);
const [flatStyle, _resizeMode, filter] = getFlatStyle(style, blurRadius);
const resizeMode = props.resizeMode || _resizeMode || 'cover';
const selectedSource = shouldDisplaySource ? source : defaultSource;
const displayImageUri = resolveAssetUri(selectedSource);
@@ -293,10 +293,8 @@ const Image: React.AbstractComponent<ImageProps, React.ElementRef<typeof View>>
{ backgroundImage, filter },
backgroundSize != null && { backgroundSize }
]}
suppressHydrationWarning={true}
/>
{hiddenImage}
{createTintColorSVG(tintColor, filterRef.current)}
</View>
);
}