diff --git a/Extensions/3D/AmbientLight.ts b/Extensions/3D/AmbientLight.ts index 3efbb8621d..438ff67616 100644 --- a/Extensions/3D/AmbientLight.ts +++ b/Extensions/3D/AmbientLight.ts @@ -73,9 +73,7 @@ namespace gdjs { } updateStringParameter(parameterName: string, value: string): void { if (parameterName === 'color') { - this.light.color.setHex( - gdjs.PixiFiltersTools.rgbOrHexToHexNumber(value) - ); + this.light.color.setHex(gdjs.rgbOrHexStringToNumber(value)); } } updateColorParameter(parameterName: string, value: number): void { diff --git a/Extensions/3D/DirectionalLight.ts b/Extensions/3D/DirectionalLight.ts index 8b8d60599b..e481412120 100644 --- a/Extensions/3D/DirectionalLight.ts +++ b/Extensions/3D/DirectionalLight.ts @@ -94,7 +94,7 @@ namespace gdjs { updateStringParameter(parameterName: string, value: string): void { if (parameterName === 'color') { this.light.color = new THREE.Color( - gdjs.PixiFiltersTools.rgbOrHexToHexNumber(value) + gdjs.rgbOrHexStringToNumber(value) ); } if (parameterName === 'top') { diff --git a/Extensions/3D/ExponentialFog.ts b/Extensions/3D/ExponentialFog.ts index 976cd9a70a..dc39b12e0f 100644 --- a/Extensions/3D/ExponentialFog.ts +++ b/Extensions/3D/ExponentialFog.ts @@ -71,7 +71,7 @@ namespace gdjs { updateStringParameter(parameterName: string, value: string): void { if (parameterName === 'color') { this.fog.color = new THREE.Color( - gdjs.PixiFiltersTools.rgbOrHexToHexNumber(value) + gdjs.rgbOrHexStringToNumber(value) ); } } diff --git a/Extensions/3D/HemisphereLight.ts b/Extensions/3D/HemisphereLight.ts index 288042205b..ed803501d4 100644 --- a/Extensions/3D/HemisphereLight.ts +++ b/Extensions/3D/HemisphereLight.ts @@ -95,12 +95,12 @@ namespace gdjs { updateStringParameter(parameterName: string, value: string): void { if (parameterName === 'skyColor') { this.light.color = new THREE.Color( - gdjs.PixiFiltersTools.rgbOrHexToHexNumber(value) + gdjs.rgbOrHexStringToNumber(value) ); } if (parameterName === 'groundColor') { this.light.groundColor = new THREE.Color( - gdjs.PixiFiltersTools.rgbOrHexToHexNumber(value) + gdjs.rgbOrHexStringToNumber(value) ); } if (parameterName === 'top') { diff --git a/Extensions/3D/LinearFog.ts b/Extensions/3D/LinearFog.ts index b2381a76b9..bf821fe05e 100644 --- a/Extensions/3D/LinearFog.ts +++ b/Extensions/3D/LinearFog.ts @@ -76,7 +76,7 @@ namespace gdjs { updateStringParameter(parameterName: string, value: string): void { if (parameterName === 'color') { this.fog.color = new THREE.Color( - gdjs.PixiFiltersTools.rgbOrHexToHexNumber(value) + gdjs.rgbOrHexStringToNumber(value) ); } } diff --git a/Extensions/Effects/bevel-pixi-filter.ts b/Extensions/Effects/bevel-pixi-filter.ts index 8148082fad..f9df16eba7 100644 --- a/Extensions/Effects/bevel-pixi-filter.ts +++ b/Extensions/Effects/bevel-pixi-filter.ts @@ -67,14 +67,10 @@ namespace gdjs { const bevelFilter = (filter as unknown) as PIXI.filters.BevelFilter & BevelFilterExtra; if (parameterName === 'lightColor') { - bevelFilter.lightColor = gdjs.PixiFiltersTools.rgbOrHexToHexNumber( - value - ); + bevelFilter.lightColor = gdjs.rgbOrHexStringToNumber(value); } if (parameterName === 'shadowColor') { - bevelFilter.shadowColor = gdjs.PixiFiltersTools.rgbOrHexToHexNumber( - value - ); + bevelFilter.shadowColor = gdjs.rgbOrHexStringToNumber(value); } } updateColorParameter( diff --git a/Extensions/Effects/color-replace-pixi-filter.ts b/Extensions/Effects/color-replace-pixi-filter.ts index 0d004f467c..eb75a8e5e5 100644 --- a/Extensions/Effects/color-replace-pixi-filter.ts +++ b/Extensions/Effects/color-replace-pixi-filter.ts @@ -45,13 +45,9 @@ namespace gdjs { const colorReplaceFilter = (filter as unknown) as PIXI.filters.ColorReplaceFilter & ColorReplaceFilterExtra; if (parameterName === 'originalColor') { - colorReplaceFilter.originalColor = gdjs.PixiFiltersTools.rgbOrHexToHexNumber( - value - ); + colorReplaceFilter.originalColor = gdjs.rgbOrHexStringToNumber(value); } else if (parameterName === 'newColor') { - colorReplaceFilter.newColor = gdjs.PixiFiltersTools.rgbOrHexToHexNumber( - value - ); + colorReplaceFilter.newColor = gdjs.rgbOrHexStringToNumber(value); } } updateColorParameter( diff --git a/Extensions/Effects/drop-shadow-pixi-filter.ts b/Extensions/Effects/drop-shadow-pixi-filter.ts index c9c163fe3d..46c878e976 100644 --- a/Extensions/Effects/drop-shadow-pixi-filter.ts +++ b/Extensions/Effects/drop-shadow-pixi-filter.ts @@ -66,9 +66,7 @@ namespace gdjs { ) { const dropShadowFilter = (filter as unknown) as PIXI.filters.DropShadowFilter; if (parameterName === 'color') { - dropShadowFilter.color = gdjs.PixiFiltersTools.rgbOrHexToHexNumber( - value - ); + dropShadowFilter.color = gdjs.rgbOrHexStringToNumber(value); } } updateColorParameter( diff --git a/Extensions/Effects/glow-pixi-filter.ts b/Extensions/Effects/glow-pixi-filter.ts index 49c2419a09..fbc16eea0a 100644 --- a/Extensions/Effects/glow-pixi-filter.ts +++ b/Extensions/Effects/glow-pixi-filter.ts @@ -53,7 +53,7 @@ namespace gdjs { const glowFilter = (filter as unknown) as PIXI.filters.GlowFilter & GlowFilterExtra; if (parameterName === 'color') { - glowFilter.color = gdjs.PixiFiltersTools.rgbOrHexToHexNumber(value); + glowFilter.color = gdjs.rgbOrHexStringToNumber(value); } } updateColorParameter( diff --git a/Extensions/Effects/outline-pixi-filter.ts b/Extensions/Effects/outline-pixi-filter.ts index e1c1a037aa..0dce37f67b 100644 --- a/Extensions/Effects/outline-pixi-filter.ts +++ b/Extensions/Effects/outline-pixi-filter.ts @@ -41,9 +41,7 @@ namespace gdjs { ) { const outlineFilter = (filter as unknown) as PIXI.filters.OutlineFilter; if (parameterName === 'color') { - outlineFilter.color = gdjs.PixiFiltersTools.rgbOrHexToHexNumber( - value - ); + outlineFilter.color = gdjs.rgbOrHexStringToNumber(value); } } updateColorParameter( diff --git a/Extensions/ParticleSystem/particleemitterobject.ts b/Extensions/ParticleSystem/particleemitterobject.ts index 09bb9fce35..83b953547e 100644 --- a/Extensions/ParticleSystem/particleemitterobject.ts +++ b/Extensions/ParticleSystem/particleemitterobject.ts @@ -922,23 +922,17 @@ namespace gdjs { } setParticleColor1(rgbColor: string): void { - const colors = rgbColor.split(';'); - if (colors.length < 3) { - return; - } - this.setParticleRed1(parseInt(colors[0], 10)); - this.setParticleGreen1(parseInt(colors[1], 10)); - this.setParticleBlue1(parseInt(colors[2], 10)); + const colors = gdjs.rgbOrHexToRGBColor(rgbColor); + this.setParticleRed1(colors[0]); + this.setParticleGreen1(colors[1]); + this.setParticleBlue1(colors[2]); } setParticleColor2(rgbColor: string): void { - const colors = rgbColor.split(';'); - if (colors.length < 3) { - return; - } - this.setParticleRed2(parseInt(colors[0], 10)); - this.setParticleGreen2(parseInt(colors[1], 10)); - this.setParticleBlue2(parseInt(colors[2], 10)); + const colors = gdjs.rgbOrHexToRGBColor(rgbColor); + this.setParticleRed2(colors[0]); + this.setParticleGreen2(colors[1]); + this.setParticleBlue2(colors[2]); } getParticleSize1(): float { diff --git a/GDJS/Runtime/gd.ts b/GDJS/Runtime/gd.ts index fd2bd17905..82e3ded9f0 100644 --- a/GDJS/Runtime/gd.ts +++ b/GDJS/Runtime/gd.ts @@ -10,6 +10,9 @@ */ namespace gdjs { const logger = new gdjs.Logger('Engine runtime'); + const hexStringRegex = /^(#{0,1}[A-Fa-f0-9]{6})$/; + const shorthandHexStringRegex = /^(#{0,1}[A-Fa-f0-9]{3})$/; + const rgbStringRegex = /^(\d{1,3};\d{1,3};\d{1,3})/; /** * Contains functions used by events (this is a convention only, functions can actually @@ -61,7 +64,7 @@ namespace gdjs { }; /** - * Convert a Hex string to an RGB color array [r, g, b], where each component is in the range [0, 255]. + * Convert a Hex string (#124FE4) to an RGB color array [r, g, b], where each component is in the range [0, 255]. * * @param {string} hex Color hexadecimal */ @@ -74,6 +77,24 @@ namespace gdjs { : [0, 0, 0]; }; + /** + * Convert a shorthand Hex string (#1F4) to an RGB color array [r, g, b], where each component is in the range [0, 255]. + * + * @param {string} hex Color hexadecimal + */ + export const shorthandHexToRGBColor = function ( + hexString: string + ): [number, number, number] { + const hexNumber = parseInt(hexString.replace('#', ''), 16); + return Number.isFinite(hexNumber) + ? [ + 17 * ((hexNumber >> 8) & 0xf), + 17 * ((hexNumber >> 4) & 0xf), + 17 * (hexNumber & 0xf), + ] + : [0, 0, 0]; + }; + /** * Convert a RGB string ("rrr;ggg;bbb") or a Hex string ("#rrggbb") to a RGB color array ([r,g,b] with each component going from 0 to 255). * @param value The color as a RGB string or Hex string @@ -81,17 +102,28 @@ namespace gdjs { export const rgbOrHexToRGBColor = function ( value: string ): [number, number, number] { - const splitValue = value.split(';'); - // If a RGB string is provided, return the RGB object. - if (splitValue.length === 3) { - return [ - parseInt(splitValue[0], 10), - parseInt(splitValue[1], 10), - parseInt(splitValue[2], 10), - ]; + const rgbColor = extractRGBString(value); + if (rgbColor) { + const splitValue = rgbColor.split(';'); + // If a RGB string is provided, return the RGB object. + if (splitValue.length === 3) { + return [ + Math.min(255, parseInt(splitValue[0], 10)), + Math.min(255, parseInt(splitValue[1], 10)), + Math.min(255, parseInt(splitValue[2], 10)), + ]; + } } - // Otherwise, convert the Hex to RGB. - return hexToRGBColor(value); + + const hexColor = extractHexString(value); + if (hexColor) { + return hexToRGBColor(hexColor); + } + const shorthandHexColor = extractShorthandHexString(value); + if (shorthandHexColor) { + return shorthandHexToRGBColor(shorthandHexColor); + } + return [0, 0, 0]; }; /** @@ -146,6 +178,23 @@ namespace gdjs { ]; }; + export const extractHexString = (str: string): string | null => { + const matches = str.match(hexStringRegex); + if (!matches) return null; + return matches[0]; + }; + export const extractShorthandHexString = (str: string): string | null => { + const matches = str.match(shorthandHexStringRegex); + if (!matches) return null; + return matches[0]; + }; + + export const extractRGBString = (str: string): string | null => { + const matches = str.match(rgbStringRegex); + if (!matches) return null; + return matches[0]; + }; + /** * Get a random integer between 0 and max. * @param max The maximum value (inclusive). diff --git a/GDJS/Runtime/pixi-renderers/pixi-filters-tools.ts b/GDJS/Runtime/pixi-renderers/pixi-filters-tools.ts index 0642b23c6c..c4f12b12a1 100644 --- a/GDJS/Runtime/pixi-renderers/pixi-filters-tools.ts +++ b/GDJS/Runtime/pixi-renderers/pixi-filters-tools.ts @@ -52,22 +52,6 @@ namespace gdjs { _filterCreators[filterName] = filterCreator; }; - /** - * Convert a string RGB color ("rrr;ggg;bbb") or a hex string (#rrggbb) to a hex number. - * @param value The color as a RGB string or hex string - */ - export const rgbOrHexToHexNumber = function (value: string): number { - const splitValue = value.split(';'); - if (splitValue.length === 3) { - return gdjs.rgbToHexNumber( - parseInt(splitValue[0], 10), - parseInt(splitValue[1], 10), - parseInt(splitValue[2], 10) - ); - } - return parseInt(value.replace('#', '0x'), 16); - }; - /** A wrapper allowing to create an effect. */ export interface FilterCreator { /** Function to call to create the filter */ diff --git a/GDJS/Runtime/pixi-renderers/spriteruntimeobject-pixi-renderer.ts b/GDJS/Runtime/pixi-renderers/spriteruntimeobject-pixi-renderer.ts index 030d3dba68..d676323b9c 100644 --- a/GDJS/Runtime/pixi-renderers/spriteruntimeobject-pixi-renderer.ts +++ b/GDJS/Runtime/pixi-renderers/spriteruntimeobject-pixi-renderer.ts @@ -142,16 +142,8 @@ namespace gdjs { this._sprite.visible = !this._object.hidden; } - setColor(rgbColor): void { - const colors = rgbColor.split(';'); - if (colors.length < 3) { - return; - } - this._sprite.tint = gdjs.rgbToHexNumber( - parseInt(colors[0], 10), - parseInt(colors[1], 10), - parseInt(colors[2], 10) - ); + setColor(rgbOrHexColor): void { + this._sprite.tint = gdjs.rgbOrHexStringToNumber(rgbOrHexColor); } getColor() { diff --git a/GDJS/Runtime/spriteruntimeobject.ts b/GDJS/Runtime/spriteruntimeobject.ts index 7dd4b90e9e..4b9227fc35 100644 --- a/GDJS/Runtime/spriteruntimeobject.ts +++ b/GDJS/Runtime/spriteruntimeobject.ts @@ -759,10 +759,10 @@ namespace gdjs { /** * Change the tint of the sprite object. * - * @param rgbColor The color, in RGB format ("128;200;255"). + * @param rgbOrHexColor The color as a string, in RGB format ("128;200;255") or Hex format. */ - setColor(rgbColor: string): void { - this._renderer.setColor(rgbColor); + setColor(rgbOrHexColor: string): void { + this._renderer.setColor(rgbOrHexColor); } /** diff --git a/GDJS/tests/tests/colors.js b/GDJS/tests/tests/colors.js new file mode 100644 index 0000000000..01bcc50f2f --- /dev/null +++ b/GDJS/tests/tests/colors.js @@ -0,0 +1,55 @@ +describe('gdjs', function () { + it('should define gdjs', function () { + expect(gdjs).to.be.ok(); + }); + + describe('Color conversion', function () { + describe('Hex strings to RGB components', () => { + it('should convert hex strings', function () { + expect(gdjs.hexToRGBColor('#FFFFfF')).to.eql([255, 255, 255]); + expect(gdjs.hexToRGBColor('#000000')).to.eql([0, 0, 0]); + expect(gdjs.hexToRGBColor('#1245F5')).to.eql([18, 69, 245]); + }); + it('should convert hex strings without hashtag', function () { + expect(gdjs.hexToRGBColor('FFFFfF')).to.eql([255, 255, 255]); + expect(gdjs.hexToRGBColor('000000')).to.eql([0, 0, 0]); + expect(gdjs.hexToRGBColor('1245F5')).to.eql([18, 69, 245]); + }); + it('should convert shorthand hex strings', function () { + expect(gdjs.shorthandHexToRGBColor('#FfF')).to.eql([255, 255, 255]); + expect(gdjs.shorthandHexToRGBColor('#000')).to.eql([0, 0, 0]); + expect(gdjs.shorthandHexToRGBColor('#F3a')).to.eql([255, 51, 170]); + }); + it('should convert shorthand hex strings without hashtag', function () { + expect(gdjs.shorthandHexToRGBColor('FFF')).to.eql([255, 255, 255]); + expect(gdjs.shorthandHexToRGBColor('000')).to.eql([0, 0, 0]); + expect(gdjs.shorthandHexToRGBColor('F3a')).to.eql([255, 51, 170]); + }); + }); + describe.only('RGB strings to RGB components', () => { + it('should convert rgb strings', function () { + expect(gdjs.rgbOrHexToRGBColor('0;0;0')).to.eql([0, 0, 0]); + expect(gdjs.rgbOrHexToRGBColor('255;255;255')).to.eql([255, 255, 255]); + expect(gdjs.rgbOrHexToRGBColor('120;12;6')).to.eql([120, 12, 6]); + }); + it('should max rgb values', function () { + expect(gdjs.rgbOrHexToRGBColor('255;255;300')).to.eql([255, 255, 255]); + expect(gdjs.rgbOrHexToRGBColor('999;12;6')).to.eql([255, 12, 6]); + }); + it('should cut rgb values if string too long', function () { + expect(gdjs.rgbOrHexToRGBColor('255;255;200456')).to.eql([ + 255, + 255, + 200, + ]); + }); + it('should return components for black if unrecognized input', function () { + expect(gdjs.rgbOrHexToRGBColor('NaN')).to.eql([0, 0, 0]); + expect(gdjs.rgbOrHexToRGBColor('19819830803')).to.eql([0, 0, 0]); + expect(gdjs.rgbOrHexToRGBColor('Infinity')).to.eql([0, 0, 0]); + expect(gdjs.rgbOrHexToRGBColor('-4564')).to.eql([0, 0, 0]); + expect(gdjs.rgbOrHexToRGBColor('9999;12;6')).to.eql([0, 0, 0]); + }); + }); + }); +}); diff --git a/newIDE/app/src/EffectsList/index.js b/newIDE/app/src/EffectsList/index.js index ff2c308f0d..8fa34fbfa4 100644 --- a/newIDE/app/src/EffectsList/index.js +++ b/newIDE/app/src/EffectsList/index.js @@ -296,6 +296,7 @@ const Effect = React.forwardRef(