| layout(builtin=15) float4 sk_FragCoord; |
| |
| //--- Luma ------------------------------------------------------------------------ |
| |
| half4 sk_luma(half3 color) { |
| return saturate(dot(half3(0.2126, 0.7152, 0.0722), color)).000r; |
| } |
| |
| //--- Decal ------------------------------------------------------------------------ |
| |
| half4 sk_decal(shader image, float2 coord, float4 decalBounds) { |
| half4 d = half4(decalBounds - coord.xyxy) * half4(-1, -1, 1, 1); |
| d = saturate(d + 0.5); |
| return (d.x * d.y * d.z * d.w) * image.eval(coord); |
| } |
| |
| //--- Displacement ----------------------------------------------------------------- |
| |
| half4 sk_displacement(shader displMap, |
| shader colorMap, |
| float2 coord, |
| half2 scale, |
| half4 xSelect, // Only one of RGBA will be 1, the rest are 0 |
| half4 ySelect) { |
| half4 displColor = unpremul(displMap.eval(coord)); |
| half2 displ = half2(dot(displColor, xSelect), dot(displColor, ySelect)); |
| displ = scale * (displ - 0.5); |
| return colorMap.eval(coord + displ); |
| } |
| |
| //--- Magnifier -------------------------------------------------------------------- |
| |
| half4 sk_magnifier(shader src, float2 coord, float4 lensBounds, float4 zoomXform, |
| float2 invInset) { |
| float2 zoomCoord = zoomXform.xy + zoomXform.zw*coord; |
| // edgeInset is the smallest distance to the lens bounds edges, in units of "insets". |
| float2 edgeInset = min(coord - lensBounds.xy, lensBounds.zw - coord) * invInset; |
| |
| // The equations for 'weight' ensure that it is 0 along the outside of |
| // lensBounds so it seams with any un-zoomed, un-filtered content. The zoomed |
| // content fills a rounded rectangle that is 1 "inset" in from lensBounds with |
| // circular corners with radii equal to the inset distance. Outside of this |
| // region, there is a non-linear weighting to compress the un-zoomed content |
| // to the zoomed content. The critical zone about each corner is limited |
| // to 2x"inset" square. |
| float weight = all(lessThan(edgeInset, float2(2.0))) |
| // Circular distortion weighted by distance to inset corner |
| ? (2.0 - length(2.0 - edgeInset)) |
| // Linear zoom, or single-axis compression outside of the inset |
| // area (if delta < 1) |
| : min(edgeInset.x, edgeInset.y); |
| |
| // Saturate before squaring so that negative weights are clamped to 0 |
| // before squaring |
| weight = saturate(weight); |
| return src.eval(mix(coord, zoomCoord, weight*weight)); |
| } |
| |
| //--- High Contrast ---------------------------------------------------------------- |
| |
| $pure half3 $high_contrast_rgb_to_hsl(half3 c) { |
| half mx = max(max(c.r,c.g),c.b), |
| mn = min(min(c.r,c.g),c.b), |
| d = mx-mn, |
| invd = 1.0 / d, |
| g_lt_b = c.g < c.b ? 6.0 : 0.0; |
| |
| // We'd prefer to write these tests like `mx == c.r`, but on some GPUs, max(x,y) is |
| // not always equal to either x or y. So we use long form, c.r >= c.g && c.r >= c.b. |
| half h = (1/6.0) * (mx == mn ? 0.0 : |
| /*mx==c.r*/ c.r >= c.g && c.r >= c.b ? invd * (c.g - c.b) + g_lt_b : |
| /*mx==c.g*/ c.g >= c.b ? invd * (c.b - c.r) + 2.0 |
| /*mx==c.b*/ : invd * (c.r - c.g) + 4.0); |
| half sum = mx+mn, |
| l = sum * 0.5, |
| s = mx == mn ? 0.0 |
| : d / (l > 0.5 ? 2.0 - sum : sum); |
| return half3(h,s,l); |
| } |
| |
| half3 sk_high_contrast(half3 color, half grayscale, half invertStyle, half contrast) { |
| if (grayscale == 1) { |
| color = dot(half3(0.2126, 0.7152, 0.0722), color).rrr; |
| } |
| if (invertStyle == 1) { // brightness |
| color = 1.0 - color; |
| } else if (invertStyle == 2) { // lightness |
| color = $high_contrast_rgb_to_hsl(color); |
| color.b = 1 - color.b; |
| color = $hsl_to_rgb(color); |
| } |
| return saturate(mix(half3(0.5), color, contrast)); |
| } |
| |
| //--- Normal ----------------------------------------------------------------------- |
| |
| $pure half3 $normal_filter(half3 alphaC0, half3 alphaC1, half3 alphaC2, half negSurfaceDepth) { |
| // The right column (or bottom row) terms of the Sobel filter. The left/top is just the |
| // negative, and the middle row/column is all 0s so those instructions are skipped. |
| const half3 kSobel = 0.25 * half3(1,2,1); |
| half3 alphaR0 = half3(alphaC0.x, alphaC1.x, alphaC2.x); |
| half3 alphaR2 = half3(alphaC0.z, alphaC1.z, alphaC2.z); |
| half nx = dot(kSobel, alphaC2) - dot(kSobel, alphaC0); |
| half ny = dot(kSobel, alphaR2) - dot(kSobel, alphaR0); |
| return normalize(half3(negSurfaceDepth * half2(nx, ny), 1)); |
| } |
| |
| half4 sk_normal(shader alphaMap, float2 coord, float4 edgeBounds, half negSurfaceDepth) { |
| half3 alphaC0 = half3( |
| alphaMap.eval(clamp(coord + float2(-1,-1), edgeBounds.LT, edgeBounds.RB)).a, |
| alphaMap.eval(clamp(coord + float2(-1, 0), edgeBounds.LT, edgeBounds.RB)).a, |
| alphaMap.eval(clamp(coord + float2(-1, 1), edgeBounds.LT, edgeBounds.RB)).a); |
| half3 alphaC1 = half3( |
| alphaMap.eval(clamp(coord + float2( 0,-1), edgeBounds.LT, edgeBounds.RB)).a, |
| alphaMap.eval(clamp(coord + float2( 0, 0), edgeBounds.LT, edgeBounds.RB)).a, |
| alphaMap.eval(clamp(coord + float2( 0, 1), edgeBounds.LT, edgeBounds.RB)).a); |
| half3 alphaC2 = half3( |
| alphaMap.eval(clamp(coord + float2( 1,-1), edgeBounds.LT, edgeBounds.RB)).a, |
| alphaMap.eval(clamp(coord + float2( 1, 0), edgeBounds.LT, edgeBounds.RB)).a, |
| alphaMap.eval(clamp(coord + float2( 1, 1), edgeBounds.LT, edgeBounds.RB)).a); |
| |
| half mainAlpha = alphaC1.y; // offset = (0,0) |
| return half4($normal_filter(alphaC0, alphaC1, alphaC2, negSurfaceDepth), mainAlpha); |
| } |
| |
| //--- Lighting --------------------------------------------------------------------- |
| |
| $pure half3 $surface_to_light(half lightType, half3 lightPos, half3 lightDir, half3 coord) { |
| // Spot (> 0) and point (== 0) have the same equation |
| return lightType >= 0 ? normalize(lightPos - coord) |
| : lightDir; |
| } |
| |
| $pure half $spotlight_scale(half3 lightDir, half3 surfaceToLight, half cosCutoffAngle, |
| half spotFalloff) { |
| const half kConeAAThreshold = 0.016; |
| const half kConeScale = 1.0 / kConeAAThreshold; |
| |
| half cosAngle = -dot(surfaceToLight, lightDir); |
| if (cosAngle < cosCutoffAngle) { |
| return 0.0; |
| } else { |
| half scale = pow(cosAngle, spotFalloff); |
| return (cosAngle < cosCutoffAngle + kConeAAThreshold) |
| ? scale * (cosAngle - cosCutoffAngle) * kConeScale |
| : scale; |
| } |
| } |
| |
| $pure half4 $compute_lighting(half3 color, half shininess, half materialType, half lightType, |
| half3 normal, half3 lightDir, half3 surfaceToLight, |
| half cosCutoffAngle, half spotFalloff) { |
| // Point and distant light color contributions are constant, but |
| // spotlights fade based on the angle away from its direction. |
| if (lightType > 0) { |
| color *= $spotlight_scale(lightDir, surfaceToLight, cosCutoffAngle, spotFalloff); |
| } |
| |
| // Diffuse and specular reflections scale the light's color differently |
| if (materialType == 0) { |
| half coeff = dot(normal, surfaceToLight); |
| color = saturate(coeff * color); |
| return half4(color, 1.0); |
| } else { |
| half3 halfDir = normalize(surfaceToLight + half3(0, 0, 1)); |
| half coeff = pow(dot(normal, halfDir), shininess); |
| color = saturate(coeff * color); |
| return half4(color, max(max(color.r, color.g), color.b)); |
| } |
| } |
| |
| half4 sk_lighting(shader normalMap, float2 coord, |
| half depth, half shininess, half materialType, half lightType, |
| half3 lightPos, half spotFalloff, |
| half3 lightDir, half cosCutoffAngle, |
| half3 lightColor) { |
| half4 normalAndA = normalMap.eval(coord); |
| half3 surfaceToLight = $surface_to_light(lightType, lightPos, lightDir, |
| half3(coord, depth * normalAndA.a)); |
| return $compute_lighting(lightColor, shininess, materialType, lightType, normalAndA.xyz, |
| lightDir, surfaceToLight, cosCutoffAngle, spotFalloff); |
| } |
| |
| //--- Arithmetic Blend ------------------------------------------------------------- |
| |
| half4 sk_arithmetic_blend(half4 src, half4 dst, half4 k, half pmClamp) { |
| half4 color = saturate(k.x * src * dst + k.y * src + k.z * dst + k.w); |
| color.rgb = min(color.rgb, max(color.a, pmClamp)); |
| return color; |
| } |
| |
| //--- Sparse Morphology ------------------------------------------------------------ |
| |
| half4 sk_sparse_morphology(shader child, float2 coord, half2 offset, half flip) { |
| half4 aggregate = max(flip * child.eval(coord + offset), |
| flip * child.eval(coord - offset)); |
| return flip * aggregate; |
| } |
| |
| //--- Linear Morphology ------------------------------------------------------------ |
| |
| half4 sk_linear_morphology(shader child, float2 coord, half2 offset, half flip, int radius) { |
| |
| // KEEP IN SYNC WITH CONSTANT IN `SkMorphologyImageFilter.cpp` |
| const int kMaxLinearRadius = 14; |
| |
| half4 aggregate = flip * child.eval(coord); // case 0 only needs a single sample |
| half2 delta = offset; |
| for (int i = 1; i <= kMaxLinearRadius; ++i) { |
| if (i > radius) break; |
| aggregate = max(aggregate, max(flip * child.eval(coord + delta), |
| flip * child.eval(coord - delta))); |
| delta += offset; |
| } |
| return flip * aggregate; |
| } |
| |
| //--- Overdraw --------------------------------------------------------------------- |
| |
| half4 sk_overdraw(half alpha, half4 color0, half4 color1, half4 color2, |
| half4 color3, half4 color4, half4 color5) { |
| return alpha < (0.5 / 255.) ? color0 |
| : alpha < (1.5 / 255.) ? color1 |
| : alpha < (2.5 / 255.) ? color2 |
| : alpha < (3.5 / 255.) ? color3 |
| : alpha < (4.5 / 255.) ? color4 |
| : color5; |
| } |