1 changed files with 388 additions and 0 deletions
@ -0,0 +1,388 @@ |
|||
// This source file is adapted from the WinUI project.
|
|||
// (https://github.com/microsoft/microsoft-ui-xaml)
|
|||
//
|
|||
// Licensed to The Avalonia Project under the MIT License.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Imaging; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Controls.Primitives |
|||
{ |
|||
internal static class ColorHelpers |
|||
{ |
|||
public const int CheckerSize = 4; |
|||
|
|||
public static bool ToDisplayNameExists |
|||
{ |
|||
get => false; |
|||
} |
|||
|
|||
public static string ToDisplayName(Color color) |
|||
{ |
|||
return string.Empty; |
|||
} |
|||
|
|||
public static Hsv IncrementColorChannel( |
|||
Hsv originalHsv, |
|||
ColorPickerHsvChannel channel, |
|||
IncrementDirection direction, |
|||
IncrementAmount amount, |
|||
bool shouldWrap, |
|||
double minBound, |
|||
double maxBound) |
|||
{ |
|||
Hsv newHsv = originalHsv; |
|||
|
|||
if (amount == IncrementAmount.Small || !ToDisplayNameExists) |
|||
{ |
|||
// In order to avoid working with small values that can incur rounding issues,
|
|||
// we'll multiple saturation and value by 100 to put them in the range of 0-100 instead of 0-1.
|
|||
newHsv.S *= 100; |
|||
newHsv.V *= 100; |
|||
|
|||
// Note: *valueToIncrement replaced with ref local variable for C#, must be initialized
|
|||
ref double valueToIncrement = ref newHsv.H; |
|||
double incrementAmount = 0.0; |
|||
|
|||
// If we're adding a small increment, then we'll just add or subtract 1.
|
|||
// If we're adding a large increment, then we want to snap to the next
|
|||
// or previous major value - for hue, this is every increment of 30;
|
|||
// for saturation and value, this is every increment of 10.
|
|||
switch (channel) |
|||
{ |
|||
case ColorPickerHsvChannel.Hue: |
|||
valueToIncrement = ref newHsv.H; |
|||
incrementAmount = amount == IncrementAmount.Small ? 1 : 30; |
|||
break; |
|||
|
|||
case ColorPickerHsvChannel.Saturation: |
|||
valueToIncrement = ref newHsv.S; |
|||
incrementAmount = amount == IncrementAmount.Small ? 1 : 10; |
|||
break; |
|||
|
|||
case ColorPickerHsvChannel.Value: |
|||
valueToIncrement = ref newHsv.V; |
|||
incrementAmount = amount == IncrementAmount.Small ? 1 : 10; |
|||
break; |
|||
|
|||
default: |
|||
throw new InvalidOperationException("Invalid ColorPickerHsvChannel."); |
|||
} |
|||
|
|||
double previousValue = valueToIncrement; |
|||
|
|||
valueToIncrement += (direction == IncrementDirection.Lower ? -incrementAmount : incrementAmount); |
|||
|
|||
// If the value has reached outside the bounds, we were previous at the boundary, and we should wrap,
|
|||
// then we'll place the selection on the other side of the spectrum.
|
|||
// Otherwise, we'll place it on the boundary that was exceeded.
|
|||
if (valueToIncrement < minBound) |
|||
{ |
|||
valueToIncrement = (shouldWrap && previousValue == minBound) ? maxBound : minBound; |
|||
} |
|||
|
|||
if (valueToIncrement > maxBound) |
|||
{ |
|||
valueToIncrement = (shouldWrap && previousValue == maxBound) ? minBound : maxBound; |
|||
} |
|||
|
|||
// We multiplied saturation and value by 100 previously, so now we want to put them back in the 0-1 range.
|
|||
newHsv.S /= 100; |
|||
newHsv.V /= 100; |
|||
} |
|||
else |
|||
{ |
|||
// While working with named colors, we're going to need to be working in actual HSV units,
|
|||
// so we'll divide the min bound and max bound by 100 in the case of saturation or value,
|
|||
// since we'll have received units between 0-100 and we need them within 0-1.
|
|||
if (channel == ColorPickerHsvChannel.Saturation || |
|||
channel == ColorPickerHsvChannel.Value) |
|||
{ |
|||
minBound /= 100; |
|||
maxBound /= 100; |
|||
} |
|||
|
|||
newHsv = FindNextNamedColor(originalHsv, channel, direction, shouldWrap, minBound, maxBound); |
|||
} |
|||
|
|||
return newHsv; |
|||
} |
|||
|
|||
public static Hsv FindNextNamedColor( |
|||
Hsv originalHsv, |
|||
ColorPickerHsvChannel channel, |
|||
IncrementDirection direction, |
|||
bool shouldWrap, |
|||
double minBound, |
|||
double maxBound) |
|||
{ |
|||
// There's no easy way to directly get the next named color, so what we'll do
|
|||
// is just iterate in the direction that we want to find it until we find a color
|
|||
// in that direction that has a color name different than our current color name.
|
|||
// Once we find a new color name, then we'll iterate across that color name until
|
|||
// we find its bounds on the other side, and then select the color that is exactly
|
|||
// in the middle of that color's bounds.
|
|||
Hsv newHsv = originalHsv; |
|||
|
|||
string originalColorName = ColorHelpers.ToDisplayName(originalHsv.ToRgb().ToColor()); |
|||
string newColorName = originalColorName; |
|||
|
|||
// Note: *newValue replaced with ref local variable for C#, must be initialized
|
|||
double originalValue = 0.0; |
|||
ref double newValue = ref newHsv.H; |
|||
double incrementAmount = 0.0; |
|||
|
|||
switch (channel) |
|||
{ |
|||
case ColorPickerHsvChannel.Hue: |
|||
originalValue = originalHsv.H; |
|||
newValue = ref newHsv.H; |
|||
incrementAmount = 1; |
|||
break; |
|||
|
|||
case ColorPickerHsvChannel.Saturation: |
|||
originalValue = originalHsv.S; |
|||
newValue = ref newHsv.S; |
|||
incrementAmount = 0.01; |
|||
break; |
|||
|
|||
case ColorPickerHsvChannel.Value: |
|||
originalValue = originalHsv.V; |
|||
newValue = ref newHsv.V; |
|||
incrementAmount = 0.01; |
|||
break; |
|||
|
|||
default: |
|||
throw new InvalidOperationException("Invalid ColorPickerHsvChannel."); |
|||
} |
|||
|
|||
bool shouldFindMidPoint = true; |
|||
|
|||
while (newColorName == originalColorName) |
|||
{ |
|||
double previousValue = newValue; |
|||
newValue += (direction == IncrementDirection.Lower ? -1 : 1) * incrementAmount; |
|||
|
|||
bool justWrapped = false; |
|||
|
|||
// If we've hit a boundary, then either we should wrap or we shouldn't.
|
|||
// If we should, then we'll perform that wrapping if we were previously up against
|
|||
// the boundary that we've now hit. Otherwise, we'll stop at that boundary.
|
|||
if (newValue > maxBound) |
|||
{ |
|||
if (shouldWrap) |
|||
{ |
|||
newValue = minBound; |
|||
justWrapped = true; |
|||
} |
|||
else |
|||
{ |
|||
newValue = maxBound; |
|||
shouldFindMidPoint = false; |
|||
newColorName = ColorHelpers.ToDisplayName(newHsv.ToRgb().ToColor()); |
|||
break; |
|||
} |
|||
} |
|||
else if (newValue < minBound) |
|||
{ |
|||
if (shouldWrap) |
|||
{ |
|||
newValue = maxBound; |
|||
justWrapped = true; |
|||
} |
|||
else |
|||
{ |
|||
newValue = minBound; |
|||
shouldFindMidPoint = false; |
|||
newColorName = ColorHelpers.ToDisplayName(newHsv.ToRgb().ToColor()); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (!justWrapped && |
|||
previousValue != originalValue && |
|||
Math.Sign(newValue - originalValue) != Math.Sign(previousValue - originalValue)) |
|||
{ |
|||
// If we've wrapped all the way back to the start and have failed to find a new color name,
|
|||
// then we'll just quit - there isn't a new color name that we're going to find.
|
|||
shouldFindMidPoint = false; |
|||
break; |
|||
} |
|||
|
|||
newColorName = ColorHelpers.ToDisplayName(newHsv.ToRgb().ToColor()); |
|||
} |
|||
|
|||
if (shouldFindMidPoint) |
|||
{ |
|||
Hsv startHsv = newHsv; |
|||
Hsv currentHsv = startHsv; |
|||
double startEndOffset = 0; |
|||
string currentColorName = newColorName; |
|||
|
|||
// Note: *startValue/*currentValue replaced with ref local variables for C#, must be initialized
|
|||
ref double startValue = ref startHsv.H; |
|||
ref double currentValue = ref currentHsv.H; |
|||
double wrapIncrement = 0; |
|||
|
|||
switch (channel) |
|||
{ |
|||
case ColorPickerHsvChannel.Hue: |
|||
startValue = ref startHsv.H; |
|||
currentValue = ref currentHsv.H; |
|||
wrapIncrement = 360.0; |
|||
break; |
|||
|
|||
case ColorPickerHsvChannel.Saturation: |
|||
startValue = ref startHsv.S; |
|||
currentValue = ref currentHsv.S; |
|||
wrapIncrement = 1.0; |
|||
break; |
|||
|
|||
case ColorPickerHsvChannel.Value: |
|||
startValue = ref startHsv.V; |
|||
currentValue = ref currentHsv.V; |
|||
wrapIncrement = 1.0; |
|||
break; |
|||
|
|||
default: |
|||
throw new InvalidOperationException("Invalid ColorPickerHsvChannel."); |
|||
} |
|||
|
|||
while (newColorName == currentColorName) |
|||
{ |
|||
currentValue += (direction == IncrementDirection.Lower ? -1 : 1) * incrementAmount; |
|||
|
|||
// If we've hit a boundary, then either we should wrap or we shouldn't.
|
|||
// If we should, then we'll perform that wrapping if we were previously up against
|
|||
// the boundary that we've now hit. Otherwise, we'll stop at that boundary.
|
|||
if (currentValue > maxBound) |
|||
{ |
|||
if (shouldWrap) |
|||
{ |
|||
currentValue = minBound; |
|||
startEndOffset = maxBound - minBound; |
|||
} |
|||
else |
|||
{ |
|||
currentValue = maxBound; |
|||
break; |
|||
} |
|||
} |
|||
else if (currentValue < minBound) |
|||
{ |
|||
if (shouldWrap) |
|||
{ |
|||
currentValue = maxBound; |
|||
startEndOffset = minBound - maxBound; |
|||
} |
|||
else |
|||
{ |
|||
currentValue = minBound; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
currentColorName = ColorHelpers.ToDisplayName(currentHsv.ToRgb().ToColor()); |
|||
} |
|||
|
|||
newValue = (startValue + currentValue + startEndOffset) / 2; |
|||
|
|||
// Dividing by 2 may have gotten us halfway through a single step, so we'll
|
|||
// remove that half-step if it exists.
|
|||
double leftoverValue = Math.Abs(newValue); |
|||
|
|||
while (leftoverValue > incrementAmount) |
|||
{ |
|||
leftoverValue -= incrementAmount; |
|||
} |
|||
|
|||
newValue -= leftoverValue; |
|||
|
|||
while (newValue < minBound) |
|||
{ |
|||
newValue += wrapIncrement; |
|||
} |
|||
|
|||
while (newValue > maxBound) |
|||
{ |
|||
newValue -= wrapIncrement; |
|||
} |
|||
} |
|||
|
|||
return newHsv; |
|||
} |
|||
|
|||
public static double IncrementAlphaChannel( |
|||
double originalAlpha, |
|||
IncrementDirection direction, |
|||
IncrementAmount amount, |
|||
bool shouldWrap, |
|||
double minBound, |
|||
double maxBound) |
|||
{ |
|||
// In order to avoid working with small values that can incur rounding issues,
|
|||
// we'll multiple alpha by 100 to put it in the range of 0-100 instead of 0-1.
|
|||
originalAlpha *= 100; |
|||
|
|||
const double smallIncrementAmount = 1; |
|||
const double largeIncrementAmount = 10; |
|||
|
|||
if (amount == IncrementAmount.Small) |
|||
{ |
|||
originalAlpha += (direction == IncrementDirection.Lower ? -1 : 1) * smallIncrementAmount; |
|||
} |
|||
else |
|||
{ |
|||
if (direction == IncrementDirection.Lower) |
|||
{ |
|||
originalAlpha = Math.Ceiling((originalAlpha - largeIncrementAmount) / largeIncrementAmount) * largeIncrementAmount; |
|||
} |
|||
else |
|||
{ |
|||
originalAlpha = Math.Floor((originalAlpha + largeIncrementAmount) / largeIncrementAmount) * largeIncrementAmount; |
|||
} |
|||
} |
|||
|
|||
// If the value has reached outside the bounds and we should wrap, then we'll place the selection
|
|||
// on the other side of the spectrum. Otherwise, we'll place it on the boundary that was exceeded.
|
|||
if (originalAlpha < minBound) |
|||
{ |
|||
originalAlpha = shouldWrap ? maxBound : minBound; |
|||
} |
|||
|
|||
if (originalAlpha > maxBound) |
|||
{ |
|||
originalAlpha = shouldWrap ? minBound : maxBound; |
|||
} |
|||
|
|||
// We multiplied alpha by 100 previously, so now we want to put it back in the 0-1 range.
|
|||
return originalAlpha / 100; |
|||
} |
|||
|
|||
public static WriteableBitmap CreateBitmapFromPixelData( |
|||
int pixelWidth, |
|||
int pixelHeight, |
|||
List<byte> bgraPixelData) |
|||
{ |
|||
Vector dpi = new Vector(96, 96); // Standard may need to change on some devices
|
|||
|
|||
WriteableBitmap bitmap = new WriteableBitmap( |
|||
new PixelSize(pixelWidth, pixelHeight), |
|||
dpi, |
|||
PixelFormat.Bgra8888, |
|||
AlphaFormat.Premul); |
|||
|
|||
// Warning: This is highly questionable
|
|||
using (var frameBuffer = bitmap.Lock()) |
|||
{ |
|||
Marshal.Copy(bgraPixelData.ToArray(), 0, frameBuffer.Address, bgraPixelData.Count); |
|||
} |
|||
|
|||
return bitmap; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue