diff --git a/docs/api/en/math/Color.html b/docs/api/en/math/Color.html index 4e4cbcfa43ecae..00d43581cdae90 100644 --- a/docs/api/en/math/Color.html +++ b/docs/api/en/math/Color.html @@ -161,13 +161,13 @@

[method:this fromBufferAttribute]( [param:BufferAttribute attribute], [param Sets this color's components from the [page:BufferAttribute attribute].

-

[method:Integer getHex]()

+

[method:Integer getHex]( [param:string colorSpace] = SRGBColorSpace )

Returns the hexadecimal value of this color.

-

[method:String getHexString]()

+

[method:String getHexString]( [param:string colorSpace] = SRGBColorSpace )

Returns the hexadecimal value of this color as a string (for example, 'FFFFFF').

-

[method:Object getHSL]( [param:Object target] )

+

[method:Object getHSL]( [param:Object target], [param:string colorSpace] = LinearSRGBColorSpace )

[page:Object target] — the result will be copied into this Object. Adds h, s and l keys to the object (if not already present).

@@ -180,7 +180,7 @@

[method:Object getHSL]( [param:Object target] )

-

[method:String getStyle]()

+

[method:String getStyle]( [param:string colorSpace] = SRGBColorSpace )

Returns the value of this color as a CSS style string. Example: 'rgb(255,0,0)'.

[method:this lerp]( [param:Color color], [param:Float alpha] )

@@ -237,14 +237,14 @@

[method:this set]( [param:Color_Hex_or_String value] )

Delegates to [page:.copy], [page:.setStyle], or [page:.setHex] depending on input type.

-

[method:this setHex]( [param:Integer hex] )

+

[method:this setHex]( [param:Integer hex], [param:string colorSpace] = SRGBColorSpace )

[page:Integer hex] — [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet hexadecimal triplet] format.

Sets this color from a hexadecimal value.

-

[method:this setHSL]( [param:Float h], [param:Float s], [param:Float l] )

+

[method:this setHSL]( [param:Float h], [param:Float s], [param:Float l], [param:string colorSpace] = LinearSRGBColorSpace )

[page:Float h] — hue value between 0.0 and 1.0
[page:Float s] — saturation value between 0.0 and 1.0
@@ -253,7 +253,7 @@

[method:this setHSL]( [param:Float h], [param:Float s], [param:Float l] ) -

[method:this setRGB]( [param:Float r], [param:Float g], [param:Float b] )

+

[method:this setRGB]( [param:Float r], [param:Float g], [param:Float b], [param:string colorSpace] = LinearSRGBColorSpace )

[page:Float r] — Red channel value between 0.0 and 1.0.
[page:Float g] — Green channel value between 0.0 and 1.0.
@@ -269,7 +269,7 @@

[method:this setScalar]( [param:Float scalar] )

Sets all three color components to the value [page:Float scalar].

-

[method:this setStyle]( [param:String style] )

+

[method:this setStyle]( [param:String style], [param:string colorSpace] = SRGBColorSpace )

[page:String style] — color as a CSS-style string.

@@ -288,7 +288,7 @@

[method:this setStyle]( [param:String style] )

Note that for X11 color names, multiple words such as Dark Orange become the string 'darkorange'.

-

[method:this setColorName]( [param:String style] )

+

[method:this setColorName]( [param:String style], [param:string colorSpace] = SRGBColorSpace )

[page:String style] — color name ( from [link:https://en.wikipedia.org/wiki/X11_color_names#Color_name_chart X11 color names] ).

diff --git a/docs/api/zh/math/Color.html b/docs/api/zh/math/Color.html index e7cb495186cb51..7c76c88121c3dc 100644 --- a/docs/api/zh/math/Color.html +++ b/docs/api/zh/math/Color.html @@ -161,13 +161,13 @@

[method:this fromBufferAttribute]( [param:BufferAttribute attribute], [param 根据参数 [page:BufferAttribute attribute] 设置该颜色。

-

[method:Integer getHex]()

+

[method:Integer getHex]( [param:string colorSpace] = SRGBColorSpace )

返回此颜色的十六进制值。

-

[method:String getHexString]()

+

[method:String getHexString]( [param:string colorSpace] = SRGBColorSpace )

将此颜色的十六进制值作为字符串返回 (例如, 'FFFFFF')。

-

[method:Object getHSL]( [param:Object target] )

+

[method:Object getHSL]( [param:Object target], [param:string colorSpace] = LinearSRGBColorSpace )

[page:Object target] — 结果将复制到这个对象中。向对象添加h、s和l键(如果不存在)。

@@ -179,7 +179,7 @@

[method:Object getHSL]( [param:Object target] )

-

[method:String getStyle]()

+

[method:String getStyle]( [param:string colorSpace] = SRGBColorSpace )

以CSS样式字符串的形式返回该颜色的值。例如:“rgb(255,0,0)”。

[method:this lerp]( [param:Color color], [param:Float alpha] )

@@ -231,7 +231,7 @@

[method:this set]( [param:Color_Hex_or_String value] )

根据输入类型,将会委托给 [page:.copy], [page:.setStyle], 或者 [page:.setHex] 函数处理。

-

[method:this setHex]( [param:Integer hex] )

+

[method:this setHex]( [param:Integer hex], [param:string colorSpace] = SRGBColorSpace )

[page:Integer hex] — [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet hexadecimal triplet] 格式。

@@ -239,7 +239,7 @@

[method:this setHex]( [param:Integer hex] )

-

[method:this setHSL]( [param:Float h], [param:Float s], [param:Float l] )

+

[method:this setHSL]( [param:Float h], [param:Float s], [param:Float l], [param:string colorSpace] = LinearSRGBColorSpace )

[page:Float h] — 色相值处于0到1之间。hue value between 0.0 and 1.0
[page:Float s] — 饱和度值处于0到1之间。
@@ -248,7 +248,7 @@

[method:this setHSL]( [param:Float h], [param:Float s], [param:Float l] ) -

[method:this setRGB]( [param:Float r], [param:Float g], [param:Float b] )

+

[method:this setRGB]( [param:Float r], [param:Float g], [param:Float b], [param:string colorSpace] = LinearSRGBColorSpace )

[page:Float r] — 红色通道的值在0到1之间。
[page:Float g] — 绿色通道的值在0到1之间。
@@ -264,7 +264,7 @@

[method:this setScalar]( [param:Float scalar] )

将颜色的RGB值都设为该 [page:Float scalar] 的值。

-

[method:this setStyle]( [param:String style] )

+

[method:this setStyle]( [param:String style], [param:string colorSpace] = SRGBColorSpace )

[page:String style] — 颜色css样式的字符串

@@ -283,7 +283,7 @@

[method:this setStyle]( [param:String style] )

注意,对于X11颜色名称,多个单词(如暗橙色)变成字符串“darkorange”。

-

[method:this setColorName]( [param:String style] )

+

[method:this setColorName]( [param:String style], [param:string colorSpace] = SRGBColorSpace )

[page:String style] — 颜色名字的英文单词 ( 具体请查阅 [link:https://en.wikipedia.org/wiki/X11_color_names#Color_name_chart X11 color names] )

diff --git a/src/Three.js b/src/Three.js index 6af1dec2990343..605a974c9b6fe7 100644 --- a/src/Three.js +++ b/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'; diff --git a/src/constants.js b/src/constants.js index cccad2ddc33a7c..6291ba78fd65ad 100644 --- a/src/constants.js +++ b/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; diff --git a/src/math/Color.js b/src/math/Color.js index 70a5a3a614083c..2b682b9b5a6bc1 100644 --- a/src/math/Color.js +++ b/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})`; } diff --git a/src/math/ColorManagement.js b/src/math/ColorManagement.js new file mode 100644 index 00000000000000..e0b3f68da5a2d1 --- /dev/null +++ b/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 ); + + }, + +};