Some Shader code doesn't work as expected on Android.
Description
Hello Skia team. I love trying something fun with Skia. I'm trying to build image filter feature with Shader and I found out some shader code doesn't work on Android devices. It works as expected on iOS devices.
I attached the code to reproduce.
Thanks :)
Version
1.2.3
Steps to reproduce
This is the result of the code below. The Shader code below works well on iOS but it doesn't on Android devices. I couldn't get what's wrong with the code because there is no error.
You can reproduce with the code below.
Thanks :)
Snack, code example, screenshot, or link to a repository
import {
Canvas,
Fill,
ImageShader,
rect,
Shader,
Skia,
useImage,
} from '@shopify/react-native-skia';
import { useWindowDimensions } from 'react-native';
type Value = string | number;
type Values = Value[];
export const glsl = (source: TemplateStringsArray, ...values: Values) => {
const processed = source.flatMap((s, i) => [s, values[i]]).filter(Boolean);
return processed.join('');
};
export const frag = (source: TemplateStringsArray, ...values: Values) => {
const code = glsl(source, ...values);
const rt = Skia.RuntimeEffect.Make(code);
if (rt === null) {
throw new Error("Couldn't Compile Shader");
}
return rt;
};
const source = frag`
uniform shader image1;
uniform shader image2;
uniform vec2 resolution;
const half3 W = half3(0.2125, 0.7154, 0.0721);
half3 overlayBlender(half3 Color, half3 filter) {
half3 filter_result;
float luminance = dot(filter, W);
if (luminance < 0.5)
filter_result = 2. * filter * Color;
else
filter_result = 1. - (1. - (2. * (filter - 0.5))) * (1. - Color);
return filter_result;
}
half3 BrightnessContrastSaturation(half3 color, float brt, float con, float sat) {
half3 black = half3(0., 0., 0.);
half3 middle = half3(0.5, 0.5, 0.5);
float luminance = dot(color, W);
half3 gray = half3(luminance, luminance, luminance);
half3 brtColor = mix(black, color, brt);
half3 conColor = mix(middle, brtColor, con);
half3 satColor = mix(gray, conColor, sat);
return satColor;
}
half4 setFilter(vec2 uv) {
half3 imageColor = image1.eval(uv * resolution).rgb;
half3 filter = image2.eval(uv * resolution).rgb;
// Adjust the brightness/contrast/saturation
float T_bright = 1.0;
float T_contrast = 1.0;
float T_saturation = 1.0;
half3 bcs_result = BrightnessContrastSaturation(imageColor, T_bright, T_contrast, T_saturation);
// More red, less blue
half3 rb_result = half3(bcs_result.r * 1.3, bcs_result.g, bcs_result.b * 0.9);
// Add filter (overlay blending)
half3 after_filter = mix(rb_result, overlayBlender(rb_result, filter), 0.55);
return half4(after_filter, 1);
}
half4 main(vec2 fragCoord) {
vec2 uv = fragCoord / resolution;
return setFilter(uv);
}
`;
const Playground = () => {
const { width: stageWidth, height: stageHeight } = useWindowDimensions();
const bounds = rect(0, 0, stageWidth, stageHeight);
const uniforms = {
resolution: [stageWidth, stageHeight],
};
const image1 = useImage(
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQmIVmEWQg8wNvhxXSpDgASADcCyYGwPl0R_eS0vwD8NA&s',
);
const image2 = useImage(
'https://github.com/yulu/Instagram_Filter/blob/master/res/drawable-hdpi/toaster.png?raw=true',
);
if (!image1 || !image2) {
return null;
}
return (
<Canvas
style={{
width: stageWidth,
height: stageHeight,
}}
>
<Fill>
<Shader source={source} uniforms={uniforms}>
<ImageShader image={image1} rect={bounds} fit="cover" />
<ImageShader image={image2} rect={bounds} fit="cover" />
</Shader>
</Fill>
</Canvas>
);
};
export default Playground;
does the shader work on https://shaders.skia.org/? It's using WebGL so closer to Android
On Mon, May 20, 2024 at 1:41 PM Daehyeon Mun @.***> wrote:
Description
Hello Skia team. I love trying something fun with Skia. I'm trying to build image filter feature with Shader and I found out some shader code doesn't work on Android devices. It works as expected on iOS devices.
I attached the code to reproduce.
Thanks :)
Version
1.2.3
Steps to reproduce
@.*** (view on web)
This is the result of the code below. Shader works well on iOS but it doesn't on Android devices. I couldn't get what's wrong with the code because there is no error.
You can reproduce with the code below.
Thanks :)
Snack, code example, screenshot, or link to a repository
import { Canvas, Fill, ImageShader, rect, Shader, Skia, useImage, } from @.***/react-native-skia'; import { useWindowDimensions } from 'react-native';
type Value = string | number; type Values = Value[];
export const glsl = (source: TemplateStringsArray, ...values: Values) => { const processed = source.flatMap((s, i) => [s, values[i]]).filter(Boolean); return processed.join(''); };
export const frag = (source: TemplateStringsArray, ...values: Values) => { const code = glsl(source, ...values); const rt = Skia.RuntimeEffect.Make(code); if (rt === null) { throw new Error("Couldn't Compile Shader"); } return rt; };
const source = frag` uniform shader image1; uniform shader image2; uniform vec2 resolution; const half3 W = half3(0.2125, 0.7154, 0.0721);
half3 overlayBlender(half3 Color, half3 filter) { half3 filter_result; float luminance = dot(filter, W);
if (luminance < 0.5) filter_result = 2. * filter * Color; else filter_result = 1. - (1. - (2. * (filter - 0.5))) * (1. - Color);
return filter_result; }
half3 BrightnessContrastSaturation(half3 color, float brt, float con, float sat) { half3 black = half3(0., 0., 0.); half3 middle = half3(0.5, 0.5, 0.5); float luminance = dot(color, W); half3 gray = half3(luminance, luminance, luminance);
half3 brtColor = mix(black, color, brt); half3 conColor = mix(middle, brtColor, con); half3 satColor = mix(gray, conColor, sat); return satColor; }
half4 setFilter(vec2 uv) { half3 imageColor = image1.eval(uv * resolution).rgb; half3 filter = image2.eval(uv * resolution).rgb;
// Adjust the brightness/contrast/saturation float T_bright = 1.0; float T_contrast = 1.0; float T_saturation = 1.0; half3 bcs_result = BrightnessContrastSaturation(imageColor, T_bright, T_contrast, T_saturation);
// More red, less blue half3 rb_result = half3(bcs_result.r * 1.3, bcs_result.g, bcs_result.b * 0.9);
// Add filter (overlay blending) half3 after_filter = mix(rb_result, overlayBlender(rb_result, filter), 0.55);
return half4(after_filter, 1); }
half4 main(vec2 fragCoord) { vec2 uv = fragCoord / resolution; return setFilter(uv); } `;
const Playground = () => { const { width: stageWidth, height: stageHeight } = useWindowDimensions(); const bounds = rect(0, 0, stageWidth, stageHeight);
const uniforms = { resolution: [stageWidth, stageHeight], };
const image1 = useImage( 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQmIVmEWQg8wNvhxXSpDgASADcCyYGwPl0R_eS0vwD8NA&s', ); const image2 = useImage( 'https://github.com/yulu/Instagram_Filter/blob/master/res/drawable-hdpi/toaster.png?raw=true', ); if (!image1 || !image2) { return null; }
return ( <Canvas style={{ width: stageWidth, height: stageHeight, }} > <Fill> <Shader source={source} uniforms={uniforms}> <ImageShader image={image1} rect={bounds} fit="cover" /> <ImageShader image={image2} rect={bounds} fit="cover" /> </Shader> </Fill> </Canvas> ); };
export default Playground;
— Reply to this email directly, view it on GitHub or unsubscribe. You are receiving this email because you are subscribed to this thread.
Triage notifications on the go with GitHub Mobile for iOS or Android.
@wcandillon I tried the code below on the web but it doesn't work. 🥲
const half3 W = half3(0.2125, 0.7154, 0.0721);
half3 overlayBlender(half3 Color, half3 filter) {
half3 filter_result;
float luminance = dot(filter, W);
if (luminance < 0.5)
filter_result = 2. * filter * Color;
else
filter_result = 1. - (1. - (2. * (filter - 0.5))) * (1. - Color);
return filter_result;
}
half3 BrightnessContrastSaturation(half3 color, float brt, float con, float sat) {
half3 black = half3(0., 0., 0.);
half3 middle = half3(0.5, 0.5, 0.5);
float luminance = dot(color, W);
half3 gray = half3(luminance, luminance, luminance);
half3 brtColor = mix(black, color, brt);
half3 conColor = mix(middle, brtColor, con);
half3 satColor = mix(gray, conColor, sat);
return satColor;
}
half4 setFilter(vec2 uv) {
half3 imageColor = iImage1.eval(uv).rgb;
// Adjust the brightness/contrast/saturation
float T_bright = 1.0;
float T_contrast = 1.0;
float T_saturation = 1.0;
half3 bcs_result = BrightnessContrastSaturation(imageColor, T_bright, T_contrast, T_saturation);
// More red, less blue
half3 rb_result = half3(bcs_result.r * 1.3, bcs_result.g, bcs_result.b * 0.9);
// Add filter (overlay blending)
half3 after_filter = mix(rb_result, overlayBlender(rb_result, imageColor), 0.55);
return half4(after_filter, 1);
}
half4 main(float2 fragCoord) {
return setFilter(fragCoord);
}
Thank you @daehyeonmun2021 for such a concise and nicely reproducible example. if you replace the use of the filter variable name, it will work nicely on Android too. I am currently investigating why this error wasn't nicely throw on the React Native side.
I'm closing this, it looks like it will be fixed upstream with Skia: https://groups.google.com/g/skia-discuss/c/WzZaQU3-Lds
I hope this helps
@wcandillon Thanks :D