Module:Hct/Cam16:修订间差异

来自Vocawiki
跳转到导航 跳转到搜索
优化速度(*与^的测试:Special:固定链接/6849377;分支判断与%的测试:Special:固定链接/6849439
 
星剑生留言 | 贡献
导入1个版本:​搬运自萌娘百科,依CC BY-NC-SA 3.0 CN导入
 
(没有差异)

2025年9月12日 (五) 13:45的最新版本

此模块的文档可以在Module:Hct/Cam16/doc创建

--[[
  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