void EnsureSync(ColorRepresentation newType) { if (_type == newType) { return; } _colors.ConvertFrom(_type, newType); }
/// <summary> /// Set the base type of the color to the new representation. /// </summary> /// <param name="representation">The new representation</param> public void Convert(ColorRepresentation representation) { if (_type == representation) { return; } _colors.ConvertFrom(_type, representation); _type = representation; }
void EnsureDesync(ColorRepresentation newType) { for (ColorRepresentation i = 0; i < ColorRepresentation.Total; ++i) { if (i == newType) { continue; } _colors[i] = false; } _type = newType; }
/// <summary> /// Check whether this type is synced or not /// </summary> /// <param name="type">The type to check</param> /// <returns>Is synced or not</returns> public unsafe bool this[ColorRepresentation type] { get { fixed(bool *vals = synced) return(vals[(int)type]); } set { fixed(bool *vals = synced) vals[(int)type] = value; } }
/// <summary> /// Create a color from the specified color representation /// </summary> /// <param name="representation">The base representation</param> /// <param name="values">The values</param> public ConvertableColor(ColorRepresentation representation, params float[] values) : this() { if (values.Length < 3 || values.Length > 4) { throw new IndexOutOfRangeException("values must contain no less than 3 and no more than 4 values"); } if (values.Length >= 4) { _colors.a = values[3]; } else { _colors.a = 1; } _type = representation; switch (_type) { case ColorRepresentation.RGB: _colors.rgb = new Colors.RGB() { R = values[0], G = values[1], B = values[2] }; break; case ColorRepresentation.HSL: _colors.hsl = new Colors.HSL() { H = values[0], S = values[1], L = values[2] }; break; } _colors[_type] = true; }
/// <summary> /// Sync two types up /// </summary> /// <param name="fromType">Type to convert from that is already synced</param> /// <param name="toType">Type to convert to that will be synced to the from type</param> public void ConvertFrom(ColorRepresentation fromType, ColorRepresentation toType) { // should never happen if (!this[fromType]) { throw new InvalidOperationException(); } // if they're already synced up, no worries if (this[toType]) { return; } switch (toType) { case ColorRepresentation.RGB: switch (fromType) { case ColorRepresentation.HSL: rgb.ConvertFrom(ref hsl); break; } break; case ColorRepresentation.HSL: switch (fromType) { case ColorRepresentation.RGB: hsl.ConvertFrom(ref rgb); break; } break; } // synced this[toType] = true; }
/// <summary> /// Generates a new bitmap of the specified size by changing a specific color channel. /// This will produce a gradient representing all possible differences of that color channel. /// </summary> /// <param name="width">The pixel width (X, horizontal) of the resulting bitmap.</param> /// <param name="height">The pixel height (Y, vertical) of the resulting bitmap.</param> /// <param name="orientation">The orientation of the resulting bitmap (gradient direction).</param> /// <param name="colorRepresentation">The color representation being used: RGBA or HSVA.</param> /// <param name="channel">The specific color channel to vary.</param> /// <param name="baseHsvColor">The base HSV color used for channels not being changed.</param> /// <param name="checkerColor">The color of the checker background square.</param> /// <param name="isAlphaMaxForced">Fix the alpha channel value to maximum during calculation. /// This will remove any alpha/transparency from the other channel backgrounds.</param> /// <param name="isSaturationValueMaxForced">Fix the saturation and value channels to maximum /// during calculation in HSVA color representation. /// This will ensure colors are always discernible regardless of saturation/value.</param> /// <returns>A new bitmap representing a gradient of color channel values.</returns> public static async Task <byte[]> CreateChannelBitmapAsync( int width, int height, Orientation orientation, ColorRepresentation colorRepresentation, ColorChannel channel, HsvColor baseHsvColor, Color?checkerColor, bool isAlphaMaxForced, bool isSaturationValueMaxForced) { if (width == 0 || height == 0) { return(null); } var bitmap = await Task.Run <byte[]>(async() => { int pixelDataIndex = 0; double channelStep; byte[] bgraPixelData; byte[] bgraCheckeredPixelData = null; Color baseRgbColor = Colors.White; Color rgbColor; int bgraPixelDataHeight; int bgraPixelDataWidth; // Allocate the buffer // BGRA formatted color channels 1 byte each (4 bytes in a pixel) bgraPixelData = new byte[width * height * 4]; bgraPixelDataHeight = height * 4; bgraPixelDataWidth = width * 4; // Maximize alpha channel value if (isAlphaMaxForced && channel != ColorChannel.Alpha) { baseHsvColor = new HsvColor() { H = baseHsvColor.H, S = baseHsvColor.S, V = baseHsvColor.V, A = 1.0 }; } // Convert HSV to RGB once if (colorRepresentation == ColorRepresentation.Rgba) { baseRgbColor = Uwp.Helpers.ColorHelper.FromHsv( baseHsvColor.H, baseHsvColor.S, baseHsvColor.V, baseHsvColor.A); } // Maximize Saturation and Value channels when in HSVA mode if (isSaturationValueMaxForced && colorRepresentation == ColorRepresentation.Hsva && channel != ColorChannel.Alpha) { switch (channel) { case ColorChannel.Channel1: baseHsvColor = new HsvColor() { H = baseHsvColor.H, S = 1.0, V = 1.0, A = baseHsvColor.A }; break; case ColorChannel.Channel2: baseHsvColor = new HsvColor() { H = baseHsvColor.H, S = baseHsvColor.S, V = 1.0, A = baseHsvColor.A }; break; case ColorChannel.Channel3: baseHsvColor = new HsvColor() { H = baseHsvColor.H, S = 1.0, V = baseHsvColor.V, A = baseHsvColor.A }; break; } } // Create a checkered background if (checkerColor != null) { bgraCheckeredPixelData = await CreateCheckeredBitmapAsync( width, height, checkerColor.Value); } // Create the color channel gradient if (orientation == Orientation.Horizontal) { // Determine the numerical increment of the color steps within the channel if (colorRepresentation == ColorRepresentation.Hsva) { if (channel == ColorChannel.Channel1) { channelStep = 360.0 / width; } else { channelStep = 1.0 / width; } } else { channelStep = 255.0 / width; } for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (y == 0) { rgbColor = GetColor(x *channelStep); // Get a new color bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(rgbColor.B *rgbColor.A / 255); bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(rgbColor.G *rgbColor.A / 255); bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(rgbColor.R *rgbColor.A / 255); bgraPixelData[pixelDataIndex + 3] = rgbColor.A; } else { // Use the color in the row above // Remember the pixel data is 1 dimensional instead of 2 bgraPixelData[pixelDataIndex + 0] = bgraPixelData[pixelDataIndex + 0 - bgraPixelDataWidth]; bgraPixelData[pixelDataIndex + 1] = bgraPixelData[pixelDataIndex + 1 - bgraPixelDataWidth]; bgraPixelData[pixelDataIndex + 2] = bgraPixelData[pixelDataIndex + 2 - bgraPixelDataWidth]; bgraPixelData[pixelDataIndex + 3] = bgraPixelData[pixelDataIndex + 3 - bgraPixelDataWidth]; } pixelDataIndex += 4; } } } else { // Determine the numerical increment of the color steps within the channel if (colorRepresentation == ColorRepresentation.Hsva) { if (channel == ColorChannel.Channel1) { channelStep = 360.0 / height; } else { channelStep = 1.0 / height; } } else { channelStep = 255.0 / height; } for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (x == 0) { // The lowest channel value should be at the 'bottom' of the bitmap rgbColor = GetColor((height - 1 - y) * channelStep); // Get a new color bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(rgbColor.B *rgbColor.A / 255); bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(rgbColor.G *rgbColor.A / 255); bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(rgbColor.R *rgbColor.A / 255); bgraPixelData[pixelDataIndex + 3] = rgbColor.A; } else { // Use the color in the column to the left // Remember the pixel data is 1 dimensional instead of 2 bgraPixelData[pixelDataIndex + 0] = bgraPixelData[pixelDataIndex - 4]; bgraPixelData[pixelDataIndex + 1] = bgraPixelData[pixelDataIndex - 3]; bgraPixelData[pixelDataIndex + 2] = bgraPixelData[pixelDataIndex - 2]; bgraPixelData[pixelDataIndex + 3] = bgraPixelData[pixelDataIndex - 1]; } pixelDataIndex += 4; } } } // Composite the checkered background with color channel gradient for final result // The height/width are not checked as both bitmaps were built with the same values if ((checkerColor != null) && (bgraCheckeredPixelData != null)) { pixelDataIndex = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { /* The following algorithm is used to blend the two bitmaps creating the final composite. * In this formula, pixel data is normalized 0..1, actual pixel data is in the range 0..255. * The color channel gradient should apply OVER the checkered background. * * R = R0 * A0 * (1 - A1) + R1 * A1 = RA0 * (1 - A1) + RA1 * G = G0 * A0 * (1 - A1) + G1 * A1 = GA0 * (1 - A1) + GA1 * B = B0 * A0 * (1 - A1) + B1 * A1 = BA0 * (1 - A1) + BA1 * A = A0 * (1 - A1) + A1 = A0 * (1 - A1) + A1 * * Considering only the red channel, some algebraic transformation is applied to * make the math quicker to solve. * * => ((RA0 / 255.0) * (1.0 - A1 / 255.0) + (RA1 / 255.0)) * 255.0 * => ((RA0 * 255) - (RA0 * A1) + (RA1 * 255)) / 255 */ // Bottom layer byte rXa0 = bgraCheckeredPixelData[pixelDataIndex + 2]; byte gXa0 = bgraCheckeredPixelData[pixelDataIndex + 1]; byte bXa0 = bgraCheckeredPixelData[pixelDataIndex + 0]; byte a0 = bgraCheckeredPixelData[pixelDataIndex + 3]; // Top layer byte rXa1 = bgraPixelData[pixelDataIndex + 2]; byte gXa1 = bgraPixelData[pixelDataIndex + 1]; byte bXa1 = bgraPixelData[pixelDataIndex + 0]; byte a1 = bgraPixelData[pixelDataIndex + 3]; bgraPixelData[pixelDataIndex + 0] = Convert.ToByte(((bXa0 * 255) - (bXa0 * a1) + (bXa1 * 255)) / 255); bgraPixelData[pixelDataIndex + 1] = Convert.ToByte(((gXa0 * 255) - (gXa0 * a1) + (gXa1 * 255)) / 255); bgraPixelData[pixelDataIndex + 2] = Convert.ToByte(((rXa0 * 255) - (rXa0 * a1) + (rXa1 * 255)) / 255); bgraPixelData[pixelDataIndex + 3] = Convert.ToByte(((a0 * 255) - (a0 * a1) + (a1 * 255)) / 255); pixelDataIndex += 4; } } } Color GetColor(double channelValue) { Color newRgbColor = Colors.White; switch (channel) { case ColorChannel.Channel1: { if (colorRepresentation == ColorRepresentation.Hsva) { // Sweep hue newRgbColor = Uwp.Helpers.ColorHelper.FromHsv( MathEx.Clamp(channelValue, 0.0, 360.0), baseHsvColor.S, baseHsvColor.V, baseHsvColor.A); } else { // Sweep red newRgbColor = new Color { R = Convert.ToByte(MathEx.Clamp(channelValue, 0.0, 255.0)), G = baseRgbColor.G, B = baseRgbColor.B, A = baseRgbColor.A }; } break; } case ColorChannel.Channel2: { if (colorRepresentation == ColorRepresentation.Hsva) { // Sweep saturation newRgbColor = Uwp.Helpers.ColorHelper.FromHsv( baseHsvColor.H, MathEx.Clamp(channelValue, 0.0, 1.0), baseHsvColor.V, baseHsvColor.A); } else { // Sweep green newRgbColor = new Color { R = baseRgbColor.R, G = Convert.ToByte(MathEx.Clamp(channelValue, 0.0, 255.0)), B = baseRgbColor.B, A = baseRgbColor.A }; } break; } case ColorChannel.Channel3: { if (colorRepresentation == ColorRepresentation.Hsva) { // Sweep value newRgbColor = Uwp.Helpers.ColorHelper.FromHsv( baseHsvColor.H, baseHsvColor.S, MathEx.Clamp(channelValue, 0.0, 1.0), baseHsvColor.A); } else { // Sweep blue newRgbColor = new Color { R = baseRgbColor.R, G = baseRgbColor.G, B = Convert.ToByte(MathEx.Clamp(channelValue, 0.0, 255.0)), A = baseRgbColor.A }; } break; } case ColorChannel.Alpha: { if (colorRepresentation == ColorRepresentation.Hsva) { // Sweep alpha newRgbColor = Uwp.Helpers.ColorHelper.FromHsv( baseHsvColor.H, baseHsvColor.S, baseHsvColor.V, MathEx.Clamp(channelValue, 0.0, 1.0)); } else { // Sweep alpha newRgbColor = new Color { R = baseRgbColor.R, G = baseRgbColor.G, B = baseRgbColor.B, A = Convert.ToByte(MathEx.Clamp(channelValue, 0.0, 255.0)) }; } break; } } return(newRgbColor); } return(bgraPixelData); }); return(bitmap); }
public Variables(ColorRepresentation initialRepresentation) { ColorRepresentation = initialRepresentation; }