/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { float threshold = this.Threshold * 255F; Rgba32 rgba = default; 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); sourcePixel.ToRgba32(ref rgba); // Convert to grayscale using ITU-R Recommendation BT.709 if required float luminance = isAlphaOnly ? rgba.A : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * 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 : (.2126F * rgba.R) + (.7152F * rgba.G) + (.0722F * rgba.B); // Setup the previous pointer previousPixel = sourcePixel; } TPixel transformedPixel = luminance >= threshold ? pair.Second : pair.First; this.Diffuser.Dither(source, sourcePixel, transformedPixel, x, y, startX, startY, endX, endY); } } }
protected PixelPair <TPixel> GetClosestPixelPair(ref TPixel pixel, TPixel[] colorPalette) { // Check if the color is in the lookup table if (this.cache.ContainsKey(pixel)) { return(this.cache[pixel]); } // Not found - loop through the palette and find the nearest match. float leastDistance = int.MaxValue; float secondLeastDistance = int.MaxValue; var vector = pixel.ToVector4(); var closest = default(TPixel); var secondClosest = default(TPixel); for (int index = 0; index < colorPalette.Length; index++) { TPixel temp = colorPalette[index]; float distance = Vector4.DistanceSquared(vector, temp.ToVector4()); if (distance < leastDistance) { leastDistance = distance; secondClosest = closest; closest = temp; } else if (distance < secondLeastDistance) { secondLeastDistance = distance; secondClosest = temp; } } // Pop it into the cache for next time var pair = new PixelPair <TPixel>(closest, secondClosest); this.cache.Add(pixel, pair); return(pair); }
/// <inheritdoc/> public bool Equals(PixelPair <TPixel> other) => this.First.Equals(other.First) && this.Second.Equals(other.Second);