From 6edd386d0b1b1a2ad6ec0bf32910f961d6408a24 Mon Sep 17 00:00:00 2001 From: Peter Amrehn Date: Sun, 19 Jan 2020 23:29:17 +0100 Subject: [PATCH] WebP: replace WebPFilterType enum by classes and implement them --- src/ImageSharp/Formats/WebP/WebPFilterType.cs | 370 +++++++++++++++++- 1 file changed, 362 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/WebP/WebPFilterType.cs b/src/ImageSharp/Formats/WebP/WebPFilterType.cs index ca7a13085a..f373da7275 100644 --- a/src/ImageSharp/Formats/WebP/WebPFilterType.cs +++ b/src/ImageSharp/Formats/WebP/WebPFilterType.cs @@ -1,17 +1,371 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Diagnostics; + namespace SixLabors.ImageSharp.Formats.WebP { // TODO from dsp.h - public enum WebPFilterType + // public enum WebPFilterType + // { + // None = 0, + // Horizontal, + // Vertical, + // Gradient, + // Last = Gradient + 1, // end marker + // Best, // meta types + // Fast + // } + + abstract class WebPFilterBase + { + /// + /// + /// + /// nullable as prevLine is nullable in the original but Span'T can't be null. + /// + /// + /// + /// + /// + public abstract void Unfilter( + Span prevLine, + int? prevLineOffset, + Span preds, + int predsOffset, + Span currentLine, + int currentLineOffset, + int width); + + public abstract void Filter( + Span input, int inputOffset, + int width, int height, int stride, + Span output, int outputOffset); + + protected static void SanityCheck( + Span input, Span output, int width, int numRows, int height, int stride, int row) + { + Debug.Assert(input != null); + Debug.Assert(output != null); + Debug.Assert(width > 0); + Debug.Assert(height > 0); + Debug.Assert(stride > width); + Debug.Assert(row >= 0); + Debug.Assert(height > 0); + Debug.Assert(row + numRows <= height); + } + + protected static void PredictLine( + Span src, int srcOffset, + Span pred, int predOffset, + Span dst, int dstOffset, + int length, bool inverse) + { + if (inverse) + { + for (int i = 0; i < length; i++) + { + dst[i] = (byte)(src[i] + pred[i]); + } + } + else + { + for (int i = 0; i < length; i++) + { + dst[i] = (byte)(src[i] - pred[i]); + } + } + } + + protected void UnfilterHorizontalOrVerticalCore( + byte pred, + Span input, int inputOffset, + Span output, int outputOffset, + int width) + { + for (int i = 0; i < width; i++) + { + output[outputOffset + i] = (byte)(pred + input[inputOffset + i]); + pred = output[i]; + } + } + } + + // TODO: check if this is a filter or just a placeholder from the C implementation details + class WebPFilterNone : WebPFilterBase + { + public override void Unfilter(Span prevLine, int? prevLineOffset, Span input, int inputOffset, Span output, int outputOffset, int width) + { } + + public override void Filter(Span input, int inputOffset, int width, int height, int stride, Span output, int outputOffset) + { } + } + + class WebPFilterHorizontal : WebPFilterBase { - None = 0, - Horizontal, - Vertical, - Gradient, - Last = Gradient + 1, // end marker - Best, // meta types - Fast + public override void Unfilter( + Span prevLine, int? prevLineOffsetNullable, + Span input, int inputOffset, + Span output, int outputOffset, + int width) + { + byte pred = prevLineOffsetNullable is int prevLineOffset + ? prevLine[prevLineOffset] + : (byte)0; + + this.UnfilterHorizontalOrVerticalCore( + pred, + input, inputOffset, + output, outputOffset, + width); + } + + public override void Filter( + Span input, int inputOffset, + int width, int height, int stride, + Span output, int outputOffset) + { + int numRows = height; + int row = 0; + + const bool inverse = false; + + int startOffset = row * stride; + int lastRow = row + height; + SanityCheck(input, output, width, height, numRows, stride, row); + inputOffset += startOffset; + outputOffset += startOffset; + + Span preds; + int predsOffset; + + if (inverse) + { + preds = output; + predsOffset = outputOffset; + } + else + { + preds = input; + predsOffset = inputOffset; + } + + + if (row == 0) + { + // leftmost pixel is the same as Input for topmost scanline + output[0] = input[0]; + PredictLine( + input, inputOffset + 1, + preds, predsOffset, + output, outputOffset + 1, + width - 1, inverse); + + row = 1; + predsOffset += stride; + inputOffset += stride; + outputOffset += stride; + } + + // filter line by line + while (row < lastRow) + { + PredictLine( + input, inputOffset, + preds, predsOffset - stride, + output, 0, + 1, inverse); + PredictLine( + input, inputOffset, + preds, predsOffset, + output,outputOffset + 1, + width - 1, inverse); + + row++; + predsOffset += stride; + inputOffset += stride; + outputOffset += stride; + } + } + } + + class WebPFilterVertical : WebPFilterBase + { + public override void Unfilter(Span prevLine, int? prevLineOffsetNullable, Span input, int inputOffset, Span output, int outputOffset, int width) + { + if (prevLineOffsetNullable is int prevLineOffset) + { + for (int i = 0; i < width; i++) + { + output[outputOffset + i] = (byte)(prevLine[prevLineOffset + i] + input[inputOffset + i]); + } + } + else + { + this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width); + } + } + + public override void Filter( + Span input, int inputOffset, + int width, int height, int stride, + Span output, int outputOffset) + { + int row = 0; + bool inverse = false; + + // TODO: DoVerticalFilter_C with parameters after stride and after height set to 0 + int startOffset = row * stride; + int lastRow = row + height; + SanityCheck(input, output, width, height, height, stride, row); + inputOffset += startOffset; + outputOffset += startOffset; + Span preds; + int predsOffset; + + if (inverse) + { + preds = output; + predsOffset = outputOffset; + } + else + { + preds = input; + predsOffset = inputOffset; + } + + if (row == 0) + { + // very first top-left pixel is copied. + output[0] = input[0]; + // rest of top scan-line is left-predicted: + PredictLine( + input, inputOffset + 1, + preds, predsOffset, + output, outputOffset + 1, + width - 1, inverse); + row = 1; + inputOffset += stride; + outputOffset += stride; + } + else + { + predsOffset -= stride; + } + + // filter line-by-line + while (row < lastRow) + { + PredictLine( + input, inputOffset, + preds, predsOffset, + output, outputOffset, + width, inverse); + row++; + predsOffset += stride; + inputOffset += stride; + outputOffset += stride; + } + } + } + + class WebPFilterGradient : WebPFilterBase + { + + public override void Unfilter( + Span prevLine, + int? prevLineOffsetNullable, + Span input, + int inputOffset, + Span output, + int outputOffset, + int width) + { + if (prevLineOffsetNullable is int prevLineOffset) + { + byte top = prevLine[prevLineOffset]; + byte topLeft = top; + byte left = top; + for (int i = 0; i < width; i++) + { + top = prevLine[prevLineOffset + i]; // need to read this first in case prev==out + left = (byte)(input[inputOffset + i] + GradientPredictor(left, top, topLeft)); + topLeft = top; + output[outputOffset + i] = left; + } + } + else + { + this.UnfilterHorizontalOrVerticalCore(0, input, inputOffset, output, outputOffset, width); + } + } + + public override void Filter( + Span input, int inputOffset, + int width, int height, int stride, + Span output, int outputOffset) + { + // calling (input, width, height, stride, 0, height, 0, output + int row = 0; + int numRows = height; + bool inverse = false; + + int startOffset = row * stride; + int lastRow = row + numRows; + SanityCheck(input, output, width, numRows, height, stride, row); + inputOffset += startOffset; + outputOffset += startOffset; + Span preds; + int predsOffset; + if (inverse) + { + preds = output; + predsOffset = outputOffset; + } + else + { + preds = input; + predsOffset = inputOffset; + } + + if (row == 0) + { + output[outputOffset] = input[inputOffset]; + PredictLine( + input, inputOffset+1, + preds, predsOffset, + output, outputOffset+1, + width-1, + inverse); + } + + while (row < lastRow) + { + PredictLine( + input, inputOffset, + preds, predsOffset-stride, + output, outputOffset, + 1, inverse); + + for (int w = 1; w < width; w++) + { + int pred = GradientPredictor(preds[w - 1], preds[w - stride], preds[w - stride - 1]); + int signedPred = inverse ? pred : -pred; + output[outputOffset + w] = (byte)(input[inputOffset + w] + signedPred); + } + + row++; + predsOffset += stride; + inputOffset += stride; + outputOffset += stride; + } + } + + private static int GradientPredictor(byte a, byte b, byte c) + { + int g = a + b + c; + return (g & ~0xff) == 0 ? g : (g < 0) ? 0 : 255; + } } }