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