Colors in image shaders being clamped to 0...1 range

13 views
Skip to first unread message

Kacper Seredyn

unread,
Oct 14, 2025, 3:24:58 PM (4 days ago) Oct 14
to skia-discuss
I'm trying to figure out how to process images containing non-color data exceeding the 0...1 range (e.g. depth and normal information), but whenever I try to evaluate those images in a SkSL shader, their colors get clamped back to the 0...1 range.
I tried messing around with color spaces, using makeRawShader, etc., but pretty much whenever there's an image involved the colors get clamped. Same does not happen when the shader itself outputs such values - they remain unclamped.

Is there a way to retain the values outside 0...1 when they are evaluated within a shader without resorting to workarounds (mapping them to 0...1, but losing precision)?

Example (here I used the Python bindings for ease of experimentation):
import struct
import skia


def main():
    data = [-0.5, 0.5, 1.5, 1] # 1x1 image
    byte_data = struct.pack("f" * 4, *data)

    image = skia.Image.MakeRasterData(skia.ImageInfo.Make(skia.ISize(1, 1), skia.ColorType.kRGBA_F32_ColorType, skia.AlphaType.kUnpremul_AlphaType), byte_data, rowBytes=16)
    raw_image_shader = image.makeRawShader()
    print(f"Original image data:\t\t\t{image.toarray()}")

    surface = skia.Surface.MakeRaster(skia.ImageInfo.Make(skia.ISize(1, 1), skia.ColorType.kRGBA_F32_ColorType, skia.AlphaType.kUnpremul_AlphaType), rowBytes=16)
    canvas = surface.getCanvas()
    paint = skia.Paint()

    # shader without an image
    paint.setShader(make_shader("""
    vec4 main(vec2 coord) {
        return vec4(-0.5, 0.5, 1.5, 1.0);
    }
    """, None))
    canvas.drawPaint(paint)

    print(f"Out-of-range data from shader:\t\t{surface.makeImageSnapshot().toarray()}")

    # shader with an image
    paint.setShader(make_shader("""
    uniform shader img;
    vec4 main(vec2 coord) {
        return img.eval(coord);
    }
    """, raw_image_shader))
    canvas.drawPaint(paint)

    print(f"Data sampled from image by shader:\t{surface.makeImageSnapshot().toarray()}")

def make_shader(sksl, image):
    sksl_effect = skia.RuntimeEffect.MakeForShader(sksl)
    shader_builder = skia.RuntimeShaderBuilder(sksl_effect)
    if image is not None:
        shader_builder.setChild("img", image)
    return shader_builder.makeShader()

Results:
Original image data:                    [[[-0.5  0.5  1.5  1. ]]]
Out-of-range data from shader:          [[[-0.5  0.5  1.5  1. ]]]
Data sampled from image by shader:      [[[0. 0.5019608  1.  1. ]]]
Reply all
Reply to author
Forward
0 new messages