mirror of https://github.com/SixLabors/ImageSharp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
267 lines
9.7 KiB
267 lines
9.7 KiB
// <copyright file="Pen{TColor}.cs" company="James Jackson-South">
|
|
// Copyright (c) James Jackson-South and contributors.
|
|
// Licensed under the Apache License, Version 2.0.
|
|
// </copyright>
|
|
|
|
namespace ImageSharp.Drawing.Pens
|
|
{
|
|
using System;
|
|
using System.Numerics;
|
|
|
|
using ImageSharp.Drawing.Brushes;
|
|
using ImageSharp.Drawing.Paths;
|
|
using Processing;
|
|
|
|
/// <summary>
|
|
/// Provides a pen that can apply a pattern to a line with a set brush and thickness
|
|
/// </summary>
|
|
/// <typeparam name="TColor">The type of the color.</typeparam>
|
|
/// <remarks>
|
|
/// The pattern will be in to the form of new float[]{ 1f, 2f, 0.5f} this will be
|
|
/// converted into a pattern that is 3.5 times longer that the width with 3 sections
|
|
/// section 1 will be width long (making a square) and will be filled by the brush
|
|
/// section 2 will be width * 2 long and will be empty
|
|
/// section 3 will be width/2 long and will be filled
|
|
/// the the pattern will imidiatly repeat without gap.
|
|
/// </remarks>
|
|
public class Pen<TColor> : IPen<TColor>
|
|
where TColor : struct, IPackedPixel, IEquatable<TColor>
|
|
{
|
|
private static readonly float[] EmptyPattern = new float[0];
|
|
private readonly float[] pattern;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ImageSharp.Drawing.Pens.Pen{TColor}"/> class.
|
|
/// </summary>
|
|
/// <param name="color">The color.</param>
|
|
/// <param name="width">The width.</param>
|
|
/// <param name="pattern">The pattern.</param>
|
|
public Pen(TColor color, float width, float[] pattern)
|
|
: this(new SolidBrush<TColor>(color), width, pattern)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ImageSharp.Drawing.Pens.Pen{TColor}"/> class.
|
|
/// </summary>
|
|
/// <param name="brush">The brush.</param>
|
|
/// <param name="width">The width.</param>
|
|
/// <param name="pattern">The pattern.</param>
|
|
public Pen(IBrush<TColor> brush, float width, float[] pattern)
|
|
{
|
|
this.Brush = brush;
|
|
this.Width = width;
|
|
this.pattern = pattern;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ImageSharp.Drawing.Pens.Pen{TColor}"/> class.
|
|
/// </summary>
|
|
/// <param name="color">The color.</param>
|
|
/// <param name="width">The width.</param>
|
|
public Pen(TColor color, float width)
|
|
: this(new SolidBrush<TColor>(color), width)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ImageSharp.Drawing.Pens.Pen{TColor}"/> class.
|
|
/// </summary>
|
|
/// <param name="brush">The brush.</param>
|
|
/// <param name="width">The width.</param>
|
|
public Pen(IBrush<TColor> brush, float width)
|
|
: this(brush, width, EmptyPattern)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ImageSharp.Drawing.Pens.Pen{TColor}"/> class.
|
|
/// </summary>
|
|
/// <param name="pen">The pen.</param>
|
|
internal Pen(Pen<TColor> pen)
|
|
: this(pen.Brush, pen.Width, pen.pattern)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the brush.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The brush.
|
|
/// </value>
|
|
public IBrush<TColor> Brush { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the width.
|
|
/// </summary>
|
|
/// <value>
|
|
/// The width.
|
|
/// </value>
|
|
public float Width { get; }
|
|
|
|
/// <summary>
|
|
/// Creates the applicator for applying this pen to an Image
|
|
/// </summary>
|
|
/// <param name="sourcePixels">The source pixels.</param>
|
|
/// <param name="region">The region the pen will be applied to.</param>
|
|
/// <returns>
|
|
/// Returns a the applicator for the pen.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// The <paramref name="region" /> when being applied to things like shapes would ussually be the
|
|
/// bounding box of the shape not necorserrally the shape of the whole image
|
|
/// </remarks>
|
|
public IPenApplicator<TColor> CreateApplicator(PixelAccessor<TColor> sourcePixels, RectangleF region)
|
|
{
|
|
if (this.pattern == null || this.pattern.Length < 2)
|
|
{
|
|
// if there is only one item in the pattern then 100% of it will
|
|
// be solid so use the quicker applicator
|
|
return new SolidPenApplicator(sourcePixels, this.Brush, region, this.Width);
|
|
}
|
|
|
|
return new PatternPenApplicator(sourcePixels, this.Brush, region, this.Width, this.pattern);
|
|
}
|
|
|
|
private class SolidPenApplicator : IPenApplicator<TColor>
|
|
{
|
|
private readonly IBrushApplicator<TColor> brush;
|
|
private readonly float halfWidth;
|
|
|
|
public SolidPenApplicator(PixelAccessor<TColor> sourcePixels, IBrush<TColor> brush, RectangleF region, float width)
|
|
{
|
|
this.brush = brush.CreateApplicator(sourcePixels, region);
|
|
this.halfWidth = width / 2;
|
|
this.RequiredRegion = RectangleF.Outset(region, width);
|
|
}
|
|
|
|
public RectangleF RequiredRegion
|
|
{
|
|
get;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
this.brush.Dispose();
|
|
}
|
|
|
|
public ColoredPointInfo<TColor> GetColor(PointInfo info)
|
|
{
|
|
var result = default(ColoredPointInfo<TColor>);
|
|
result.Color = this.brush.GetColor(info.SearchPoint);
|
|
|
|
if (info.DistanceFromPath < this.halfWidth)
|
|
{
|
|
// inside strip
|
|
result.DistanceFromElement = 0;
|
|
}
|
|
else
|
|
{
|
|
result.DistanceFromElement = info.DistanceFromPath - this.halfWidth;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private class PatternPenApplicator : IPenApplicator<TColor>
|
|
{
|
|
private readonly IBrushApplicator<TColor> brush;
|
|
private readonly float halfWidth;
|
|
private readonly float[] pattern;
|
|
private readonly float totalLength;
|
|
|
|
public PatternPenApplicator(PixelAccessor<TColor> sourcePixels, IBrush<TColor> brush, RectangleF region, float width, float[] pattern)
|
|
{
|
|
this.brush = brush.CreateApplicator(sourcePixels, region);
|
|
this.halfWidth = width / 2;
|
|
this.totalLength = 0;
|
|
|
|
this.pattern = new float[pattern.Length + 1];
|
|
this.pattern[0] = 0;
|
|
for (var i = 0; i < pattern.Length; i++)
|
|
{
|
|
this.totalLength += pattern[i] * width;
|
|
this.pattern[i + 1] = this.totalLength;
|
|
}
|
|
|
|
this.RequiredRegion = RectangleF.Outset(region, width);
|
|
}
|
|
|
|
public RectangleF RequiredRegion
|
|
{
|
|
get;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
this.brush.Dispose();
|
|
}
|
|
|
|
public ColoredPointInfo<TColor> GetColor(PointInfo info)
|
|
{
|
|
var infoResult = default(ColoredPointInfo<TColor>);
|
|
infoResult.DistanceFromElement = float.MaxValue; // is really outside the element
|
|
|
|
var length = info.DistanceAlongPath % this.totalLength;
|
|
|
|
// we can treat the DistanceAlongPath and DistanceFromPath as x,y coords for the pattern
|
|
// we need to calcualte the distance from the outside edge of the pattern
|
|
// and set them on the ColoredPointInfo<TColor> along with the color.
|
|
infoResult.Color = this.brush.GetColor(info.SearchPoint);
|
|
|
|
float distanceWAway = 0;
|
|
|
|
if (info.DistanceFromPath < this.halfWidth)
|
|
{
|
|
// inside strip
|
|
distanceWAway = 0;
|
|
}
|
|
else
|
|
{
|
|
distanceWAway = info.DistanceFromPath - this.halfWidth;
|
|
}
|
|
|
|
for (var i = 0; i < this.pattern.Length - 1; i++)
|
|
{
|
|
var start = this.pattern[i];
|
|
var end = this.pattern[i + 1];
|
|
|
|
if (length >= start && length < end)
|
|
{
|
|
// in section
|
|
if (i % 2 == 0)
|
|
{
|
|
// solid part return the maxDistance
|
|
infoResult.DistanceFromElement = distanceWAway;
|
|
return infoResult;
|
|
}
|
|
else
|
|
{
|
|
// this is a none solid part
|
|
var distanceFromStart = length - start;
|
|
var distanceFromEnd = end - length;
|
|
|
|
var closestEdge = Math.Min(distanceFromStart, distanceFromEnd);
|
|
|
|
var distanceAcross = closestEdge;
|
|
|
|
if (distanceWAway > 0)
|
|
{
|
|
infoResult.DistanceFromElement = new Vector2(distanceAcross, distanceWAway).Length();
|
|
}
|
|
else
|
|
{
|
|
infoResult.DistanceFromElement = closestEdge;
|
|
}
|
|
|
|
return infoResult;
|
|
}
|
|
}
|
|
}
|
|
|
|
return infoResult;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|