diff --git a/app/src/main/java/com/besome/sketch/editor/view/ViewEditor.java b/app/src/main/java/com/besome/sketch/editor/view/ViewEditor.java index 9b61ce39b5..d18c44ecf8 100644 --- a/app/src/main/java/com/besome/sketch/editor/view/ViewEditor.java +++ b/app/src/main/java/com/besome/sketch/editor/view/ViewEditor.java @@ -734,6 +734,7 @@ private void setPreviewColors(String str) { k.setBackgroundColor(ProjectFile.getColor(str, ProjectFile.COLOR_PRIMARY_DARK)); imgPhoneTopBg.setBackgroundColor(ProjectFile.getColor(str, ProjectFile.COLOR_PRIMARY_DARK)); toolbar.setBackgroundColor(ProjectFile.getColor(str, ProjectFile.COLOR_PRIMARY)); + viewPane.setRootBackgroundColor(ProjectFile.getColor(str, ProjectFile.COLOR_BACKGROUND)); } private void b(boolean z) { diff --git a/app/src/main/java/com/besome/sketch/editor/view/ViewPane.java b/app/src/main/java/com/besome/sketch/editor/view/ViewPane.java index c425d8b597..7ab3b41d3a 100644 --- a/app/src/main/java/com/besome/sketch/editor/view/ViewPane.java +++ b/app/src/main/java/com/besome/sketch/editor/view/ViewPane.java @@ -30,6 +30,8 @@ import android.widget.SeekBar; import android.widget.TextView; +import androidx.annotation.ColorInt; + import com.besome.sketch.beans.ImageBean; import com.besome.sketch.beans.LayoutBean; import com.besome.sketch.beans.ProjectResourceBean; @@ -322,6 +324,10 @@ private void addRootLayout() { addView(rootView); } + public void setRootBackgroundColor(@ColorInt int backgroundColor) { + rootLayout.setBackgroundColor(backgroundColor); + } + private void updateItemView(View view, ViewBean viewBean) { ImageBean imageBean; String str; diff --git a/app/src/main/java/mod/hey/studios/util/ProjectFile.java b/app/src/main/java/mod/hey/studios/util/ProjectFile.java index ccc0e91cb9..4b16b0c383 100644 --- a/app/src/main/java/mod/hey/studios/util/ProjectFile.java +++ b/app/src/main/java/mod/hey/studios/util/ProjectFile.java @@ -1,5 +1,8 @@ package mod.hey.studios.util; +// import static mod.remaker.util.CamUtils.modulateColorWithLStar; + +import android.content.Context; import android.graphics.Color; import android.os.Build; @@ -15,6 +18,9 @@ public class ProjectFile { public static final String COLOR_CONTROL_HIGHLIGHT = "color_control_highlight"; public static final String COLOR_CONTROL_NORMAL = "color_control_normal"; + // Android Framework + public static final String COLOR_BACKGROUND = "color_background"; + public static int getColor(String sc_id, String color) { return yB.a(lC.b(sc_id), color, getDefaultColor(color)); /* @@ -42,19 +48,23 @@ public static int a(Map paramMap, String paramString, int paramI */ public static int getDefaultColor(String color) { - // Check if the device is running Android 12 or above + final Context ctx = SketchApplication.getContext(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - // Android 12+ (API level 31): Use Material 3 dynamic colors + // final int defaultColor = modulateColorWithLStar( + // ctx.getColor(android.R.color.system_accentl1_600), 90.0f); + // final int colorBackground = modulateColorWithLStar( + // ctx.getColor(android.R.color.system_neutral2_600), 98.0f); + return switch (color) { - case COLOR_PRIMARY_DARK -> SketchApplication.getContext().getColor(android.R.color.system_accent1_500); - case COLOR_CONTROL_HIGHLIGHT -> SketchApplication.getContext().getColor(android.R.color.system_accent1_100); - default -> SketchApplication.getContext().getColor(android.R.color.system_accent1_500); + case COLOR_CONTROL_HIGHLIGHT -> ctx.getColor(android.R.color.system_accent1_100); + case COLOR_BACKGROUND -> ctx.getColor(android.R.color.system_neutral2_600); + default -> ctx.getColor(android.R.color.system_accent1_600); }; } else { - // For Android versions below 12: use static fallback colors return switch (color) { - case COLOR_PRIMARY_DARK -> Color.parseColor("#ff1976d2"); case COLOR_CONTROL_HIGHLIGHT -> Color.parseColor("#202196f3"); + case COLOR_BACKGROUND -> Color.parseColor("#fffafafa"); default -> Color.parseColor("#ff2196f3"); }; } diff --git a/app/src/main/java/mod/remaker/util/CamUtils.java b/app/src/main/java/mod/remaker/util/CamUtils.java index 710b1d0758..91d794c66d 100644 --- a/app/src/main/java/mod/remaker/util/CamUtils.java +++ b/app/src/main/java/mod/remaker/util/CamUtils.java @@ -1,379 +1,556 @@ package mod.remaker.util; +import android.graphics.Color; + import androidx.annotation.ColorInt; import androidx.annotation.FloatRange; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.Size; import androidx.core.math.MathUtils; public final class CamUtils { private CamUtils() { } - // Transforms XYZ color space coordinates to 'cone'/'RGB' responses in CAM16. - public static final float[][] XYZ_TO_CAM16RGB = { - {0.401288f, 0.650173f, -0.051461f}, - {-0.250268f, 1.204414f, 0.045854f}, - {-0.002079f, 0.048952f, 0.953127f} - }; - - // sRGB specification has D65 whitepoint - Stokes, Anderson, Chandrasekar, Motta - A Standard - // Default Color Space for the Internet: sRGB, 1996 - public static final float[] WHITE_POINT_D65 = {95.047f, 100.0f, 108.883f}; - - public static int modulateColorWithLStar(@ColorInt int color, @FloatRange(from = 0f, to = 100f) float lStar) { - final boolean validLStar = lStar >= 0.0f && lStar <= 100.0f; - if (!validLStar) { - return color; - } - - final int baseAlpha = Color.alpha(color); - final int alpha = MathUtils.clamp((int) (baseAlpha * 1f + 0.5f), 0, 255); - - if (validLStar) { - final CamColor baseCam = CamColor.fromColor(color); - color = CamColor.toColor(baseCam.getHue(), baseCam.getChroma(), lStar); - } - - return (color & 0xFFFFFF) | (alpha << 24); - } - - public static void xyzFromInt(int argb, @NonNull float[] outXYZ) { - final float r = linearized(Color.red(argb)); - final float g = linearized(Color.green(argb)); - final float b = linearized(Color.blue(argb)); - - float[][] matrix = SRGB_TO_XYZ; - outXYZ[0] = (r * matrix[0][0]) + (g * matrix[0][1]) + (b * matrix[0][2]); - outXYZ[1] = (r * matrix[1][0]) + (g * matrix[1][1]) + (b * matrix[1][2]); - outXYZ[2] = (r * matrix[2][0]) + (g * matrix[2][1]) + (b * matrix[2][2]); - } - - public static float yFromLStar(float lstar) { - float ke = 8.0f; - if (lstar > ke) { - return (float) Math.pow(((lstar + 16.0) / 116.0), 3) * 100f; - } else { - return lstar / (24389f / 27f) * 100f; - } - } - - private static float linearized(int rgbComponent) { - float normalized = (float) rgbComponent / 255.0f; - - if (normalized <= 0.04045f) { - return (normalized / 12.92f) * 100.0f; - } else { - return (float) Math.pow(((normalized + 0.055f) / 1.055f), 2.4f) * 100.0f; - } - } - - private static class CamColor { - CamColor(float hue, float chroma, float j, float q, float m, float s, float jStar, float aStar, - float bStar) { - mHue = hue; - mChroma = chroma; - mJ = j; - mQ = q; - mM = m; - mS = s; - mJstar = jStar; - mAstar = aStar; - mBstar = bStar; - } - - @FloatRange(from = 0.0, to = 360.0, toInclusive = false) - public float getHue() { - return mHue; - } - - /** Chroma in CAM16 */ - @FloatRange(from = 0.0, to = Double.POSITIVE_INFINITY, toInclusive = false) - public float getChroma() { - return mChroma; - } - - /** - * Given a hue & chroma in CAM16, L* in L*a*b*, return an ARGB integer. The chroma of the color - * returned may, and frequently will, be lower than requested. Assumes the color is viewed in - * the default ViewingConditions. - */ - public static int toColor(@FloatRange(from = 0.0, to = 360.0) float hue, - @FloatRange(from = 0.0, to = Double.POSITIVE_INFINITY, toInclusive = false) - float chroma, - @FloatRange(from = 0.0, to = 100.0) float lStar) { - return toColor(hue, chroma, lStar, ViewingConditions.DEFAULT); - } - - /** - * Create a color appearance model from a ARGB integer representing a color. It is assumed the - * color was viewed in the default ViewingConditions. - * - * The alpha component is ignored, CamColor only represents opaque colors. - */ - @NonNull - private static CamColor fromColor(@ColorInt int color) { - float[] outCamColor = new float[7]; - float[] outM3HCT = new float[3]; - fromColorInViewingConditions(color, ViewingConditions.DEFAULT, outCamColor, outM3HCT); - return new CamColor(outM3HCT[0], outM3HCT[1], outCamColor[0], outCamColor[1], - outCamColor[2], outCamColor[3], outCamColor[4], outCamColor[5], outCamColor[6]); - } - - /** - * Create a color appearance model from a ARGB integer representing a color, specifying the - * ViewingConditions in which the color was viewed. Prefer Cam.fromColor. - */ - private static void fromColorInViewingConditions(@ColorInt int color, - @NonNull ViewingConditions viewingConditions, @Nullable @Size(7) float[] outCamColor, - @NonNull @Size(3) float[] outM3HCT) { - // Transform ARGB int to XYZ, reusing outM3HCT array to avoid a new allocation. - CamUtils.xyzFromInt(color, outM3HCT); - float[] xyz = outM3HCT; - - // Transform XYZ to 'cone'/'rgb' responses - float[][] matrix = CamUtils.XYZ_TO_CAM16RGB; - float rT = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]); - float gT = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]); - float bT = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]); - - // Discount illuminant - float rD = viewingConditions.getRgbD()[0] * rT; - float gD = viewingConditions.getRgbD()[1] * gT; - float bD = viewingConditions.getRgbD()[2] * bT; - - // Chromatic adaptation - float rAF = (float) Math.pow(viewingConditions.getFl() * Math.abs(rD) / 100.0, 0.42); - float gAF = (float) Math.pow(viewingConditions.getFl() * Math.abs(gD) / 100.0, 0.42); - float bAF = (float) Math.pow(viewingConditions.getFl() * Math.abs(bD) / 100.0, 0.42); - float rA = Math.signum(rD) * 400.0f * rAF / (rAF + 27.13f); - float gA = Math.signum(gD) * 400.0f * gAF / (gAF + 27.13f); - float bA = Math.signum(bD) * 400.0f * bAF / (bAF + 27.13f); - - // redness-greenness - float a = (float) (11.0 * rA + -12.0 * gA + bA) / 11.0f; - // yellowness-blueness - float b = (float) (rA + gA - 2.0 * bA) / 9.0f; - - // auxiliary components - float u = (20.0f * rA + 20.0f * gA + 21.0f * bA) / 20.0f; - float p2 = (40.0f * rA + 20.0f * gA + bA) / 20.0f; - - // hue - float atan2 = (float) Math.atan2(b, a); - float atanDegrees = atan2 * 180.0f / (float) Math.PI; - float hue = - atanDegrees < 0 - ? atanDegrees + 360.0f - : atanDegrees >= 360 ? atanDegrees - 360.0f : atanDegrees; - float hueRadians = hue * (float) Math.PI / 180.0f; - - // achromatic response to color - float ac = p2 * viewingConditions.getNbb(); - - // CAM16 lightness and brightness - float j = 100.0f * (float) Math.pow(ac / viewingConditions.getAw(), - viewingConditions.getC() * viewingConditions.getZ()); - float q = - 4.0f - / viewingConditions.getC() - * (float) Math.sqrt(j / 100.0f) - * (viewingConditions.getAw() + 4.0f) - * viewingConditions.getFlRoot(); + // // Transforms XYZ color space coordinates to 'cone'/'RGB' responses in CAM16. + // public static final float[][] XYZ_TO_CAM16RGB = { + // {0.401288f, 0.650173f, -0.051461f}, + // {-0.250268f, 1.204414f, 0.045854f}, + // {-0.002079f, 0.048952f, 0.953127f} + // }; + + // // sRGB specification has D65 whitepoint - Stokes, Anderson, Chandrasekar, Motta - A Standard + // // Default Color Space for the Internet: sRGB, 1996 + // public static final float[] WHITE_POINT_D65 = {95.047f, 100.0f, 108.883f}; + + // // This is a more precise sRGB to XYZ transformation matrix than traditionally + // // used. It was derived using Schlomer's technique of transforming the xyY + // // primaries to XYZ, then applying a correction to ensure mapping from sRGB + // // 1, 1, 1 to the reference white point, D65. + // public static final float[][] SRGB_TO_XYZ = { + // {0.41233894f, 0.35762063f, 0.18051042f}, + // {0.2126f, 0.7152f, 0.0722f}, + // {0.01932141f, 0.11916382f, 0.9503448f} + // }; + + // public static int modulateColorWithLStar(@ColorInt int color, @FloatRange(from = 0f, to = 100f) float lStar) { + // final boolean validLStar = lStar >= 0.0f && lStar <= 100.0f; + // if (!validLStar) { + // return color; + // } + + // final int baseAlpha = Color.alpha(color); + // final int alpha = MathUtils.clamp((int) (baseAlpha * 1f + 0.5f), 0, 255); + + // if (validLStar) { + // final CamColor baseCam = CamColor.fromColor(color); + // color = CamColor.toColor(baseCam.getHue(), baseCam.getChroma(), lStar); + // } + + // return (color & 0xFFFFFF) | (alpha << 24); + // } + + // public static void xyzFromInt(int argb, @NonNull float[] outXYZ) { + // final float r = linearized(Color.red(argb)); + // final float g = linearized(Color.green(argb)); + // final float b = linearized(Color.blue(argb)); + + // float[][] matrix = SRGB_TO_XYZ; + // outXYZ[0] = (r * matrix[0][0]) + (g * matrix[0][1]) + (b * matrix[0][2]); + // outXYZ[1] = (r * matrix[1][0]) + (g * matrix[1][1]) + (b * matrix[1][2]); + // outXYZ[2] = (r * matrix[2][0]) + (g * matrix[2][1]) + (b * matrix[2][2]); + // } + + // public static float yFromLStar(float lstar) { + // float ke = 8.0f; + // if (lstar > ke) { + // return (float) Math.pow(((lstar + 16.0) / 116.0), 3) * 100f; + // } else { + // return lstar / (24389f / 27f) * 100f; + // } + // } + + // public static float lerp(float start, float stop, float amount) { + // return start + (stop - start) * amount; + // } + + // private static float linearized(int rgbComponent) { + // float normalized = (float) rgbComponent / 255.0f; + + // if (normalized <= 0.04045f) { + // return (normalized / 12.92f) * 100.0f; + // } else { + // return (float) Math.pow(((normalized + 0.055f) / 1.055f), 2.4f) * 100.0f; + // } + // } + + // private static class CamColor { + // CamColor(float hue, float chroma) { + // mHue = hue; + // mChroma = chroma; + // } + + // @FloatRange(from = 0.0, to = 360.0, toInclusive = false) + // public float getHue() { + // return mHue; + // } + + // /** Chroma in CAM16 */ + // @FloatRange(from = 0.0, to = Double.POSITIVE_INFINITY, toInclusive = false) + // public float getChroma() { + // return mChroma; + // } + + // /** + // * Given a hue & chroma in CAM16, L* in L*a*b*, return an ARGB integer. The chroma of the color + // * returned may, and frequently will, be lower than requested. Assumes the color is viewed in + // * the default ViewingConditions. + // */ + // public static int toColor(@FloatRange(from = 0.0, to = 360.0) float hue, + // @FloatRange(from = 0.0, to = Double.POSITIVE_INFINITY, toInclusive = false) + // float chroma, + // @FloatRange(from = 0.0, to = 100.0) float lStar) { + // return toColor(hue, chroma, lStar, ViewingConditions.DEFAULT); + // } + + // /** + // * Create a color appearance model from a ARGB integer representing a color. It is assumed the + // * color was viewed in the default ViewingConditions. + // * + // * The alpha component is ignored, CamColor only represents opaque colors. + // */ + // @NonNull + // private static CamColor fromColor(@ColorInt int color) { + // float[] outM3HCT = new float[3]; + // fromColorInViewingConditions(color, ViewingConditions.DEFAULT, outM3HCT); + // return new CamColor(outM3HCT[0], outM3HCT[1]); + // } + + // /** + // * Create a color appearance model from a ARGB integer representing a color, specifying the + // * ViewingConditions in which the color was viewed. Prefer Cam.fromColor. + // */ + // private static void fromColorInViewingConditions(@ColorInt int color, + // @NonNull ViewingConditions viewingConditions, @NonNull @Size(3) float[] outM3HCT) { + // // Transform ARGB int to XYZ, reusing outM3HCT array to avoid a new allocation. + // CamUtils.xyzFromInt(color, outM3HCT); + // float[] xyz = outM3HCT; + + // // Transform XYZ to 'cone'/'rgb' responses + // float[][] matrix = CamUtils.XYZ_TO_CAM16RGB; + // float rT = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]); + // float gT = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]); + // float bT = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]); + + // // Discount illuminant + // float rD = viewingConditions.getRgbD()[0] * rT; + // float gD = viewingConditions.getRgbD()[1] * gT; + // float bD = viewingConditions.getRgbD()[2] * bT; + + // // Chromatic adaptation + // float rAF = (float) Math.pow(viewingConditions.getFl() * Math.abs(rD) / 100.0, 0.42); + // float gAF = (float) Math.pow(viewingConditions.getFl() * Math.abs(gD) / 100.0, 0.42); + // float bAF = (float) Math.pow(viewingConditions.getFl() * Math.abs(bD) / 100.0, 0.42); + // float rA = Math.signum(rD) * 400.0f * rAF / (rAF + 27.13f); + // float gA = Math.signum(gD) * 400.0f * gAF / (gAF + 27.13f); + // float bA = Math.signum(bD) * 400.0f * bAF / (bAF + 27.13f); + + // // redness-greenness + // float a = (float) (11.0 * rA + -12.0 * gA + bA) / 11.0f; + // // yellowness-blueness + // float b = (float) (rA + gA - 2.0 * bA) / 9.0f; + + // // auxiliary components + // float u = (20.0f * rA + 20.0f * gA + 21.0f * bA) / 20.0f; + // float p2 = (40.0f * rA + 20.0f * gA + bA) / 20.0f; + + // // hue + // float atan2 = (float) Math.atan2(b, a); + // float atanDegrees = atan2 * 180.0f / (float) Math.PI; + // float hue = + // atanDegrees < 0 + // ? atanDegrees + 360.0f + // : atanDegrees >= 360 ? atanDegrees - 360.0f : atanDegrees; + // float hueRadians = hue * (float) Math.PI / 180.0f; + + // // achromatic response to color + // float ac = p2 * viewingConditions.getNbb(); + + // // CAM16 lightness and brightness + // float j = 100.0f * (float) Math.pow(ac / viewingConditions.getAw(), + // viewingConditions.getC() * viewingConditions.getZ()); + // float q = + // 4.0f + // / viewingConditions.getC() + // * (float) Math.sqrt(j / 100.0f) + // * (viewingConditions.getAw() + 4.0f) + // * viewingConditions.getFlRoot(); - // CAM16 chroma, colorfulness, and saturation. - float huePrime = (hue < 20.14) ? hue + 360 : hue; - float eHue = 0.25f * (float) (Math.cos(huePrime * Math.PI / 180.0 + 2.0) + 3.8); - float p1 = 50000.0f / 13.0f * eHue * viewingConditions.getNc() * viewingConditions.getNcb(); - float t = p1 * (float) Math.sqrt(a * a + b * b) / (u + 0.305f); - float alpha = (float) Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73) - * (float) Math.pow(t, 0.9); - // CAM16 chroma, colorfulness, saturation - float c = alpha * (float) Math.sqrt(j / 100.0); - float m = c * viewingConditions.getFlRoot(); - float s = 50.0f * (float) Math.sqrt((alpha * viewingConditions.getC()) / ( - viewingConditions.getAw() + 4.0f)); - - // CAM16-UCS components - float jstar = (1.0f + 100.0f * 0.007f) * j / (1.0f + 0.007f * j); - float mstar = 1.0f / 0.0228f * (float) Math.log(1.0f + 0.0228f * m); - float astar = mstar * (float) Math.cos(hueRadians); - float bstar = mstar * (float) Math.sin(hueRadians); - - outM3HCT[0] = hue; - outM3HCT[1] = c; - - if (outCamColor != null) { - outCamColor[0] = j; - outCamColor[1] = q; - outCamColor[2] = m; - outCamColor[3] = s; - outCamColor[4] = jstar; - outCamColor[5] = astar; - outCamColor[6] = bstar; - } - } - } - - private static class ViewingConditions { - // Standard viewing conditions assumed in RGB specification - Stokes, Anderson, Chandrasekar, - // Motta - A Standard Default Color Space for the Internet: sRGB, 1996. - // - // White point = D65 - // Luminance of adapting field: 200 / Pi / 5, units are cd/m^2. - // sRGB ambient illuminance = 64 lux (per sRGB spec). However, the spec notes this is - // artificially low and based on monitors in 1990s. Use 200, the sRGB spec says this is the - // real average, and a survey of lux values on Wikipedia confirms this is a comfortable - // default: somewhere between a very dark overcast day and office lighting. - // Per CAM16 introduction paper (Li et al, 2017) Ew = pi * lw, and La = lw * Yb/Yw - // Ew = ambient environment luminance, in lux. - // Yb/Yw is taken to be midgray, ~20% relative luminance (XYZ Y 18.4, CIELAB L* 50). - // Therefore La = (Ew / pi) * .184 - // La = 200 / pi * .184 - // Image surround to 10 degrees = ~20% relative luminance = CIELAB L* 50 - // - // Not from sRGB standard: - // Surround = average, 2.0. - // Discounting illuminant = false, doesn't occur for self-luminous displays - static final ViewingConditions DEFAULT = - ViewingConditions.make( - CamUtils.WHITE_POINT_D65, - (float) (200.0f / Math.PI * CamUtils.yFromLStar(50.0f) / 100.f), 50.0f, 2.0f, - false); - - private final float mAw; - private final float mNbb; - private final float mNcb; - private final float mC; - private final float mNc; - private final float mN; - private final float[] mRgbD; - private final float mFl; - private final float mFlRoot; - private final float mZ; - - float getAw() { - return mAw; - } - - float getN() { - return mN; - } - - float getNbb() { - return mNbb; - } - - float getNcb() { - return mNcb; - } - - float getC() { - return mC; - } - - float getNc() { - return mNc; - } - - @NonNull - float[] getRgbD() { - return mRgbD; - } - - float getFl() { - return mFl; - } - - float getFlRoot() { - return mFlRoot; - } - - float getZ() { - return mZ; - } - - private ViewingConditions(float n, float aw, float nbb, float ncb, float c, float nc, - float[] rgbD, float fl, float fLRoot, float z) { - mN = n; - mAw = aw; - mNbb = nbb; - mNcb = ncb; - mC = c; - mNc = nc; - mRgbD = rgbD; - mFl = fl; - mFlRoot = fLRoot; - mZ = z; - } - - /** Create a custom camFrame. */ - @NonNull - static ViewingConditions make(@NonNull float[] whitepoint, float adaptingLuminance, - float backgroundLstar, float surround, boolean discountingIlluminant) { - // Transform white point XYZ to 'cone'/'rgb' responses - float[][] matrix = CamUtils.XYZ_TO_CAM16RGB; - float[] xyz = whitepoint; - float rW = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]); - float gW = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]); - float bW = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]); - - // Scale input surround, domain (0, 2), to CAM16 surround, domain (0.8, 1.0) - float f = 0.8f + (surround / 10.0f); - // "Exponential non-linearity" - float c = (f >= 0.9) ? CamUtils.lerp(0.59f, 0.69f, ((f - 0.9f) * 10.0f)) : CamUtils.lerp( - 0.525f, 0.59f, ((f - 0.8f) * 10.0f)); - // Calculate degree of adaptation to illuminant - float d = discountingIlluminant ? 1.0f : f * (1.0f - ((1.0f / 3.6f) * (float) Math.exp( - (-adaptingLuminance - 42.0f) / 92.0f))); - // Per Li et al, if D is greater than 1 or less than 0, set it to 1 or 0. - d = (d > 1.0) ? 1.0f : (d < 0.0) ? 0.0f : d; - // Chromatic induction factor - float nc = f; - - // Cone responses to the whitepoint, adjusted for illuminant discounting. - // - // Why use 100.0 instead of the white point's relative luminance? - // - // Some papers and implementations, for both CAM02 and CAM16, use the Y - // value of the reference white instead of 100. Fairchild's Color Appearance - // Models (3rd edition) notes that this is in error: it was included in the - // CIE 2004a report on CIECAM02, but, later parts of the conversion process - // account for scaling of appearance relative to the white point relative - // luminance. This part should simply use 100 as luminance. - float[] rgbD = new float[]{d * (100.0f / rW) + 1.0f - d, d * (100.0f / gW) + 1.0f - d, - d * (100.0f / bW) + 1.0f - d}; - // Luminance-level adaptation factor - float k = 1.0f / (5.0f * adaptingLuminance + 1.0f); - float k4 = k * k * k * k; - float k4F = 1.0f - k4; - float fl = (k4 * adaptingLuminance) + (0.1f * k4F * k4F * (float) Math.cbrt( - 5.0 * adaptingLuminance)); - - // Intermediate factor, ratio of background relative luminance to white relative luminance - float n = CamUtils.yFromLStar(backgroundLstar) / whitepoint[1]; - - // Base exponential nonlinearity - // note Schlomer 2018 has a typo and uses 1.58, the correct factor is 1.48 - float z = 1.48f + (float) Math.sqrt(n); - - // Luminance-level induction factors - float nbb = 0.725f / (float) Math.pow(n, 0.2); - float ncb = nbb; - - // Discounted cone responses to the white point, adjusted for post-chromatic - // adaptation perceptual nonlinearities. - float[] rgbAFactors = new float[]{(float) Math.pow(fl * rgbD[0] * rW / 100.0, 0.42), - (float) Math.pow(fl * rgbD[1] * gW / 100.0, 0.42), (float) Math.pow( - fl * rgbD[2] * bW / 100.0, 0.42)}; - - float[] rgbA = new float[]{(400.0f * rgbAFactors[0]) / (rgbAFactors[0] + 27.13f), - (400.0f * rgbAFactors[1]) / (rgbAFactors[1] + 27.13f), - (400.0f * rgbAFactors[2]) / (rgbAFactors[2] + 27.13f)}; - - float aw = ((2.0f * rgbA[0]) + rgbA[1] + (0.05f * rgbA[2])) * nbb; - - return new ViewingConditions(n, aw, nbb, ncb, c, nc, rgbD, fl, (float) Math.pow(fl, 0.25), - z); - } - } + // // CAM16 chroma, colorfulness, and saturation. + // float huePrime = (hue < 20.14) ? hue + 360 : hue; + // float eHue = 0.25f * (float) (Math.cos(huePrime * Math.PI / 180.0 + 2.0) + 3.8); + // float p1 = 50000.0f / 13.0f * eHue * viewingConditions.getNc() * viewingConditions.getNcb(); + // float t = p1 * (float) Math.sqrt(a * a + b * b) / (u + 0.305f); + // float alpha = (float) Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73) + // * (float) Math.pow(t, 0.9); + // // CAM16 chroma, colorfulness, saturation + // float c = alpha * (float) Math.sqrt(j / 100.0); + + // outM3HCT[0] = hue; + // outM3HCT[1] = c; + // } + + // /** Returns color perceived in a ViewingConditions as an ARGB integer. */ + // @ColorInt + // int viewed(@NonNull ViewingConditions viewingConditions) { + // float alpha = + // (getChroma() == 0.0 || getJ() == 0.0) + // ? 0.0f + // : getChroma() / (float) Math.sqrt(getJ() / 100.0); + + // float t = (float) Math.pow(alpha / Math.pow(1.64 + // - Math.pow(0.29, viewingConditions.getN()), 0.73), 1.0 / 0.9); + // float hRad = getHue() * (float) Math.PI / 180.0f; + + // float eHue = 0.25f * (float) (Math.cos(hRad + 2.0) + 3.8); + // float ac = viewingConditions.getAw() * (float) Math.pow(getJ() / 100.0, + // 1.0 / viewingConditions.getC() / viewingConditions.getZ()); + // float p1 = + // eHue * (50000.0f / 13.0f) * viewingConditions.getNc() * viewingConditions.getNcb(); + // float p2 = (ac / viewingConditions.getNbb()); + + // float hSin = (float) Math.sin(hRad); + // float hCos = (float) Math.cos(hRad); + + // float gamma = + // 23.0f * (p2 + 0.305f) * t / (23.0f * p1 + 11.0f * t * hCos + 108.0f * t * hSin); + // float a = gamma * hCos; + // float b = gamma * hSin; + // float rA = (460.0f * p2 + 451.0f * a + 288.0f * b) / 1403.0f; + // float gA = (460.0f * p2 - 891.0f * a - 261.0f * b) / 1403.0f; + // float bA = (460.0f * p2 - 220.0f * a - 6300.0f * b) / 1403.0f; + + // float rCBase = (float) Math.max(0, (27.13 * Math.abs(rA)) / (400.0 - Math.abs(rA))); + // float rC = Math.signum(rA) * (100.0f / viewingConditions.getFl()) * (float) Math.pow(rCBase, + // 1.0 / 0.42); + // float gCBase = (float) Math.max(0, (27.13 * Math.abs(gA)) / (400.0 - Math.abs(gA))); + // float gC = Math.signum(gA) * (100.0f / viewingConditions.getFl()) * (float) Math.pow(gCBase, + // 1.0 / 0.42); + // float bCBase = (float) Math.max(0, (27.13 * Math.abs(bA)) / (400.0 - Math.abs(bA))); + // float bC = Math.signum(bA) * (100.0f / viewingConditions.getFl()) * (float) Math.pow(bCBase, + // 1.0 / 0.42); + // float rF = rC / viewingConditions.getRgbD()[0]; + // float gF = gC / viewingConditions.getRgbD()[1]; + // float bF = bC / viewingConditions.getRgbD()[2]; + + // float[][] matrix = CamUtils.CAM16RGB_TO_XYZ; + // float x = (rF * matrix[0][0]) + (gF * matrix[0][1]) + (bF * matrix[0][2]); + // float y = (rF * matrix[1][0]) + (gF * matrix[1][1]) + (bF * matrix[1][2]); + // float z = (rF * matrix[2][0]) + (gF * matrix[2][1]) + (bF * matrix[2][2]); + + // int argb = ColorUtils.XYZToColor(x, y, z); + // return argb; + // } + + // /** + // * Given a hue & chroma in CAM16, L* in L*a*b*, and the ViewingConditions in which the + // * color will be viewed, return an ARGB integer. + // * + // *

The chroma of the color returned may, and frequently will, be lower than requested. This + // * is a fundamental property of color that cannot be worked around by engineering. For example, + // * a red hue, with high chroma, and high L* does not exist: red hues have a maximum chroma + // * below 10 in light shades, creating pink. + // */ + // public static @ColorInt int toColor(@FloatRange(from = 0.0, to = 360.0) float hue, + // @FloatRange(from = 0.0, to = Double.POSITIVE_INFINITY, toInclusive = false) + // float chroma, + // @FloatRange(from = 0.0, to = 100.0) float lstar, + // @NonNull ViewingConditions viewingConditions) { + // if (chroma < 1.0 || Math.round(lstar) <= 0.0 || Math.round(lstar) >= 100.0) { + // return CamUtils.intFromLStar(lstar); + // } + + // hue = hue < 0 ? 0 : Math.min(360, hue); + + // // The highest chroma possible. Updated as binary search proceeds. + // float high = chroma; + + // // The guess for the current binary search iteration. Starts off at the highest chroma, + // // thus, if a color is possible at the requested chroma, the search can stop after one try. + // float mid = chroma; + // float low = 0.0f; + // boolean isFirstLoop = true; + + // CamColor answer = null; + + // while (Math.abs(low - high) >= CHROMA_SEARCH_ENDPOINT) { + // // Given the current chroma guess, mid, and the desired hue, find J, lightness in + // // CAM16 color space, that creates a color with L* = `lstar` in the L*a*b* color space. + // CamColor possibleAnswer = findCamByJ(hue, mid, lstar); + + // if (isFirstLoop) { + // if (possibleAnswer != null) { + // return possibleAnswer.viewed(viewingConditions); + // } else { + // // If this binary search iteration was the first iteration, and this point + // // has been reached, it means the requested chroma was not available at the + // // requested hue and L*. + // // Proceed to a traditional binary search that starts at the midpoint between + // // the requested chroma and 0. + // isFirstLoop = false; + // mid = low + (high - low) / 2.0f; + // continue; + // } + // } + + // if (possibleAnswer == null) { + // // There isn't a CAM16 J that creates a color with L* `lstar`. Try a lower chroma. + // high = mid; + // } else { + // answer = possibleAnswer; + // // It is possible to create a color. Try higher chroma. + // low = mid; + // } + + // mid = low + (high - low) / 2.0f; + // } + + // // There was no answer: meaning, for the desired hue, there was no chroma low enough to + // // generate a color with the desired L*. + // // All values of L* are possible when there is 0 chroma. Return a color with 0 chroma, i.e. + // // a shade of gray, with the desired L*. + // if (answer == null) { + // return CamUtils.intFromLStar(lstar); + // } + + // return answer.viewed(viewingConditions); + // } + + // // Find J, lightness in CAM16 color space, that creates a color with L* = `lstar` in the L*a*b* + // // color space. + // // + // // Returns null if no J could be found that generated a color with L* `lstar`. + // @Nullable + // private static CamColor findCamByJ(@FloatRange(from = 0.0, to = 360.0) float hue, + // @FloatRange(from = 0.0, to = Double.POSITIVE_INFINITY, toInclusive = false) + // float chroma, + // @FloatRange(from = 0.0, to = 100.0) float lstar) { + // float low = 0.0f; + // float high = 100.0f; + // float mid = 0.0f; + // float bestdL = 1000.0f; + // float bestdE = 1000.0f; + + // CamColor bestCam = null; + // while (Math.abs(low - high) > LIGHTNESS_SEARCH_ENDPOINT) { + // mid = low + (high - low) / 2; + // // Create the intended CAM color + // CamColor camBeforeClip = CamColor.fromJch(mid, chroma, hue); + // // Convert the CAM color to RGB. If the color didn't fit in RGB, during the conversion, + // // the initial RGB values will be outside 0 to 255. The final RGB values are clipped to + // // 0 to 255, distorting the intended color. + // int clipped = camBeforeClip.viewedInSrgb(); + // float clippedLstar = CamUtils.lStarFromInt(clipped); + // float dL = Math.abs(lstar - clippedLstar); + + // // If the clipped color's L* is within error margin... + // if (dL < DL_MAX) { + // // ...check if the CAM equivalent of the clipped color is far away from intended CAM + // // color. For the intended color, use lightness and chroma from the clipped color, + // // and the intended hue. Callers are wondering what the lightness is, they know + // // chroma may be distorted, so the only concern here is if the hue slipped too far. + // CamColor camClipped = CamColor.fromColor(clipped); + // float dE = camClipped.distance( + // CamColor.fromJch(camClipped.getJ(), camClipped.getChroma(), hue)); + // if (dE <= DE_MAX) { + // bestdL = dL; + // bestdE = dE; + // bestCam = camClipped; + // } + // } + + // // If there's no error at all, there's no need to search more. + // // + // // Note: this happens much more frequently than expected, but this is a very delicate + // // property which relies on extremely precise sRGB <=> XYZ calculations, as well as fine + // // tuning of the constants that determine error margins and when the binary search can + // // terminate. + // if (bestdL == 0 && bestdE == 0) { + // break; + // } + + // if (clippedLstar < lstar) { + // low = mid; + // } else { + // high = mid; + // } + // } + + // return bestCam; + // } + // } + + // private static class ViewingConditions { + // // Standard viewing conditions assumed in RGB specification - Stokes, Anderson, Chandrasekar, + // // Motta - A Standard Default Color Space for the Internet: sRGB, 1996. + // // + // // White point = D65 + // // Luminance of adapting field: 200 / Pi / 5, units are cd/m^2. + // // sRGB ambient illuminance = 64 lux (per sRGB spec). However, the spec notes this is + // // artificially low and based on monitors in 1990s. Use 200, the sRGB spec says this is the + // // real average, and a survey of lux values on Wikipedia confirms this is a comfortable + // // default: somewhere between a very dark overcast day and office lighting. + // // Per CAM16 introduction paper (Li et al, 2017) Ew = pi * lw, and La = lw * Yb/Yw + // // Ew = ambient environment luminance, in lux. + // // Yb/Yw is taken to be midgray, ~20% relative luminance (XYZ Y 18.4, CIELAB L* 50). + // // Therefore La = (Ew / pi) * .184 + // // La = 200 / pi * .184 + // // Image surround to 10 degrees = ~20% relative luminance = CIELAB L* 50 + // // + // // Not from sRGB standard: + // // Surround = average, 2.0. + // // Discounting illuminant = false, doesn't occur for self-luminous displays + // static final ViewingConditions DEFAULT = + // ViewingConditions.make( + // CamUtils.WHITE_POINT_D65, + // (float) (200.0f / Math.PI * CamUtils.yFromLStar(50.0f) / 100.f), 50.0f, 2.0f, + // false); + + // private final float mAw; + // private final float mNbb; + // private final float mNcb; + // private final float mC; + // private final float mNc; + // private final float mN; + // private final float[] mRgbD; + // private final float mFl; + // private final float mFlRoot; + // private final float mZ; + + // float getAw() { + // return mAw; + // } + + // float getN() { + // return mN; + // } + + // float getNbb() { + // return mNbb; + // } + + // float getNcb() { + // return mNcb; + // } + + // float getC() { + // return mC; + // } + + // float getNc() { + // return mNc; + // } + + // @NonNull + // float[] getRgbD() { + // return mRgbD; + // } + + // float getFl() { + // return mFl; + // } + + // float getFlRoot() { + // return mFlRoot; + // } + + // float getZ() { + // return mZ; + // } + + // private ViewingConditions(float n, float aw, float nbb, float ncb, float c, float nc, + // float[] rgbD, float fl, float fLRoot, float z) { + // mN = n; + // mAw = aw; + // mNbb = nbb; + // mNcb = ncb; + // mC = c; + // mNc = nc; + // mRgbD = rgbD; + // mFl = fl; + // mFlRoot = fLRoot; + // mZ = z; + // } + + // /** Create a custom camFrame. */ + // @NonNull + // static ViewingConditions make(@NonNull float[] whitepoint, float adaptingLuminance, + // float backgroundLstar, float surround, boolean discountingIlluminant) { + // // Transform white point XYZ to 'cone'/'rgb' responses + // float[][] matrix = CamUtils.XYZ_TO_CAM16RGB; + // float[] xyz = whitepoint; + // float rW = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]); + // float gW = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]); + // float bW = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]); + + // // Scale input surround, domain (0, 2), to CAM16 surround, domain (0.8, 1.0) + // float f = 0.8f + (surround / 10.0f); + // // "Exponential non-linearity" + // float c = (f >= 0.9) ? CamUtils.lerp(0.59f, 0.69f, ((f - 0.9f) * 10.0f)) : CamUtils.lerp( + // 0.525f, 0.59f, ((f - 0.8f) * 10.0f)); + // // Calculate degree of adaptation to illuminant + // float d = discountingIlluminant ? 1.0f : f * (1.0f - ((1.0f / 3.6f) * (float) Math.exp( + // (-adaptingLuminance - 42.0f) / 92.0f))); + // // Per Li et al, if D is greater than 1 or less than 0, set it to 1 or 0. + // d = (d > 1.0) ? 1.0f : (d < 0.0) ? 0.0f : d; + // // Chromatic induction factor + // float nc = f; + + // // Cone responses to the whitepoint, adjusted for illuminant discounting. + // // + // // Why use 100.0 instead of the white point's relative luminance? + // // + // // Some papers and implementations, for both CAM02 and CAM16, use the Y + // // value of the reference white instead of 100. Fairchild's Color Appearance + // // Models (3rd edition) notes that this is in error: it was included in the + // // CIE 2004a report on CIECAM02, but, later parts of the conversion process + // // account for scaling of appearance relative to the white point relative + // // luminance. This part should simply use 100 as luminance. + // float[] rgbD = new float[]{d * (100.0f / rW) + 1.0f - d, d * (100.0f / gW) + 1.0f - d, + // d * (100.0f / bW) + 1.0f - d}; + // // Luminance-level adaptation factor + // float k = 1.0f / (5.0f * adaptingLuminance + 1.0f); + // float k4 = k * k * k * k; + // float k4F = 1.0f - k4; + // float fl = (k4 * adaptingLuminance) + (0.1f * k4F * k4F * (float) Math.cbrt( + // 5.0 * adaptingLuminance)); + + // // Intermediate factor, ratio of background relative luminance to white relative luminance + // float n = CamUtils.yFromLStar(backgroundLstar) / whitepoint[1]; + + // // Base exponential nonlinearity + // // note Schlomer 2018 has a typo and uses 1.58, the correct factor is 1.48 + // float z = 1.48f + (float) Math.sqrt(n); + + // // Luminance-level induction factors + // float nbb = 0.725f / (float) Math.pow(n, 0.2); + // float ncb = nbb; + + // // Discounted cone responses to the white point, adjusted for post-chromatic + // // adaptation perceptual nonlinearities. + // float[] rgbAFactors = new float[]{(float) Math.pow(fl * rgbD[0] * rW / 100.0, 0.42), + // (float) Math.pow(fl * rgbD[1] * gW / 100.0, 0.42), (float) Math.pow( + // fl * rgbD[2] * bW / 100.0, 0.42)}; + + // float[] rgbA = new float[]{(400.0f * rgbAFactors[0]) / (rgbAFactors[0] + 27.13f), + // (400.0f * rgbAFactors[1]) / (rgbAFactors[1] + 27.13f), + // (400.0f * rgbAFactors[2]) / (rgbAFactors[2] + 27.13f)}; + + // float aw = ((2.0f * rgbA[0]) + rgbA[1] + (0.05f * rgbA[2])) * nbb; + + // return new ViewingConditions(n, aw, nbb, ncb, c, nc, rgbD, fl, (float) Math.pow(fl, 0.25), + // z); + // } + // } }