mirror of https://github.com/SixLabors/ImageSharp
10 changed files with 370 additions and 242 deletions
@ -0,0 +1,81 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Dithering |
|||
{ |
|||
/// <summary>
|
|||
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class ErrorDiffusionPaletteProcessor<TPixel> : PaletteDitherProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
public ErrorDiffusionPaletteProcessor(ErrorDiffusionPaletteProcessor definition) |
|||
: base(definition) |
|||
{ |
|||
} |
|||
|
|||
private new ErrorDiffusionPaletteProcessor Definition => (ErrorDiffusionPaletteProcessor)base.Definition; |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|||
{ |
|||
byte threshold = (byte)MathF.Round(this.Definition.Threshold * 255F); |
|||
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); |
|||
|
|||
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); |
|||
int startY = interest.Y; |
|||
int endY = interest.Bottom; |
|||
int startX = interest.X; |
|||
int endX = interest.Right; |
|||
|
|||
// Collect the values before looping so we can reduce our calculation count for identical sibling pixels
|
|||
TPixel sourcePixel = source[startX, startY]; |
|||
TPixel previousPixel = sourcePixel; |
|||
PixelPair<TPixel> pair = this.GetClosestPixelPair(ref sourcePixel); |
|||
Rgba32 rgba = default; |
|||
sourcePixel.ToRgba32(ref rgba); |
|||
|
|||
// Convert to grayscale using ITU-R Recommendation BT.709 if required
|
|||
byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); |
|||
|
|||
for (int y = startY; y < endY; y++) |
|||
{ |
|||
Span<TPixel> row = source.GetPixelRowSpan(y); |
|||
|
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
sourcePixel = row[x]; |
|||
|
|||
// Check if this is the same as the last pixel. If so use that value
|
|||
// rather than calculating it again. This is an inexpensive optimization.
|
|||
if (!previousPixel.Equals(sourcePixel)) |
|||
{ |
|||
pair = this.GetClosestPixelPair(ref sourcePixel); |
|||
|
|||
// No error to spread, exact match.
|
|||
if (sourcePixel.Equals(pair.First)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
sourcePixel.ToRgba32(ref rgba); |
|||
luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); |
|||
|
|||
// Setup the previous pointer
|
|||
previousPixel = sourcePixel; |
|||
} |
|||
|
|||
TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First; |
|||
this.Definition.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Dithering |
|||
{ |
|||
/// <summary>
|
|||
/// An <see cref="IImageProcessor{TPixel}"/> that dithers an image using error diffusion.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
internal class OrderedDitherPaletteProcessor<TPixel> : PaletteDitherProcessor<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
public OrderedDitherPaletteProcessor(OrderedDitherPaletteProcessor definition) |
|||
: base(definition) |
|||
{ |
|||
} |
|||
|
|||
private new OrderedDitherPaletteProcessor Definition => (OrderedDitherPaletteProcessor)base.Definition; |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration) |
|||
{ |
|||
bool isAlphaOnly = typeof(TPixel) == typeof(Alpha8); |
|||
|
|||
var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); |
|||
int startY = interest.Y; |
|||
int endY = interest.Bottom; |
|||
int startX = interest.X; |
|||
int endX = interest.Right; |
|||
|
|||
// Collect the values before looping so we can reduce our calculation count for identical sibling pixels
|
|||
TPixel sourcePixel = source[startX, startY]; |
|||
TPixel previousPixel = sourcePixel; |
|||
PixelPair<TPixel> pair = this.GetClosestPixelPair(ref sourcePixel); |
|||
Rgba32 rgba = default; |
|||
sourcePixel.ToRgba32(ref rgba); |
|||
|
|||
// Convert to grayscale using ITU-R Recommendation BT.709 if required
|
|||
byte luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); |
|||
|
|||
for (int y = startY; y < endY; y++) |
|||
{ |
|||
Span<TPixel> row = source.GetPixelRowSpan(y); |
|||
|
|||
for (int x = startX; x < endX; x++) |
|||
{ |
|||
sourcePixel = row[x]; |
|||
|
|||
// Check if this is the same as the last pixel. If so use that value
|
|||
// rather than calculating it again. This is an inexpensive optimization.
|
|||
if (!previousPixel.Equals(sourcePixel)) |
|||
{ |
|||
pair = this.GetClosestPixelPair(ref sourcePixel); |
|||
|
|||
// No error to spread, exact match.
|
|||
if (sourcePixel.Equals(pair.First)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
sourcePixel.ToRgba32(ref rgba); |
|||
luminance = isAlphaOnly ? rgba.A : ImageMaths.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B); |
|||
|
|||
// Setup the previous pointer
|
|||
previousPixel = sourcePixel; |
|||
} |
|||
|
|||
this.Definition.Dither.Dither(source, sourcePixel, pair.Second, pair.First, luminance, x, y); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Dithering |
|||
{ |
|||
/// <summary>
|
|||
/// The base class for dither and diffusion processors that consume a palette.
|
|||
/// </summary>
|
|||
public abstract class PaletteDitherProcessor : IImageProcessor |
|||
{ |
|||
private readonly Color[] palette; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="PaletteDitherProcessor"/> class.
|
|||
/// </summary>
|
|||
/// <param name="palette">The palette to select substitute colors from.</param>
|
|||
protected PaletteDitherProcessor(ReadOnlySpan<Color> palette) |
|||
{ |
|||
// This shouldn't be a perf issue:
|
|||
// these arrays are small, and created with low frequency.
|
|||
this.palette = palette.ToArray(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the palette to select substitute colors from.
|
|||
/// </summary>
|
|||
public ReadOnlySpan<Color> Palette => this.palette; |
|||
|
|||
/// <inheritdoc />
|
|||
public abstract IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>() |
|||
where TPixel : struct, IPixel<TPixel>; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue