Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2024, Google LLC, OpenSavvy and contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package opensavvy.material3.colors.hct
import opensavvy.material3.colors.utils.Color.Companion.whitePointD65
import opensavvy.material3.colors.utils.Color.Companion.yFromLstar
import opensavvy.material3.colors.utils.clampDouble
import opensavvy.material3.colors.utils.lerp
import kotlin.math.*
/**
* In traditional color spaces, a color can be identified solely by the observer's measurement of
* the color. Color appearance models such as CAM16 also use information about the environment where
* the color was observed, known as the viewing conditions.
*
*
* For example, white under the traditional assumption of a midday sun white point is accurately
* measured as a slightly chromatic blue by CAM16. (roughly, hue 203, chroma 3, lightness 100)
*
*
* This class caches intermediate values of the CAM16 conversion process that depend only on
* viewing conditions, enabling speed ups.
*/
class ViewingConditions
/**
* Parameters are intermediate values of the CAM16 conversion process. Their names are shorthand
* for technical color science terminology, this class would not benefit from documenting them
* individually. A brief overview is available in the CAM16 specification, and a complete overview
* requires a color science textbook, such as Fairchild's Color Appearance Models.
*/ private constructor(
val n: Double,
val aw: Double,
val nbb: Double,
val ncb: Double,
val c: Double,
val nc: Double,
val rgbD: DoubleArray,
val fl: Double,
val flRoot: Double,
val z: Double,
) {
companion object {
/** sRGB-like viewing conditions. */
val DEFAULT: ViewingConditions = defaultWithBackgroundLstar(50.0)
/**
* Create ViewingConditions from a simple, physically relevant, set of parameters.
*
* @param whitePoint White point, measured in the XYZ color space. default = D65, or sunny day
* afternoon
* @param adaptingLuminance The luminance of the adapting field. Informally, how bright it is in
* the room where the color is viewed. Can be calculated from lux by multiplying lux by
* 0.0586. default = 11.72, or 200 lux.
* @param backgroundLstar The lightness of the area surrounding the color. measured by L* in
* L*a*b*. default = 50.0
* @param surround A general description of the lighting surrounding the color. 0 is pitch dark,
* like watching a movie in a theater. 1.0 is a dimly light room, like watching TV at home at
* night. 2.0 means there is no difference between the lighting on the color and around it.
* default = 2.0
* @param discountingIlluminant Whether the eye accounts for the tint of the ambient lighting,
* such as knowing an apple is still red in green light. default = false, the eye does not
* perform this process on self-luminous objects like displays.
*/
fun make(
whitePoint: DoubleArray,
adaptingLuminance: Double,
backgroundLstar: Double,
surround: Double,
discountingIlluminant: Boolean,
): ViewingConditions {
// A background of pure black is non-physical and leads to infinities that represent the idea
// that any color viewed in pure black can't be seen.
var backgroundLstar = backgroundLstar
backgroundLstar = max(0.1, backgroundLstar)
// Transform white point XYZ to 'cone'/'rgb' responses
val matrix = Cam16.XYZ_TO_CAM16RGB
val xyz = whitePoint
val rW = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2])
val gW = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2])
val bW = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2])
val f = 0.8 + (surround / 10.0)
val c =
if ((f >= 0.9))
lerp(0.59, 0.69, ((f - 0.9) * 10.0))
else
lerp(0.525, 0.59, ((f - 0.8) * 10.0))
var d =
if (discountingIlluminant)
1.0
else
f * (1.0 - ((1.0 / 3.6) * exp((-adaptingLuminance - 42.0) / 92.0)))
d = clampDouble(0.0, 1.0, d)
val nc = f
val rgbD =
doubleArrayOf(d * (100.0 / rW) + 1.0 - d, d * (100.0 / gW) + 1.0 - d, d * (100.0 / bW) + 1.0 - d
)
val k = 1.0 / (5.0 * adaptingLuminance + 1.0)
val k4 = k * k * k * k
val k4F = 1.0 - k4
val fl = (k4 * adaptingLuminance) + (0.1 * k4F * k4F * cbrt(5.0 * adaptingLuminance))
val n = (yFromLstar(backgroundLstar) / whitePoint[1])
val z = 1.48 + sqrt(n)
val nbb = 0.725 / n.pow(0.2)
val ncb = nbb
val rgbAFactors =
doubleArrayOf((fl * rgbD[0] * rW / 100.0).pow(0.42),
(fl * rgbD[1] * gW / 100.0).pow(0.42),
(fl * rgbD[2] * bW / 100.0).pow(0.42)
)
val rgbA =
doubleArrayOf((400.0 * rgbAFactors[0]) / (rgbAFactors[0] + 27.13),
(400.0 * rgbAFactors[1]) / (rgbAFactors[1] + 27.13),
(400.0 * rgbAFactors[2]) / (rgbAFactors[2] + 27.13)
)
val aw = ((2.0 * rgbA[0]) + rgbA[1] + (0.05 * rgbA[2])) * nbb
return ViewingConditions(n, aw, nbb, ncb, c, nc, rgbD, fl, fl.pow(0.25), z)
}
/**
* Create sRGB-like viewing conditions with a custom background lstar.
*
*
* Default viewing conditions have a lstar of 50, midgray.
*/
fun defaultWithBackgroundLstar(lstar: Double): ViewingConditions {
return make(
whitePointD65,
(200.0 / PI * yFromLstar(50.0) / 100f),
lstar,
2.0,
false)
}
}
}