mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
1066 changed files with 36869 additions and 28815 deletions
@ -0,0 +1,112 @@ |
|||||
|
param( |
||||
|
[string]$targetFramework, |
||||
|
[string]$is32Bit = "False" |
||||
|
) |
||||
|
|
||||
|
if (!$targetFramework){ |
||||
|
Write-Host "run-tests.ps1 ERROR: targetFramework is undefined!" |
||||
|
exit 1 |
||||
|
} |
||||
|
|
||||
|
function VerifyPath($path, $errorMessage) { |
||||
|
if (!(Test-Path -Path $path)) { |
||||
|
Write-Host "run-tests.ps1 $errorMessage `n $xunitRunnerPath" |
||||
|
exit 1 |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function CheckSubmoduleStatus() { |
||||
|
$submoduleStatus = (git submodule status) | Out-String |
||||
|
# if the result string is empty, the command failed to run (we didn't capture the error stream) |
||||
|
if ($submoduleStatus) { |
||||
|
# git has been called successfully, what about the status? |
||||
|
if (($submoduleStatus -match "\-") -or ($submoduleStatus -match "\(\(null\)\)")) |
||||
|
{ |
||||
|
# submodule has not been initialized! |
||||
|
return 2; |
||||
|
} |
||||
|
elseif ($submoduleStatus -match "\+") |
||||
|
{ |
||||
|
# submodule is not synced: |
||||
|
return 1; |
||||
|
} |
||||
|
else { |
||||
|
# everything fine: |
||||
|
return 0; |
||||
|
} |
||||
|
} else { |
||||
|
# git call failed, so we should warn |
||||
|
return 3; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
if ( ($targetFramework -eq "netcoreapp2.1") -and ($env:CI -eq "True") -and ($is32Bit -ne "True")) { |
||||
|
# We execute CodeCoverage.cmd only for one specific job on CI (netcoreapp2.1 + 64bit ) |
||||
|
$testRunnerCmd = ".\tests\CodeCoverage\CodeCoverage.cmd" |
||||
|
} |
||||
|
elseif ($targetFramework -eq "mono") { |
||||
|
$testDllPath = "$PSScriptRoot\tests\ImageSharp.Tests\bin\Release\net462\SixLabors.ImageSharp.Tests.dll" |
||||
|
VerifyPath($testDllPath, "test dll missing:") |
||||
|
|
||||
|
$xunitRunnerPath = "${env:HOMEPATH}\.nuget\packages\xunit.runner.console\2.3.1\tools\net452\" |
||||
|
|
||||
|
VerifyPath($xunitRunnerPath, "xunit console runner is missing on path:") |
||||
|
|
||||
|
cd "$xunitRunnerPath" |
||||
|
|
||||
|
if ($is32Bit -ne "True") { |
||||
|
$monoPath = "${env:PROGRAMFILES}\Mono\bin\mono.exe" |
||||
|
} |
||||
|
else { |
||||
|
$monoPath = "${env:ProgramFiles(x86)}\Mono\bin\mono.exe" |
||||
|
} |
||||
|
|
||||
|
VerifyPath($monoPath, "mono runtime missing:") |
||||
|
|
||||
|
$testRunnerCmd = "& `"${monoPath}`" .\xunit.console.exe `"${testDllPath}`"" |
||||
|
} |
||||
|
else { |
||||
|
cd .\tests\ImageSharp.Tests |
||||
|
$xunitArgs = "-nobuild -c Release -framework $targetFramework" |
||||
|
|
||||
|
if ($targetFramework -eq "netcoreapp2.1") { |
||||
|
# There were issues matching the correct installed runtime if we do not specify it explicitly: |
||||
|
$xunitArgs += " --fx-version 2.1.0" |
||||
|
} |
||||
|
|
||||
|
if ($is32Bit -eq "True") { |
||||
|
$xunitArgs += " -x86" |
||||
|
} |
||||
|
|
||||
|
$testRunnerCmd = "dotnet xunit $xunitArgs" |
||||
|
} |
||||
|
|
||||
|
Write-Host "running:" |
||||
|
Write-Host $testRunnerCmd |
||||
|
Write-Host "..." |
||||
|
|
||||
|
Invoke-Expression $testRunnerCmd |
||||
|
|
||||
|
cd $PSScriptRoot |
||||
|
|
||||
|
$exitCodeOfTests = $LASTEXITCODE; |
||||
|
|
||||
|
if (0 -ne ([int]$exitCodeOfTests)) { |
||||
|
# check submodule status |
||||
|
$submoduleStatus = CheckSubmoduleStatus |
||||
|
if ([int]$submoduleStatus -eq 1) { |
||||
|
# not synced |
||||
|
Write-Host -ForegroundColor Yellow "Check if submodules are up to date. You can use 'git submodule update' to fix this"; |
||||
|
} elseif ($submoduleStatus -eq 2) { |
||||
|
# not initialized |
||||
|
Write-Host -ForegroundColor Yellow "Check if submodules are initialized. You can run 'git submodule init' to initialize them." |
||||
|
} elseif ($submoduleStatus -eq 3) { |
||||
|
# git not found, maybe submodules not synced? |
||||
|
Write-Host -ForegroundColor Yellow "Could not check if submodules are initialized correctly. Maybe git is not installed?" |
||||
|
} else { |
||||
|
#Write-Host "Submodules are up to date"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
exit $exitCodeOfTests |
||||
@ -0,0 +1,39 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Diagnostics; |
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A struct that defines a single color stop.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
[DebuggerDisplay("ColorStop({Ratio} -> {Color}")] |
||||
|
public struct ColorStop<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ColorStop{TPixel}" /> struct.
|
||||
|
/// </summary>
|
||||
|
/// <param name="ratio">Where should it be? 0 is at the start, 1 at the end of the Gradient.</param>
|
||||
|
/// <param name="color">What color should be used at that point?</param>
|
||||
|
public ColorStop(float ratio, TPixel color) |
||||
|
{ |
||||
|
this.Ratio = ratio; |
||||
|
this.Color = color; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the point along the defined gradient axis.
|
||||
|
/// </summary>
|
||||
|
public float Ratio { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the color to be used.
|
||||
|
/// </summary>
|
||||
|
public TPixel Color { get; } |
||||
|
} |
||||
|
} |
||||
@ -1,155 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
using SixLabors.ImageSharp.Advanced; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.ImageSharp.Processing.Processors; |
|
||||
using SixLabors.Primitives; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Drawing.Processors |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Combines two images together by blending the pixels.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|
||||
internal class DrawImageProcessor<TPixel> : ImageProcessor<TPixel> |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="image">The image to blend with the currently processing image.</param>
|
|
||||
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
|
|
||||
public DrawImageProcessor(Image<TPixel> image, float opacity) |
|
||||
: this(image, Point.Empty, opacity) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="image">The image to blend with the currently processing image.</param>
|
|
||||
/// <param name="options">
|
|
||||
/// The options containing the opacity of the image to blend and blending mode.
|
|
||||
/// Opacity must be between 0 and 1.
|
|
||||
/// </param>
|
|
||||
public DrawImageProcessor(Image<TPixel> image, GraphicsOptions options) |
|
||||
: this(image, Point.Empty, options) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="image">The image to blend with the currently processing image.</param>
|
|
||||
/// <param name="location">The location to draw the blended image.</param>
|
|
||||
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
|
|
||||
public DrawImageProcessor(Image<TPixel> image, Point location, float opacity) |
|
||||
: this(image, location, opacity, GraphicsOptions.Default.BlenderMode) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="image">The image to blend with the currently processing image.</param>
|
|
||||
/// <param name="location">The location to draw the blended image.</param>
|
|
||||
/// <param name="options">
|
|
||||
/// The options containing the opacity of the image to blend and blending mode.
|
|
||||
/// Opacity must be between 0 and 1.
|
|
||||
/// </param>
|
|
||||
public DrawImageProcessor(Image<TPixel> image, Point location, GraphicsOptions options) |
|
||||
: this(image, location, options.BlendPercentage, options.BlenderMode) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="image">The image to blend with the currently processing image.</param>
|
|
||||
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
|
|
||||
/// <param name="blenderMode">The blending mode to use when drawing the image.</param>
|
|
||||
public DrawImageProcessor(Image<TPixel> image, float opacity, PixelBlenderMode blenderMode) |
|
||||
: this(image, Point.Empty, opacity, blenderMode) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
|
|
||||
/// </summary>
|
|
||||
/// <param name="image">The image to blend with the currently processing image.</param>
|
|
||||
/// <param name="location">The location to draw the blended image.</param>
|
|
||||
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
|
|
||||
/// <param name="blenderMode">The blending mode to use when drawing the image.</param>
|
|
||||
public DrawImageProcessor(Image<TPixel> image, Point location, float opacity, PixelBlenderMode blenderMode) |
|
||||
{ |
|
||||
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); |
|
||||
|
|
||||
this.Image = image; |
|
||||
this.Opacity = opacity; |
|
||||
this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(blenderMode); |
|
||||
this.Location = location; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the image to blend
|
|
||||
/// </summary>
|
|
||||
public Image<TPixel> Image { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the opacity of the image to blend
|
|
||||
/// </summary>
|
|
||||
public float Opacity { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the pixel blender
|
|
||||
/// </summary>
|
|
||||
public PixelBlender<TPixel> Blender { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the location to draw the blended image
|
|
||||
/// </summary>
|
|
||||
public Point Location { get; } |
|
||||
|
|
||||
/// <inheritdoc/>
|
|
||||
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|
||||
{ |
|
||||
Image<TPixel> targetImage = this.Image; |
|
||||
PixelBlender<TPixel> blender = this.Blender; |
|
||||
int locationY = this.Location.Y; |
|
||||
|
|
||||
// Align start/end positions.
|
|
||||
Rectangle bounds = targetImage.Bounds(); |
|
||||
|
|
||||
int minX = Math.Max(this.Location.X, sourceRectangle.X); |
|
||||
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); |
|
||||
int targetX = minX - this.Location.X; |
|
||||
|
|
||||
int minY = Math.Max(this.Location.Y, sourceRectangle.Y); |
|
||||
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); |
|
||||
|
|
||||
int width = maxX - minX; |
|
||||
|
|
||||
MemoryManager memoryManager = this.Image.GetConfiguration().MemoryManager; |
|
||||
|
|
||||
using (IBuffer<float> amount = memoryManager.Allocate<float>(width)) |
|
||||
{ |
|
||||
amount.Span.Fill(this.Opacity); |
|
||||
|
|
||||
Parallel.For( |
|
||||
minY, |
|
||||
maxY, |
|
||||
configuration.ParallelOptions, |
|
||||
y => |
|
||||
{ |
|
||||
Span<TPixel> background = source.GetPixelRowSpan(y).Slice(minX, width); |
|
||||
Span<TPixel> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); |
|
||||
blender.Blend(memoryManager, background, background, foreground, amount.Span); |
|
||||
}); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,170 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gradient Brush with elliptic shape.
|
||||
|
/// The ellipse is defined by a center point,
|
||||
|
/// a point on the longest extension of the ellipse and
|
||||
|
/// the ratio between longest and shortest extension.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The Pixel format that is used.</typeparam>
|
||||
|
public sealed class EllipticGradientBrush<TPixel> : GradientBrushBase<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
private readonly Point center; |
||||
|
|
||||
|
private readonly Point referenceAxisEnd; |
||||
|
|
||||
|
private readonly float axisRatio; |
||||
|
|
||||
|
/// <inheritdoc cref="GradientBrushBase{TPixel}" />
|
||||
|
/// <param name="center">The center of the elliptical gradient and 0 for the color stops.</param>
|
||||
|
/// <param name="referenceAxisEnd">The end point of the reference axis of the ellipse.</param>
|
||||
|
/// <param name="axisRatio">
|
||||
|
/// The ratio of the axis widths.
|
||||
|
/// The second axis' is perpendicular to the reference axis and
|
||||
|
/// it's length is the reference axis' length multiplied by this factor.
|
||||
|
/// </param>
|
||||
|
/// <param name="repetitionMode">Defines how the colors of the gradients are repeated.</param>
|
||||
|
/// <param name="colorStops">the color stops as defined in base class.</param>
|
||||
|
public EllipticGradientBrush( |
||||
|
Point center, |
||||
|
Point referenceAxisEnd, |
||||
|
float axisRatio, |
||||
|
GradientRepetitionMode repetitionMode, |
||||
|
params ColorStop<TPixel>[] colorStops) |
||||
|
: base(repetitionMode, colorStops) |
||||
|
{ |
||||
|
this.center = center; |
||||
|
this.referenceAxisEnd = referenceAxisEnd; |
||||
|
this.axisRatio = axisRatio; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="CreateApplicator" />
|
||||
|
public override BrushApplicator<TPixel> CreateApplicator( |
||||
|
ImageFrame<TPixel> source, |
||||
|
RectangleF region, |
||||
|
GraphicsOptions options) => |
||||
|
new RadialGradientBrushApplicator( |
||||
|
source, |
||||
|
options, |
||||
|
this.center, |
||||
|
this.referenceAxisEnd, |
||||
|
this.axisRatio, |
||||
|
this.ColorStops, |
||||
|
this.RepetitionMode); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
private sealed class RadialGradientBrushApplicator : GradientBrushApplicatorBase |
||||
|
{ |
||||
|
private readonly Point center; |
||||
|
|
||||
|
private readonly Point referenceAxisEnd; |
||||
|
|
||||
|
private readonly float axisRatio; |
||||
|
|
||||
|
private readonly double rotation; |
||||
|
|
||||
|
private readonly float referenceRadius; |
||||
|
|
||||
|
private readonly float secondRadius; |
||||
|
|
||||
|
private readonly float cosRotation; |
||||
|
|
||||
|
private readonly float sinRotation; |
||||
|
|
||||
|
private readonly float secondRadiusSquared; |
||||
|
|
||||
|
private readonly float referenceRadiusSquared; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="RadialGradientBrushApplicator" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="target">The target image</param>
|
||||
|
/// <param name="options">The options</param>
|
||||
|
/// <param name="center">Center of the ellipse</param>
|
||||
|
/// <param name="referenceAxisEnd">Point on one angular points of the ellipse.</param>
|
||||
|
/// <param name="axisRatio">
|
||||
|
/// Ratio of the axis length's. Used to determine the length of the second axis,
|
||||
|
/// the first is defined by <see cref="center"/> and <see cref="referenceAxisEnd"/>.</param>
|
||||
|
/// <param name="colorStops">Definition of colors</param>
|
||||
|
/// <param name="repetitionMode">Defines how the gradient colors are repeated.</param>
|
||||
|
public RadialGradientBrushApplicator( |
||||
|
ImageFrame<TPixel> target, |
||||
|
GraphicsOptions options, |
||||
|
Point center, |
||||
|
Point referenceAxisEnd, |
||||
|
float axisRatio, |
||||
|
ColorStop<TPixel>[] colorStops, |
||||
|
GradientRepetitionMode repetitionMode) |
||||
|
: base(target, options, colorStops, repetitionMode) |
||||
|
{ |
||||
|
this.center = center; |
||||
|
this.referenceAxisEnd = referenceAxisEnd; |
||||
|
this.axisRatio = axisRatio; |
||||
|
this.rotation = this.AngleBetween( |
||||
|
this.center, |
||||
|
new PointF(this.center.X + 1, this.center.Y), |
||||
|
this.referenceAxisEnd); |
||||
|
this.referenceRadius = this.DistanceBetween(this.center, this.referenceAxisEnd); |
||||
|
this.secondRadius = this.referenceRadius * this.axisRatio; |
||||
|
|
||||
|
this.referenceRadiusSquared = this.referenceRadius * this.referenceRadius; |
||||
|
this.secondRadiusSquared = this.secondRadius * this.secondRadius; |
||||
|
|
||||
|
this.sinRotation = (float)Math.Sin(this.rotation); |
||||
|
this.cosRotation = (float)Math.Cos(this.rotation); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override void Dispose() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected override float PositionOnGradient(int xt, int yt) |
||||
|
{ |
||||
|
float x0 = xt - this.center.X; |
||||
|
float y0 = yt - this.center.Y; |
||||
|
|
||||
|
float x = (x0 * this.cosRotation) - (y0 * this.sinRotation); |
||||
|
float y = (x0 * this.sinRotation) + (y0 * this.cosRotation); |
||||
|
|
||||
|
float xSquared = x * x; |
||||
|
float ySquared = y * y; |
||||
|
|
||||
|
var inBoundaryChecker = (xSquared / this.referenceRadiusSquared) |
||||
|
+ (ySquared / this.secondRadiusSquared); |
||||
|
|
||||
|
return inBoundaryChecker; |
||||
|
} |
||||
|
|
||||
|
private float AngleBetween(PointF junction, PointF a, PointF b) |
||||
|
{ |
||||
|
var vA = a - junction; |
||||
|
var vB = b - junction; |
||||
|
return (float)(Math.Atan2(vB.Y, vB.X) |
||||
|
- Math.Atan2(vA.Y, vA.X)); |
||||
|
} |
||||
|
|
||||
|
private float DistanceBetween( |
||||
|
PointF p1, |
||||
|
PointF p2) |
||||
|
{ |
||||
|
float dX = p1.X - p2.X; |
||||
|
float dXsquared = dX * dX; |
||||
|
|
||||
|
float dY = p1.Y - p2.Y; |
||||
|
float dYsquared = dY * dY; |
||||
|
return (float)Math.Sqrt(dXsquared + dYsquared); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,177 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Numerics; |
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.PixelFormats.PixelBlenders; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Base class for Gradient brushes
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format</typeparam>
|
||||
|
public abstract class GradientBrushBase<TPixel> : IBrush<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <inheritdoc cref="IBrush{TPixel}"/>
|
||||
|
/// <param name="repetitionMode">Defines how the colors are repeated beyond the interval [0..1]</param>
|
||||
|
/// <param name="colorStops">The gradient colors.</param>
|
||||
|
protected GradientBrushBase( |
||||
|
GradientRepetitionMode repetitionMode, |
||||
|
params ColorStop<TPixel>[] colorStops) |
||||
|
{ |
||||
|
this.RepetitionMode = repetitionMode; |
||||
|
this.ColorStops = colorStops; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets how the colors are repeated beyond the interval [0..1].
|
||||
|
/// </summary>
|
||||
|
protected GradientRepetitionMode RepetitionMode { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the list of color stops for this gradient.
|
||||
|
/// </summary>
|
||||
|
protected ColorStop<TPixel>[] ColorStops { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IBrush{TPixel}" />
|
||||
|
public abstract BrushApplicator<TPixel> CreateApplicator( |
||||
|
ImageFrame<TPixel> source, |
||||
|
RectangleF region, |
||||
|
GraphicsOptions options); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Base class for gradient brush applicators
|
||||
|
/// </summary>
|
||||
|
protected abstract class GradientBrushApplicatorBase : BrushApplicator<TPixel> |
||||
|
{ |
||||
|
private readonly ColorStop<TPixel>[] colorStops; |
||||
|
|
||||
|
private readonly GradientRepetitionMode repetitionMode; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="GradientBrushApplicatorBase"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="target">The target.</param>
|
||||
|
/// <param name="options">The options.</param>
|
||||
|
/// <param name="colorStops">An array of color stops sorted by their position.</param>
|
||||
|
/// <param name="repetitionMode">Defines if and how the gradient should be repeated.</param>
|
||||
|
protected GradientBrushApplicatorBase( |
||||
|
ImageFrame<TPixel> target, |
||||
|
GraphicsOptions options, |
||||
|
ColorStop<TPixel>[] colorStops, |
||||
|
GradientRepetitionMode repetitionMode) |
||||
|
: base(target, options) |
||||
|
{ |
||||
|
this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked?
|
||||
|
this.repetitionMode = repetitionMode; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Base implementation of the indexer for gradients
|
||||
|
/// (follows the facade pattern, using abstract methods)
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">X coordinate of the Pixel.</param>
|
||||
|
/// <param name="y">Y coordinate of the Pixel.</param>
|
||||
|
internal override TPixel this[int x, int y] |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
float positionOnCompleteGradient = this.PositionOnGradient(x, y); |
||||
|
|
||||
|
switch (this.repetitionMode) |
||||
|
{ |
||||
|
case GradientRepetitionMode.None: |
||||
|
// do nothing. The following could be done, but is not necessary:
|
||||
|
// onLocalGradient = Math.Min(0, Math.Max(1, onLocalGradient));
|
||||
|
break; |
||||
|
case GradientRepetitionMode.Repeat: |
||||
|
positionOnCompleteGradient = positionOnCompleteGradient % 1; |
||||
|
break; |
||||
|
case GradientRepetitionMode.Reflect: |
||||
|
positionOnCompleteGradient = positionOnCompleteGradient % 2; |
||||
|
if (positionOnCompleteGradient > 1) |
||||
|
{ |
||||
|
positionOnCompleteGradient = 2 - positionOnCompleteGradient; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
case GradientRepetitionMode.DontFill: |
||||
|
if (positionOnCompleteGradient > 1 || positionOnCompleteGradient < 0) |
||||
|
{ |
||||
|
return NamedColors<TPixel>.Transparent; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
default: |
||||
|
throw new ArgumentOutOfRangeException(); |
||||
|
} |
||||
|
|
||||
|
(ColorStop<TPixel> from, ColorStop<TPixel> to) = this.GetGradientSegment(positionOnCompleteGradient); |
||||
|
|
||||
|
if (from.Color.Equals(to.Color)) |
||||
|
{ |
||||
|
return from.Color; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var fromAsVector = from.Color.ToVector4(); |
||||
|
var toAsVector = to.Color.ToVector4(); |
||||
|
float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / to.Ratio; |
||||
|
|
||||
|
// TODO: this should be changeble for different gradienting functions
|
||||
|
Vector4 result = PorterDuffFunctions.NormalSrcOver( |
||||
|
fromAsVector, |
||||
|
toAsVector, |
||||
|
onLocalGradient); |
||||
|
|
||||
|
TPixel resultColor = default; |
||||
|
resultColor.PackFromVector4(result); |
||||
|
return resultColor; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// calculates the position on the gradient for a given pixel.
|
||||
|
/// This method is abstract as it's content depends on the shape of the gradient.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The x coordinate of the pixel</param>
|
||||
|
/// <param name="y">The y coordinate of the pixel</param>
|
||||
|
/// <returns>
|
||||
|
/// The position the given pixel has on the gradient.
|
||||
|
/// The position is not bound to the [0..1] interval.
|
||||
|
/// Values outside of that interval may be treated differently,
|
||||
|
/// e.g. for the <see cref="GradientRepetitionMode" /> enum.
|
||||
|
/// </returns>
|
||||
|
protected abstract float PositionOnGradient(int x, int y); |
||||
|
|
||||
|
private (ColorStop<TPixel> from, ColorStop<TPixel> to) GetGradientSegment( |
||||
|
float positionOnCompleteGradient) |
||||
|
{ |
||||
|
ColorStop<TPixel> localGradientFrom = this.colorStops[0]; |
||||
|
ColorStop<TPixel> localGradientTo = default; |
||||
|
|
||||
|
// TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient)
|
||||
|
foreach (ColorStop<TPixel> colorStop in this.colorStops) |
||||
|
{ |
||||
|
localGradientTo = colorStop; |
||||
|
|
||||
|
if (colorStop.Ratio > positionOnCompleteGradient) |
||||
|
{ |
||||
|
// we're done here, so break it!
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
localGradientFrom = localGradientTo; |
||||
|
} |
||||
|
|
||||
|
return (localGradientFrom, localGradientTo); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Modes to repeat a gradient.
|
||||
|
/// </summary>
|
||||
|
public enum GradientRepetitionMode |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// don't repeat, keep the color of start and end beyond those points stable.
|
||||
|
/// </summary>
|
||||
|
None, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Repeat the gradient.
|
||||
|
/// If it's a black-white gradient, with Repeat it will be Black->{gray}->White|Black->{gray}->White|...
|
||||
|
/// </summary>
|
||||
|
Repeat, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Reflect the gradient.
|
||||
|
/// Similar to <see cref="Repeat"/>, but each other repetition uses inverse order of <see cref="ColorStop{TPixel}"/>s.
|
||||
|
/// Used on a Black-White gradient, Reflect leads to Black->{gray}->White->{gray}->White...
|
||||
|
/// </summary>
|
||||
|
Reflect, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// With DontFill a gradient does not touch any pixel beyond it's borders.
|
||||
|
/// For the <see cref="LinearGradientBrush{TPixel}" /> this is beyond the orthogonal through start and end,
|
||||
|
/// TODO For the cref="PolygonalGradientBrush" it's outside the polygon,
|
||||
|
/// For <see cref="RadialGradientBrush{TPixel}" /> and <see cref="EllipticGradientBrush{TPixel}" /> it's beyond 1.0.
|
||||
|
/// </summary>
|
||||
|
DontFill |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,155 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides an implementation of a brush for painting linear gradients within areas.
|
||||
|
/// Supported right now:
|
||||
|
/// - a set of colors in relative distances to each other.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format</typeparam>
|
||||
|
public sealed class LinearGradientBrush<TPixel> : GradientBrushBase<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
private readonly Point p1; |
||||
|
|
||||
|
private readonly Point p2; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="LinearGradientBrush{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="p1">Start point</param>
|
||||
|
/// <param name="p2">End point</param>
|
||||
|
/// <param name="repetitionMode">defines how colors are repeated.</param>
|
||||
|
/// <param name="colorStops"><inheritdoc /></param>
|
||||
|
public LinearGradientBrush( |
||||
|
Point p1, |
||||
|
Point p2, |
||||
|
GradientRepetitionMode repetitionMode, |
||||
|
params ColorStop<TPixel>[] colorStops) |
||||
|
: base(repetitionMode, colorStops) |
||||
|
{ |
||||
|
this.p1 = p1; |
||||
|
this.p2 = p2; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override BrushApplicator<TPixel> CreateApplicator(ImageFrame<TPixel> source, RectangleF region, GraphicsOptions options) |
||||
|
=> new LinearGradientBrushApplicator(source, this.p1, this.p2, this.ColorStops, this.RepetitionMode, options); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The linear gradient brush applicator.
|
||||
|
/// </summary>
|
||||
|
private sealed class LinearGradientBrushApplicator : GradientBrushApplicatorBase |
||||
|
{ |
||||
|
private readonly Point start; |
||||
|
|
||||
|
private readonly Point end; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// the vector along the gradient, x component
|
||||
|
/// </summary>
|
||||
|
private readonly float alongX; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// the vector along the gradient, y component
|
||||
|
/// </summary>
|
||||
|
private readonly float alongY; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// the vector perpendicular to the gradient, y component
|
||||
|
/// </summary>
|
||||
|
private readonly float acrossY; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// the vector perpendicular to the gradient, x component
|
||||
|
/// </summary>
|
||||
|
private readonly float acrossX; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// the result of <see cref="alongX"/>^2 + <see cref="alongY"/>^2
|
||||
|
/// </summary>
|
||||
|
private readonly float alongsSquared; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// the length of the defined gradient (between source and end)
|
||||
|
/// </summary>
|
||||
|
private readonly float length; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="LinearGradientBrushApplicator" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">The source</param>
|
||||
|
/// <param name="start">start point of the gradient</param>
|
||||
|
/// <param name="end">end point of the gradient</param>
|
||||
|
/// <param name="colorStops">tuple list of colors and their respective position between 0 and 1 on the line</param>
|
||||
|
/// <param name="repetitionMode">defines how the gradient colors are repeated.</param>
|
||||
|
/// <param name="options">the graphics options</param>
|
||||
|
public LinearGradientBrushApplicator( |
||||
|
ImageFrame<TPixel> source, |
||||
|
Point start, |
||||
|
Point end, |
||||
|
ColorStop<TPixel>[] colorStops, |
||||
|
GradientRepetitionMode repetitionMode, |
||||
|
GraphicsOptions options) |
||||
|
: base(source, options, colorStops, repetitionMode) |
||||
|
{ |
||||
|
this.start = start; |
||||
|
this.end = end; |
||||
|
|
||||
|
// the along vector:
|
||||
|
this.alongX = this.end.X - this.start.X; |
||||
|
this.alongY = this.end.Y - this.start.Y; |
||||
|
|
||||
|
// the cross vector:
|
||||
|
this.acrossX = this.alongY; |
||||
|
this.acrossY = -this.alongX; |
||||
|
|
||||
|
// some helpers:
|
||||
|
this.alongsSquared = (this.alongX * this.alongX) + (this.alongY * this.alongY); |
||||
|
this.length = (float)Math.Sqrt(this.alongsSquared); |
||||
|
} |
||||
|
|
||||
|
protected override float PositionOnGradient(int x, int y) |
||||
|
{ |
||||
|
if (this.acrossX == 0) |
||||
|
{ |
||||
|
return (x - this.start.X) / (float)(this.end.X - this.start.X); |
||||
|
} |
||||
|
else if (this.acrossY == 0) |
||||
|
{ |
||||
|
return (y - this.start.Y) / (float)(this.end.Y - this.start.Y); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
float deltaX = x - this.start.X; |
||||
|
float deltaY = y - this.start.Y; |
||||
|
float k = ((this.alongY * deltaX) - (this.alongX * deltaY)) / this.alongsSquared; |
||||
|
|
||||
|
// point on the line:
|
||||
|
float x4 = x - (k * this.alongY); |
||||
|
float y4 = y + (k * this.alongX); |
||||
|
|
||||
|
// get distance from (x4,y4) to start
|
||||
|
float distance = (float)Math.Sqrt( |
||||
|
Math.Pow(x4 - this.start.X, 2) |
||||
|
+ Math.Pow(y4 - this.start.Y, 2)); |
||||
|
|
||||
|
// get and return ratio
|
||||
|
float ratio = distance / this.length; |
||||
|
return ratio; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override void Dispose() |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,98 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.Threading.Tasks; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Memory; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors.Drawing |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Combines two images together by blending the pixels.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
internal class DrawImageProcessor<TPixel> : ImageProcessor<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="image">The image to blend with the currently processing image.</param>
|
||||
|
/// <param name="location">The location to draw the blended image.</param>
|
||||
|
/// <param name="colorBlendingMode">The blending mode to use when drawing the image.</param>
|
||||
|
/// <param name="alphaCompositionMode">The Alpha blending mode to use when drawing the image.</param>
|
||||
|
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
|
||||
|
public DrawImageProcessor(Image<TPixel> image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity) |
||||
|
{ |
||||
|
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); |
||||
|
|
||||
|
this.Image = image; |
||||
|
this.Opacity = opacity; |
||||
|
this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); |
||||
|
this.Location = location; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the image to blend
|
||||
|
/// </summary>
|
||||
|
public Image<TPixel> Image { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the opacity of the image to blend
|
||||
|
/// </summary>
|
||||
|
public float Opacity { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the pixel blender
|
||||
|
/// </summary>
|
||||
|
public PixelBlender<TPixel> Blender { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the location to draw the blended image
|
||||
|
/// </summary>
|
||||
|
public Point Location { get; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
||||
|
{ |
||||
|
Image<TPixel> targetImage = this.Image; |
||||
|
PixelBlender<TPixel> blender = this.Blender; |
||||
|
int locationY = this.Location.Y; |
||||
|
|
||||
|
// Align start/end positions.
|
||||
|
Rectangle bounds = targetImage.Bounds(); |
||||
|
|
||||
|
int minX = Math.Max(this.Location.X, sourceRectangle.X); |
||||
|
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); |
||||
|
int targetX = minX - this.Location.X; |
||||
|
|
||||
|
int minY = Math.Max(this.Location.Y, sourceRectangle.Y); |
||||
|
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); |
||||
|
|
||||
|
int width = maxX - minX; |
||||
|
|
||||
|
MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator; |
||||
|
|
||||
|
using (IMemoryOwner<float> amount = memoryAllocator.Allocate<float>(width)) |
||||
|
{ |
||||
|
amount.GetSpan().Fill(this.Opacity); |
||||
|
|
||||
|
ParallelFor.WithConfiguration( |
||||
|
minY, |
||||
|
maxY, |
||||
|
configuration, |
||||
|
y => |
||||
|
{ |
||||
|
Span<TPixel> background = source.GetPixelRowSpan(y).Slice(minX, width); |
||||
|
Span<TPixel> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); |
||||
|
blender.Blend(memoryAllocator, background, background, foreground, amount.GetSpan()); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,471 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.Collections.Generic; |
||||
|
using SixLabors.Fonts; |
||||
|
using SixLabors.ImageSharp.Advanced; |
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.ImageSharp.Utils; |
||||
|
using SixLabors.Memory; |
||||
|
using SixLabors.Primitives; |
||||
|
using SixLabors.Shapes; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Processors.Text |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Using the brush as a source of pixels colors blends the brush color with source.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
internal class DrawTextProcessor<TPixel> : ImageProcessor<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
private CachingGlyphRenderer textRenderer; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="DrawTextProcessor{TPixel}"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="options">The options</param>
|
||||
|
/// <param name="text">The text we want to render</param>
|
||||
|
/// <param name="font">The font we want to render with</param>
|
||||
|
/// <param name="brush">The brush to source pixel colors from.</param>
|
||||
|
/// <param name="pen">The pen to outline text with.</param>
|
||||
|
/// <param name="location">The location on the image to start drawign the text from.</param>
|
||||
|
public DrawTextProcessor(TextGraphicsOptions options, string text, Font font, IBrush<TPixel> brush, IPen<TPixel> pen, PointF location) |
||||
|
{ |
||||
|
Guard.NotNull(text, nameof(text)); |
||||
|
Guard.NotNull(font, nameof(font)); |
||||
|
|
||||
|
if (brush is null && pen is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException($"Expected a {nameof(brush)} or {nameof(pen)}. Both were null"); |
||||
|
} |
||||
|
|
||||
|
this.Options = options; |
||||
|
this.Text = text; |
||||
|
this.Font = font; |
||||
|
this.Location = location; |
||||
|
this.Brush = brush; |
||||
|
this.Pen = pen; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the brush.
|
||||
|
/// </summary>
|
||||
|
public IBrush<TPixel> Brush { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the options
|
||||
|
/// </summary>
|
||||
|
public TextGraphicsOptions Options { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the text
|
||||
|
/// </summary>
|
||||
|
public string Text { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the pen used for outlining the text, if Null then we will not outline
|
||||
|
/// </summary>
|
||||
|
public IPen<TPixel> Pen { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the font used to render the text.
|
||||
|
/// </summary>
|
||||
|
public Font Font { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the location to draw the text at.
|
||||
|
/// </summary>
|
||||
|
public PointF Location { get; } |
||||
|
|
||||
|
protected override void BeforeImageApply(Image<TPixel> source, Rectangle sourceRectangle) |
||||
|
{ |
||||
|
base.BeforeImageApply(source, sourceRectangle); |
||||
|
|
||||
|
// do everythign at the image level as we are deligating the processing down to other processors
|
||||
|
var style = new RendererOptions(this.Font, this.Options.DpiX, this.Options.DpiY, this.Location) |
||||
|
{ |
||||
|
ApplyKerning = this.Options.ApplyKerning, |
||||
|
TabWidth = this.Options.TabWidth, |
||||
|
WrappingWidth = this.Options.WrapTextWidth, |
||||
|
HorizontalAlignment = this.Options.HorizontalAlignment, |
||||
|
VerticalAlignment = this.Options.VerticalAlignment |
||||
|
}; |
||||
|
|
||||
|
this.textRenderer = new CachingGlyphRenderer(source.GetMemoryAllocator(), this.Text.Length, this.Pen, this.Brush != null); |
||||
|
this.textRenderer.Options = (GraphicsOptions)this.Options; |
||||
|
TextRenderer.RenderTextTo(this.textRenderer, this.Text, style); |
||||
|
} |
||||
|
|
||||
|
protected override void AfterImageApply(Image<TPixel> source, Rectangle sourceRectangle) |
||||
|
{ |
||||
|
base.AfterImageApply(source, sourceRectangle); |
||||
|
this.textRenderer?.Dispose(); |
||||
|
this.textRenderer = null; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
||||
|
{ |
||||
|
// this is a no-op as we have processes all as an image, we should be able to pass out of before email apply a skip frames outcome
|
||||
|
Draw(this.textRenderer.FillOperations, this.Brush); |
||||
|
Draw(this.textRenderer.OutlineOperations, this.Pen?.StrokeFill); |
||||
|
|
||||
|
void Draw(List<DrawingOperation> operations, IBrush<TPixel> brush) |
||||
|
{ |
||||
|
if (operations?.Count > 0) |
||||
|
{ |
||||
|
using (BrushApplicator<TPixel> app = brush.CreateApplicator(source, sourceRectangle, this.textRenderer.Options)) |
||||
|
{ |
||||
|
foreach (DrawingOperation operation in operations) |
||||
|
{ |
||||
|
Buffer2D<float> buffer = operation.Map; |
||||
|
int startY = operation.Location.Y; |
||||
|
int startX = operation.Location.X; |
||||
|
int offSetSpan = 0; |
||||
|
if (startX < 0) |
||||
|
{ |
||||
|
offSetSpan = -startX; |
||||
|
startX = 0; |
||||
|
} |
||||
|
|
||||
|
int fistRow = 0; |
||||
|
if (startY < 0) |
||||
|
{ |
||||
|
fistRow = -startY; |
||||
|
} |
||||
|
|
||||
|
int end = operation.Map.Height; |
||||
|
|
||||
|
int maxHeight = source.Height - startY; |
||||
|
end = Math.Min(end, maxHeight); |
||||
|
|
||||
|
for (int row = fistRow; row < end; row++) |
||||
|
{ |
||||
|
int y = startY + row; |
||||
|
Span<float> span = buffer.GetRowSpan(row).Slice(offSetSpan); |
||||
|
app.Apply(span, startX, y); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private struct DrawingOperation |
||||
|
{ |
||||
|
public Buffer2D<float> Map { get; set; } |
||||
|
|
||||
|
public Point Location { get; set; } |
||||
|
} |
||||
|
|
||||
|
private class CachingGlyphRenderer : IGlyphRenderer, IDisposable |
||||
|
{ |
||||
|
private PathBuilder builder; |
||||
|
|
||||
|
private Point currentRenderPosition = default; |
||||
|
private GlyphRendererParameters currentGlyphRenderParams = default; |
||||
|
private int offset = 0; |
||||
|
private PointF currentPoint = default(PointF); |
||||
|
|
||||
|
private readonly Dictionary<GlyphRendererParameters, GlyphRenderData> glyphData = new Dictionary<GlyphRendererParameters, GlyphRenderData>(); |
||||
|
|
||||
|
private bool renderOutline = false; |
||||
|
private bool renderFill = false; |
||||
|
private bool raterizationRequired = false; |
||||
|
|
||||
|
public CachingGlyphRenderer(MemoryAllocator memoryAllocator, int size, IPen pen, bool renderFill) |
||||
|
{ |
||||
|
this.MemoryAllocator = memoryAllocator; |
||||
|
this.Pen = pen; |
||||
|
this.renderFill = renderFill; |
||||
|
this.renderOutline = pen != null; |
||||
|
this.offset = 2; |
||||
|
if (this.renderFill) |
||||
|
{ |
||||
|
this.FillOperations = new List<DrawingOperation>(size); |
||||
|
} |
||||
|
|
||||
|
if (this.renderOutline) |
||||
|
{ |
||||
|
this.offset = (int)MathF.Ceiling((pen.StrokeWidth * 2) + 2); |
||||
|
this.OutlineOperations = new List<DrawingOperation>(size); |
||||
|
} |
||||
|
|
||||
|
this.builder = new PathBuilder(); |
||||
|
} |
||||
|
|
||||
|
public List<DrawingOperation> FillOperations { get; } |
||||
|
|
||||
|
public List<DrawingOperation> OutlineOperations { get; } |
||||
|
|
||||
|
public MemoryAllocator MemoryAllocator { get; internal set; } |
||||
|
|
||||
|
public IPen Pen { get; internal set; } |
||||
|
|
||||
|
public GraphicsOptions Options { get; internal set; } |
||||
|
|
||||
|
public void BeginFigure() |
||||
|
{ |
||||
|
this.builder.StartFigure(); |
||||
|
} |
||||
|
|
||||
|
public bool BeginGlyph(RectangleF bounds, GlyphRendererParameters paramters) |
||||
|
{ |
||||
|
this.currentRenderPosition = Point.Truncate(bounds.Location); |
||||
|
|
||||
|
// we have offset our rendering origion a little bit down to prevent edge cropping, move the draw origin up to compensate
|
||||
|
this.currentRenderPosition = new Point(this.currentRenderPosition.X - this.offset, this.currentRenderPosition.Y - this.offset); |
||||
|
this.currentGlyphRenderParams = paramters; |
||||
|
if (this.glyphData.ContainsKey(paramters)) |
||||
|
{ |
||||
|
// we have already drawn the glyph vectors skip trying again
|
||||
|
this.raterizationRequired = false; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// we check to see if we have a render cache and if we do then we render else
|
||||
|
this.builder.Clear(); |
||||
|
|
||||
|
// ensure all glyphs render around [zero, zero] so offset negative root positions so when we draw the glyph we can offet it back
|
||||
|
this.builder.SetOrigin(new PointF(-(int)bounds.X + this.offset, -(int)bounds.Y + this.offset)); |
||||
|
|
||||
|
this.raterizationRequired = true; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public void BeginText(RectangleF bounds) |
||||
|
{ |
||||
|
// not concerned about this one
|
||||
|
this.OutlineOperations?.Clear(); |
||||
|
this.FillOperations?.Clear(); |
||||
|
} |
||||
|
|
||||
|
public void CubicBezierTo(PointF secondControlPoint, PointF thirdControlPoint, PointF point) |
||||
|
{ |
||||
|
this.builder.AddBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point); |
||||
|
this.currentPoint = point; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
foreach (KeyValuePair<GlyphRendererParameters, GlyphRenderData> kv in this.glyphData) |
||||
|
{ |
||||
|
kv.Value.Dispose(); |
||||
|
} |
||||
|
|
||||
|
this.glyphData.Clear(); |
||||
|
} |
||||
|
|
||||
|
public void EndFigure() |
||||
|
{ |
||||
|
this.builder.CloseFigure(); |
||||
|
} |
||||
|
|
||||
|
public void EndGlyph() |
||||
|
{ |
||||
|
GlyphRenderData renderData = default; |
||||
|
|
||||
|
// has the glyoh been rendedered already????
|
||||
|
if (this.raterizationRequired) |
||||
|
{ |
||||
|
IPath path = this.builder.Build(); |
||||
|
|
||||
|
if (this.renderFill) |
||||
|
{ |
||||
|
renderData.FillMap = this.Render(path); |
||||
|
} |
||||
|
|
||||
|
if (this.renderOutline) |
||||
|
{ |
||||
|
if (this.Pen.StrokePattern.Length == 0) |
||||
|
{ |
||||
|
path = path.GenerateOutline(this.Pen.StrokeWidth); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
path = path.GenerateOutline(this.Pen.StrokeWidth, this.Pen.StrokePattern); |
||||
|
} |
||||
|
|
||||
|
renderData.OutlineMap = this.Render(path); |
||||
|
} |
||||
|
|
||||
|
this.glyphData[this.currentGlyphRenderParams] = renderData; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
renderData = this.glyphData[this.currentGlyphRenderParams]; |
||||
|
} |
||||
|
|
||||
|
if (this.renderFill) |
||||
|
{ |
||||
|
this.FillOperations.Add(new DrawingOperation |
||||
|
{ |
||||
|
Location = this.currentRenderPosition, |
||||
|
Map = renderData.FillMap |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (this.renderOutline) |
||||
|
{ |
||||
|
this.OutlineOperations.Add(new DrawingOperation |
||||
|
{ |
||||
|
Location = this.currentRenderPosition, |
||||
|
Map = renderData.OutlineMap |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private Buffer2D<float> Render(IPath path) |
||||
|
{ |
||||
|
Size size = Rectangle.Ceiling(path.Bounds).Size; |
||||
|
size = new Size(size.Width + (this.offset * 2), size.Height + (this.offset * 2)); |
||||
|
|
||||
|
float subpixelCount = 4; |
||||
|
float offset = 0.5f; |
||||
|
if (this.Options.Antialias) |
||||
|
{ |
||||
|
offset = 0f; // we are antialising skip offsetting as real antalising should take care of offset.
|
||||
|
subpixelCount = this.Options.AntialiasSubpixelDepth; |
||||
|
if (subpixelCount < 4) |
||||
|
{ |
||||
|
subpixelCount = 4; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// take the path inside the path builder, scan thing and generate a Buffer2d representing the glyph and cache it.
|
||||
|
Buffer2D<float> fullBuffer = this.MemoryAllocator.Allocate2D<float>(size.Width + 1, size.Height + 1, AllocationOptions.Clean); |
||||
|
|
||||
|
using (IMemoryOwner<float> bufferBacking = this.MemoryAllocator.Allocate<float>(path.MaxIntersections)) |
||||
|
using (IMemoryOwner<PointF> rowIntersectionBuffer = this.MemoryAllocator.Allocate<PointF>(size.Width)) |
||||
|
{ |
||||
|
float subpixelFraction = 1f / subpixelCount; |
||||
|
float subpixelFractionPoint = subpixelFraction / subpixelCount; |
||||
|
|
||||
|
for (int y = 0; y <= size.Height; y++) |
||||
|
{ |
||||
|
Span<float> scanline = fullBuffer.GetRowSpan(y); |
||||
|
bool scanlineDirty = false; |
||||
|
float yPlusOne = y + 1; |
||||
|
|
||||
|
for (float subPixel = (float)y; subPixel < yPlusOne; subPixel += subpixelFraction) |
||||
|
{ |
||||
|
var start = new PointF(path.Bounds.Left - 1, subPixel); |
||||
|
var end = new PointF(path.Bounds.Right + 1, subPixel); |
||||
|
Span<PointF> intersectionSpan = rowIntersectionBuffer.GetSpan(); |
||||
|
Span<float> buffer = bufferBacking.GetSpan(); |
||||
|
int pointsFound = path.FindIntersections(start, end, intersectionSpan); |
||||
|
|
||||
|
if (pointsFound == 0) |
||||
|
{ |
||||
|
// nothing on this line skip
|
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
for (int i = 0; i < pointsFound && i < intersectionSpan.Length; i++) |
||||
|
{ |
||||
|
buffer[i] = intersectionSpan[i].X; |
||||
|
} |
||||
|
|
||||
|
QuickSort.Sort(buffer.Slice(0, pointsFound)); |
||||
|
|
||||
|
for (int point = 0; point < pointsFound; point += 2) |
||||
|
{ |
||||
|
// points will be paired up
|
||||
|
float scanStart = buffer[point]; |
||||
|
float scanEnd = buffer[point + 1]; |
||||
|
int startX = (int)MathF.Floor(scanStart + offset); |
||||
|
int endX = (int)MathF.Floor(scanEnd + offset); |
||||
|
|
||||
|
if (startX >= 0 && startX < scanline.Length) |
||||
|
{ |
||||
|
for (float x = scanStart; x < startX + 1; x += subpixelFraction) |
||||
|
{ |
||||
|
scanline[startX] += subpixelFractionPoint; |
||||
|
scanlineDirty = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (endX >= 0 && endX < scanline.Length) |
||||
|
{ |
||||
|
for (float x = endX; x < scanEnd; x += subpixelFraction) |
||||
|
{ |
||||
|
scanline[endX] += subpixelFractionPoint; |
||||
|
scanlineDirty = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int nextX = startX + 1; |
||||
|
endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge
|
||||
|
nextX = Math.Max(nextX, 0); |
||||
|
for (int x = nextX; x < endX; x++) |
||||
|
{ |
||||
|
scanline[x] += subpixelFraction; |
||||
|
scanlineDirty = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (scanlineDirty) |
||||
|
{ |
||||
|
if (!this.Options.Antialias) |
||||
|
{ |
||||
|
for (int x = 0; x < size.Width; x++) |
||||
|
{ |
||||
|
if (scanline[x] >= 0.5) |
||||
|
{ |
||||
|
scanline[x] = 1; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
scanline[x] = 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return fullBuffer; |
||||
|
} |
||||
|
|
||||
|
public void EndText() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public void LineTo(PointF point) |
||||
|
{ |
||||
|
this.builder.AddLine(this.currentPoint, point); |
||||
|
this.currentPoint = point; |
||||
|
} |
||||
|
|
||||
|
public void MoveTo(PointF point) |
||||
|
{ |
||||
|
this.builder.StartFigure(); |
||||
|
this.currentPoint = point; |
||||
|
} |
||||
|
|
||||
|
public void QuadraticBezierTo(PointF secondControlPoint, PointF point) |
||||
|
{ |
||||
|
this.builder.AddBezier(this.currentPoint, secondControlPoint, point); |
||||
|
this.currentPoint = point; |
||||
|
} |
||||
|
|
||||
|
private struct GlyphRenderData : IDisposable |
||||
|
{ |
||||
|
public Buffer2D<float> FillMap; |
||||
|
|
||||
|
public Buffer2D<float> OutlineMap; |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
this.FillMap?.Dispose(); |
||||
|
this.OutlineMap?.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,105 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A Circular Gradient Brush, defined by center point and radius.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
public sealed class RadialGradientBrush<TPixel> : GradientBrushBase<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
private readonly Point center; |
||||
|
|
||||
|
private readonly float radius; |
||||
|
|
||||
|
/// <inheritdoc cref="GradientBrushBase{TPixel}" />
|
||||
|
/// <param name="center">The center of the circular gradient and 0 for the color stops.</param>
|
||||
|
/// <param name="radius">The radius of the circular gradient and 1 for the color stops.</param>
|
||||
|
/// <param name="repetitionMode">Defines how the colors in the gradient are repeated.</param>
|
||||
|
/// <param name="colorStops">the color stops as defined in base class.</param>
|
||||
|
public RadialGradientBrush( |
||||
|
Point center, |
||||
|
float radius, |
||||
|
GradientRepetitionMode repetitionMode, |
||||
|
params ColorStop<TPixel>[] colorStops) |
||||
|
: base(repetitionMode, colorStops) |
||||
|
{ |
||||
|
this.center = center; |
||||
|
this.radius = radius; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="CreateApplicator" />
|
||||
|
public override BrushApplicator<TPixel> CreateApplicator( |
||||
|
ImageFrame<TPixel> source, |
||||
|
RectangleF region, |
||||
|
GraphicsOptions options) => |
||||
|
new RadialGradientBrushApplicator( |
||||
|
source, |
||||
|
options, |
||||
|
this.center, |
||||
|
this.radius, |
||||
|
this.ColorStops, |
||||
|
this.RepetitionMode); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
private sealed class RadialGradientBrushApplicator : GradientBrushApplicatorBase |
||||
|
{ |
||||
|
private readonly Point center; |
||||
|
|
||||
|
private readonly float radius; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="RadialGradientBrushApplicator" /> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="target">The target image</param>
|
||||
|
/// <param name="options">The options.</param>
|
||||
|
/// <param name="center">Center point of the gradient.</param>
|
||||
|
/// <param name="radius">Radius of the gradient.</param>
|
||||
|
/// <param name="colorStops">Definition of colors.</param>
|
||||
|
/// <param name="repetitionMode">How the colors are repeated beyond the first gradient.</param>
|
||||
|
public RadialGradientBrushApplicator( |
||||
|
ImageFrame<TPixel> target, |
||||
|
GraphicsOptions options, |
||||
|
Point center, |
||||
|
float radius, |
||||
|
ColorStop<TPixel>[] colorStops, |
||||
|
GradientRepetitionMode repetitionMode) |
||||
|
: base(target, options, colorStops, repetitionMode) |
||||
|
{ |
||||
|
this.center = center; |
||||
|
this.radius = radius; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="Dispose" />
|
||||
|
public override void Dispose() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// As this is a circular gradient, the position on the gradient is based on
|
||||
|
/// the distance of the point to the center.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The X coordinate of the target pixel.</param>
|
||||
|
/// <param name="y">The Y coordinate of the target pixel.</param>
|
||||
|
/// <returns>the position on the color gradient.</returns>
|
||||
|
protected override float PositionOnGradient(int x, int y) |
||||
|
{ |
||||
|
float distance = (float)Math.Sqrt(Math.Pow(this.center.X - x, 2) + Math.Pow(this.center.Y - y, 2)); |
||||
|
return distance / this.radius; |
||||
|
} |
||||
|
|
||||
|
internal override void Apply(Span<float> scanline, int x, int y) |
||||
|
{ |
||||
|
// TODO: each row is symmetric across center, so we can calculate half of it and mirror it to improve performance.
|
||||
|
base.Apply(scanline, x, y); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,179 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using SixLabors.Fonts; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
using SixLabors.ImageSharp.Processing.Drawing; |
|
||||
using SixLabors.ImageSharp.Processing.Drawing.Brushes; |
|
||||
using SixLabors.ImageSharp.Processing.Drawing.Pens; |
|
||||
using SixLabors.Shapes; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Processing.Text |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Adds extensions that allow the drawing of text along given paths to the <see cref="Image{TPixel}"/> type.
|
|
||||
/// </summary>
|
|
||||
public static partial class DrawTextExtensions |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Draws the text onto the the image filled via the brush.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|
||||
/// <param name="source">The image this method extends.</param>
|
|
||||
/// <param name="text">The text.</param>
|
|
||||
/// <param name="font">The font.</param>
|
|
||||
/// <param name="color">The color.</param>
|
|
||||
/// <param name="path">The path.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Image{TPixel}" />.
|
|
||||
/// </returns>
|
|
||||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, string text, Font font, TPixel color, IPath path) |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
=> source.DrawText(text, font, color, path, TextGraphicsOptions.Default); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Draws the text onto the the image filled via the brush.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|
||||
/// <param name="source">The image this method extends.</param>
|
|
||||
/// <param name="text">The text.</param>
|
|
||||
/// <param name="font">The font.</param>
|
|
||||
/// <param name="color">The color.</param>
|
|
||||
/// <param name="path">The path.</param>
|
|
||||
/// <param name="options">The options.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Image{TPixel}" />.
|
|
||||
/// </returns>
|
|
||||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, string text, Font font, TPixel color, IPath path, TextGraphicsOptions options) |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
=> source.DrawText(text, font, Brushes.Solid(color), null, path, options); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Draws the text onto the the image filled via the brush.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|
||||
/// <param name="source">The image this method extends.</param>
|
|
||||
/// <param name="text">The text.</param>
|
|
||||
/// <param name="font">The font.</param>
|
|
||||
/// <param name="brush">The brush.</param>
|
|
||||
/// <param name="path">The location.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Image{TPixel}" />.
|
|
||||
/// </returns>
|
|
||||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, string text, Font font, IBrush<TPixel> brush, IPath path) |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
=> source.DrawText(text, font, brush, path, TextGraphicsOptions.Default); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Draws the text onto the the image filled via the brush.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|
||||
/// <param name="source">The image this method extends.</param>
|
|
||||
/// <param name="text">The text.</param>
|
|
||||
/// <param name="font">The font.</param>
|
|
||||
/// <param name="brush">The brush.</param>
|
|
||||
/// <param name="path">The path.</param>
|
|
||||
/// <param name="options">The options.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Image{TPixel}" />.
|
|
||||
/// </returns>
|
|
||||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, string text, Font font, IBrush<TPixel> brush, IPath path, TextGraphicsOptions options) |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
=> source.DrawText(text, font, brush, null, path, options); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Draws the text onto the the image outlined via the pen.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|
||||
/// <param name="source">The image this method extends.</param>
|
|
||||
/// <param name="text">The text.</param>
|
|
||||
/// <param name="font">The font.</param>
|
|
||||
/// <param name="pen">The pen.</param>
|
|
||||
/// <param name="path">The path.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Image{TPixel}" />.
|
|
||||
/// </returns>
|
|
||||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, string text, Font font, IPen<TPixel> pen, IPath path) |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
=> source.DrawText(text, font, pen, path, TextGraphicsOptions.Default); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Draws the text onto the the image outlined via the pen.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|
||||
/// <param name="source">The image this method extends.</param>
|
|
||||
/// <param name="text">The text.</param>
|
|
||||
/// <param name="font">The font.</param>
|
|
||||
/// <param name="pen">The pen.</param>
|
|
||||
/// <param name="path">The path.</param>
|
|
||||
/// <param name="options">The options.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Image{TPixel}" />.
|
|
||||
/// </returns>
|
|
||||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, string text, Font font, IPen<TPixel> pen, IPath path, TextGraphicsOptions options) |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
=> source.DrawText(text, font, null, pen, path, options); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Draws the text onto the the image filled via the brush then outlined via the pen.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|
||||
/// <param name="source">The image this method extends.</param>
|
|
||||
/// <param name="text">The text.</param>
|
|
||||
/// <param name="font">The font.</param>
|
|
||||
/// <param name="brush">The brush.</param>
|
|
||||
/// <param name="pen">The pen.</param>
|
|
||||
/// <param name="path">The path.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Image{TPixel}" />.
|
|
||||
/// </returns>
|
|
||||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, string text, Font font, IBrush<TPixel> brush, IPen<TPixel> pen, IPath path) |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
=> source.DrawText(text, font, brush, pen, path, TextGraphicsOptions.Default); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Draws the text onto the the image filled via the brush then outlined via the pen.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TPixel">The type of the color.</typeparam>
|
|
||||
/// <param name="source">The image this method extends.</param>
|
|
||||
/// <param name="text">The text.</param>
|
|
||||
/// <param name="font">The font.</param>
|
|
||||
/// <param name="brush">The brush.</param>
|
|
||||
/// <param name="pen">The pen.</param>
|
|
||||
/// <param name="path">The path.</param>
|
|
||||
/// <param name="options">The options.</param>
|
|
||||
/// <returns>
|
|
||||
/// The <see cref="Image{TPixel}" />.
|
|
||||
/// </returns>
|
|
||||
public static IImageProcessingContext<TPixel> DrawText<TPixel>(this IImageProcessingContext<TPixel> source, string text, Font font, IBrush<TPixel> brush, IPen<TPixel> pen, IPath path, TextGraphicsOptions options) |
|
||||
where TPixel : struct, IPixel<TPixel> |
|
||||
{ |
|
||||
float dpiX = DefaultTextDpi; |
|
||||
float dpiY = DefaultTextDpi; |
|
||||
|
|
||||
var style = new RendererOptions(font, dpiX, dpiY) |
|
||||
{ |
|
||||
ApplyKerning = options.ApplyKerning, |
|
||||
TabWidth = options.TabWidth, |
|
||||
WrappingWidth = options.WrapTextWidth, |
|
||||
HorizontalAlignment = options.HorizontalAlignment, |
|
||||
VerticalAlignment = options.VerticalAlignment |
|
||||
}; |
|
||||
|
|
||||
IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, path, style); |
|
||||
|
|
||||
var pathOptions = (GraphicsOptions)options; |
|
||||
if (brush != null) |
|
||||
{ |
|
||||
source.Fill(brush, glyphs, pathOptions); |
|
||||
} |
|
||||
|
|
||||
if (pen != null) |
|
||||
{ |
|
||||
source.Draw(pen, glyphs, pathOptions); |
|
||||
} |
|
||||
|
|
||||
return source; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,84 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Utils |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Optimized quick sort implementation for Span{float} input
|
||||
|
/// </summary>
|
||||
|
internal class QuickSort |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Sorts the elements of <paramref name="data"/> in ascending order
|
||||
|
/// </summary>
|
||||
|
/// <param name="data">The items to sort</param>
|
||||
|
public static void Sort(Span<float> data) |
||||
|
{ |
||||
|
if (data.Length < 2) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (data.Length == 2) |
||||
|
{ |
||||
|
if (data[0] > data[1]) |
||||
|
{ |
||||
|
Swap(ref data[0], ref data[1]); |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Sort(ref data[0], 0, data.Length - 1); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static void Swap(ref float left, ref float right) |
||||
|
{ |
||||
|
float tmp = left; |
||||
|
left = right; |
||||
|
right = tmp; |
||||
|
} |
||||
|
|
||||
|
private static void Sort(ref float data0, int lo, int hi) |
||||
|
{ |
||||
|
if (lo < hi) |
||||
|
{ |
||||
|
int p = Partition(ref data0, lo, hi); |
||||
|
Sort(ref data0, lo, p); |
||||
|
Sort(ref data0, p + 1, hi); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static int Partition(ref float data0, int lo, int hi) |
||||
|
{ |
||||
|
float pivot = Unsafe.Add(ref data0, lo); |
||||
|
int i = lo - 1; |
||||
|
int j = hi + 1; |
||||
|
while (true) |
||||
|
{ |
||||
|
do |
||||
|
{ |
||||
|
i = i + 1; |
||||
|
} |
||||
|
while (Unsafe.Add(ref data0, i) < pivot && i < hi); |
||||
|
|
||||
|
do |
||||
|
{ |
||||
|
j = j - 1; |
||||
|
} |
||||
|
while (Unsafe.Add(ref data0, j) > pivot && j > lo); |
||||
|
|
||||
|
if (i >= j) |
||||
|
{ |
||||
|
return j; |
||||
|
} |
||||
|
|
||||
|
Swap(ref Unsafe.Add(ref data0, i), ref Unsafe.Add(ref data0, j)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue