|
|
|
|
@@ -17,19 +17,15 @@
|
|
|
|
|
package android.graphics;
|
|
|
|
|
|
|
|
|
|
import android.annotation.AnyThread;
|
|
|
|
|
import android.annotation.ColorInt;
|
|
|
|
|
import android.annotation.IntRange;
|
|
|
|
|
import android.annotation.NonNull;
|
|
|
|
|
import android.annotation.Nullable;
|
|
|
|
|
import android.annotation.Size;
|
|
|
|
|
import android.annotation.SuppressAutoDoc;
|
|
|
|
|
import android.util.Pair;
|
|
|
|
|
|
|
|
|
|
import libcore.util.NativeAllocationRegistry;
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.function.DoubleUnaryOperator;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -872,8 +868,7 @@ public abstract class ColorSpace {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @hide */
|
|
|
|
|
ColorSpace(
|
|
|
|
|
/*package*/ ColorSpace(
|
|
|
|
|
@NonNull String name,
|
|
|
|
|
@NonNull Model model,
|
|
|
|
|
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
|
|
|
|
|
@@ -1441,22 +1436,6 @@ public abstract class ColorSpace {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* <p>Creates a new {@link Renderer} that can be used to visualize and
|
|
|
|
|
* debug color spaces. See the documentation of {@link Renderer} for
|
|
|
|
|
* more information.</p>
|
|
|
|
|
*
|
|
|
|
|
* @return A new non-null {@link Renderer} instance
|
|
|
|
|
*
|
|
|
|
|
* @see Renderer
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
@NonNull
|
|
|
|
|
public static Renderer createRenderer() {
|
|
|
|
|
return new Renderer();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static {
|
|
|
|
|
sNamedColorSpaces[Named.SRGB.ordinal()] = new ColorSpace.Rgb(
|
|
|
|
|
"sRGB IEC61966-2.1",
|
|
|
|
|
@@ -1701,12 +1680,10 @@ public abstract class ColorSpace {
|
|
|
|
|
* @param rhs 3x3 matrix, as a non-null array of 9 floats
|
|
|
|
|
* @return A new array of 9 floats containing the result of the multiplication
|
|
|
|
|
* of rhs by lhs
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
@NonNull
|
|
|
|
|
@Size(9)
|
|
|
|
|
public static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) {
|
|
|
|
|
private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) {
|
|
|
|
|
float[] r = new float[9];
|
|
|
|
|
r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2];
|
|
|
|
|
r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2];
|
|
|
|
|
@@ -1774,28 +1751,6 @@ public abstract class ColorSpace {
|
|
|
|
|
return new float[] { xyY[0] / xyY[1], 1.0f, (1 - xyY[0] - xyY[1]) / xyY[1] };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts values from CIE xyY to CIE L*u*v*. Y is assumed to be 1 so the
|
|
|
|
|
* input xyY array only contains the x and y components. After this method
|
|
|
|
|
* returns, the xyY array contains the converted u and v components.
|
|
|
|
|
*
|
|
|
|
|
* @param xyY The xyY value to convert to XYZ, cannot be null,
|
|
|
|
|
* length must be a multiple of 2
|
|
|
|
|
*/
|
|
|
|
|
private static void xyYToUv(@NonNull @Size(multiple = 2) float[] xyY) {
|
|
|
|
|
for (int i = 0; i < xyY.length; i += 2) {
|
|
|
|
|
float x = xyY[i];
|
|
|
|
|
float y = xyY[i + 1];
|
|
|
|
|
|
|
|
|
|
float d = -2.0f * x + 12.0f * y + 3;
|
|
|
|
|
float u = (4.0f * x) / d;
|
|
|
|
|
float v = (9.0f * y) / d;
|
|
|
|
|
|
|
|
|
|
xyY[i] = u;
|
|
|
|
|
xyY[i + 1] = v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* <p>Computes the chromatic adaptation transform from the specified
|
|
|
|
|
* source white point to the specified destination white point.</p>
|
|
|
|
|
@@ -1834,8 +1789,6 @@ public abstract class ColorSpace {
|
|
|
|
|
* @param cct The correlated color temperature, in Kelvin
|
|
|
|
|
* @return Corresponding XYZ values
|
|
|
|
|
* @throws IllegalArgumentException If cct is invalid
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
@NonNull
|
|
|
|
|
@Size(3)
|
|
|
|
|
@@ -1860,38 +1813,6 @@ public abstract class ColorSpace {
|
|
|
|
|
return xyYToXyz(new float[] {x, y});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* <p>Computes the chromaticity coordinates of a CIE series D illuminant
|
|
|
|
|
* from the specified correlated color temperature (CCT). The specified CCT
|
|
|
|
|
* must be greater than 0. A meaningful CCT range is [4000, 25000].</p>
|
|
|
|
|
*
|
|
|
|
|
* <p>The transform is computed using the methods referred to in Kang et
|
|
|
|
|
* al., <i>Design of Advanced Color - Temperature Control System for HDTV
|
|
|
|
|
* Applications</i>, Journal of Korean Physical Society 41, 865-871
|
|
|
|
|
* (2002).</p>
|
|
|
|
|
*
|
|
|
|
|
* @param cct The correlated color temperature, in Kelvin
|
|
|
|
|
* @return Corresponding XYZ values
|
|
|
|
|
* @throws IllegalArgumentException If cct is invalid
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
@NonNull
|
|
|
|
|
@Size(3)
|
|
|
|
|
public static float[] cctToIlluminantdXyz(@IntRange(from = 1) int cct) {
|
|
|
|
|
if (cct < 1) {
|
|
|
|
|
throw new IllegalArgumentException("Temperature must be greater than 0");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final float icct = 1.0f / cct;
|
|
|
|
|
final float icct2 = icct * icct;
|
|
|
|
|
final float x = cct <= 7000.0f ?
|
|
|
|
|
0.244063f + 0.09911e3f * icct + 2.9678e6f * icct2 - 4.6070e9f * icct2 * icct :
|
|
|
|
|
0.237040f + 0.24748e3f * icct + 1.9018e6f * icct2 - 2.0064e9f * icct2 * icct;
|
|
|
|
|
final float y = -3.0f * x * x + 2.87f * x - 0.275f;
|
|
|
|
|
return xyYToXyz(new float[] {x, y});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* <p>Computes the chromatic adaptation transform from the specified
|
|
|
|
|
* source white point to the specified destination white point.</p>
|
|
|
|
|
@@ -1905,14 +1826,16 @@ public abstract class ColorSpace {
|
|
|
|
|
* @param srcWhitePoint The white point to adapt from
|
|
|
|
|
* @param dstWhitePoint The white point to adapt to
|
|
|
|
|
* @return A 3x3 matrix as a non-null array of 9 floats
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
@NonNull
|
|
|
|
|
@Size(9)
|
|
|
|
|
public static float[] chromaticAdaptation(@NonNull Adaptation adaptation,
|
|
|
|
|
@NonNull @Size(min = 2, max = 3) float[] srcWhitePoint,
|
|
|
|
|
@NonNull @Size(min = 2, max = 3) float[] dstWhitePoint) {
|
|
|
|
|
if ((srcWhitePoint.length != 2 && srcWhitePoint.length != 3)
|
|
|
|
|
|| (dstWhitePoint.length != 2 && dstWhitePoint.length != 3)) {
|
|
|
|
|
throw new IllegalArgumentException("A white point array must have 2 or 3 floats");
|
|
|
|
|
}
|
|
|
|
|
float[] srcXyz = srcWhitePoint.length == 3 ?
|
|
|
|
|
Arrays.copyOf(srcWhitePoint, 3) : xyYToXyz(srcWhitePoint);
|
|
|
|
|
float[] dstXyz = dstWhitePoint.length == 3 ?
|
|
|
|
|
@@ -3925,771 +3848,4 @@ public abstract class ColorSpace {
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* <p>A color space renderer can be used to visualize and compare the gamut and
|
|
|
|
|
* white point of one or more color spaces. The output is an sRGB {@link Bitmap}
|
|
|
|
|
* showing a CIE 1931 xyY or a CIE 1976 UCS chromaticity diagram.</p>
|
|
|
|
|
*
|
|
|
|
|
* <p>The following code snippet shows how to compare the {@link Named#SRGB}
|
|
|
|
|
* and {@link Named#DCI_P3} color spaces in a CIE 1931 diagram:</p>
|
|
|
|
|
*
|
|
|
|
|
* <pre class="prettyprint">
|
|
|
|
|
* Bitmap bitmap = ColorSpace.createRenderer()
|
|
|
|
|
* .size(768)
|
|
|
|
|
* .clip(true)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
|
|
|
|
|
* .render();
|
|
|
|
|
* </pre>
|
|
|
|
|
* <p>
|
|
|
|
|
* <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" />
|
|
|
|
|
* <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption>
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* <p>A renderer can also be used to show the location of specific colors,
|
|
|
|
|
* associated with a color space, in the CIE 1931 xyY chromaticity diagram.
|
|
|
|
|
* See {@link #add(ColorSpace, float, float, float, int)} for more information.</p>
|
|
|
|
|
*
|
|
|
|
|
* @see ColorSpace#createRenderer()
|
|
|
|
|
*
|
|
|
|
|
* @hide
|
|
|
|
|
*/
|
|
|
|
|
public static class Renderer {
|
|
|
|
|
private static final int NATIVE_SIZE = 1440;
|
|
|
|
|
private static final float UCS_SCALE = 9.0f / 6.0f;
|
|
|
|
|
|
|
|
|
|
// Number of subdivision of the inside of the spectral locus
|
|
|
|
|
private static final int CHROMATICITY_RESOLUTION = 32;
|
|
|
|
|
private static final double ONE_THIRD = 1.0 / 3.0;
|
|
|
|
|
|
|
|
|
|
@IntRange(from = 128, to = Integer.MAX_VALUE)
|
|
|
|
|
private int mSize = 1024;
|
|
|
|
|
|
|
|
|
|
private boolean mShowWhitePoint = true;
|
|
|
|
|
private boolean mClip = false;
|
|
|
|
|
private boolean mUcs = false;
|
|
|
|
|
|
|
|
|
|
private final List<Pair<ColorSpace, Integer>> mColorSpaces = new ArrayList<>(2);
|
|
|
|
|
private final List<Point> mPoints = new ArrayList<>(0);
|
|
|
|
|
|
|
|
|
|
private Renderer() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* <p>Defines whether the chromaticity diagram should be clipped by the first
|
|
|
|
|
* registered color space. The default value is false.</p>
|
|
|
|
|
*
|
|
|
|
|
* <p>The following code snippet and image show the default behavior:</p>
|
|
|
|
|
* <pre class="prettyprint">
|
|
|
|
|
* Bitmap bitmap = ColorSpace.createRenderer()
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
|
|
|
|
|
* .render();
|
|
|
|
|
* </pre>
|
|
|
|
|
* <p>
|
|
|
|
|
* <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" />
|
|
|
|
|
* <figcaption style="text-align: center;">Clipping disabled</figcaption>
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* <p>Here is the same example with clipping enabled:</p>
|
|
|
|
|
* <pre class="prettyprint">
|
|
|
|
|
* Bitmap bitmap = ColorSpace.createRenderer()
|
|
|
|
|
* .clip(true)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
|
|
|
|
|
* .render();
|
|
|
|
|
* </pre>
|
|
|
|
|
* <p>
|
|
|
|
|
* <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_clipped.png" />
|
|
|
|
|
* <figcaption style="text-align: center;">Clipping enabled</figcaption>
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param clip True to clip the chromaticity diagram to the first registered color space,
|
|
|
|
|
* false otherwise
|
|
|
|
|
* @return This instance of {@link Renderer}
|
|
|
|
|
*/
|
|
|
|
|
@NonNull
|
|
|
|
|
public Renderer clip(boolean clip) {
|
|
|
|
|
mClip = clip;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* <p>Defines whether the chromaticity diagram should use the uniform
|
|
|
|
|
* chromaticity scale (CIE 1976 UCS). When the uniform chromaticity scale
|
|
|
|
|
* is used, the distance between two points on the diagram is approximately
|
|
|
|
|
* proportional to the perceived color difference.</p>
|
|
|
|
|
*
|
|
|
|
|
* <p>The following code snippet shows how to enable the uniform chromaticity
|
|
|
|
|
* scale. The image below shows the result:</p>
|
|
|
|
|
* <pre class="prettyprint">
|
|
|
|
|
* Bitmap bitmap = ColorSpace.createRenderer()
|
|
|
|
|
* .uniformChromaticityScale(true)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
|
|
|
|
|
* .render();
|
|
|
|
|
* </pre>
|
|
|
|
|
* <p>
|
|
|
|
|
* <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_ucs.png" />
|
|
|
|
|
* <figcaption style="text-align: center;">CIE 1976 UCS diagram</figcaption>
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param ucs True to render the chromaticity diagram as the CIE 1976 UCS diagram
|
|
|
|
|
* @return This instance of {@link Renderer}
|
|
|
|
|
*/
|
|
|
|
|
@NonNull
|
|
|
|
|
public Renderer uniformChromaticityScale(boolean ucs) {
|
|
|
|
|
mUcs = ucs;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets the dimensions (width and height) in pixels of the output bitmap.
|
|
|
|
|
* The size must be at least 128px and defaults to 1024px.
|
|
|
|
|
*
|
|
|
|
|
* @param size The size in pixels of the output bitmap
|
|
|
|
|
* @return This instance of {@link Renderer}
|
|
|
|
|
*/
|
|
|
|
|
@NonNull
|
|
|
|
|
public Renderer size(@IntRange(from = 128, to = Integer.MAX_VALUE) int size) {
|
|
|
|
|
mSize = Math.max(128, size);
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Shows or hides the white point of each color space in the output bitmap.
|
|
|
|
|
* The default is true.
|
|
|
|
|
*
|
|
|
|
|
* @param show True to show the white point of each color space, false
|
|
|
|
|
* otherwise
|
|
|
|
|
* @return This instance of {@link Renderer}
|
|
|
|
|
*/
|
|
|
|
|
@NonNull
|
|
|
|
|
public Renderer showWhitePoint(boolean show) {
|
|
|
|
|
mShowWhitePoint = show;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* <p>Adds a color space to represent on the output CIE 1931 chromaticity
|
|
|
|
|
* diagram. The color space is represented as a triangle showing the
|
|
|
|
|
* footprint of its color gamut and, optionally, the location of its
|
|
|
|
|
* white point.</p>
|
|
|
|
|
*
|
|
|
|
|
* <p class="note">Color spaces with a color model that is not RGB are
|
|
|
|
|
* accepted but ignored.</p>
|
|
|
|
|
*
|
|
|
|
|
* <p>The following code snippet and image show an example of calling this
|
|
|
|
|
* method to compare {@link Named#SRGB sRGB} and {@link Named#DCI_P3 DCI-P3}:</p>
|
|
|
|
|
* <pre class="prettyprint">
|
|
|
|
|
* Bitmap bitmap = ColorSpace.createRenderer()
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
|
|
|
|
|
* .render();
|
|
|
|
|
* </pre>
|
|
|
|
|
* <p>
|
|
|
|
|
* <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison.png" />
|
|
|
|
|
* <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption>
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* <p>Adding a color space extending beyond the boundaries of the
|
|
|
|
|
* spectral locus will alter the size of the diagram within the output
|
|
|
|
|
* bitmap as shown in this example:</p>
|
|
|
|
|
* <pre class="prettyprint">
|
|
|
|
|
* Bitmap bitmap = ColorSpace.createRenderer()
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.ACES), 0xff097ae9)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), 0xff000000)
|
|
|
|
|
* .render();
|
|
|
|
|
* </pre>
|
|
|
|
|
* <p>
|
|
|
|
|
* <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_comparison2.png" />
|
|
|
|
|
* <figcaption style="text-align: center;">sRGB, DCI-P3, ACES and scRGB</figcaption>
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param colorSpace The color space whose gamut to render on the diagram
|
|
|
|
|
* @param color The sRGB color to use to render the color space's gamut and white point
|
|
|
|
|
* @return This instance of {@link Renderer}
|
|
|
|
|
*
|
|
|
|
|
* @see #clip(boolean)
|
|
|
|
|
* @see #showWhitePoint(boolean)
|
|
|
|
|
*/
|
|
|
|
|
@NonNull
|
|
|
|
|
public Renderer add(@NonNull ColorSpace colorSpace, @ColorInt int color) {
|
|
|
|
|
mColorSpaces.add(new Pair<>(colorSpace, color));
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* <p>Adds a color to represent as a point on the chromaticity diagram.
|
|
|
|
|
* The color is associated with a color space which will be used to
|
|
|
|
|
* perform the conversion to CIE XYZ and compute the location of the point
|
|
|
|
|
* on the diagram. The point is rendered as a colored circle.</p>
|
|
|
|
|
*
|
|
|
|
|
* <p>The following code snippet and image show an example of calling this
|
|
|
|
|
* method to render the location of several sRGB colors as white circles:</p>
|
|
|
|
|
* <pre class="prettyprint">
|
|
|
|
|
* Bitmap bitmap = ColorSpace.createRenderer()
|
|
|
|
|
* .clip(true)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.0f, 0.1f, 0xffffffff)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.1f, 0.1f, 0xffffffff)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.2f, 0.1f, 0xffffffff)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.3f, 0.1f, 0xffffffff)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.4f, 0.1f, 0xffffffff)
|
|
|
|
|
* .add(ColorSpace.get(ColorSpace.Named.SRGB), 0.1f, 0.5f, 0.1f, 0xffffffff)
|
|
|
|
|
* .render();
|
|
|
|
|
* </pre>
|
|
|
|
|
* <p>
|
|
|
|
|
* <img style="display: block; margin: 0 auto;" src="{@docRoot}reference/android/images/graphics/colorspace_points.png" />
|
|
|
|
|
* <figcaption style="text-align: center;">
|
|
|
|
|
* Locating colors on the chromaticity diagram
|
|
|
|
|
* </figcaption>
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param colorSpace The color space of the color to locate on the diagram
|
|
|
|
|
* @param r The first component of the color to locate on the diagram
|
|
|
|
|
* @param g The second component of the color to locate on the diagram
|
|
|
|
|
* @param b The third component of the color to locate on the diagram
|
|
|
|
|
* @param pointColor The sRGB color to use to render the point on the diagram
|
|
|
|
|
* @return This instance of {@link Renderer}
|
|
|
|
|
*/
|
|
|
|
|
@NonNull
|
|
|
|
|
public Renderer add(@NonNull ColorSpace colorSpace, float r, float g, float b,
|
|
|
|
|
@ColorInt int pointColor) {
|
|
|
|
|
mPoints.add(new Point(colorSpace, new float[] { r, g, b }, pointColor));
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* <p>Renders the {@link #add(ColorSpace, int) color spaces} and
|
|
|
|
|
* {@link #add(ColorSpace, float, float, float, int) points} registered
|
|
|
|
|
* with this renderer. The output bitmap is an sRGB image with the
|
|
|
|
|
* dimensions specified by calling {@link #size(int)} (1204x1024px by
|
|
|
|
|
* default).</p>
|
|
|
|
|
*
|
|
|
|
|
* @return A new non-null {@link Bitmap} with the dimensions specified
|
|
|
|
|
* by {@link #size(int)} (1024x1024 by default)
|
|
|
|
|
*/
|
|
|
|
|
@NonNull
|
|
|
|
|
public Bitmap render() {
|
|
|
|
|
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
|
|
|
Bitmap bitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ARGB_8888);
|
|
|
|
|
Canvas canvas = new Canvas(bitmap);
|
|
|
|
|
|
|
|
|
|
float[] primaries = new float[6];
|
|
|
|
|
float[] whitePoint = new float[2];
|
|
|
|
|
|
|
|
|
|
int width = NATIVE_SIZE;
|
|
|
|
|
int height = NATIVE_SIZE;
|
|
|
|
|
|
|
|
|
|
Path path = new Path();
|
|
|
|
|
|
|
|
|
|
setTransform(canvas, width, height, primaries);
|
|
|
|
|
drawBox(canvas, width, height, paint, path);
|
|
|
|
|
setUcsTransform(canvas, height);
|
|
|
|
|
drawLocus(canvas, width, height, paint, path, primaries);
|
|
|
|
|
drawGamuts(canvas, width, height, paint, path, primaries, whitePoint);
|
|
|
|
|
drawPoints(canvas, width, height, paint);
|
|
|
|
|
|
|
|
|
|
return bitmap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Draws registered points at their correct position in the xyY coordinates.
|
|
|
|
|
* Each point is positioned according to its associated color space.
|
|
|
|
|
*
|
|
|
|
|
* @param canvas The canvas to transform
|
|
|
|
|
* @param width Width in pixel of the final image
|
|
|
|
|
* @param height Height in pixel of the final image
|
|
|
|
|
* @param paint A pre-allocated paint used to avoid temporary allocations
|
|
|
|
|
*/
|
|
|
|
|
private void drawPoints(@NonNull Canvas canvas, int width, int height,
|
|
|
|
|
@NonNull Paint paint) {
|
|
|
|
|
|
|
|
|
|
paint.setStyle(Paint.Style.FILL);
|
|
|
|
|
|
|
|
|
|
float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f);
|
|
|
|
|
|
|
|
|
|
float[] v = new float[3];
|
|
|
|
|
float[] xy = new float[2];
|
|
|
|
|
|
|
|
|
|
for (final Point point : mPoints) {
|
|
|
|
|
v[0] = point.mRgb[0];
|
|
|
|
|
v[1] = point.mRgb[1];
|
|
|
|
|
v[2] = point.mRgb[2];
|
|
|
|
|
point.mColorSpace.toXyz(v);
|
|
|
|
|
|
|
|
|
|
paint.setColor(point.mColor);
|
|
|
|
|
|
|
|
|
|
// XYZ to xyY, assuming Y=1.0, then to L*u*v* if needed
|
|
|
|
|
float sum = v[0] + v[1] + v[2];
|
|
|
|
|
xy[0] = v[0] / sum;
|
|
|
|
|
xy[1] = v[1] / sum;
|
|
|
|
|
if (mUcs) xyYToUv(xy);
|
|
|
|
|
|
|
|
|
|
canvas.drawCircle(width * xy[0], height - height * xy[1], radius, paint);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Draws the color gamuts and white points of all the registered color
|
|
|
|
|
* spaces. Only color spaces with an RGB color model are rendered, the
|
|
|
|
|
* others are ignored.
|
|
|
|
|
*
|
|
|
|
|
* @param canvas The canvas to transform
|
|
|
|
|
* @param width Width in pixel of the final image
|
|
|
|
|
* @param height Height in pixel of the final image
|
|
|
|
|
* @param paint A pre-allocated paint used to avoid temporary allocations
|
|
|
|
|
* @param path A pre-allocated path used to avoid temporary allocations
|
|
|
|
|
* @param primaries A pre-allocated array of 6 floats to avoid temporary allocations
|
|
|
|
|
* @param whitePoint A pre-allocated array of 2 floats to avoid temporary allocations
|
|
|
|
|
*/
|
|
|
|
|
private void drawGamuts(
|
|
|
|
|
@NonNull Canvas canvas, int width, int height,
|
|
|
|
|
@NonNull Paint paint, @NonNull Path path,
|
|
|
|
|
@NonNull @Size(6) float[] primaries, @NonNull @Size(2) float[] whitePoint) {
|
|
|
|
|
|
|
|
|
|
float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f);
|
|
|
|
|
|
|
|
|
|
for (final Pair<ColorSpace, Integer> item : mColorSpaces) {
|
|
|
|
|
ColorSpace colorSpace = item.first;
|
|
|
|
|
int color = item.second;
|
|
|
|
|
|
|
|
|
|
if (colorSpace.getModel() != Model.RGB) continue;
|
|
|
|
|
|
|
|
|
|
Rgb rgb = (Rgb) colorSpace;
|
|
|
|
|
getPrimaries(rgb, primaries, mUcs);
|
|
|
|
|
|
|
|
|
|
path.rewind();
|
|
|
|
|
path.moveTo(width * primaries[0], height - height * primaries[1]);
|
|
|
|
|
path.lineTo(width * primaries[2], height - height * primaries[3]);
|
|
|
|
|
path.lineTo(width * primaries[4], height - height * primaries[5]);
|
|
|
|
|
path.close();
|
|
|
|
|
|
|
|
|
|
paint.setStyle(Paint.Style.STROKE);
|
|
|
|
|
paint.setColor(color);
|
|
|
|
|
canvas.drawPath(path, paint);
|
|
|
|
|
|
|
|
|
|
// Draw the white point
|
|
|
|
|
if (mShowWhitePoint) {
|
|
|
|
|
rgb.getWhitePoint(whitePoint);
|
|
|
|
|
if (mUcs) xyYToUv(whitePoint);
|
|
|
|
|
|
|
|
|
|
paint.setStyle(Paint.Style.FILL);
|
|
|
|
|
paint.setColor(color);
|
|
|
|
|
canvas.drawCircle(
|
|
|
|
|
width * whitePoint[0], height - height * whitePoint[1], radius, paint);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the primaries of the specified RGB color space. This method handles
|
|
|
|
|
* the special case of the {@link Named#EXTENDED_SRGB} family of color spaces.
|
|
|
|
|
*
|
|
|
|
|
* @param rgb The color space whose primaries to extract
|
|
|
|
|
* @param primaries A pre-allocated array of 6 floats that will hold the result
|
|
|
|
|
* @param asUcs True if the primaries should be returned in Luv, false for xyY
|
|
|
|
|
*/
|
|
|
|
|
@NonNull
|
|
|
|
|
@Size(6)
|
|
|
|
|
private static void getPrimaries(@NonNull Rgb rgb,
|
|
|
|
|
@NonNull @Size(6) float[] primaries, boolean asUcs) {
|
|
|
|
|
// TODO: We should find a better way to handle these cases
|
|
|
|
|
if (rgb.equals(ColorSpace.get(Named.EXTENDED_SRGB)) ||
|
|
|
|
|
rgb.equals(ColorSpace.get(Named.LINEAR_EXTENDED_SRGB))) {
|
|
|
|
|
primaries[0] = 1.41f;
|
|
|
|
|
primaries[1] = 0.33f;
|
|
|
|
|
primaries[2] = 0.27f;
|
|
|
|
|
primaries[3] = 1.24f;
|
|
|
|
|
primaries[4] = -0.23f;
|
|
|
|
|
primaries[5] = -0.57f;
|
|
|
|
|
} else {
|
|
|
|
|
rgb.getPrimaries(primaries);
|
|
|
|
|
}
|
|
|
|
|
if (asUcs) xyYToUv(primaries);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Draws the CIE 1931 chromaticity diagram: the spectral locus and its inside.
|
|
|
|
|
* This method respect the clip parameter.
|
|
|
|
|
*
|
|
|
|
|
* @param canvas The canvas to transform
|
|
|
|
|
* @param width Width in pixel of the final image
|
|
|
|
|
* @param height Height in pixel of the final image
|
|
|
|
|
* @param paint A pre-allocated paint used to avoid temporary allocations
|
|
|
|
|
* @param path A pre-allocated path used to avoid temporary allocations
|
|
|
|
|
* @param primaries A pre-allocated array of 6 floats to avoid temporary allocations
|
|
|
|
|
*/
|
|
|
|
|
private void drawLocus(
|
|
|
|
|
@NonNull Canvas canvas, int width, int height, @NonNull Paint paint,
|
|
|
|
|
@NonNull Path path, @NonNull @Size(6) float[] primaries) {
|
|
|
|
|
|
|
|
|
|
int vertexCount = SPECTRUM_LOCUS_X.length * CHROMATICITY_RESOLUTION * 6;
|
|
|
|
|
float[] vertices = new float[vertexCount * 2];
|
|
|
|
|
int[] colors = new int[vertices.length];
|
|
|
|
|
computeChromaticityMesh(vertices, colors);
|
|
|
|
|
|
|
|
|
|
if (mUcs) xyYToUv(vertices);
|
|
|
|
|
for (int i = 0; i < vertices.length; i += 2) {
|
|
|
|
|
vertices[i] *= width;
|
|
|
|
|
vertices[i + 1] = height - vertices[i + 1] * height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw the spectral locus
|
|
|
|
|
if (mClip && mColorSpaces.size() > 0) {
|
|
|
|
|
for (final Pair<ColorSpace, Integer> item : mColorSpaces) {
|
|
|
|
|
ColorSpace colorSpace = item.first;
|
|
|
|
|
if (colorSpace.getModel() != Model.RGB) continue;
|
|
|
|
|
|
|
|
|
|
Rgb rgb = (Rgb) colorSpace;
|
|
|
|
|
getPrimaries(rgb, primaries, mUcs);
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
path.rewind();
|
|
|
|
|
path.moveTo(width * primaries[0], height - height * primaries[1]);
|
|
|
|
|
path.lineTo(width * primaries[2], height - height * primaries[3]);
|
|
|
|
|
path.lineTo(width * primaries[4], height - height * primaries[5]);
|
|
|
|
|
path.close();
|
|
|
|
|
|
|
|
|
|
int[] solid = new int[colors.length];
|
|
|
|
|
Arrays.fill(solid, 0xff6c6c6c);
|
|
|
|
|
canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0,
|
|
|
|
|
null, 0, solid, 0, null, 0, 0, paint);
|
|
|
|
|
|
|
|
|
|
canvas.save();
|
|
|
|
|
canvas.clipPath(path);
|
|
|
|
|
|
|
|
|
|
canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0,
|
|
|
|
|
null, 0, colors, 0, null, 0, 0, paint);
|
|
|
|
|
|
|
|
|
|
canvas.restore();
|
|
|
|
|
} else {
|
|
|
|
|
canvas.drawVertices(Canvas.VertexMode.TRIANGLES, vertices.length, vertices, 0,
|
|
|
|
|
null, 0, colors, 0, null, 0, 0, paint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw the non-spectral locus
|
|
|
|
|
int index = (CHROMATICITY_RESOLUTION - 1) * 12;
|
|
|
|
|
path.reset();
|
|
|
|
|
path.moveTo(vertices[index], vertices[index + 1]);
|
|
|
|
|
for (int x = 2; x < SPECTRUM_LOCUS_X.length; x++) {
|
|
|
|
|
index += CHROMATICITY_RESOLUTION * 12;
|
|
|
|
|
path.lineTo(vertices[index], vertices[index + 1]);
|
|
|
|
|
}
|
|
|
|
|
path.close();
|
|
|
|
|
|
|
|
|
|
paint.setStrokeWidth(4.0f / (mUcs ? UCS_SCALE : 1.0f));
|
|
|
|
|
paint.setStyle(Paint.Style.STROKE);
|
|
|
|
|
paint.setColor(0xff000000);
|
|
|
|
|
canvas.drawPath(path, paint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Draws the diagram box, including borders, tick marks, grid lines
|
|
|
|
|
* and axis labels.
|
|
|
|
|
*
|
|
|
|
|
* @param canvas The canvas to transform
|
|
|
|
|
* @param width Width in pixel of the final image
|
|
|
|
|
* @param height Height in pixel of the final image
|
|
|
|
|
* @param paint A pre-allocated paint used to avoid temporary allocations
|
|
|
|
|
* @param path A pre-allocated path used to avoid temporary allocations
|
|
|
|
|
*/
|
|
|
|
|
private void drawBox(@NonNull Canvas canvas, int width, int height, @NonNull Paint paint,
|
|
|
|
|
@NonNull Path path) {
|
|
|
|
|
|
|
|
|
|
int lineCount = 10;
|
|
|
|
|
float scale = 1.0f;
|
|
|
|
|
if (mUcs) {
|
|
|
|
|
lineCount = 7;
|
|
|
|
|
scale = UCS_SCALE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw the unit grid
|
|
|
|
|
paint.setStyle(Paint.Style.STROKE);
|
|
|
|
|
paint.setStrokeWidth(2.0f);
|
|
|
|
|
paint.setColor(0xffc0c0c0);
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i < lineCount - 1; i++) {
|
|
|
|
|
float v = i / 10.0f;
|
|
|
|
|
float x = (width * v) * scale;
|
|
|
|
|
float y = height - (height * v) * scale;
|
|
|
|
|
|
|
|
|
|
canvas.drawLine(0.0f, y, 0.9f * width, y, paint);
|
|
|
|
|
canvas.drawLine(x, height, x, 0.1f * height, paint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw tick marks
|
|
|
|
|
paint.setStrokeWidth(4.0f);
|
|
|
|
|
paint.setColor(0xff000000);
|
|
|
|
|
for (int i = 1; i < lineCount - 1; i++) {
|
|
|
|
|
float v = i / 10.0f;
|
|
|
|
|
float x = (width * v) * scale;
|
|
|
|
|
float y = height - (height * v) * scale;
|
|
|
|
|
|
|
|
|
|
canvas.drawLine(0.0f, y, width / 100.0f, y, paint);
|
|
|
|
|
canvas.drawLine(x, height, x, height - (height / 100.0f), paint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw the axis labels
|
|
|
|
|
paint.setStyle(Paint.Style.FILL);
|
|
|
|
|
paint.setTextSize(36.0f);
|
|
|
|
|
paint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
|
|
|
|
|
|
|
|
|
|
Rect bounds = new Rect();
|
|
|
|
|
for (int i = 1; i < lineCount - 1; i++) {
|
|
|
|
|
String text = "0." + i;
|
|
|
|
|
paint.getTextBounds(text, 0, text.length(), bounds);
|
|
|
|
|
|
|
|
|
|
float v = i / 10.0f;
|
|
|
|
|
float x = (width * v) * scale;
|
|
|
|
|
float y = height - (height * v) * scale;
|
|
|
|
|
|
|
|
|
|
canvas.drawText(text, -0.05f * width + 10, y + bounds.height() / 2.0f, paint);
|
|
|
|
|
canvas.drawText(text, x - bounds.width() / 2.0f,
|
|
|
|
|
height + bounds.height() + 16, paint);
|
|
|
|
|
}
|
|
|
|
|
paint.setStyle(Paint.Style.STROKE);
|
|
|
|
|
|
|
|
|
|
// Draw the diagram box
|
|
|
|
|
path.moveTo(0.0f, height);
|
|
|
|
|
path.lineTo(0.9f * width, height);
|
|
|
|
|
path.lineTo(0.9f * width, 0.1f * height);
|
|
|
|
|
path.lineTo(0.0f, 0.1f * height);
|
|
|
|
|
path.close();
|
|
|
|
|
canvas.drawPath(path, paint);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Computes and applies the Canvas transforms required to make the color
|
|
|
|
|
* gamut of each color space visible in the final image.
|
|
|
|
|
*
|
|
|
|
|
* @param canvas The canvas to transform
|
|
|
|
|
* @param width Width in pixel of the final image
|
|
|
|
|
* @param height Height in pixel of the final image
|
|
|
|
|
* @param primaries Array of 6 floats used to avoid temporary allocations
|
|
|
|
|
*/
|
|
|
|
|
private void setTransform(@NonNull Canvas canvas, int width, int height,
|
|
|
|
|
@NonNull @Size(6) float[] primaries) {
|
|
|
|
|
|
|
|
|
|
RectF primariesBounds = new RectF();
|
|
|
|
|
for (final Pair<ColorSpace, Integer> item : mColorSpaces) {
|
|
|
|
|
ColorSpace colorSpace = item.first;
|
|
|
|
|
if (colorSpace.getModel() != Model.RGB) continue;
|
|
|
|
|
|
|
|
|
|
Rgb rgb = (Rgb) colorSpace;
|
|
|
|
|
getPrimaries(rgb, primaries, mUcs);
|
|
|
|
|
|
|
|
|
|
primariesBounds.left = Math.min(primariesBounds.left, primaries[4]);
|
|
|
|
|
primariesBounds.top = Math.min(primariesBounds.top, primaries[5]);
|
|
|
|
|
primariesBounds.right = Math.max(primariesBounds.right, primaries[0]);
|
|
|
|
|
primariesBounds.bottom = Math.max(primariesBounds.bottom, primaries[3]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float max = mUcs ? 0.6f : 0.9f;
|
|
|
|
|
|
|
|
|
|
primariesBounds.left = Math.min(0.0f, primariesBounds.left);
|
|
|
|
|
primariesBounds.top = Math.min(0.0f, primariesBounds.top);
|
|
|
|
|
primariesBounds.right = Math.max(max, primariesBounds.right);
|
|
|
|
|
primariesBounds.bottom = Math.max(max, primariesBounds.bottom);
|
|
|
|
|
|
|
|
|
|
float scaleX = max / primariesBounds.width();
|
|
|
|
|
float scaleY = max / primariesBounds.height();
|
|
|
|
|
float scale = Math.min(scaleX, scaleY);
|
|
|
|
|
|
|
|
|
|
canvas.scale(mSize / (float) NATIVE_SIZE, mSize / (float) NATIVE_SIZE);
|
|
|
|
|
canvas.scale(scale, scale);
|
|
|
|
|
canvas.translate(
|
|
|
|
|
(primariesBounds.width() - max) * width / 2.0f,
|
|
|
|
|
(primariesBounds.height() - max) * height / 2.0f);
|
|
|
|
|
|
|
|
|
|
// The spectrum extends ~0.85 vertically and ~0.65 horizontally
|
|
|
|
|
// We shift the canvas a little bit to get nicer margins
|
|
|
|
|
canvas.translate(0.05f * width, -0.05f * height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Computes and applies the Canvas transforms required to render the CIE
|
|
|
|
|
* 197 UCS chromaticity diagram.
|
|
|
|
|
*
|
|
|
|
|
* @param canvas The canvas to transform
|
|
|
|
|
* @param height Height in pixel of the final image
|
|
|
|
|
*/
|
|
|
|
|
private void setUcsTransform(@NonNull Canvas canvas, int height) {
|
|
|
|
|
if (mUcs) {
|
|
|
|
|
canvas.translate(0.0f, (height - height * UCS_SCALE));
|
|
|
|
|
canvas.scale(UCS_SCALE, UCS_SCALE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// X coordinates of the spectral locus in CIE 1931
|
|
|
|
|
private static final float[] SPECTRUM_LOCUS_X = {
|
|
|
|
|
0.175596f, 0.172787f, 0.170806f, 0.170085f, 0.160343f,
|
|
|
|
|
0.146958f, 0.139149f, 0.133536f, 0.126688f, 0.115830f,
|
|
|
|
|
0.109616f, 0.099146f, 0.091310f, 0.078130f, 0.068717f,
|
|
|
|
|
0.054675f, 0.040763f, 0.027497f, 0.016270f, 0.008169f,
|
|
|
|
|
0.004876f, 0.003983f, 0.003859f, 0.004646f, 0.007988f,
|
|
|
|
|
0.013870f, 0.022244f, 0.027273f, 0.032820f, 0.038851f,
|
|
|
|
|
0.045327f, 0.052175f, 0.059323f, 0.066713f, 0.074299f,
|
|
|
|
|
0.089937f, 0.114155f, 0.138695f, 0.154714f, 0.192865f,
|
|
|
|
|
0.229607f, 0.265760f, 0.301588f, 0.337346f, 0.373083f,
|
|
|
|
|
0.408717f, 0.444043f, 0.478755f, 0.512467f, 0.544767f,
|
|
|
|
|
0.575132f, 0.602914f, 0.627018f, 0.648215f, 0.665746f,
|
|
|
|
|
0.680061f, 0.691487f, 0.700589f, 0.707901f, 0.714015f,
|
|
|
|
|
0.719017f, 0.723016f, 0.734674f, 0.717203f, 0.699732f,
|
|
|
|
|
0.682260f, 0.664789f, 0.647318f, 0.629847f, 0.612376f,
|
|
|
|
|
0.594905f, 0.577433f, 0.559962f, 0.542491f, 0.525020f,
|
|
|
|
|
0.507549f, 0.490077f, 0.472606f, 0.455135f, 0.437664f,
|
|
|
|
|
0.420193f, 0.402721f, 0.385250f, 0.367779f, 0.350308f,
|
|
|
|
|
0.332837f, 0.315366f, 0.297894f, 0.280423f, 0.262952f,
|
|
|
|
|
0.245481f, 0.228010f, 0.210538f, 0.193067f, 0.175596f
|
|
|
|
|
};
|
|
|
|
|
// Y coordinates of the spectral locus in CIE 1931
|
|
|
|
|
private static final float[] SPECTRUM_LOCUS_Y = {
|
|
|
|
|
0.005295f, 0.004800f, 0.005472f, 0.005976f, 0.014496f,
|
|
|
|
|
0.026643f, 0.035211f, 0.042704f, 0.053441f, 0.073601f,
|
|
|
|
|
0.086866f, 0.112037f, 0.132737f, 0.170464f, 0.200773f,
|
|
|
|
|
0.254155f, 0.317049f, 0.387997f, 0.463035f, 0.538504f,
|
|
|
|
|
0.587196f, 0.610526f, 0.654897f, 0.675970f, 0.715407f,
|
|
|
|
|
0.750246f, 0.779682f, 0.792153f, 0.802971f, 0.812059f,
|
|
|
|
|
0.819430f, 0.825200f, 0.829460f, 0.832306f, 0.833833f,
|
|
|
|
|
0.833316f, 0.826231f, 0.814796f, 0.805884f, 0.781648f,
|
|
|
|
|
0.754347f, 0.724342f, 0.692326f, 0.658867f, 0.624470f,
|
|
|
|
|
0.589626f, 0.554734f, 0.520222f, 0.486611f, 0.454454f,
|
|
|
|
|
0.424252f, 0.396516f, 0.372510f, 0.351413f, 0.334028f,
|
|
|
|
|
0.319765f, 0.308359f, 0.299317f, 0.292044f, 0.285945f,
|
|
|
|
|
0.280951f, 0.276964f, 0.265326f, 0.257200f, 0.249074f,
|
|
|
|
|
0.240948f, 0.232822f, 0.224696f, 0.216570f, 0.208444f,
|
|
|
|
|
0.200318f, 0.192192f, 0.184066f, 0.175940f, 0.167814f,
|
|
|
|
|
0.159688f, 0.151562f, 0.143436f, 0.135311f, 0.127185f,
|
|
|
|
|
0.119059f, 0.110933f, 0.102807f, 0.094681f, 0.086555f,
|
|
|
|
|
0.078429f, 0.070303f, 0.062177f, 0.054051f, 0.045925f,
|
|
|
|
|
0.037799f, 0.029673f, 0.021547f, 0.013421f, 0.005295f
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Computes a 2D mesh representation of the CIE 1931 chromaticity
|
|
|
|
|
* diagram.
|
|
|
|
|
*
|
|
|
|
|
* @param vertices Array of floats that will hold the mesh vertices
|
|
|
|
|
* @param colors Array of floats that will hold the mesh colors
|
|
|
|
|
*/
|
|
|
|
|
private static void computeChromaticityMesh(@NonNull float[] vertices,
|
|
|
|
|
@NonNull int[] colors) {
|
|
|
|
|
|
|
|
|
|
ColorSpace colorSpace = get(Named.SRGB);
|
|
|
|
|
|
|
|
|
|
float[] color = new float[3];
|
|
|
|
|
|
|
|
|
|
int vertexIndex = 0;
|
|
|
|
|
int colorIndex = 0;
|
|
|
|
|
|
|
|
|
|
for (int x = 0; x < SPECTRUM_LOCUS_X.length; x++) {
|
|
|
|
|
int nextX = (x % (SPECTRUM_LOCUS_X.length - 1)) + 1;
|
|
|
|
|
|
|
|
|
|
float a1 = (float) Math.atan2(
|
|
|
|
|
SPECTRUM_LOCUS_Y[x] - ONE_THIRD,
|
|
|
|
|
SPECTRUM_LOCUS_X[x] - ONE_THIRD);
|
|
|
|
|
float a2 = (float) Math.atan2(
|
|
|
|
|
SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD,
|
|
|
|
|
SPECTRUM_LOCUS_X[nextX] - ONE_THIRD);
|
|
|
|
|
|
|
|
|
|
float radius1 = (float) Math.pow(
|
|
|
|
|
sqr(SPECTRUM_LOCUS_X[x] - ONE_THIRD) +
|
|
|
|
|
sqr(SPECTRUM_LOCUS_Y[x] - ONE_THIRD),
|
|
|
|
|
0.5);
|
|
|
|
|
float radius2 = (float) Math.pow(
|
|
|
|
|
sqr(SPECTRUM_LOCUS_X[nextX] - ONE_THIRD) +
|
|
|
|
|
sqr(SPECTRUM_LOCUS_Y[nextX] - ONE_THIRD),
|
|
|
|
|
0.5);
|
|
|
|
|
|
|
|
|
|
// Compute patches; each patch is a quad with a different
|
|
|
|
|
// color associated with each vertex
|
|
|
|
|
for (int c = 1; c <= CHROMATICITY_RESOLUTION; c++) {
|
|
|
|
|
float f1 = c / (float) CHROMATICITY_RESOLUTION;
|
|
|
|
|
float f2 = (c - 1) / (float) CHROMATICITY_RESOLUTION;
|
|
|
|
|
|
|
|
|
|
double cr1 = radius1 * Math.cos(a1);
|
|
|
|
|
double sr1 = radius1 * Math.sin(a1);
|
|
|
|
|
double cr2 = radius2 * Math.cos(a2);
|
|
|
|
|
double sr2 = radius2 * Math.sin(a2);
|
|
|
|
|
|
|
|
|
|
// Compute the XYZ coordinates of the 4 vertices of the patch
|
|
|
|
|
float v1x = (float) (ONE_THIRD + cr1 * f1);
|
|
|
|
|
float v1y = (float) (ONE_THIRD + sr1 * f1);
|
|
|
|
|
float v1z = 1 - v1x - v1y;
|
|
|
|
|
|
|
|
|
|
float v2x = (float) (ONE_THIRD + cr1 * f2);
|
|
|
|
|
float v2y = (float) (ONE_THIRD + sr1 * f2);
|
|
|
|
|
float v2z = 1 - v2x - v2y;
|
|
|
|
|
|
|
|
|
|
float v3x = (float) (ONE_THIRD + cr2 * f2);
|
|
|
|
|
float v3y = (float) (ONE_THIRD + sr2 * f2);
|
|
|
|
|
float v3z = 1 - v3x - v3y;
|
|
|
|
|
|
|
|
|
|
float v4x = (float) (ONE_THIRD + cr2 * f1);
|
|
|
|
|
float v4y = (float) (ONE_THIRD + sr2 * f1);
|
|
|
|
|
float v4z = 1 - v4x - v4y;
|
|
|
|
|
|
|
|
|
|
// Compute the sRGB representation of each XYZ coordinate of the patch
|
|
|
|
|
colors[colorIndex ] = computeColor(color, v1x, v1y, v1z, colorSpace);
|
|
|
|
|
colors[colorIndex + 1] = computeColor(color, v2x, v2y, v2z, colorSpace);
|
|
|
|
|
colors[colorIndex + 2] = computeColor(color, v3x, v3y, v3z, colorSpace);
|
|
|
|
|
colors[colorIndex + 3] = colors[colorIndex];
|
|
|
|
|
colors[colorIndex + 4] = colors[colorIndex + 2];
|
|
|
|
|
colors[colorIndex + 5] = computeColor(color, v4x, v4y, v4z, colorSpace);
|
|
|
|
|
colorIndex += 6;
|
|
|
|
|
|
|
|
|
|
// Flip the mesh upside down to match Canvas' coordinates system
|
|
|
|
|
vertices[vertexIndex++] = v1x;
|
|
|
|
|
vertices[vertexIndex++] = v1y;
|
|
|
|
|
vertices[vertexIndex++] = v2x;
|
|
|
|
|
vertices[vertexIndex++] = v2y;
|
|
|
|
|
vertices[vertexIndex++] = v3x;
|
|
|
|
|
vertices[vertexIndex++] = v3y;
|
|
|
|
|
vertices[vertexIndex++] = v1x;
|
|
|
|
|
vertices[vertexIndex++] = v1y;
|
|
|
|
|
vertices[vertexIndex++] = v3x;
|
|
|
|
|
vertices[vertexIndex++] = v3y;
|
|
|
|
|
vertices[vertexIndex++] = v4x;
|
|
|
|
|
vertices[vertexIndex++] = v4y;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ColorInt
|
|
|
|
|
private static int computeColor(@NonNull @Size(3) float[] color,
|
|
|
|
|
float x, float y, float z, @NonNull ColorSpace cs) {
|
|
|
|
|
color[0] = x;
|
|
|
|
|
color[1] = y;
|
|
|
|
|
color[2] = z;
|
|
|
|
|
cs.fromXyz(color);
|
|
|
|
|
return 0xff000000 |
|
|
|
|
|
(((int) (color[0] * 255.0f) & 0xff) << 16) |
|
|
|
|
|
(((int) (color[1] * 255.0f) & 0xff) << 8) |
|
|
|
|
|
(((int) (color[2] * 255.0f) & 0xff) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static double sqr(double v) {
|
|
|
|
|
return v * v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static class Point {
|
|
|
|
|
@NonNull final ColorSpace mColorSpace;
|
|
|
|
|
@NonNull final float[] mRgb;
|
|
|
|
|
final int mColor;
|
|
|
|
|
|
|
|
|
|
Point(@NonNull ColorSpace colorSpace,
|
|
|
|
|
@NonNull @Size(3) float[] rgb, @ColorInt int color) {
|
|
|
|
|
mColorSpace = colorSpace;
|
|
|
|
|
mRgb = rgb;
|
|
|
|
|
mColor = color;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|