浏览代码

Color: Add 'colorSpace' argument for getters/setters (#23392)

* ColorManagement: Initial commit

* Color: revert to original CSS whitespace.

* Constants: NonColorData → NoColorSpace
Don McCurdy 3 年之前
父节点
当前提交
5c94e668c5
共有 6 个文件被更改,包括 161 次插入41 次删除
  1. 9 9
      docs/api/en/math/Color.html
  2. 9 9
      docs/api/zh/math/Color.html
  3. 1 0
      src/Three.js
  4. 5 0
      src/constants.js
  5. 63 23
      src/math/Color.js
  6. 74 0
      src/math/ColorManagement.js

+ 9 - 9
docs/api/en/math/Color.html

@@ -161,13 +161,13 @@ const color7 = new THREE.Color( 1, 0, 0 );
 		Sets this color's components from the [page:BufferAttribute attribute].
 		</p>
 
-		<h3>[method:Integer getHex]()</h3>
+		<h3>[method:Integer getHex]( [param:string colorSpace] = SRGBColorSpace )</h3>
 		<p>Returns the hexadecimal value of this color.</p>
 
-		<h3>[method:String getHexString]()</h3>
+		<h3>[method:String getHexString]( [param:string colorSpace] = SRGBColorSpace )</h3>
 		<p>Returns the hexadecimal value of this color as a string (for example, 'FFFFFF').</p>
 
-		<h3>[method:Object getHSL]( [param:Object target] )</h3>
+		<h3>[method:Object getHSL]( [param:Object target], [param:string colorSpace] = LinearSRGBColorSpace )</h3>
 		<p>
 			[page:Object target] — the result will be copied into this Object. Adds h, s and l keys to the object (if not already present).<br /><br />
 
@@ -180,7 +180,7 @@ const color7 = new THREE.Color( 1, 0, 0 );
 
 		</p>
 
-		<h3>[method:String getStyle]()</h3>
+		<h3>[method:String getStyle]( [param:string colorSpace] = SRGBColorSpace )</h3>
 		<p>Returns the value of this color as a CSS style string. Example: 'rgb(255,0,0)'.</p>
 
 		<h3>[method:this lerp]( [param:Color color], [param:Float alpha] ) </h3>
@@ -237,14 +237,14 @@ const color7 = new THREE.Color( 1, 0, 0 );
 		Delegates to [page:.copy], [page:.setStyle], or [page:.setHex] depending on input type.
 		</p>
 
-		<h3>[method:this setHex]( [param:Integer hex] ) </h3>
+		<h3>[method:this setHex]( [param:Integer hex], [param:string colorSpace] = SRGBColorSpace ) </h3>
 		<p>
 		[page:Integer hex] — [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet hexadecimal triplet] format.<br /><br />
 
 		Sets this color from a hexadecimal value.
 		</p>
 
-		<h3>[method:this setHSL]( [param:Float h], [param:Float s], [param:Float l] ) </h3>
+		<h3>[method:this setHSL]( [param:Float h], [param:Float s], [param:Float l], [param:string colorSpace] = LinearSRGBColorSpace ) </h3>
 		<p>
 		[page:Float h] — hue value between 0.0 and 1.0 <br />
 		[page:Float s] — saturation value between 0.0 and 1.0 <br />
@@ -253,7 +253,7 @@ const color7 = new THREE.Color( 1, 0, 0 );
 		Sets color from HSL values.
 		</p>
 
-		<h3>[method:this setRGB]( [param:Float r], [param:Float g], [param:Float b] ) </h3>
+		<h3>[method:this setRGB]( [param:Float r], [param:Float g], [param:Float b], [param:string colorSpace] = LinearSRGBColorSpace ) </h3>
 		<p>
 		[page:Float r] — Red channel value between 0.0 and 1.0.<br />
 		[page:Float g] — Green channel value between 0.0 and 1.0.<br />
@@ -269,7 +269,7 @@ const color7 = new THREE.Color( 1, 0, 0 );
 		Sets all three color components to the value [page:Float scalar].
 		</p>
 
-		<h3>[method:this setStyle]( [param:String style] ) </h3>
+		<h3>[method:this setStyle]( [param:String style], [param:string colorSpace] = SRGBColorSpace ) </h3>
 		<p>
 		[page:String style] — color as a CSS-style string.<br /><br />
 
@@ -288,7 +288,7 @@ const color7 = new THREE.Color( 1, 0, 0 );
 		Note that for X11 color names, multiple words such as Dark Orange become the string 'darkorange'.
 		</p>
 
-		<h3>[method:this setColorName]( [param:String style] ) </h3>
+		<h3>[method:this setColorName]( [param:String style], [param:string colorSpace] = SRGBColorSpace ) </h3>
 		<p>
 		[page:String style] — color name ( from [link:https://en.wikipedia.org/wiki/X11_color_names#Color_name_chart X11 color names] ).<br /><br />
 

+ 9 - 9
docs/api/zh/math/Color.html

@@ -161,13 +161,13 @@
 		根据参数 [page:BufferAttribute attribute] 设置该颜色。
 		</p>
 
-		<h3>[method:Integer getHex]()</h3>
+		<h3>[method:Integer getHex]( [param:string colorSpace] = SRGBColorSpace )</h3>
 		<p>返回此颜色的十六进制值。</p>
 
-		<h3>[method:String getHexString]()</h3>
+		<h3>[method:String getHexString]( [param:string colorSpace] = SRGBColorSpace )</h3>
 		<p>将此颜色的十六进制值作为字符串返回 (例如, 'FFFFFF')。</p>
 
-		<h3>[method:Object getHSL]( [param:Object target] )</h3>
+		<h3>[method:Object getHSL]( [param:Object target], [param:string colorSpace] = LinearSRGBColorSpace )</h3>
 		<p>
 			[page:Object target] — 结果将复制到这个对象中。向对象添加h、s和l键(如果不存在)。<br /><br />
 
@@ -179,7 +179,7 @@
 
 		</p>
 
-		<h3>[method:String getStyle]()</h3>
+		<h3>[method:String getStyle]( [param:string colorSpace] = SRGBColorSpace )</h3>
 		<p>以CSS样式字符串的形式返回该颜色的值。例如:“rgb(255,0,0)”。</p>
 
 		<h3>[method:this lerp]( [param:Color color], [param:Float alpha] ) </h3>
@@ -231,7 +231,7 @@
 		根据输入类型,将会委托给 [page:.copy], [page:.setStyle], 或者 [page:.setHex] 函数处理。
 		</p>
 
-		<h3>[method:this setHex]( [param:Integer hex] ) </h3>
+		<h3>[method:this setHex]( [param:Integer hex], [param:string colorSpace] = SRGBColorSpace ) </h3>
 		<p>
 		[page:Integer hex] — [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet hexadecimal triplet] 格式。<br /><br />
 
@@ -239,7 +239,7 @@
 
 		</p>
 
-		<h3>[method:this setHSL]( [param:Float h], [param:Float s], [param:Float l] ) </h3>
+		<h3>[method:this setHSL]( [param:Float h], [param:Float s], [param:Float l], [param:string colorSpace] = LinearSRGBColorSpace ) </h3>
 		<p>
 		[page:Float h] — 色相值处于0到1之间。hue value between 0.0 and 1.0 <br />
 		[page:Float s] — 饱和度值处于0到1之间。<br />
@@ -248,7 +248,7 @@
 		采用HLS值设置此颜色。
 		</p>
 
-		<h3>[method:this setRGB]( [param:Float r], [param:Float g], [param:Float b] ) </h3>
+		<h3>[method:this setRGB]( [param:Float r], [param:Float g], [param:Float b], [param:string colorSpace] = LinearSRGBColorSpace ) </h3>
 		<p>
 		[page:Float r] — 红色通道的值在0到1之间。<br />
 		[page:Float g] — 绿色通道的值在0到1之间。<br />
@@ -264,7 +264,7 @@
 		将颜色的RGB值都设为该 [page:Float scalar] 的值。
 		</p>
 
-		<h3>[method:this setStyle]( [param:String style] ) </h3>
+		<h3>[method:this setStyle]( [param:String style], [param:string colorSpace] = SRGBColorSpace ) </h3>
 		<p>
 		[page:String style] — 颜色css样式的字符串<br /><br />
 
@@ -283,7 +283,7 @@
 		注意,对于X11颜色名称,多个单词(如暗橙色)变成字符串“darkorange”。
 		</p>
 
-		<h3>[method:this setColorName]( [param:String style] ) </h3>
+		<h3>[method:this setColorName]( [param:String style], [param:string colorSpace] = SRGBColorSpace ) </h3>
 		<p>
 		[page:String style] — 颜色名字的英文单词 ( 具体请查阅 [link:https://en.wikipedia.org/wiki/X11_color_names#Color_name_chart X11 color names] )<br /><br />
 

+ 1 - 0
src/Three.js

@@ -127,6 +127,7 @@ export { Vector3 } from './math/Vector3.js';
 export { Vector2 } from './math/Vector2.js';
 export { Quaternion } from './math/Quaternion.js';
 export { Color } from './math/Color.js';
+export { ColorManagement } from './math/ColorManagement.js';
 export { SphericalHarmonics3 } from './math/SphericalHarmonics3.js';
 export { SpotLightHelper } from './helpers/SpotLightHelper.js';
 export { SkeletonHelper } from './helpers/SkeletonHelper.js';

+ 5 - 0
src/constants.js

@@ -145,6 +145,11 @@ export const RGBADepthPacking = 3201;
 export const TangentSpaceNormalMap = 0;
 export const ObjectSpaceNormalMap = 1;
 
+// Color space string identifiers, matching CSS Color Module Level 4 and WebGPU names where available.
+export const NoColorSpace = '';
+export const SRGBColorSpace = 'srgb';
+export const LinearSRGBColorSpace = 'srgb-linear';
+
 export const ZeroStencilOp = 0;
 export const KeepStencilOp = 7680;
 export const ReplaceStencilOp = 7681;

+ 63 - 23
src/math/Color.js

@@ -1,4 +1,6 @@
 import { clamp, euclideanModulo, lerp } from './MathUtils.js';
+import { ColorManagement, SRGBToLinear, LinearToSRGB } from './ColorManagement.js';
+import { SRGBColorSpace, LinearSRGBColorSpace } from '../constants.js';
 
 const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF,
 	'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2,
@@ -25,6 +27,7 @@ const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua'
 	'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0,
 	'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 };
 
+const _rgb = { r: 0, g: 0, b: 0 };
 const _hslA = { h: 0, s: 0, l: 0 };
 const _hslB = { h: 0, s: 0, l: 0 };
 
@@ -39,15 +42,13 @@ function hue2rgb( p, q, t ) {
 
 }
 
-function SRGBToLinear( c ) {
+function toComponents( source, target ) {
 
-	return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 );
+	target.r = source.r;
+	target.g = source.g;
+	target.b = source.b;
 
-}
-
-function LinearToSRGB( c ) {
-
-	return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055;
+	return target;
 
 }
 
@@ -96,7 +97,7 @@ class Color {
 
 	}
 
-	setHex( hex ) {
+	setHex( hex, colorSpace = SRGBColorSpace ) {
 
 		hex = Math.floor( hex );
 
@@ -104,21 +105,25 @@ class Color {
 		this.g = ( hex >> 8 & 255 ) / 255;
 		this.b = ( hex & 255 ) / 255;
 
+		ColorManagement.toWorkingColorSpace( this, colorSpace );
+
 		return this;
 
 	}
 
-	setRGB( r, g, b ) {
+	setRGB( r, g, b, colorSpace = LinearSRGBColorSpace ) {
 
 		this.r = r;
 		this.g = g;
 		this.b = b;
 
+		ColorManagement.toWorkingColorSpace( this, colorSpace );
+
 		return this;
 
 	}
 
-	setHSL( h, s, l ) {
+	setHSL( h, s, l, colorSpace = LinearSRGBColorSpace ) {
 
 		// h,s,l ranges are in 0.0 - 1.0
 		h = euclideanModulo( h, 1 );
@@ -140,11 +145,13 @@ class Color {
 
 		}
 
+		ColorManagement.toWorkingColorSpace( this, colorSpace );
+
 		return this;
 
 	}
 
-	setStyle( style ) {
+	setStyle( style, colorSpace = SRGBColorSpace ) {
 
 		function handleAlpha( string ) {
 
@@ -181,6 +188,8 @@ class Color {
 						this.g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255;
 						this.b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255;
 
+						ColorManagement.toWorkingColorSpace( this, colorSpace );
+
 						handleAlpha( color[ 4 ] );
 
 						return this;
@@ -194,6 +203,8 @@ class Color {
 						this.g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100;
 						this.b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100;
 
+						ColorManagement.toWorkingColorSpace( this, colorSpace );
+
 						handleAlpha( color[ 4 ] );
 
 						return this;
@@ -214,7 +225,7 @@ class Color {
 
 						handleAlpha( color[ 4 ] );
 
-						return this.setHSL( h, s, l );
+						return this.setHSL( h, s, l, colorSpace );
 
 					}
 
@@ -236,6 +247,8 @@ class Color {
 				this.g = parseInt( hex.charAt( 1 ) + hex.charAt( 1 ), 16 ) / 255;
 				this.b = parseInt( hex.charAt( 2 ) + hex.charAt( 2 ), 16 ) / 255;
 
+				ColorManagement.toWorkingColorSpace( this, colorSpace );
+
 				return this;
 
 			} else if ( size === 6 ) {
@@ -245,6 +258,8 @@ class Color {
 				this.g = parseInt( hex.charAt( 2 ) + hex.charAt( 3 ), 16 ) / 255;
 				this.b = parseInt( hex.charAt( 4 ) + hex.charAt( 5 ), 16 ) / 255;
 
+				ColorManagement.toWorkingColorSpace( this, colorSpace );
+
 				return this;
 
 			}
@@ -253,7 +268,7 @@ class Color {
 
 		if ( style && style.length > 0 ) {
 
-			return this.setColorName( style );
+			return this.setColorName( style, colorSpace );
 
 		}
 
@@ -261,7 +276,7 @@ class Color {
 
 	}
 
-	setColorName( style ) {
+	setColorName( style, colorSpace = SRGBColorSpace ) {
 
 		// color keywords
 		const hex = _colorKeywords[ style.toLowerCase() ];
@@ -269,7 +284,7 @@ class Color {
 		if ( hex !== undefined ) {
 
 			// red
-			this.setHex( hex );
+			this.setHex( hex, colorSpace );
 
 		} else {
 
@@ -334,23 +349,27 @@ class Color {
 
 	}
 
-	getHex() {
+	getHex( colorSpace = SRGBColorSpace ) {
+
+		ColorManagement.fromWorkingColorSpace( toComponents( this, _rgb ), colorSpace );
 
-		return clamp( this.r * 255, 0, 255 ) << 16 ^ clamp( this.g * 255, 0, 255 ) << 8 ^ clamp( this.b * 255, 0, 255 ) << 0;
+		return clamp( _rgb.r * 255, 0, 255 ) << 16 ^ clamp( _rgb.g * 255, 0, 255 ) << 8 ^ clamp( _rgb.b * 255, 0, 255 ) << 0;
 
 	}
 
-	getHexString() {
+	getHexString( colorSpace = SRGBColorSpace ) {
 
-		return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );
+		return ( '000000' + this.getHex( colorSpace ).toString( 16 ) ).slice( - 6 );
 
 	}
 
-	getHSL( target ) {
+	getHSL( target, colorSpace = LinearSRGBColorSpace ) {
 
 		// h,s,l ranges are in 0.0 - 1.0
 
-		const r = this.r, g = this.g, b = this.b;
+		ColorManagement.fromWorkingColorSpace( toComponents( this, _rgb ), colorSpace );
+
+		const r = _rgb.r, g = _rgb.g, b = _rgb.b;
 
 		const max = Math.max( r, g, b );
 		const min = Math.min( r, g, b );
@@ -389,9 +408,30 @@ class Color {
 
 	}
 
-	getStyle() {
+	getRGB( target, colorSpace = LinearSRGBColorSpace ) {
+
+		ColorManagement.fromWorkingColorSpace( toComponents( this, _rgb ), colorSpace );
+
+		target.r = _rgb.r;
+		target.g = _rgb.g;
+		target.b = _rgb.b;
+
+		return target;
+
+	}
+
+	getStyle( colorSpace = SRGBColorSpace ) {
+
+		ColorManagement.fromWorkingColorSpace( toComponents( this, _rgb ), colorSpace );
+
+		if ( colorSpace !== SRGBColorSpace ) {
+
+			// Requires CSS Color Module Level 4 (https://www.w3.org/TR/css-color-4/).
+			return `color(${ colorSpace } ${ _rgb.r } ${ _rgb.g } ${ _rgb.b })`;
+
+		}
 
-		return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')';
+		return `rgb(${( _rgb.r * 255 ) | 0},${( _rgb.g * 255 ) | 0},${( _rgb.b * 255 ) | 0})`;
 
 	}
 

+ 74 - 0
src/math/ColorManagement.js

@@ -0,0 +1,74 @@
+import { SRGBColorSpace, LinearSRGBColorSpace } from '../constants.js';
+
+export function SRGBToLinear( c ) {
+
+	return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 );
+
+}
+
+export function LinearToSRGB( c ) {
+
+	return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055;
+
+}
+
+// JavaScript RGB-to-RGB transforms, defined as
+// FN[InputColorSpace][OutputColorSpace] callback functions.
+const FN = {
+	[ SRGBColorSpace ]: { [ LinearSRGBColorSpace ]: SRGBToLinear },
+	[ LinearSRGBColorSpace ]: { [ SRGBColorSpace ]: LinearToSRGB },
+};
+
+export const ColorManagement = {
+
+	legacyMode: true,
+
+	get workingColorSpace() {
+
+		return LinearSRGBColorSpace;
+
+	},
+
+	set workingColorSpace( colorSpace ) {
+
+		console.warn( 'THREE.ColorManagement: .workingColorSpace is readonly.' );
+
+	},
+
+	convert: function ( color, sourceColorSpace, targetColorSpace ) {
+
+		if ( this.legacyMode || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) {
+
+			return color;
+
+		}
+
+		if ( FN[ sourceColorSpace ] && FN[ sourceColorSpace ][ targetColorSpace ] !== undefined ) {
+
+			const fn = FN[ sourceColorSpace ][ targetColorSpace ];
+
+			color.r = fn( color.r );
+			color.g = fn( color.g );
+			color.b = fn( color.b );
+
+			return color;
+
+		}
+
+		throw new Error( 'Unsupported color space conversion.' );
+
+	},
+
+	fromWorkingColorSpace: function ( color, targetColorSpace ) {
+
+		return this.convert( color, this.workingColorSpace, targetColorSpace );
+
+	},
+
+	toWorkingColorSpace: function ( color, sourceColorSpace ) {
+
+		return this.convert( color, sourceColorSpace, this.workingColorSpace );
+
+	},
+
+};