/*
* Copyright (c) 2021 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Modified by Woochan Lee(wc0917.lee@samsung.com)
*/
using System;
namespace Tizen.NUI
{
internal sealed class ColorUtils
{
private const int minAlphaSearchMaxIterations = 10;
private const int minAlphaSearchPrecision = 1;
///
/// Convert the ARGB color to its CIE XYZ representative components.
///
/// The resulting XYZ representation will use the D65 illuminant and the CIE
/// 2° Standard Observer (1931).
///
/// outXyz[0] is X [0 ...95.047)
/// outXyz[1] is Y [0...100)
/// outXyz[2] is Z [0...108.883)
///
/// param color the ARGB color to convert. The alpha component is ignored
/// param outXyz 3-element array which holds the resulting LAB components
///
public static void ColorToXyz(int color, double[] outXyz)
{
RgbToXyz((color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff, outXyz);
}
///
/// Convert RGB components to its CIE XYZ representative components.
///
/// The resulting XYZ representation will use the D65 illuminant and the CIE
/// 2° Standard Observer (1931).
///
/// outXyz[0] is X [0 ...95.047)
/// outXyz[1] is Y [0...100)
/// outXyz[2] is Z [0...108.883)
///
/// r red component value [0..255]
/// g green component value [0..255]
/// b blue component value [0..255]
/// outXyz 3-element array which holds the resulting XYZ components
///
public static void RgbToXyz(int red, int green, int blue, double[] outXyz)
{
if (outXyz.Length != 3)
{
throw new ArgumentException("Array legnth must be 3", nameof(outXyz));
}
double floatRed = red / 255.0;
floatRed = floatRed < 0.04045 ? floatRed / 12.92 : Math.Pow((floatRed + 0.055) / 1.055, 2.4);
double floatGreen = green / 255.0;
floatGreen = floatGreen < 0.04045 ? floatGreen / 12.92 : Math.Pow((floatGreen + 0.055) / 1.055, 2.4);
double floatBlue = blue / 255.0;
floatBlue = floatBlue < 0.04045 ? floatBlue / 12.92 : Math.Pow((floatBlue + 0.055) / 1.055, 2.4);
outXyz[0] = 100 * (floatRed * 0.4124 + floatGreen * 0.3576 + floatBlue * 0.1805);
outXyz[1] = 100 * (floatRed * 0.2126 + floatGreen * 0.7152 + floatBlue * 0.0722);
outXyz[2] = 100 * (floatRed * 0.0193 + floatGreen * 0.1192 + floatBlue * 0.9505);
}
///
/// Returns the luminance of a color as a float between 0.0 and 1.0
/// Defined as the Y component in the XYZ representation of color.
///
public static double CalculateLuminance(int color)
{
double[] result = new double[3];
ColorToXyz(color, result);
// Luminance is the Y component
return result[1] / 100;
}
///
/// Composite two potentially translucent colors over each other and returns the result.
///
public static int CompositeColors(int foreground, int background)
{
int bgAlpha = (background >> 24) & 0xff;
int fgAlpha = (foreground >> 24) & 0xff;
int alpha = CompositeAlpha(fgAlpha, bgAlpha);
int red = CompositeComponent((foreground >> 16) & 0xff, fgAlpha,
(background >> 16) & 0xff, bgAlpha, alpha);
int green = CompositeComponent((foreground >> 8) & 0xff, fgAlpha,
(background >> 8) & 0xff, bgAlpha, alpha);
int blue = CompositeComponent(foreground & 0xff, fgAlpha,
background & 0xff, bgAlpha, alpha);
return ((alpha & 0xff) << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff));
}
///
/// Returns the contrast ratio between foreground and background.
/// background must be opaque.
///
/// Formula defined
/// here.
///
public static double CalculateContrast(int foreground, int background)
{
if (((background >> 24) & 0xff) != 255)
{
throw new ArgumentException("background can not be translucent.");
}
if (((foreground >> 24) & 0xff) < 255)
{
// If the foreground is translucent, composite the foreground over the background
foreground = CompositeColors(foreground, background);
}
double luminance1 = CalculateLuminance(foreground) + 0.05;
double luminance2 = CalculateLuminance(background) + 0.05;
// Now return the lighter luminance divided by the darker luminance
return Math.Max(luminance1, luminance2) / Math.Min(luminance1, luminance2);
}
///
/// Set the alpha component of color to be alpha.
///
public static int SetAlphaComponent(int color, int alpha)
{
if (alpha < 0 || alpha > 255)
{
throw new ArgumentException("alpha must be between 0 and 255.");
}
return (color & 0x00ffffff) | (alpha << 24);
}
///
/// Calculates the minimum alpha value which can be applied to foreground so that would
/// have a contrast value of at least minContrastRatio when compared to
/// background.
///
/// param foreground the foreground color
/// param background the opaque background color
/// param minContrastRatio the minimum contrast ratio
/// return the alpha value in the range 0-255, or -1 if no value could be calculated
///
public static int CalculateMinimumAlpha(int foreground, int background, float minContrastRatio)
{
if (((background >> 24) & 0xff) != 255)
{
throw new ArgumentException("background can not be translucent");
}
// First lets check that a fully opaque foreground has sufficient contrast
int testForeground = SetAlphaComponent(foreground, 255);
double testRatio = CalculateContrast(testForeground, background);
if (testRatio < minContrastRatio)
{
// Fully opaque foreground does not have sufficient contrast, return error
return -1;
}
// Binary search to find a value with the minimum value which provides sufficient contrast
int numIterations = 0;
int minAlpha = 0;
int maxAlpha = 255;
while (numIterations <= minAlphaSearchMaxIterations &&
(maxAlpha - minAlpha) > minAlphaSearchPrecision)
{
int testAlpha = (minAlpha + maxAlpha) / 2;
testForeground = SetAlphaComponent(foreground, testAlpha);
testRatio = CalculateContrast(testForeground, background);
if (testRatio < minContrastRatio)
{
minAlpha = testAlpha;
}
else
{
maxAlpha = testAlpha;
}
numIterations++;
}
// Conservatively return the max of the range of possible alphas, which is known to pass.
return maxAlpha;
}
public static void RgbToHsl(int red, int green, int blue, float[] hsl)
{
float floatRed = red / 255f;
float floatGreen = green / 255f;
float floatBlue = blue / 255f;
float max = Math.Max(floatRed, Math.Max(floatGreen, floatBlue));
float min = Math.Min(floatRed, Math.Min(floatGreen, floatBlue));
float deltaMaxMin = max - min;
float hue, saturation;
float lightness = (max + min) / 2f;
if (max == min)
{
// Monochromatic
hue = saturation = 0f;
}
else
{
if (max == floatRed)
{
hue = ((floatGreen - floatBlue) / deltaMaxMin) % 6f;
}
else if (max == floatGreen)
{
hue = ((floatBlue - floatRed) / deltaMaxMin) + 2f;
}
else
{
hue = ((floatRed - floatGreen) / deltaMaxMin) + 4f;
}
saturation = deltaMaxMin / (1f - Math.Abs(2f * lightness - 1f));
}
hsl[0] = ((hue * 60f) + 360f) % 360f;
hsl[1] = saturation;
hsl[2] = lightness;
}
public static int HslToRgb(float[] hsl)
{
float hue = hsl[0];
float saturation = hsl[1];
float lightness = hsl[2];
float constC = (1f - Math.Abs(2 * lightness - 1f)) * saturation;
float constM = lightness - 0.5f * constC;
float constX = constC * (1f - Math.Abs((hue / 60f % 2f) - 1f));
int hueSegment = (int)hue / 60;
int red = 0, green = 0, blue = 0;
switch (hueSegment)
{
case 0:
red = (int)Math.Round(255 * (constC + constM));
green = (int)Math.Round(255 * (constX + constM));
blue = (int)Math.Round(255 * constM);
break;
case 1:
red = (int)Math.Round(255 * (constX + constM));
green = (int)Math.Round(255 * (constC + constM));
blue = (int)Math.Round(255 * constM);
break;
case 2:
red = (int)Math.Round(255 * constM);
green = (int)Math.Round(255 * (constC + constM));
blue = (int)Math.Round(255 * (constX + constM));
break;
case 3:
red = (int)Math.Round(255 * constM);
green = (int)Math.Round(255 * (constX + constM));
blue = (int)Math.Round(255 * (constC + constM));
break;
case 4:
red = (int)Math.Round(255 * (constX + constM));
green = (int)Math.Round(255 * constM);
blue = (int)Math.Round(255 * (constC + constM));
break;
case 5:
case 6:
red = (int)Math.Round(255 * (constC + constM));
green = (int)Math.Round(255 * constM);
blue = (int)Math.Round(255 * (constX + constM));
break;
}
red = Math.Max(0, Math.Min(255, red));
green = Math.Max(0, Math.Min(255, green));
blue = Math.Max(0, Math.Min(255, blue));
//ARGB Encoding
return (255 << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff));
}
public static uint GetBytesPerPixel(PixelFormat pixelFormat)
{
switch (pixelFormat)
{
case PixelFormat.L8:
case PixelFormat.A8:
{
return 1;
}
case PixelFormat.LA88:
case PixelFormat.RGB565:
case PixelFormat.RGBA4444:
case PixelFormat.RGBA5551:
case PixelFormat.BGR565:
case PixelFormat.BGRA4444:
case PixelFormat.BGRA5551:
{
return 2;
}
case PixelFormat.RGB888:
{
return 3;
}
case PixelFormat.RGB8888:
case PixelFormat.BGR8888:
case PixelFormat.RGBA8888:
case PixelFormat.BGRA8888:
{
return 4;
}
default:
Tizen.Log.Error("Palette", "Invalided PixelFormat(" + pixelFormat + ") has been given \n");
return 0;
}
}
///
/// return luma value according to XYZ color space in the range 0.0 - 1.0
///
private static int CompositeAlpha(int foregroundAlpha, int backgroundAlpha)
{
return 0xFF - (((0xFF - backgroundAlpha) * (0xFF - foregroundAlpha)) / 0xFF);
}
private static int CompositeComponent(int fgC, int fgA, int bgC, int bgA, int alpha)
{
if (alpha == 0) return 0;
return ((0xFF * fgC * fgA) + (bgC * bgA * (0xFF - fgA))) / (alpha * 0xFF);
}
}
}