Пример #1
0
 /// <summary>
 /// Emits a layer containing a filled box.
 /// </summary>
 /// <param name="context">The <see cref="View.RenderContext" /> object used to emit
 /// layers.</param>
 /// <param name="bounds">The bounding box of the box to be rendered.</param>
 /// <param name="fillColor">The interior color of the box to be rendered.</param>
 public static void EmitBoxFill(View.RenderContext context, Box2 bounds, Rgba fillColor)
 {
     context.EmitLayer(new SolidColorLayerInfo()
     {
         Bounds    = bounds,
         FillColor = fillColor,
     });
 }
Пример #2
0
        /// <summary>
        /// Emits a layer set containing the stroke of a box.
        /// </summary>
        /// <param name="context">The <see cref="View.RenderContext" /> object used to emit
        /// layers.</param>
        /// <param name="bounds">The bounding box of the box to be rendered.</param>
        /// <param name="strokeColor">The color of the box to be rendered.</param>
        /// <param name="width">The width of the stroked line. Must not be negative.</param>
        public static void EmitBoxStroke(View.RenderContext context, Box2 bounds, Rgba strokeColor, float width)
        {
            if (!(width >= 0))
            {
                throw new ArgumentOutOfRangeException(nameof(width));
            }
            bounds = bounds.Normalized;

            if (width <= 0)
            {
                // Make sure to always emit the same number of layers
                context.EmitLayer(null); context.EmitLayer(null);
                context.EmitLayer(null); context.EmitLayer(null);
                return;
            }

            if (bounds.Width < width * 2 || bounds.Height < width * 2)
            {
                // The bounding rect is narrower than the stroke width
                context.EmitLayer(new SolidColorLayerInfo()
                {
                    Bounds    = bounds,
                    FillColor = strokeColor,
                });
                context.EmitLayer(null); context.EmitLayer(null); context.EmitLayer(null);
                return;
            }

            // Render the stroke of a box using four filled rectangles. This has a T-junction
            // issue but should be okay in practice.
            context.EmitLayer(new SolidColorLayerInfo()
            {
                Bounds    = new Box2(bounds.Min, new Vector2(bounds.Max.X, bounds.Min.Y + width)),
                FillColor = strokeColor,
            });
            context.EmitLayer(new SolidColorLayerInfo()
            {
                Bounds    = new Box2(new Vector2(bounds.Min.X, bounds.Max.Y - width), bounds.Max),
                FillColor = strokeColor,
            });
            context.EmitLayer(new SolidColorLayerInfo()
            {
                Bounds = new Box2(
                    new Vector2(bounds.Min.X, bounds.Min.Y + width),
                    new Vector2(bounds.Min.X + width, bounds.Max.Y - width)
                    ),
                FillColor = strokeColor,
            });
            context.EmitLayer(new SolidColorLayerInfo()
            {
                Bounds = new Box2(
                    new Vector2(bounds.Max.X - width, bounds.Min.Y + width),
                    new Vector2(bounds.Max.X, bounds.Max.Y - width)
                    ),
                FillColor = strokeColor,
            });
        }
Пример #3
0
        /// <summary>
        /// Emits a layer containing a blurred, filled box.
        /// </summary>
        /// <param name="context">The <see cref="View.RenderContext" /> object used to emit
        /// layers.</param>
        /// <param name="bounds">The bounding box of the box to be rendered.</param>
        /// <param name="opacity">The opacity of the box to be rendered.</param>
        /// <param name="radius">The blur radius. Must not be negative.</param>
        public static void EmitBoxFillShadow(View.RenderContext context, Box2 bounds, float opacity, float radius)
        {
            if (!(radius >= 0))
            {
                throw new ArgumentOutOfRangeException(nameof(radius));
            }

            // exp(-x^2) is sufficiently small for `x > 2`, so map `[-radius, radius]` to
            // `[-2, 2]` in the blur kernel's space.
            radius *= 1 / 2f;

            bounds = bounds.Normalized;
            Vector2 relativeSize = bounds.Size * (0.5f / radius);

            if (Math.Max(relativeSize.X, relativeSize.Y) > 1e10)
            {
                // Guard against extremely large bounds and/or small (or zero) `radius`
                context.EmitLayer(new SolidColorLayerInfo()
                {
                    Bounds    = bounds,
                    FillColor = new Rgba(0, 0, 0, opacity),
                });

                // Make sure to always emit the same number of layers
                context.EmitLayer(null); context.EmitLayer(null); context.EmitLayer(null);
                return;
            }

            Vector2 center = (bounds.Min + bounds.Max) * 0.5f;

            // Approximate `erf(x+w) - erf(x-w)` for each axis (where `w = relativeSize.{X,Y}`)
            // by transforming `erf(x)`. Down below is where the magic happens.
            const float a1 = 1.06527f, a2 = 1.13511f, a3 = -3.1682f;
            Vector2     erfOffset  = default;
            Vector2     erfDensity = default;

            for (int axis = 0; axis < 2; ++axis)
            {
                float w = relativeSize.GetElementAt(axis);
                erfOffset.ElementAt(axis)  = w + a1 - a1 / (1 + a2 * MathF.Exp(a3 * w));
                erfDensity.ElementAt(axis) = 2 / (1 + MathF.Exp(w * (-10 / 3f))) - 1;
            }

            opacity *= erfDensity.X * erfDensity.Y;

            // Render pieces of transformed `erf(x) * erf(y)`
            var     source            = BlurredBox.Image;
            Vector2 sourceOrigin      = new Vector2(BlurredBox.Width * BlurredBox.Resolution);
            Vector2 sourceInset       = erfOffset * BlurredBox.Resolution;
            Vector2 sourceOuterCorner = new Vector2(BlurredBox.Width) * BlurredBox.Resolution;
            Box2    sourceBox         = new Box2(
                sourceOrigin - sourceInset,
                sourceOrigin + sourceOuterCorner
                );

            Vector2 layerSize = (erfOffset + new Vector2(BlurredBox.Width)) * radius;

            for (int i = 0; i < 4; ++i)
            {
                // For each quadrant...
                bool    ix = (i & 1) != 0, iy = (i & 2) != 0;
                float   fx = ix ? 1f : -1f, fy = iy ? 1f : -1f;
                Vector2 dvec = new Vector2(fx, fy);

                context.EmitLayer(new ImageLayerInfo()
                {
                    Bounds  = new Box2(center, center + layerSize * dvec),
                    Image   = source,
                    Source  = sourceBox,
                    Opacity = opacity,
                });
            }
        }