///////////////////////////////////////////////////////////////////////////////// // Paint.NET // // Copyright (C) Rick Brewster, Tom Jackson, and past contributors. // // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // // See license-pdn.txt for full licensing and attribution details. // ///////////////////////////////////////////////////////////////////////////////// using System; namespace Pinta.Core { /// /// Provides a set of standard UnaryPixelOps. /// public sealed class UnaryPixelOps { private UnaryPixelOps() { } /// /// Passes through the given color value. /// result(color) = color /// [Serializable] public class Identity : UnaryPixelOp { public override ColorBgra Apply(ColorBgra color) { return color; } public unsafe override void Apply(ColorBgra *dst, ColorBgra *src, int length) { for (int i = 0; i < length; i++) { *dst = *src; dst++; src++; } } public unsafe override void Apply(ColorBgra* ptr, int length) { return; } } /// /// Always returns a constant color. /// [Serializable] public class Constant : UnaryPixelOp { private ColorBgra setColor; public override ColorBgra Apply(ColorBgra color) { return setColor; } public unsafe override void Apply(ColorBgra* dst, ColorBgra* src, int length) { while (length > 0) { *dst = setColor; ++dst; --length; } } public unsafe override void Apply(ColorBgra* ptr, int length) { while (length > 0) { *ptr = setColor; ++ptr; --length; } } public Constant(ColorBgra setColor) { this.setColor = setColor; } } /// /// Blends pixels with the specified constant color. /// [Serializable] public class BlendConstant : UnaryPixelOp { private ColorBgra blendColor; public override ColorBgra Apply(ColorBgra color) { int a = blendColor.A; int invA = 255 - a; int r = ((color.R * invA) + (blendColor.R * a)) / 256; int g = ((color.G * invA) + (blendColor.G * a)) / 256; int b = ((color.B * invA) + (blendColor.B * a)) / 256; byte a2 = ComputeAlpha(color.A, blendColor.A); return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, a2); } public BlendConstant(ColorBgra blendColor) { this.blendColor = blendColor; } } /// /// Used to set a given channel of a pixel to a given, predefined color. /// Useful if you want to set only the alpha value of a given region. /// [Serializable] public class SetChannel : UnaryPixelOp { private int channel; private byte setValue; public override ColorBgra Apply(ColorBgra color) { color[channel] = setValue; return color; } public override unsafe void Apply(ColorBgra* dst, ColorBgra* src, int length) { while (length > 0) { *dst = *src; (*dst)[channel] = setValue; ++dst; ++src; --length; } } public override unsafe void Apply(ColorBgra* ptr, int length) { while (length > 0) { (*ptr)[channel] = setValue; ++ptr; --length; } } public SetChannel(int channel, byte setValue) { this.channel = channel; this.setValue = setValue; } } /// /// Specialization of SetChannel that sets the alpha channel. /// /// This class depends on the system being litte-endian with the alpha channel /// occupying the 8 most-significant-bits of a ColorBgra instance. /// By the way, we use addition instead of bitwise-OR because an addition can be /// perform very fast (0.5 cycles) on a Pentium 4. [Serializable] public class SetAlphaChannel : UnaryPixelOp { private UInt32 addValue; public override ColorBgra Apply(ColorBgra color) { return ColorBgra.FromUInt32((color.Bgra & 0x00ffffff) + addValue); } public override unsafe void Apply(ColorBgra* dst, ColorBgra* src, int length) { while (length > 0) { dst->Bgra = (src->Bgra & 0x00ffffff) + addValue; ++dst; ++src; --length; } } public override unsafe void Apply(ColorBgra* ptr, int length) { while (length > 0) { ptr->Bgra = (ptr->Bgra & 0x00ffffff) + addValue; ++ptr; --length; } } public SetAlphaChannel(byte alphaValue) { addValue = (uint)alphaValue << 24; } } /// /// Specialization of SetAlphaChannel that always sets alpha to 255. /// [Serializable] public class SetAlphaChannelTo255 : UnaryPixelOp { public override ColorBgra Apply(ColorBgra color) { return ColorBgra.FromUInt32(color.Bgra | 0xff000000); } public override unsafe void Apply(ColorBgra* dst, ColorBgra* src, int length) { while (length > 0) { dst->Bgra = src->Bgra | 0xff000000; ++dst; ++src; --length; } } public override unsafe void Apply(ColorBgra* ptr, int length) { while (length > 0) { ptr->Bgra |= 0xff000000; ++ptr; --length; } } } /// /// Inverts a pixel's color, and passes through the alpha component. /// [Serializable] public class Invert : UnaryPixelOp { public override ColorBgra Apply(ColorBgra color) { return ColorBgra.FromBgra((byte)(255 - color.B), (byte)(255 - color.G), (byte)(255 - color.R), color.A); } } /// /// If the color is within the red tolerance, remove it /// [Serializable] public class RedEyeRemove : UnaryPixelOp { private int tolerence; private double setSaturation; public RedEyeRemove(int tol, int sat) { tolerence = tol; setSaturation = (double)sat / 100; } public override ColorBgra Apply(ColorBgra color) { // The higher the saturation, the more red it is int saturation = GetSaturation(color); // The higher the difference between the other colors, the more red it is int difference = color.R - Math.Max(color.B,color.G); // If it is within tolerence, and the saturation is high if ((difference > tolerence) && (saturation > 100)) { double i = 255.0 * color.GetIntensity(); byte ib = (byte)(i * setSaturation); // adjust the red color for user inputted saturation return ColorBgra.FromBgra((byte)color.B,(byte)color.G, ib, color.A); } else { return color; } } //Saturation formula from RgbColor.cs, public HsvColor ToHsv() private int GetSaturation(ColorBgra color) { double min; double max; double delta; double r = (double) color.R / 255; double g = (double) color.G / 255; double b = (double) color.B / 255; double s; min = Math.Min(Math.Min(r, g), b); max = Math.Max(Math.Max(r, g), b); delta = max - min; if (max == 0 || delta == 0) { // R, G, and B must be 0, or all the same. // In this case, S is 0, and H is undefined. // Using H = 0 is as good as any... s = 0; } else { s = delta / max; } return (int)(s * 255); } } /// /// Inverts a pixel's color and its alpha component. /// [Serializable] public class InvertWithAlpha : UnaryPixelOp { public override ColorBgra Apply(ColorBgra color) { return ColorBgra.FromBgra((byte)(255 - color.B), (byte)(255 - color.G), (byte)(255 - color.R), (byte)(255 - color.A)); } } /// /// Averages the input color's red, green, and blue channels. The alpha component /// is unaffected. /// [Serializable] public class AverageChannels : UnaryPixelOp { public override ColorBgra Apply(ColorBgra color) { byte average = (byte)(((int)color.R + (int)color.G + (int)color.B) / 3); return ColorBgra.FromBgra(average, average, average, color.A); } } [Serializable] public class Desaturate : UnaryPixelOp { public override ColorBgra Apply(ColorBgra color) { byte i = color.GetIntensityByte(); return ColorBgra.FromBgra(i, i, i, color.A); } public unsafe override void Apply(ColorBgra* ptr, int length) { while (length > 0) { byte i = ptr->GetIntensityByte(); ptr->R = i; ptr->G = i; ptr->B = i; ++ptr; --length; } } public unsafe override void Apply(ColorBgra* dst, ColorBgra* src, int length) { while (length > 0) { byte i = src->GetIntensityByte(); dst->B = i; dst->G = i; dst->R = i; dst->A = src->A; ++dst; ++src; --length; } } } [Serializable] public class LuminosityCurve : UnaryPixelOp { public byte[] Curve = new byte[256]; public LuminosityCurve() { for (int i = 0; i < 256; ++i) { Curve[i] = (byte)i; } } public override ColorBgra Apply(ColorBgra color) { byte lumi = color.GetIntensityByte(); int diff = Curve[lumi] - lumi; return ColorBgra.FromBgraClamped( color.B + diff, color.G + diff, color.R + diff, color.A); } } [Serializable] public class ChannelCurve : UnaryPixelOp { public byte[] CurveB = new byte[256]; public byte[] CurveG = new byte[256]; public byte[] CurveR = new byte[256]; public ChannelCurve() { for (int i = 0; i < 256; ++i) { CurveB[i] = (byte)i; CurveG[i] = (byte)i; CurveR[i] = (byte)i; } } public override unsafe void Apply(ColorBgra* dst, ColorBgra* src, int length) { while (--length >= 0) { dst->B = CurveB[src->B]; dst->G = CurveG[src->G]; dst->R = CurveR[src->R]; dst->A = src->A; ++dst; ++src; } } public override unsafe void Apply(ColorBgra* ptr, int length) { while (--length >= 0) { ptr->B = CurveB[ptr->B]; ptr->G = CurveG[ptr->G]; ptr->R = CurveR[ptr->R]; ++ptr; } } public override ColorBgra Apply(ColorBgra color) { return ColorBgra.FromBgra(CurveB[color.B], CurveG[color.G], CurveR[color.R], color.A); } // public override void Apply(Surface dst, Point dstOffset, Surface src, Point srcOffset, int scanLength) // { // base.Apply (dst, dstOffset, src, srcOffset, scanLength); // } } [Serializable] public class Level : ChannelCurve, ICloneable { private ColorBgra colorInLow; public ColorBgra ColorInLow { get { return colorInLow; } set { if (value.R == 255) { value.R = 254; } if (value.G == 255) { value.G = 254; } if (value.B == 255) { value.B = 254; } if (colorInHigh.R < value.R + 1) { colorInHigh.R = (byte)(value.R + 1); } if (colorInHigh.G < value.G + 1) { colorInHigh.G = (byte)(value.R + 1); } if (colorInHigh.B < value.B + 1) { colorInHigh.B = (byte)(value.R + 1); } colorInLow = value; UpdateLookupTable(); } } private ColorBgra colorInHigh; public ColorBgra ColorInHigh { get { return colorInHigh; } set { if (value.R == 0) { value.R = 1; } if (value.G == 0) { value.G = 1; } if (value.B == 0) { value.B = 1; } if (colorInLow.R > value.R - 1) { colorInLow.R = (byte)(value.R - 1); } if (colorInLow.G > value.G - 1) { colorInLow.G = (byte)(value.R - 1); } if (colorInLow.B > value.B - 1) { colorInLow.B = (byte)(value.R - 1); } colorInHigh = value; UpdateLookupTable(); } } private ColorBgra colorOutLow; public ColorBgra ColorOutLow { get { return colorOutLow; } set { if (value.R == 255) { value.R = 254; } if (value.G == 255) { value.G = 254; } if (value.B == 255) { value.B = 254; } if (colorOutHigh.R < value.R + 1) { colorOutHigh.R = (byte)(value.R + 1); } if (colorOutHigh.G < value.G + 1) { colorOutHigh.G = (byte)(value.G + 1); } if (colorOutHigh.B < value.B + 1) { colorOutHigh.B = (byte)(value.B + 1); } colorOutLow = value; UpdateLookupTable(); } } private ColorBgra colorOutHigh; public ColorBgra ColorOutHigh { get { return colorOutHigh; } set { if (value.R == 0) { value.R = 1; } if (value.G == 0) { value.G = 1; } if (value.B == 0) { value.B = 1; } if (colorOutLow.R > value.R - 1) { colorOutLow.R = (byte)(value.R - 1); } if (colorOutLow.G > value.G - 1) { colorOutLow.G = (byte)(value.G - 1); } if (colorOutLow.B > value.B - 1) { colorOutLow.B = (byte)(value.B - 1); } colorOutHigh = value; UpdateLookupTable(); } } private float[] gamma = new float[3]; public float GetGamma(int index) { if (index < 0 || index >= 3) { throw new ArgumentOutOfRangeException("index", index, "Index must be between 0 and 2"); } return gamma[index]; } public void SetGamma(int index, float val) { if (index < 0 || index >= 3) { throw new ArgumentOutOfRangeException("index", index, "Index must be between 0 and 2"); } gamma[index] = Utility.Clamp(val, 0.1f, 10.0f); UpdateLookupTable(); } public bool isValid = true; public static Level AutoFromLoMdHi(ColorBgra lo, ColorBgra md, ColorBgra hi) { float[] gamma = new float[3]; for (int i = 0; i < 3; i++) { if (lo[i] < md[i] && md[i] < hi[i]) { gamma[i] = (float)Utility.Clamp(Math.Log(0.5, (float)(md[i] - lo[i]) / (float)(hi[i] - lo[i])), 0.1, 10.0); } else { gamma[i] = 1.0f; } } return new Level(lo, hi, gamma, ColorBgra.Black, ColorBgra.White); } private void UpdateLookupTable() { for (int i = 0; i < 3; i++) { if (colorOutHigh[i] < colorOutLow[i] || colorInHigh[i] <= colorInLow[i] || gamma[i] < 0) { isValid = false; return; } for (int j = 0; j < 256; j++) { ColorBgra col = Apply(j, j, j); CurveB[j] = col.B; CurveG[j] = col.G; CurveR[j] = col.R; } } } public Level() : this(ColorBgra.Black, ColorBgra.White, new float[] { 1, 1, 1 }, ColorBgra.Black, ColorBgra.White) { } public Level(ColorBgra in_lo, ColorBgra in_hi, float[] gamma, ColorBgra out_lo, ColorBgra out_hi) { colorInLow = in_lo; colorInHigh = in_hi; colorOutLow = out_lo; colorOutHigh = out_hi; if (gamma.Length != 3) { throw new ArgumentException("gamma", "gamma must be a float[3]"); } this.gamma = gamma; UpdateLookupTable(); } public ColorBgra Apply(float r, float g, float b) { ColorBgra ret = new ColorBgra(); float[] input = new float[] { b, g, r }; for (int i = 0; i < 3; i++) { float v = (input[i] - colorInLow[i]); if (v < 0) { ret[i] = colorOutLow[i]; } else if (v + colorInLow[i] >= colorInHigh[i]) { ret[i] = colorOutHigh[i]; } else { ret[i] = (byte)Utility.Clamp( colorOutLow[i] + (colorOutHigh[i] - colorOutLow[i]) * Math.Pow(v / (colorInHigh[i] - colorInLow[i]), gamma[i]), 0.0f, 255.0f); } } return ret; } public void UnApply(ColorBgra after, float[] beforeOut, float[] slopesOut) { if (beforeOut.Length != 3) { throw new ArgumentException("before must be a float[3]", "before"); } if (slopesOut.Length != 3) { throw new ArgumentException("slopes must be a float[3]", "slopes"); } for (int i = 0; i < 3; i++) { beforeOut[i] = colorInLow[i] + (colorInHigh[i] - colorInLow[i]) * (float)Math.Pow((float)(after[i] - colorOutLow[i]) / (colorOutHigh[i] - colorOutLow[i]), 1 / gamma[i]); slopesOut[i] = (float)(colorInHigh[i] - colorInLow[i]) / ((colorOutHigh[i] - colorOutLow[i]) * gamma[i]) * (float)Math.Pow((float)(after[i] - colorOutLow[i]) / (colorOutHigh[i] - colorOutLow[i]), 1 / gamma[i] - 1); if (float.IsInfinity(slopesOut[i]) || float.IsNaN(slopesOut[i])) { slopesOut[i] = 0; } } } public object Clone() { Level copy = new Level(colorInLow, colorInHigh, (float[])gamma.Clone(), colorOutLow, colorOutHigh); copy.CurveB = (byte[])this.CurveB.Clone(); copy.CurveG = (byte[])this.CurveG.Clone(); copy.CurveR = (byte[])this.CurveR.Clone(); return copy; } } [Serializable] public class HueSaturationLightness : UnaryPixelOp { private int hueDelta; private int satFactor; private UnaryPixelOp blendOp; public HueSaturationLightness(int hueDelta, int satDelta, int lightness) { this.hueDelta = hueDelta; this.satFactor = (satDelta * 1024) / 100; if (lightness == 0) { blendOp = new UnaryPixelOps.Identity(); } else if (lightness > 0) { blendOp = new UnaryPixelOps.BlendConstant(ColorBgra.FromBgra(255, 255, 255, (byte)((lightness * 255) / 100))); } else // if (lightness < 0) { blendOp = new UnaryPixelOps.BlendConstant(ColorBgra.FromBgra(0, 0, 0, (byte)((-lightness * 255) / 100))); } } public override ColorBgra Apply (ColorBgra color) { //adjust saturation byte intensity = color.GetIntensityByte(); color.R = Utility.ClampToByte((intensity * 1024 + (color.R - intensity) * satFactor) >> 10); color.G = Utility.ClampToByte((intensity * 1024 + (color.G - intensity) * satFactor) >> 10); color.B = Utility.ClampToByte((intensity * 1024 + (color.B - intensity) * satFactor) >> 10); HsvColor hsvColor = (new RgbColor(color.R, color.G, color.B)).ToHsv(); int hue = hsvColor.Hue; hue += hueDelta; while (hue < 0) { hue += 360; } while (hue > 360) { hue -= 360; } hsvColor.Hue = hue; RgbColor rgbColor=hsvColor.ToRgb(); ColorBgra newColor = ColorBgra.FromBgr((byte)rgbColor.Blue, (byte)rgbColor.Green, (byte)rgbColor.Red); newColor = blendOp.Apply(newColor); newColor.A = color.A; return newColor; } } [Serializable] public class PosterizePixel : UnaryPixelOp { private byte[] redLevels; private byte[] greenLevels; private byte[] blueLevels; public PosterizePixel(int red, int green, int blue) { this.redLevels = CalcLevels(red); this.greenLevels = CalcLevels(green); this.blueLevels = CalcLevels(blue); } private static byte[] CalcLevels(int levelCount) { byte[] t1 = new byte[levelCount]; for (int i = 1; i < levelCount; i++) { t1[i] = (byte)((255 * i) / (levelCount - 1)); } byte[] levels = new byte[256]; int j = 0; int k = 0; for (int i = 0; i < 256; i++) { levels[i] = t1[j]; k += levelCount; if (k > 255) { k -= 255; j++; } } return levels; } public override ColorBgra Apply(ColorBgra color) { return ColorBgra.FromBgra(blueLevels[color.B], greenLevels[color.G], redLevels[color.R], color.A); } public unsafe override void Apply(ColorBgra* ptr, int length) { while (length > 0) { ptr->B = this.blueLevels[ptr->B]; ptr->G = this.greenLevels[ptr->G]; ptr->R = this.redLevels[ptr->R]; ++ptr; --length; } } public unsafe override void Apply(ColorBgra* dst, ColorBgra* src, int length) { while (length > 0) { dst->B = this.blueLevels[src->B]; dst->G = this.greenLevels[src->G]; dst->R = this.redLevels[src->R]; dst->A = src->A; ++dst; ++src; --length; } } } } }