react-native-skia icon indicating copy to clipboard operation
react-native-skia copied to clipboard

Some Shader code doesn't work as expected on Android.

Open daehyeonmun2021 opened this issue 1 year ago • 2 comments

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

CleanShot 2024-05-20 at 20 33 33@2x

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;

daehyeonmun2021 avatar May 20 '24 11:05 daehyeonmun2021

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 avatar May 20 '24 12:05 wcandillon

@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);
}

daehyeonmun2021 avatar May 20 '24 12:05 daehyeonmun2021

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.

wcandillon avatar May 28 '24 11:05 wcandillon

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 avatar May 28 '24 13:05 wcandillon

@wcandillon Thanks :D

daehyeonmun2021 avatar May 28 '24 13:05 daehyeonmun2021