blob: 3c001e63099db0f2195e79f43a8fb082674775fa [file]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
precision mediump float;
#include <impeller/color.glsl>
#include <impeller/types.glsl>
uniform FragInfo {
vec4 color;
vec2 center;
vec2 size;
float stroke_width;
float stroke_join;
float aa_pixels;
float stroked;
float type;
vec4 radii;
vec4 radii_right;
vec2 superellipse_degree;
vec2 superellipse_a;
vec2 corner_angle_span;
vec2 corner_circle_center_top;
vec2 corner_circle_center_right;
float superellipse_c;
vec2 superellipse_scale;
}
frag_info;
out vec4 frag_color;
highp in vec2 v_position;
const float PI = 3.14159265;
const float TWO_PI = 6.28318531;
const float PI_OVER_FOUR = 0.78539816;
float distanceFromCircle(vec2 p, float radius) {
return length(p) - radius;
}
float distanceFromRect(vec2 p, vec2 b) {
vec2 d = abs(p) - b;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
}
// SDF for a superellipse defined by (x/a)^n + (y/b)^n = 1
//
// `p` is the coordinate of the point relative to the center of the superellipse
// normalized by the length of the ellipse semi-axes (a, b)
// `n` is the exponent of the superellipse
//
// https://iquilezles.org/articles/ellipsedist/
//
// The MIT License
// Copyright © 2015 Inigo Quilez
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions: The above copyright
// notice and this permission notice shall be included in all copies or
// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS",
// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
// THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// https://www.youtube.com/c/InigoQuilez
// https://iquilezles.org
float sdSuperellipse(vec2 p, float n) {
// symmetries
p = abs(p);
if (p.y > p.x)
p = p.yx;
n = 2.0 / n; // note the remapping in order to match the implicit versions
float xa = 0.0, xb = TWO_PI / 8.0;
for (int i = 0; i < 6; i++) {
float x = 0.5 * (xa + xb);
float c = cos(x);
float s = sin(x);
float cn = pow(c, n);
float sn = pow(s, n);
float y = (p.x - cn) * cn * s * s - (p.y - sn) * sn * c * c;
if (y < 0.0)
xa = x;
else
xb = x;
}
// compute distance
vec2 qa = pow(vec2(cos(xa), sin(xa)), vec2(n));
vec2 qb = pow(vec2(cos(xb), sin(xb)), vec2(n));
vec2 pa = p - qa, ba = qb - qa;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h) * sign(pa.x * ba.y - pa.y * ba.x);
}
float distanceFromRoundedSuperellipse(vec2 p,
vec2 degree,
vec2 se_a,
vec4 radii_top,
vec2 angle_span,
vec2 circle_center_top,
vec4 radii_right,
vec2 circle_center_right,
float c,
vec2 scale) {
// Do work in the first quadrant to simply things.
p = abs(p);
// Map p in to a square.
vec2 p_norm = p / scale;
// Declare all RSE params for a single octant.
float se_degree, span, radius, axis_length;
vec2 circle_center;
// 'p' in the coordinate system of the octant.
vec2 p_oct;
// We split the quadrant along the diagonal of the transition (p_norm.y + c ==
// p_norm.x). This allows us to grab the correct set of parameters for the
// "top" and "right" halves of the corner.
if (p_norm.y + c > p_norm.x) {
p_oct = p_norm + vec2(0.0, c);
se_degree = degree.x;
span = angle_span.x;
radius = radii_top.x;
circle_center = circle_center_top;
axis_length = se_a.x;
} else {
// For the 'right' octant, we flip the point and shift it according to
// the CPU's OctantContains/Flip logic.
p_oct = p_norm.yx - vec2(0.0, c);
se_degree = degree.y;
span = angle_span.y;
radius = radii_right.x;
circle_center = circle_center_right;
axis_length = se_a.y;
}
// Move the point to the corner circle's coordinate system.
vec2 p_rel = p_oct - circle_center;
// Grab the angle offset of the point.
float theta = atan(p_rel.y, p_rel.x);
// The angular distance between the point and the 45 degree midline.
float d_theta = theta - PI_OVER_FOUR;
d_theta = mod(d_theta + PI, TWO_PI) - PI;
// If the point is within the span of the corner circle's arc,
// use a circle SDF.
// This works because the normals of the circular and superelliptical sections
// agree at the transition angle, the total RSE curve is continuous and
// the closest point on a continuous curve to a point lies along the normal.
if (abs(d_theta) < abs(span)) {
return distanceFromCircle(p_rel, radius);
}
return sdSuperellipse(p_oct / axis_length, se_degree) * axis_length;
}
// Define an ellipse as q(w) = (a*cos(w), b*sin(w)), and p = (x, y) on the
// plane. Let q(w0) be the closest point on q to p, then q(w0) - p is tangent to
// q(w0), and (q(w0) - p) dot q'(w0) = 0. This function uses the Newton-Raphson
// method to find q(w0).
//
// `p` is the coordinate of the point relative to the center of the oval
// `ab` is the extent of the oval from the center to the x and y axis
//
// https://iquilezles.org/articles/ellipsedist/
//
// The MIT License
// Copyright © 2015 Inigo Quilez
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions: The above copyright
// notice and this permission notice shall be included in all copies or
// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS",
// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
// THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// https://www.youtube.com/c/InigoQuilez
// https://iquilezles.org
float distanceFromOval(vec2 p, vec2 ab) {
// The ellipse is symmetric along both axes, do the calculation in the upper
// right quadrant.
p = abs(p);
// Initial guess for w0. Determine whether q is closer to the top of the
// ellipse or closer to the righthand side. Use the top (0) or righthand side
// (pi/2) as the initial guess for w0.
vec2 q = ab * (p - ab);
float w = (q.x < q.y) ? 1.570796327 : 0.0;
for (int i = 0; i < 5; i++) {
vec2 cs = vec2(cos(w), sin(w));
// u = q(w) = (a*cos(w), b*sin(w))
vec2 u = ab * vec2(cs.x, cs.y);
// v = q'(w) = (a*-sin(w), b*cos(w))
vec2 v = ab * vec2(-cs.y, cs.x);
// Newton-Raphson update step, w_n = w_n-1 + f(w_n-1)/f'(w_n-1)
// In this case f(w) = (p - q(w)) dot q'(w) = (p - u) dot v
w = w + dot(p - u, v) / (dot(p - u, u) + dot(v, v));
}
// Compute final point and distance
float d = length(p - ab * vec2(cos(w), sin(w)));
// Return signed distance.
// p is outside the ellipse if (p.x/a)^2 + (p.y/b)^2 > 0
return (dot(p / ab, p / ab) > 1.0) ? d : -d;
}
float distanceFromChamferRect(vec2 p, vec2 b, float chamfer) {
vec2 d = abs(p) - b;
d = (d.y > d.x) ? d.yx : d.xy;
d.y += chamfer;
const float k = 1.0 - sqrt(2.0);
if (d.y < 0.0 && d.y + d.x * k < 0.0) {
return d.x;
}
if (d.x < d.y) {
return (d.x + d.y) * sqrt(0.5);
}
return length(d);
}
// Exact math for rounded rect.
//
// `p` is position relative to the center of the shape.
// `b` is the size of box, .x is the distance between center and left/right, .y
// is the distance between center and top/bottom. `r` is radii for each corner
// in order [bottom_right, top_right, bottom_left, top_left].
//
// See https://iquilezles.org/articles/distfunctions2d/
//
// The MIT License
// Copyright © 2015 Inigo Quilez
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions: The above copyright
// notice and this permission notice shall be included in all copies or
// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS",
// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
// THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// https://www.youtube.com/c/InigoQuilez
// https://iquilezles.org
float distanceFromRoundedRect(in vec2 p, in vec2 b, in vec4 r) {
r.xy = (p.x > 0.0) ? r.xy : r.zw;
r.x = (p.y > 0.0) ? r.x : r.y;
vec2 q = abs(p) - b + r.x;
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x;
}
// Returns the pixel size for the given SDF value.
//
// This is the size of a pixel at the current fragment, measured in the
// direction perpendicular to the SDF's shape.
float pixelSize(float sdf) {
// Gradient vector of the SDF at point p. Points in the direction of steepest
// increase away from SDF's shape. At the edges of the shape, this is
// perpendicular to the edge.
//
// The x and y magnitudes of the gradient are determined by the dFdx and dFdy
// of the SDF value. dFdx and dFdy return the change of a value in the x and y
// direction per screen-space unit (physical pixel). So this gradient
// is the change in the SDF, at point p, in local space units per pixel.
vec2 gradient = vec2(dFdx(sdf), dFdy(sdf));
// The length of the gradient vector is how fast the SDF changes per
// screen-space pixel distance. In other words, it is the size of a pixel
// measured in the units of the SDF calculation.
//
// In local space, the SDF always increases by 1 in the gradient's direction
// per unit distance. That's the definition of an SDF: it is the distance to
// the closest point of the shape. But in terms of screen-space, the SDF may
// increase by a different amount than 1 per unit distance (in screen-space
// units, i.e. physical pixels), due to scales/skews/rotations.
//
// As an example, consider the SDF of an unscaled/unskewed circle centered at
// the origin. The gradient is vec2(1.0, 0.0) for points along the positive x
// axis[^1]: for every one pixel we move along the positive x axis,
// the SDF value increases by 1.0. Now consider the same circle with a
// transformation that scales it by 2 along the x axis. With a transformation,
// the local space size of the circle remains the same, but the way it maps
// onto screen-space pixels is changed. In screen-space the circle is
// stretched to be twice as wide as the original circle in the postive and
// negative x directions. The gradient for this will be vec2(0.5, 0.0) along
// the positive x axis: for every physical pixel we move along the positive x
// axis, we move only 0.5 units in the SDF's local space.
//
// [^1]: In the real world, there would not be a pixel where the gradient
// vector for a circle is exactly (1.0, 0.0) due to the way dFdx and dFdy are
// approximated from pixel samples. This does not affect the applicability
// of this example.
return length(gradient);
}
// Computes the SDF value and pixel size for a filled shape.
//
// `p` is position relative to the center of the shape.
//
// Returns a vec2 with:
// x: The SDF value at `p`.
// y: The pixel size at `p`.
vec2 filledSDF(vec2 p) {
float sdf;
if (frag_info.type < 0.5) { // Circle
sdf = distanceFromCircle(p, frag_info.size.x);
} else if (frag_info.type < 1.5) { // Rect
sdf = distanceFromRect(p, frag_info.size);
} else if (frag_info.type < 2.5) { // Oval
sdf = distanceFromOval(p, frag_info.size);
} else if (frag_info.type < 3.5) { // Rounded Rect
sdf = distanceFromRoundedRect(p, frag_info.size, frag_info.radii);
} else {
sdf = distanceFromRoundedSuperellipse(
p, frag_info.superellipse_degree, frag_info.superellipse_a,
frag_info.radii, frag_info.corner_angle_span,
frag_info.corner_circle_center_top, frag_info.radii_right,
frag_info.corner_circle_center_right, frag_info.superellipse_c,
frag_info.superellipse_scale);
}
return vec2(sdf, pixelSize(sdf));
}
// Computes the SDF value and pixel size for a stroked shape.
//
// `p` is position relative to the center of the shape.
//
// Returns a vec2 with:
// x: The SDF value at `p`.
// y: The pixel size at `p`.
vec2 strokedSDF(vec2 p) {
// Get the base (filled) SDF for this shape. The filled SDF pixel size is used
// to calculate a minimum stroke width, and the filled SDF value is used to
// calculate the stroked SDF value for many shapes.
vec2 base_sdf_and_pixel_size = filledSDF(p);
float base_sdf = base_sdf_and_pixel_size.x;
float base_pixel_size = base_sdf_and_pixel_size.y;
// Stroke width is clamped to be at least the base sdf's pixel size.
float half_stroke = max(frag_info.stroke_width, base_pixel_size) * 0.5;
// Some cases need special handling because their stroked SDFs have a
// different shape from their base SDFs.
if (frag_info.type >= 0.5 && frag_info.type < 1.5) { // Rect
if (frag_info.stroke_join < 0.5) { // Miter
// Outer edge is the SDF for a rect with size expanded by half_stroke.
float outer = distanceFromRect(p, frag_info.size + half_stroke);
// Inner edge is base_sdf's -half_stroke isoline.
float inner = base_sdf + half_stroke;
float sdf = max(outer, -inner);
return vec2(sdf, pixelSize(sdf));
} else if (frag_info.stroke_join < 1.5) { // Bevel
// Outer edge is the SDF for a rect with size expanded by half_stroke,
// with a half_stroke chamfer.
float outer =
distanceFromChamferRect(p, frag_info.size + half_stroke, half_stroke);
// Inner edge is base_sdf's -half_stroke isoline.
float inner = base_sdf + half_stroke;
float sdf = max(outer, -inner);
return vec2(sdf, pixelSize(sdf));
} // else stroke_join is Round. Fall through to the common case.
}
// For most shapes, the stroked SDF is defined by the +/- half_stroke
// isolines of the base SDF. See the "Making shapes annular" section in
// https://iquilezles.org/articles/distfunctions2d/.
float sdf = abs(base_sdf) - half_stroke;
// For these shapes, the stroked pixel size is the same as the base pixel
// size. This is because the stroked SDF's gradient has the same magnitudes as
// the base SDF's gradient (except for a discontinuity at the center of the
// stroke, which does not affect the final render).
return vec2(sdf, base_pixel_size);
}
void main() {
vec2 p = v_position - frag_info.center;
vec2 sdf_and_pixel_size =
(frag_info.stroked < 0.5) ? filledSDF(p) : strokedSDF(p);
float sdf = sdf_and_pixel_size.x;
float pixel_size = sdf_and_pixel_size.y;
// Anti-aliasing. Fade from alpha 1 to 0 across the edge of the SDF (where it
// goes from negative to positive). Fade through distance of half
// (pixel_size * aa_pixels) in each direction.
float fade_size = pixel_size * frag_info.aa_pixels * 0.5;
float alpha = 1.0 - smoothstep(-fade_size, fade_size, sdf);
frag_color = vec4(frag_info.color.rgb, frag_info.color.a * alpha);
frag_color = IPPremultiply(frag_color);
}