置顶公告:【置顶】关于临时开启评论区所有功能的公告(2022.10.22) | 【置顶】关于本站Widget恢复使用的公告
  • 你好~!欢迎来到萌娘百科镜像站!如需查看或编辑,请联系本站管理员注册账号。
  • 本镜像站和其他萌娘百科的镜像站无关,请注意分别。

Module:Hct/Cam16

贴贴♀百科,万娘皆可贴的百科全书!转载请标注来源页面的网页链接,并声明引自贴贴百科。内容不可商用。
跳到导航 跳到搜索
Template-info.svg 模块文档  [创建] [刷新]
--[[
  Copyright 2021 Google LLC

  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.
]]

--[[
  This file has been modified. The original version is at
      https://github.com/material-foundation/material-color-utilities
]]

local bit32 = require('bit32');
local mathU = require('Module:Hct/MathUtils');
local defaultViewingConditions = require('Module:Hct/ViewingConditions');
local utils = require('Module:Hct/ColorUtils');


--[[
  CAM16, a color appearance model. Colors are not just defined by their hex code, but rather, a hex
  code and viewing conditions.

  CAM16 instances also have coordinates in the CAM16-UCS space, called J*, a*, b*, or jstar,
  astar, bstar in code. CAM16-UCS is included in the CAM16 specification, and should be used when
  measuring distances between colors.

  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)
]]
Cam16 = {}

--[[
  CAM16 instances also have coordinates in the CAM16-UCS space, called J*, a*, b*, or jstar,
  astar, bstar in code. CAM16-UCS is included in the CAM16 specification, and is used to measure
  distances between colors.
]]
function Cam16:distance(other)
	local dJ = self.jstar - other.jstar;
	local dA = self.astar - other.astar;
	local dB = self.bstar - other.bstar;
	local dEPrime = math.sqrt(dJ * dJ + dA * dA + dB * dB);
	local dE = 1.41 * math.pow(dEPrime, 0.63);
	return dE;
end

--[[
  All of the CAM16 dimensions can be calculated from 3 of the dimensions, in the following
  combinations: - {j or q} and {c, m, or s} and hue - jstar, astar, bstar Prefer using a static
  method that constructs from 3 of those dimensions. This constructor is intended for those
  methods to use to return all possible dimensions.

  @param hue for example, red, orange, yellow, green, etc.
  @param chroma informally, colorfulness / color intensity. like saturation in HSL, except
      perceptually accurate.
  @param j lightness
  @param q brightness; ratio of lightness to white point's lightness
  @param m colorfulness
  @param s saturation; ratio of chroma to white point's chroma
  @param jstar CAM16-UCS J coordinate
  @param astar CAM16-UCS a coordinate
  @param bstar CAM16-UCS b coordinate
]]
-- private constructor
local function newCam16(hue, chroma, j, q, m, s, jstar, astar, bstar)
	-- 没有太大必要实现只读,请自行避免直接修改值。
	local o = {
		hue = hue,
		chroma = chroma,
		j = j,
		q = q,
		m = m,
		s = s,
		jstar = jstar,
		astar = astar,
		bstar = bstar,
	};
	setmetatable(o, {__index = Cam16});
	return o;
end

--[[
  Create a CAM16 color from a color, assuming the color was viewed in default viewing conditions.

  @param argb ARGB representation of a color.
]]
function Cam16.fromInt(argb)
	return Cam16.fromIntInViewingConditions(argb, defaultViewingConditions);
end

