///////////////////////////////////////////////////////////////////////////////// // Paint.NET // // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // // See license-pdn.txt for full licensing and attribution details. // ///////////////////////////////////////////////////////////////////////////////// using System; using Cairo; namespace Pinta.Core { public abstract class GradientRenderer { private BinaryPixelOp normalBlendOp; private ColorBgra startColor; private ColorBgra endColor; private PointD startPoint; private PointD endPoint; private bool alphaBlending; private bool alphaOnly; private bool lerpCacheIsValid = false; private byte[] lerpAlphas; private ColorBgra[] lerpColors; public ColorBgra StartColor { get { return this.startColor; } set { if (this.startColor != value) { this.startColor = value; this.lerpCacheIsValid = false; } } } public ColorBgra EndColor { get { return this.endColor; } set { if (this.endColor != value) { this.endColor = value; this.lerpCacheIsValid = false; } } } public PointD StartPoint { get { return this.startPoint; } set { this.startPoint = value; } } public PointD EndPoint { get { return this.endPoint; } set { this.endPoint = value; } } public bool AlphaBlending { get { return this.alphaBlending; } set { this.alphaBlending = value; } } public bool AlphaOnly { get { return this.alphaOnly; } set { this.alphaOnly = value; } } public virtual void BeforeRender () { if (!this.lerpCacheIsValid) { byte startAlpha; byte endAlpha; if (this.alphaOnly) { ComputeAlphaOnlyValuesFromColors (this.startColor, this.endColor, out startAlpha, out endAlpha); } else { startAlpha = this.startColor.A; endAlpha = this.endColor.A; } this.lerpAlphas = new byte[256]; this.lerpColors = new ColorBgra[256]; for (int i = 0; i < 256; ++i) { byte a = (byte)i; this.lerpColors[a] = ColorBgra.Blend (this.startColor, this.endColor, a); this.lerpAlphas[a] = (byte)(startAlpha + ((endAlpha - startAlpha) * a) / 255); } this.lerpCacheIsValid = true; } } public abstract double ComputeUnboundedLerp (int x, int y); public abstract double BoundLerp (double t); public virtual void AfterRender () { } private static void ComputeAlphaOnlyValuesFromColors (ColorBgra startColor, ColorBgra endColor, out byte startAlpha, out byte endAlpha) { startAlpha = startColor.A; endAlpha = (byte)(255 - endColor.A); } unsafe public void Render (ImageSurface surface, Gdk.Rectangle[] rois) { byte startAlpha; byte endAlpha; if (this.alphaOnly) { ComputeAlphaOnlyValuesFromColors (this.startColor, this.endColor, out startAlpha, out endAlpha); } else { startAlpha = this.startColor.A; endAlpha = this.endColor.A; } surface.Flush (); ColorBgra* src_data_ptr = (ColorBgra*)surface.DataPtr; int src_width = surface.Width; for (int ri = 0; ri < rois.Length; ++ri) { Gdk.Rectangle rect = rois[ri]; if (this.startPoint.X == this.endPoint.X && this.startPoint.Y == this.endPoint.Y) { // Start and End point are the same ... fill with solid color. for (int y = rect.Top; y < rect.Bottom; ++y) { ColorBgra* pixelPtr = surface.GetPointAddress(rect.Left, y); for (int x = rect.Left; x < rect.Right; ++x) { ColorBgra result; if (this.alphaOnly && this.alphaBlending) { byte resultAlpha = (byte)Utility.FastDivideShortByByte ((ushort)(pixelPtr->A * endAlpha), 255); result = *pixelPtr; result.A = resultAlpha; } else if (this.alphaOnly && !this.alphaBlending) { result = *pixelPtr; result.A = endAlpha; } else if (!this.alphaOnly && this.alphaBlending) { result = this.normalBlendOp.Apply (*pixelPtr, this.endColor); //if (!this.alphaOnly && !this.alphaBlending) } else { result = this.endColor; } *pixelPtr = result; ++pixelPtr; } } } else { for (int y = rect.Top; y < rect.Bottom; ++y) { ColorBgra* pixelPtr = surface.GetPointAddress(rect.Left, y); if (this.alphaOnly && this.alphaBlending) { for (int x = rect.Left; x < rect.Right; ++x) { double lerpUnbounded = ComputeUnboundedLerp (x, y); double lerpBounded = BoundLerp (lerpUnbounded); byte lerpByte = (byte)(lerpBounded * 255f); byte lerpAlpha = this.lerpAlphas[lerpByte]; byte resultAlpha = Utility.FastScaleByteByByte (pixelPtr->A, lerpAlpha); pixelPtr->A = resultAlpha; ++pixelPtr; } } else if (this.alphaOnly && !this.alphaBlending) { for (int x = rect.Left; x < rect.Right; ++x) { double lerpUnbounded = ComputeUnboundedLerp (x, y); double lerpBounded = BoundLerp (lerpUnbounded); byte lerpByte = (byte)(lerpBounded * 255f); byte lerpAlpha = this.lerpAlphas[lerpByte]; pixelPtr->A = lerpAlpha; ++pixelPtr; } } else if (!this.alphaOnly && (this.alphaBlending && (startAlpha != 255 || endAlpha != 255))) { // If we're doing all color channels, and we're doing alpha blending, and if alpha blending is necessary for (int x = rect.Left; x < rect.Right; ++x) { double lerpUnbounded = ComputeUnboundedLerp (x, y); double lerpBounded = BoundLerp (lerpUnbounded); byte lerpByte = (byte)(lerpBounded * 255f); ColorBgra lerpColor = this.lerpColors[lerpByte]; ColorBgra result = this.normalBlendOp.Apply (*pixelPtr, lerpColor); *pixelPtr = result; ++pixelPtr; } //if (!this.alphaOnly && !this.alphaBlending) // or sC.A == 255 && eC.A == 255 } else { for (int x = rect.Left; x < rect.Right; ++x) { double lerpUnbounded = ComputeUnboundedLerp (x, y); double lerpBounded = BoundLerp (lerpUnbounded); byte lerpByte = (byte)(lerpBounded * 255f); ColorBgra lerpColor = this.lerpColors[lerpByte]; *pixelPtr = lerpColor; ++pixelPtr; } } } } } surface.MarkDirty (); AfterRender (); } protected internal GradientRenderer (bool alphaOnly, BinaryPixelOp normalBlendOp) { this.normalBlendOp = normalBlendOp; this.alphaOnly = alphaOnly; } } }