diff --git a/app/src/main/java/a/a/a/lC.java b/app/src/main/java/a/a/a/lC.java index ef5072e0f6..5fe93fe3b8 100644 --- a/app/src/main/java/a/a/a/lC.java +++ b/app/src/main/java/a/a/a/lC.java @@ -164,6 +164,7 @@ public static HashMap b(String str) { public static void b(String str, HashMap hashMap) { HashMap hashMap2 = hashMap; + String str1 = "color_background"; String str2 = "color_control_highlight"; String str3 = "color_primary_dark"; String str4 = "color_primary"; @@ -208,6 +209,7 @@ public static void b(String str, HashMap hashMap) { a.put(str5, hashMap2.get(str5)); a.put(str4, hashMap2.get(str4)); a.put(str3, hashMap2.get(str3)); + a.put(str1, hashMap2.get(str1)); path = str2; a.put(path, hashMap2.get(path)); path = str15; diff --git a/app/src/main/java/a/a/a/yq.java b/app/src/main/java/a/a/a/yq.java index c487f7833a..75aea47cd7 100644 --- a/app/src/main/java/a/a/a/yq.java +++ b/app/src/main/java/a/a/a/yq.java @@ -110,6 +110,8 @@ public class yq { public final int colorControlNormal; + public final int colorBackground; + public final String versionCode; public final String versionName; @@ -227,6 +229,7 @@ public yq(Context context, String myscFolderPath, HashMap metada colorPrimaryDark = yB.a(metadata, ProjectFile.COLOR_PRIMARY_DARK, getDefaultColor(ProjectFile.COLOR_PRIMARY_DARK)); colorControlHighlight = yB.a(metadata, ProjectFile.COLOR_CONTROL_HIGHLIGHT, getDefaultColor(ProjectFile.COLOR_CONTROL_HIGHLIGHT)); colorControlNormal = yB.a(metadata, ProjectFile.COLOR_CONTROL_NORMAL, getDefaultColor(ProjectFile.COLOR_CONTROL_NORMAL)); + colorBackground = yB.a(metadata, ProjectFile.COLOR_BACKGROUND, getDefaultColor(ProjectFile.COLOR_BACKGROUND)); projectSettings = new ProjectSettings(sc_id); fileUtil = new oB(true); @@ -837,11 +840,13 @@ public ArrayList a(hC projectFileManager, eC projectDataManager, Bu colorsFileBuilder.addColor("colorAccent", String.format("#%06X", colorAccent & 0xffffff)); colorsFileBuilder.addColor("colorControlHighlight", String.format("#%06X", colorControlHighlight & 0xffffff)); colorsFileBuilder.addColor("colorControlNormal", String.format("#%06X", colorControlNormal & 0xffffff)); + colorsFileBuilder.addColor("colorBackground", String.format("#%06X", colorBackground & 0xffffff)); srcCodeBeans.add(new SrcCodeBean("colors.xml", CommandBlock.applyCommands("colors.xml", colorsFileBuilder.toCode()))); XmlBuilderHelper stylesFileBuilder = new XmlBuilderHelper(); stylesFileBuilder.addStyle("AppTheme", "Theme.MaterialComponents.Light.NoActionBar" + (useNewMaterialComponentsTheme ? "" : ".Bridge")); + stylesFileBuilder.addItemToStyle("AppTheme", "android:colorBackground", "@color/colorBackground"); stylesFileBuilder.addItemToStyle("AppTheme", "colorPrimary", "@color/colorPrimary"); stylesFileBuilder.addItemToStyle("AppTheme", "colorPrimaryDark", "@color/colorPrimaryDark"); stylesFileBuilder.addItemToStyle("AppTheme", "colorAccent", "@color/colorAccent"); @@ -857,18 +862,21 @@ public ArrayList a(hC projectFileManager, eC projectDataManager, Bu } else { XmlBuilderHelper stylesFileBuilder = new XmlBuilderHelper(); stylesFileBuilder.addStyle("AppTheme", "@android:style/Theme.Material.Light.DarkActionBar"); + stylesFileBuilder.addItemToStyle("AppTheme", "android:colorBackground", "@color/colorBackground"); stylesFileBuilder.addItemToStyle("AppTheme", "android:colorPrimary", "@color/colorPrimary"); stylesFileBuilder.addItemToStyle("AppTheme", "android:colorPrimaryDark", "@color/colorPrimaryDark"); stylesFileBuilder.addItemToStyle("AppTheme", "android:colorAccent", "@color/colorAccent"); stylesFileBuilder.addItemToStyle("AppTheme", "android:colorControlHighlight", "@color/colorControlHighlight"); stylesFileBuilder.addItemToStyle("AppTheme", "android:colorControlNormal", "@color/colorControlNormal"); stylesFileBuilder.addStyle("FullScreen", "@android:style/Theme.Material.Light.NoActionBar.Fullscreen"); + stylesFileBuilder.addItemToStyle("FullScreen", "android:colorBackground", "@color/colorBackground"); stylesFileBuilder.addItemToStyle("FullScreen", "android:colorPrimary", "@color/colorPrimary"); stylesFileBuilder.addItemToStyle("FullScreen", "android:colorPrimaryDark", "@color/colorPrimaryDark"); stylesFileBuilder.addItemToStyle("FullScreen", "android:colorAccent", "@color/colorAccent"); stylesFileBuilder.addItemToStyle("FullScreen", "android:colorControlHighlight", "@color/colorControlHighlight"); stylesFileBuilder.addItemToStyle("FullScreen", "android:colorControlNormal", "@color/colorControlNormal"); stylesFileBuilder.addStyle("NoActionBar", "@android:style/Theme.Material.Light.NoActionBar"); + stylesFileBuilder.addItemToStyle("NoActionBar", "android:colorBackground", "@color/colorBackground"); stylesFileBuilder.addItemToStyle("NoActionBar", "android:colorPrimary", "@color/colorPrimary"); stylesFileBuilder.addItemToStyle("NoActionBar", "android:colorPrimaryDark", "@color/colorPrimaryDark"); stylesFileBuilder.addItemToStyle("NoActionBar", "android:colorAccent", "@color/colorAccent"); @@ -880,6 +888,7 @@ public ArrayList a(hC projectFileManager, eC projectDataManager, Bu CommandBlock.applyCommands("styles.xml", stylesFileBuilder.toCode()))); XmlBuilderHelper colorsFileBuilder = new XmlBuilderHelper(); + colorsFileBuilder.addColor("colorBackground", String.format("#%06X", colorBackground & 0xffffff)); colorsFileBuilder.addColor("colorPrimary", String.format("#%06X", colorPrimary & 0xffffff)); colorsFileBuilder.addColor("colorPrimaryDark", String.format("#%06X", colorPrimaryDark & 0xffffff)); colorsFileBuilder.addColor("colorAccent", String.format("#%06X", colorAccent & 0xffffff)); 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/com/besome/sketch/projects/MyProjectSettingActivity.java b/app/src/main/java/com/besome/sketch/projects/MyProjectSettingActivity.java index 3e3b0ddec3..7d9612176d 100644 --- a/app/src/main/java/com/besome/sketch/projects/MyProjectSettingActivity.java +++ b/app/src/main/java/com/besome/sketch/projects/MyProjectSettingActivity.java @@ -1,5 +1,11 @@ package com.besome.sketch.projects; +import static mod.hey.studios.util.ProjectFile.COLOR_ACCENT; +import static mod.hey.studios.util.ProjectFile.COLOR_BACKGROUND; +import static mod.hey.studios.util.ProjectFile.COLOR_CONTROL_HIGHLIGHT; +import static mod.hey.studios.util.ProjectFile.COLOR_CONTROL_NORMAL; +import static mod.hey.studios.util.ProjectFile.COLOR_PRIMARY; +import static mod.hey.studios.util.ProjectFile.COLOR_PRIMARY_DARK; import static mod.hey.studios.util.ProjectFile.getDefaultColor; import android.content.Context; @@ -57,8 +63,8 @@ public class MyProjectSettingActivity extends BaseAppCompatActivity implements V public MyprojectSettingBinding binding; private static final int REQUEST_CODE_CREATE_ICON = 200212; - private final String[] themeColorKeys = {"color_accent", "color_primary", "color_primary_dark", "color_control_highlight", "color_control_normal"}; - private final String[] themeColorLabels = {"colorAccent", "colorPrimary", "colorPrimaryDark", "colorControlHighlight", "colorControlNormal"}; + private final String[] themeColorKeys = {COLOR_ACCENT, COLOR_PRIMARY, COLOR_PRIMARY_DARK, COLOR_CONTROL_HIGHLIGHT, COLOR_CONTROL_NORMAL, COLOR_BACKGROUND}; + private final String[] themeColorLabels = {"colorAccent", "colorPrimary", "colorPrimaryDark", "colorControlHighlight", "colorControlNormal", "colorBackground"}; private final int[] projectThemeColors = new int[themeColorKeys.length]; private UB projectPackageNameValidator; private VB projectNameValidator; @@ -116,6 +122,7 @@ public void onCreate(Bundle savedInstanceState) { projectThemeColors[2] = getDefaultColor(ProjectFile.COLOR_PRIMARY_DARK); projectThemeColors[3] = getDefaultColor(ProjectFile.COLOR_CONTROL_HIGHLIGHT); projectThemeColors[4] = getDefaultColor(ProjectFile.COLOR_CONTROL_NORMAL); + projectThemeColors[5] = getDefaultColor(ProjectFile.COLOR_BACKGROUND); for (int i = 0; i < themeColorKeys.length; i++) { ThemeColorView colorView = new ThemeColorView(this, i); 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..f4f5c87f6e 100644 --- a/app/src/main/java/mod/hey/studios/util/ProjectFile.java +++ b/app/src/main/java/mod/hey/studios/util/ProjectFile.java @@ -1,13 +1,20 @@ package mod.hey.studios.util; +import static pro.sketchware.utility.ThemeUtils.isDarkThemeEnabled; + +import android.content.Context; import android.graphics.Color; import android.os.Build; +import androidx.core.content.res.ColorStateListInflaterCompat; + import pro.sketchware.SketchApplication; import a.a.a.lC; import a.a.a.yB; +import java.lang.reflect.Method; + public class ProjectFile { public static final String COLOR_ACCENT = "color_accent"; public static final String COLOR_PRIMARY = "color_primary"; @@ -15,6 +22,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,21 +52,35 @@ 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(); + final boolean isDark = isDarkThemeEnabled(ctx); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - // Android 12+ (API level 31): Use Material 3 dynamic colors 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(isDark ? android.R.color.system_accent1_100 : android.R.color.system_accent1_900); + case COLOR_BACKGROUND -> + getLStaredColor(ctx, android.R.color.system_neutral2_600, + 0xfffafafa, isDark ? 6.0f : 98.0f); + 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"); - default -> Color.parseColor("#ff2196f3"); + case COLOR_CONTROL_HIGHLIGHT -> 0x202196f3; + case COLOR_BACKGROUND -> isDark ? 0xff121212 : 0xfffafafa; + default -> 0xff2196f3; }; } } + + private static int getLStaredColor(Context ctx, int colorId, int defaultColor, float lStar) { + try { + Method method = ColorStateListInflaterCompat.class.getDeclaredMethod( + "modulateColorAlpha", int.class, float.class, float.class); + method.setAccessible(true); + return (int) method.invoke(ColorStateListInflaterCompat.class, ctx.getColor(colorId), 1.0f, lStar); + } catch (Exception e) { + e.printStackTrace(); + return defaultColor; + } + } } diff --git a/app/src/main/java/mod/remaker/util/CamUtils.java b/app/src/main/java/mod/remaker/util/CamUtils.java deleted file mode 100644 index 710b1d0758..0000000000 --- a/app/src/main/java/mod/remaker/util/CamUtils.java +++ /dev/null @@ -1,379 +0,0 @@ -package mod.remaker.util; - -import androidx.annotation.ColorInt; -import androidx.annotation.FloatRange; -import androidx.annotation.NonNull; -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(); - - // 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); - } - } -}