--[[
  Create a CAM16 color from a color in defined viewing conditions.

  @param argb ARGB representation of a color.
  @param viewingConditions Information about the environment where the color was observed.
]]
-- The RGB => XYZ conversion matrix elements are derived scientific constants. While the values
-- may differ at runtime due to floating point imprecision, keeping the values the same, and
-- accurate, across implementations takes precedence.
function Cam16.fromIntInViewingConditions(argb, viewingConditions)
	-- Transform ARGB int to XYZ
	local red = bit32.rshift(bit32.band(argb, 0x00ff0000), 16);
	local green = bit32.rshift(bit32.band(argb, 0x0000ff00), 8);
	local blue = bit32.band(argb, 0x000000ff);
	local redL = utils.linearized(red);
	local greenL = utils.linearized(green);
	local blueL = utils.linearized(blue);
	local x = 0.41233895 * redL + 0.35762064 * greenL + 0.18051042 * blueL;
	local y = 0.2126 * redL + 0.7152 * greenL + 0.0722 * blueL;
	local z = 0.01932141 * redL + 0.11916382 * greenL + 0.95034478 * blueL;

	-- Transform XYZ to 'cone'/'rgb' responses
	local rC = 0.401288 * x + 0.650173 * y - 0.051461 * z;
	local gC = -0.250268 * x + 1.204414 * y + 0.045854 * z;
	local bC = -0.002079 * x + 0.048952 * y + 0.953127 * z;

	-- Discount illuminant
	local rD = viewingConditions.rgbD[1] * rC;
	local gD = viewingConditions.rgbD[2] * gC;
	local bD = viewingConditions.rgbD[3] * bC;

	-- Chromatic adaptation
	local rAF = math.pow(viewingConditions.fl * math.abs(rD) / 100.0, 0.42);
	local gAF = math.pow(viewingConditions.fl * math.abs(gD) / 100.0, 0.42);
	local bAF = math.pow(viewingConditions.fl * math.abs(bD) / 100.0, 0.42);
	local rA = mathU.signum(rD) * 400.0 * rAF / (rAF + 27.13);
	local gA = mathU.signum(gD) * 400.0 * gAF / (gAF + 27.13);
	local bA = mathU.signum(bD) * 400.0 * bAF / (bAF + 27.13);

	-- redness-greenness
	local a = (11.0 * rA + -12.0 * gA + bA) / 11.0;
	-- yellowness-blueness
	local b = (rA + gA - 2.0 * bA) / 9.0;

	-- auxiliary components
	local u = (20.0 * rA + 20.0 * gA + 21.0 * bA) / 20.0;
	local p2 = (40.0 * rA + 20.0 * gA + bA) / 20.0;

	-- hue
	local atan2 = math.atan2(b, a);
	local atanDegrees = math.deg(atan2);
	local hue = atanDegrees % 360.0;
	local hueRadians = math.rad(hue);

	-- achromatic response to color
	local ac = p2 * viewingConditions.nbb;

	-- CAM16 lightness and brightness
	local j = 100.0
			* math.pow(
				ac / viewingConditions.aw,
				viewingConditions.c * viewingConditions.z);
	local q = 4.0
			/ viewingConditions.c
			* math.sqrt(j / 100.0)
			* (viewingConditions.aw + 4.0)
			* viewingConditions.flRoot;

	-- CAM16 chroma, colorfulness, and saturation.
	local huePrime = (hue < 20.14) and (hue + 360) or hue;
	local eHue = 0.25 * (math.cos(math.rad(huePrime) + 2.0) + 3.8);
	local p1 = 50000.0 / 13.0 * eHue * viewingConditions.nc * viewingConditions.nbb;
	local t = (p1 * math.sqrt(a * a + b * b)) / (u + 0.305);
	local alpha =
		math.pow(1.64 - math.pow(0.29, viewingConditions.n), 0.73) * math.pow(t, 0.9);
	-- CAM16 chroma, colorfulness, saturation
	local c = alpha * math.sqrt(j / 100.0);
	local m = c * viewingConditions.flRoot;
	local s =
		50.0 * math.sqrt((alpha * viewingConditions.c) / (viewingConditions.aw + 4.0));

	-- CAM16-UCS components
	local jstar = ((1.0 + 100.0 * 0.007) * j) / (1.0 + 0.007 * j);
	local mstar = (1.0 / 0.0228) * math.log(1 + 0.0228 * m);
	local astar = mstar * math.cos(hueRadians);
	local bstar = mstar * math.sin(hueRadians);

	return newCam16(hue, c, j, q, m, s, jstar, astar, bstar);
end

--[[
  @param j CAM16 lightness
  @param c CAM16 chroma
  @param h CAM16 hue
]]
function Cam16.fromJch(j, c, h)
	return Cam16.fromJchInViewingConditions(j, c, h, defaultViewingConditions);
end

--[[
  @param j CAM16 lightness
  @param c CAM16 chroma
  @param h CAM16 hue
  @param viewingConditions Information about the environment where the color was observed.
]]
function Cam16.fromJchInViewingConditions(j, c, h, viewingConditions)
	local q = 4.0
			/ viewingConditions.c
			* math.sqrt(j / 100.0)
			* (viewingConditions.aw + 4.0)
			* viewingConditions.flRoot;
	local m = c * viewingConditions.flRoot;
	local alpha = c / math.sqrt(j / 100.0);
	local s =
		50.0 * math.sqrt((alpha * viewingConditions.c) / (viewingConditions.aw + 4.0));

	local hueRadians = math.rad(h);
	local jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j);
	local mstar = 1.0 / 0.0228 * Math.log1p(0.0228 * m);
	local astar = mstar * math.cos(hueRadians);
	local bstar = mstar * math.sin(hueRadians);
	return newCam16(h, c, j, q, m, s, jstar, astar, bstar);
end

--[[
  Create a CAM16 color from CAM16-UCS coordinates.

  @param jstar CAM16-UCS lightness.
  @param astar CAM16-UCS a dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the Y
      axis.
  @param bstar CAM16-UCS b dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the X
      axis.
]]
function Cam16.fromUcs(jstar, astar, bstar)
	return Cam16.fromUcsInViewingConditions(jstar, astar, bstar, defaultViewingConditions);
end

--[[
  Create a CAM16 color from CAM16-UCS coordinates in defined viewing conditions.

  @param jstar CAM16-UCS lightness.
  @param astar CAM16-UCS a dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the Y
      axis.
  @param bstar CAM16-UCS b dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the X
      axis.
  @param viewingConditions Information about the environment where the color was observed.
]]
function Cam16.fromUcsInViewingConditions(jstar, astar, bstar, viewingConditions)
	local m = math.sqrt(astar * astar + bstar * bstar);
	local m2 = (math.exp(m * 0.0228) - 1.0) / 0.0228;
	local c = m2 / viewingConditions.flRoot;
	local h = math.atan2(bstar, astar) * (180.0 / math.pi);
	if h < 0.0 then h = h + 360.0 end;
	local j = jstar / (1 - (jstar - 100) * 0.007);
	return Cam16.fromJchInViewingConditions(j, c, h, viewingConditions);
end

--[[
  ARGB representation of the color. Assumes the color was viewed in default viewing conditions,
  which are near-identical to the default viewing conditions for sRGB.
]]
function Cam16:toInt()
	return self:viewed(defaultViewingConditions);
end

--[[
  ARGB representation of the color, in defined viewing conditions.

  @param viewingConditions Information about the environment where the color will be viewed.
  @return ARGB representation of color
]]
function Cam16:viewed(viewingConditions)
	local alpha =
		(self.chroma == 0.0 or self.j == 0.0) and 0.0 or self.chroma / math.sqrt(self.j / 100.0);

	local t =
		math.pow(
			alpha / math.pow(1.64 - math.pow(0.29, viewingConditions.n), 0.73), 1.0 / 0.9);
	local hRad = math.rad(self.hue);

	local eHue = 0.25 * (math.cos(hRad + 2.0) + 3.8);
	local ac =
		viewingConditions.aw
			* math.pow(self.j / 100.0, 1.0 / viewingConditions.c / viewingConditions.z);
	local p1 = eHue * (50000.0 / 13.0) * viewingConditions.nc * viewingConditions.ncb;
	local p2 = ac / viewingConditions.nbb;

	local hSin = math.sin(hRad);
	local hCos = math.cos(hRad);

	local gamma = 23.0 * (p2 + 0.305) * t / (23.0 * p1 + 11.0 * t * hCos + 108.0 * t * hSin);
	local a = gamma * hCos;
	local b = gamma * hSin;
	local rA = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0;
	local gA = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0;
	local bA = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0;

	local rCBase = math.max(0, (27.13 * math.abs(rA)) / (400.0 - math.abs(rA)));
	local rC =
		mathU.signum(rA) * (100.0 / viewingConditions.fl) * math.pow(rCBase, 1.0 / 0.42);
	local gCBase = math.max(0, (27.13 * math.abs(gA)) / (400.0 - math.abs(gA)));
	local gC =
		mathU.signum(gA) * (100.0 / viewingConditions.fl) * math.pow(gCBase, 1.0 / 0.42);
	local bCBase = math.max(0, (27.13 * math.abs(bA)) / (400.0 - math.abs(bA)));
	local bC =
		mathU.signum(bA) * (100.0 / viewingConditions.fl) * math.pow(bCBase, 1.0 / 0.42);
	local rF = rC / viewingConditions.rgbD[1];
	local gF = gC / viewingConditions.rgbD[2];
	local bF = bC / viewingConditions.rgbD[3];

	local x = 1.86206786 * rF - 1.01125463 * gF + 0.14918677 * bF;
	local y = 0.38752654 * rF + 0.62144744 * gF - 0.00897398 * bF;
	local z = -0.01584150 * rF - 0.03412294 * gF + 1.04996444 * bF;

	return utils.argbFromXyz(x, y, z);
end

return Cam16