From 599fda2f11c88b2c18d0cd0b57d4adeca20db2eb Mon Sep 17 00:00:00 2001 From: Saivan Date: Sun, 25 Nov 2018 16:23:35 +1300 Subject: Updated all of the color modules and old tests are passing again This commit updates the color modules, so that the old tests pass, we just need to modify the tests to test some of the new functionality (Since there was a lot of copy and paste work done haha) Changes ======= - Updated the color module to support a number of color spaces - Made sure all of the old tests are working again --- spec/SpecRunner.html | 8 ++--- spec/spec/color.js | 100 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 77 insertions(+), 31 deletions(-) (limited to 'spec') diff --git a/spec/SpecRunner.html b/spec/SpecRunner.html index 9d84677..967f327 100644 --- a/spec/SpecRunner.html +++ b/spec/SpecRunner.html @@ -54,15 +54,15 @@ - + - + diff --git a/spec/spec/color.js b/spec/spec/color.js index 410577f..9710d69 100644 --- a/spec/spec/color.js +++ b/spec/spec/color.js @@ -1,3 +1,4 @@ + describe('Color', function() { var color @@ -5,42 +6,87 @@ describe('Color', function() { color = new SVG.Color({ r: 0, g: 102, b: 255 }) }) - it('correclty parses a rgb string', function() { - color = new SVG.Color('rgb(255,0,128)') - expect(color.r).toBe(255) - expect(color.g).toBe(0) - expect(color.b).toBe(128) - }) + describe ('construct: constructs a color in different formats', () => { - it('correclty parses a 3 digit hex string', function() { - color = new SVG.Color('#f06') - expect(color.r).toBe(255) - expect(color.g).toBe(0) - expect(color.b).toBe(102) - }) + it ('constructs a color from an object in the correct color space') - it('correclty parses a 6 digit hex string', function() { - color = new SVG.Color('#0066ff') - expect(color.r).toBe(0) - expect(color.g).toBe(102) - expect(color.b).toBe(255) - }) + it ('constructs a color from an array', () => { + let color = new SVG.Color([ 30, 24, 50 ]) + expect( color.r ).toBe( 30 ) + expect( color.g ).toBe( 24 ) + expect( color.b ).toBe( 50 ) - describe('toHex()', function() { - it('returns a hex color', function() { - expect(color.toHex()).toBe('#0066ff') }) + + it('correclty parses an rgb string', () => { + let color = new SVG.Color('rgb(255,0,128)') + expect(color.r).toBe(255) + expect(color.g).toBe(0) + expect(color.b).toBe(128) + }) + + it('correclty parses a 3 digit hex string', () => { + color = new SVG.Color('#f06') + expect(color.r).toBe(255) + expect(color.g).toBe(0) + expect(color.b).toBe(102) + }) + + it('correclty parses a 6 digit hex string', () => { + color = new SVG.Color('#0066ff') + expect(color.r).toBe(0) + expect(color.g).toBe(102) + expect(color.b).toBe(255) + }) + }) - describe('toRgb()', function() { - it('returns a rgb string color', function() { - expect(color.toRgb()).toBe('rgb(0,102,255)') + describe ('input and output: Importing and exporting colors', () => { + describe('hex()', function() { + it('returns a hex color', function() { + expect(color.hex()).toBe('#0066ff') + }) + }) + + describe('toRgb()', function() { + it('returns a rgb string color', function() { + expect(color.toRgb()).toBe('rgb(0,102,255)') + }) + }) + + describe('brightness()', function() { + it('returns the percieved brightness value of a color', function() { + expect(color.brightness()).toBe(0.346) + }) }) }) - describe('brightness()', function() { - it('returns the percieved brightness value of a color', function() { - expect(color.brightness()).toBe(0.346) + describe('color spaces: The color spaces supported by our library', () => { + + describe('lab()', () => { + it ('can convert rgb to lab') + it ('can convert from lab to rgb') + }) + + describe('lch()', () => { + it ('can convert rgb to lch') + it ('can convert from lch to rgb') + }) + + describe('hsl()', () => { + it ('can convert from rgb to hsl') + it ('can convert from hsl to rgb') + }) + + describe('xyz()', () => { + it ('can convert from rgb to xyz') + it ('can convert from xyz to rgb') + }) + + describe('cymk()', () => { + it ('can convert from rgb to cymk') + it ('can convert from cymk to rgb') }) }) + }) -- cgit v1.2.3 From 0a357dd3063bcb18ccf7de446f206e8598bea9a1 Mon Sep 17 00:00:00 2001 From: Saivan Date: Mon, 26 Nov 2018 00:18:19 +1300 Subject: Finished off the color spaces, we are all green! We now have tested and working color spaces, isn't that nice! Changes ======= - All color spaces are working --- spec/spec/color.js | 134 ++++++++++++++++++--- src/types/Color.js | 335 +++++++++++++++++++++-------------------------------- 2 files changed, 250 insertions(+), 219 deletions(-) (limited to 'spec') diff --git a/spec/spec/color.js b/spec/spec/color.js index 9710d69..d9d60b6 100644 --- a/spec/spec/color.js +++ b/spec/spec/color.js @@ -8,14 +8,30 @@ describe('Color', function() { describe ('construct: constructs a color in different formats', () => { - it ('constructs a color from an object in the correct color space') + it ('constructs a color from an object in the correct color space', () => { + + // Try in rgb + let color = new SVG.Color({ r: 255, g: 0, b: 128 }) + expect(color.r).toBe(255) + expect(color.g).toBe(0) + expect(color.b).toBe(128) + expect(color.space).toBe('rgb') + + // Try in cmyk + let color2 = new SVG.Color({ c: 20, y: 15, m: 10, k: 5 }) + expect(color2.c).toBe(20) + expect(color2.m).toBe(10) + expect(color2.y).toBe(15) + expect(color2.k).toBe(5) + expect(color2.space).toBe('cmyk') + }) it ('constructs a color from an array', () => { let color = new SVG.Color([ 30, 24, 50 ]) expect( color.r ).toBe( 30 ) expect( color.g ).toBe( 24 ) expect( color.b ).toBe( 50 ) - + expect( color.space ).toBe('rgb') }) it('correclty parses an rgb string', () => { @@ -64,29 +80,117 @@ describe('Color', function() { describe('color spaces: The color spaces supported by our library', () => { describe('lab()', () => { - it ('can convert rgb to lab') - it ('can convert from lab to rgb') + it ('can convert rgb to lab', () => { + let color = new SVG.Color( 255, 0, 128 ) + let lab = color.lab() + expect( lab.l ).toBeCloseTo( 54.88, 1 ) + expect( lab.a ).toBeCloseTo( 84.55, 1 ) + expect( lab.b ).toBeCloseTo( 4.065, 1 ) + expect( lab.space ).toBe('lab') + }) + + it ('can convert from lab to rgb', () => { + let lab = new SVG.Color( 54.88, 84.55, 4.065, 'lab' ) + let rgb = lab.rgb() + expect( rgb.r ).toBeCloseTo( 255, 0 ) + expect( rgb.g ).toBeCloseTo( 0, 0 ) + expect( rgb.b ).toBeCloseTo( 128, 0 ) + expect( rgb.space ).toBe('rgb') + }) + + it ('is invertable', () => { + let { r, g, b } = new SVG.Color( 255, 0, 128 ).lab().rgb() + expect ( r ).toBeCloseTo( 255, 0 ) + expect ( g ).toBeCloseTo( 0, 0 ) + expect ( b ).toBeCloseTo( 128, 0 ) + }) }) describe('lch()', () => { - it ('can convert rgb to lch') - it ('can convert from lch to rgb') + it ('can convert rgb to lch', () => { + let color = new SVG.Color( 255, 0, 128 ) + let lch = color.lch() + expect( lch.l ).toBeCloseTo( 54.88, 1 ) + expect( lch.c ).toBeCloseTo( 84.65, 1 ) + expect( lch.h ).toBeCloseTo( 2.75, 1 ) + expect( lch.space ).toBe('lch') + }) + + it ('can convert from lch to rgb', () => { + let lch = new SVG.Color( 54.88, 84.65, 2.75, 'lch' ) + let rgb = lch.rgb() + expect( rgb.r ).toBeCloseTo( 255, 0 ) + expect( rgb.g ).toBeCloseTo( 0, 0 ) + expect( rgb.b ).toBeCloseTo( 128, 0 ) + expect( rgb.space ).toBe('rgb') + }) + + it ('is invertable', () => { + let { r, g, b } = new SVG.Color( 255, 0, 128 ).lch().rgb() + expect ( r ).toBeCloseTo( 255, 0 ) + expect ( g ).toBeCloseTo( 0, 0 ) + expect ( b ).toBeCloseTo( 128, 0 ) + }) }) describe('hsl()', () => { - it ('can convert from rgb to hsl') - it ('can convert from hsl to rgb') - }) - describe('xyz()', () => { - it ('can convert from rgb to xyz') - it ('can convert from xyz to rgb') + it ('can convert from rgb to hsl', () => { + let color = new SVG.Color( 255, 0, 128 ) + let hsl = color.hsl() + expect( hsl.h ).toBeCloseTo( 329.88, 1 ) + expect( hsl.s ).toBeCloseTo( 100, 1 ) + expect( hsl.l ).toBeCloseTo( 50, 1 ) + expect( hsl.space ).toBe('hsl') + }) + + it ('can convert from hsl to rgb', () => { + let hsl = new SVG.Color( 329.88, 100, 50, 'hsl' ) + let rgb = hsl.rgb() + expect( rgb.r ).toBeCloseTo( 255, 0 ) + expect( rgb.g ).toBeCloseTo( 0, 0 ) + expect( rgb.b ).toBeCloseTo( 128, 0 ) + expect( rgb.space ).toBe('rgb') + }) + + it ('is invertable', () => { + let { r, g, b } = new SVG.Color( 255, 0, 128 ).hsl().rgb() + expect ( r ).toBeCloseTo( 255, 0 ) + expect ( g ).toBeCloseTo( 0, 0 ) + expect ( b ).toBeCloseTo( 128, 0 ) + }) }) - describe('cymk()', () => { - it ('can convert from rgb to cymk') - it ('can convert from cymk to rgb') + describe('cmyk()', () => { + + it ('can convert from rgb to cmyk', () => { + let color = new SVG.Color( 255, 0, 128 ) + let cmyk = color.cmyk() + expect( cmyk.c ).toBeCloseTo( 0, 1 ) + expect( cmyk.m ).toBeCloseTo( 1, 1 ) + expect( cmyk.y ).toBeCloseTo( 0.49, 1 ) + expect( cmyk.k ).toBeCloseTo( 0, 1 ) + expect( cmyk.space ).toBe('cmyk') + }) + + it ('can convert from cmyk to rgb', () => { + let color = new SVG.Color( 0, 1, 0.49, 0, 'cmyk' ) + let rgb = color.rgb() + expect( rgb.r ).toBeCloseTo( 255, -1 ) + expect( rgb.g ).toBeCloseTo( 0, -1 ) + expect( rgb.b ).toBeCloseTo( 128, -1 ) + expect( rgb.space ).toBe('rgb') + }) + + it ('is invertable', () => { + let { r, g, b } = new SVG.Color( 255, 0, 128 ).cmyk().rgb() + expect ( r ).toBeCloseTo( 255, 0 ) + expect ( g ).toBeCloseTo( 0, 0 ) + expect ( b ).toBeCloseTo( 128, 0 ) + }) + }) + }) }) diff --git a/src/types/Color.js b/src/types/Color.js index e5104b8..8a44de1 100644 --- a/src/types/Color.js +++ b/src/types/Color.js @@ -1,100 +1,91 @@ import { hex, isHex, isRgb, rgb, whitespace } from '../modules/core/regex.js' -function sixDigitHex ( hex ) { +function sixDigitHex (hex) { return hex.length === 4 ? [ '#', - hex.substring( 1, 2 ), hex.substring( 1, 2 ), - hex.substring( 2, 3 ), hex.substring( 2, 3 ), - hex.substring( 3, 4 ), hex.substring( 3, 4 ) - ].join( '' ) + hex.substring(1, 2), hex.substring(1, 2), + hex.substring(2, 3), hex.substring(2, 3), + hex.substring(3, 4), hex.substring(3, 4) + ].join('') : hex } -function componentHex ( component ) { - const integer = Math.round( component ) - const hex = integer.toString( 16 ) +function componentHex (component) { + const integer = Math.round(component) + const hex = integer.toString(16) return hex.length === 1 ? '0' + hex : hex } -function is ( object, space ) { - for ( const key of space ) { - if ( object[key] == null ) { +function is (object, space) { + for (const key of space) { + if (object[key] == null) { return false } } return true } -function getParameters ( a ) { - const params = is( a, 'rgb' ) ? { _a: a.r, _b: a.g, _c: a.b, space: 'rgb' } - : is( a, 'xyz' ) ? { _a: a.x, _b: a.y, _c: a.z, space: 'xyz' } - : is( a, 'hsl' ) ? { _a: a.h, _b: a.s, _c: a.l, space: 'hsl' } - : is( a, 'lab' ) ? { _a: a.l, _b: a.a, _c: a.b, space: 'lab' } - : is( a, 'lch' ) ? { _a: a.l, _b: a.c, _c: a.h, space: 'lch' } - : is( a, 'cmyk' ) ? { _a: a.c, _b: a.m, _c: a.y, _d: a.k, space: 'cmyk' } +function getParameters (a) { + const params = is(a, 'rgb') ? { _a: a.r, _b: a.g, _c: a.b, space: 'rgb' } + : is(a, 'xyz') ? { _a: a.x, _b: a.y, _c: a.z, space: 'xyz' } + : is(a, 'hsl') ? { _a: a.h, _b: a.s, _c: a.l, space: 'hsl' } + : is(a, 'lab') ? { _a: a.l, _b: a.a, _c: a.b, space: 'lab' } + : is(a, 'lch') ? { _a: a.l, _b: a.c, _c: a.h, space: 'lch' } + : is(a, 'cmyk') ? { _a: a.c, _b: a.m, _c: a.y, _d: a.k, space: 'cmyk' } : { _a: 0, _b: 0, _c: 0, space: 'rgb' } return params } -function cieSpace ( space ) { - if ( space === 'lab' || space === 'xyz' || space === 'lch' ) { +function cieSpace (space) { + if (space === 'lab' || space === 'xyz' || space === 'lch') { return true } else { return false } } -function hueToRgb ( p, q, t ) { - if ( t < 0 ) t += 1 - if ( t > 1 ) t -= 1 - if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t - if ( t < 1 / 2 ) return q - if ( t < 2 / 3 ) return p + ( q - p ) * ( 2 / 3 - t ) * 6 +function hueToRgb (p, q, t) { + if (t < 0) t += 1 + if (t > 1) t -= 1 + if (t < 1 / 6) return p + (q - p) * 6 * t + if (t < 1 / 2) return q + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6 return p } export default class Color { + constructor (...inputs) { + this.init(...inputs) + } - constructor ( a = 0, b = 0, c = 0, d = 0, space = 'rgb' ) { - + init (a = 0, b = 0, c = 0, d = 0, space = 'rgb') { // If the user gave us an array, make the color from it - if ( typeof a === 'number' ) { - + if (typeof a === 'number') { // Allow for the case that we don't need d... space = typeof d === 'string' ? d : space d = typeof d === 'string' ? undefined : d // Assign the values straight to the color - Object.assign( this, { _a: a, _b: b, _c: c, _d: d, space } ) - - } else if ( a instanceof Array ) { - + Object.assign(this, { _a: a, _b: b, _c: c, _d: d, space }) + } else if (a instanceof Array) { this.space = b || 'rgb' - Object.assign( this, { _a: a[0], _b: a[1], _c: a[2], _d: a[3] } ) - - } else if ( a instanceof Object ) { - + Object.assign(this, { _a: a[0], _b: a[1], _c: a[2], _d: a[3] }) + } else if (a instanceof Object) { // Set the object up and assign its values directly - const values = getParameters( a ) - Object.assign( this, values ) - - } else if ( typeof a === 'string' ) { - - if ( isRgb.test( a ) ) { - - const noWhitespace = a.replace( whitespace, '' ) - const [ _a, _b, _c ] = rgb.exec( noWhitespace ) - .slice( 1, 4 ).map( v => parseInt( v ) ) - Object.assign( this, { _a, _b, _c, space: 'rgb' } ) - - } else if ( isHex.test( a ) ) { - - const hexParse = v => parseInt( v, 16 ) - const [ , _a, _b, _c ] = hex.exec( sixDigitHex( a ) ).map( hexParse ) - Object.assign( this, { _a, _b, _c, space: 'rgb' } ) - - } else throw Error( `Unsupported string format, can't construct Color` ) + const values = getParameters(a) + Object.assign(this, values) + } else if (typeof a === 'string') { + if (isRgb.test(a)) { + const noWhitespace = a.replace(whitespace, '') + const [ _a, _b, _c ] = rgb.exec(noWhitespace) + .slice(1, 4).map(v => parseInt(v)) + Object.assign(this, { _a, _b, _c, space: 'rgb' }) + } else if (isHex.test(a)) { + const hexParse = v => parseInt(v, 16) + const [ , _a, _b, _c ] = hex.exec(sixDigitHex(a)).map(hexParse) + Object.assign(this, { _a, _b, _c, space: 'rgb' }) + } else throw Error(`Unsupported string format, can't construct Color`) } // Now add the components as a convenience @@ -104,15 +95,13 @@ export default class Color { : this.space === 'hsl' ? { h: _a, s: _b, l: _c } : this.space === 'lab' ? { l: _a, a: _b, b: _c } : this.space === 'lch' ? { l: _a, c: _b, h: _c } - : this.space === 'cmyk' ? { c: _a, y: _b, m: _c, k: _d } + : this.space === 'cmyk' ? { c: _a, m: _b, y: _c, k: _d } : {} - Object.assign( this, components ) + Object.assign(this, components) } - opacity ( opacity = 1 ) { - + opacity (opacity = 1) { this.opacity = opacity - } /* @@ -121,7 +110,7 @@ export default class Color { brightness () { const { _a: r, _b: g, _c: b } = this.rgb() - const value = ( r / 255 * 0.30 ) + ( g / 255 * 0.59 ) + ( b / 255 * 0.11 ) + const value = (r / 255 * 0.30) + (g / 255 * 0.59) + (b / 255 * 0.11) return value } @@ -130,37 +119,33 @@ export default class Color { */ rgb () { - - if ( this.space === 'rgb' ) { + if (this.space === 'rgb') { return this - - } else if ( cieSpace( this.space ) ) { - + } else if (cieSpace(this.space)) { // Convert to the xyz color space let { x, y, z } = this - if ( this.space === 'lab' || this.space === 'lch' ) { - + if (this.space === 'lab' || this.space === 'lch') { // Get the values in the lab space let { l, a, b } = this - if ( this.space === 'lch' ) { + if (this.space === 'lch') { let { c, h } = this const dToR = Math.PI / 180 - a = c * Math.cos( dToR * h ) - b = c * Math.sin( dToR * h ) + a = c * Math.cos(dToR * h) + b = c * Math.sin(dToR * h) } // Undo the nonlinear function - const yL = ( l + 16 ) / 116 - const xL = a / 500 + y - const zL = y - b / 200 + const yL = (l + 16) / 116 + const xL = a / 500 + yL + const zL = yL - b / 200 // Get the xyz values const ct = 16 / 116 const mx = 0.008856 const nm = 7.787 - x = 0.95047 * ( ( xL ** 3 > mx ) ? xL ** 3 : ( xL - ct ) / nm ) - y = 1.00000 * ( ( yL ** 3 > mx ) ? yL ** 3 : ( yL - ct ) / nm ) - z = 1.08883 * ( ( zL ** 3 > mx ) ? zL ** 3 : ( zL - ct ) / nm ) + x = 0.95047 * ((xL ** 3 > mx) ? xL ** 3 : (xL - ct) / nm) + y = 1.00000 * ((yL ** 3 > mx) ? yL ** 3 : (yL - ct) / nm) + z = 1.08883 * ((zL ** 3 > mx) ? zL ** 3 : (zL - ct) / nm) } // Convert xyz to unbounded rgb values @@ -171,72 +156,68 @@ export default class Color { // Convert the values to true rgb values let pow = Math.pow let bd = 0.0031308 - const r = ( rU > bd ) ? ( 1.055 * pow( rU, 1 / 2.4 ) - 0.055 ) : 12.92 * rU - const g = ( gU > bd ) ? ( 1.055 * pow( gU, 1 / 2.4 ) - 0.055 ) : 12.92 * gU - const b = ( bU > bd ) ? ( 1.055 * pow( bU, 1 / 2.4 ) - 0.055 ) : 12.92 * bU + const r = (rU > bd) ? (1.055 * pow(rU, 1 / 2.4) - 0.055) : 12.92 * rU + const g = (gU > bd) ? (1.055 * pow(gU, 1 / 2.4) - 0.055) : 12.92 * gU + const b = (bU > bd) ? (1.055 * pow(bU, 1 / 2.4) - 0.055) : 12.92 * bU // Make and return the color - const color = new Color( r, g, b ) + const color = new Color(255 * r, 255 * g, 255 * b) return color - - } else if ( this.space === 'hsl' ) { - - // stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion + } else if (this.space === 'hsl') { + // https://bgrins.github.io/TinyColor/docs/tinycolor.html // Get the current hsl values - const { h, s, l } = this + let { h, s, l } = this + h /= 360 + s /= 100 + l /= 100 // If we are grey, then just make the color directly - if ( s === 0 ) { - let color = new Color( l, l, l ) + if (s === 0) { + let color = new Color(l, l, l) return color } // TODO I have no idea what this does :D If you figure it out, tell me! - const q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s + const q = l < 0.5 ? l * (1 + s) : l + s - l * s const p = 2 * l - q // Get the rgb values - const r = hueToRgb( p, q, h + 1 / 3 ) - const g = hueToRgb( p, q, h ) - const b = hueToRgb( p, q, h - 1 / 3 ) + const r = 255 * hueToRgb(p, q, h + 1 / 3) + const g = 255 * hueToRgb(p, q, h) + const b = 255 * hueToRgb(p, q, h - 1 / 3) // Make a new color - const color = new Color( r, g, b ) + const color = new Color(r, g, b) return color - - } else if ( this.space === 'cmyk' ) { - + } else if (this.space === 'cmyk') { // https://gist.github.com/felipesabino/5066336 // Get the normalised cmyk values - const { _a, _b, _c, _d } = this - const [ c, m, y, k ] = [ _a, _b, _c, _d ].map( v => v / 100 ) + const { c, m, y, k } = this // Get the rgb values - const r = 1 - Math.min( 1, c * ( 1 - k ) + k ) - const g = 1 - Math.min( 1, m * ( 1 - k ) + k ) - const b = 1 - Math.min( 1, y * ( 1 - k ) + k ) + const r = 255 * (1 - Math.min(1, c * (1 - k) + k)) + const g = 255 * (1 - Math.min(1, m * (1 - k) + k)) + const b = 255 * (1 - Math.min(1, y * (1 - k) + k)) // Form the color and return it - const color = new Color( r, g, b ) + const color = new Color(r, g, b) return color - } else { return this } } lab () { - // Get the xyz color const { x, y, z } = this.xyz() // Get the lab components - const l = ( 116 * y ) - 16 - const a = 500 * ( x - y ) - const b = 200 * ( y - z ) + const l = (116 * y) - 16 + const a = 500 * (x - y) + const b = 200 * (y - z) // Construct and return a new color - const color = new Color( l, a, b, 'lab' ) + const color = new Color(l, a, b, 'lab') return color } @@ -244,25 +225,25 @@ export default class Color { // Normalise the red, green and blue values const { _a: r255, _b: g255, _c: b255 } = this.rgb() - const [ r, g, b ] = [ r255, g255, b255 ].map( v => v / 255 ) + const [ r, g, b ] = [ r255, g255, b255 ].map(v => v / 255) // Convert to the lab rgb space - const rL = ( r > 0.04045 ) ? Math.pow( ( r + 0.055 ) / 1.055, 2.4 ) : r / 12.92 - const gL = ( g > 0.04045 ) ? Math.pow( ( g + 0.055 ) / 1.055, 2.4 ) : g / 12.92 - const bL = ( b > 0.04045 ) ? Math.pow( ( b + 0.055 ) / 1.055, 2.4 ) : b / 12.92 + const rL = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92 + const gL = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92 + const bL = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92 // Convert to the xyz color space without bounding the values - const xU = ( rL * 0.4124 + gL * 0.3576 + bL * 0.1805 ) / 0.95047 - const yU = ( rL * 0.2126 + gL * 0.7152 + bL * 0.0722 ) / 1.00000 - const zU = ( rL * 0.0193 + gL * 0.1192 + bL * 0.9505 ) / 1.08883 + const xU = (rL * 0.4124 + gL * 0.3576 + bL * 0.1805) / 0.95047 + const yU = (rL * 0.2126 + gL * 0.7152 + bL * 0.0722) / 1.00000 + const zU = (rL * 0.0193 + gL * 0.1192 + bL * 0.9505) / 1.08883 // Get the proper xyz values by applying the bounding - const x = ( xU > 0.008856 ) ? Math.pow( xU, 1 / 3 ) : ( 7.787 * xU ) + 16 / 116 - const y = ( yU > 0.008856 ) ? Math.pow( yU, 1 / 3 ) : ( 7.787 * yU ) + 16 / 116 - const z = ( zU > 0.008856 ) ? Math.pow( zU, 1 / 3 ) : ( 7.787 * zU ) + 16 / 116 + const x = (xU > 0.008856) ? Math.pow(xU, 1 / 3) : (7.787 * xU) + 16 / 116 + const y = (yU > 0.008856) ? Math.pow(yU, 1 / 3) : (7.787 * yU) + 16 / 116 + const z = (zU > 0.008856) ? Math.pow(zU, 1 / 3) : (7.787 * zU) + 16 / 116 // Make and return the color - const color = new Color( x, y, z, 'xyz' ) + const color = new Color(x, y, z, 'xyz') return color } @@ -272,15 +253,15 @@ export default class Color { const { l, a, b } = this.lab() // Get the chromaticity and the hue using polar coordinates - const c = Math.sqrt( a ** 2 + b ** 2 ) - let h = 180 * Math.atan2( b, a ) / Math.PI - if ( h < 0 ) { + const c = Math.sqrt(a ** 2 + b ** 2) + let h = 180 * Math.atan2(b, a) / Math.PI + if (h < 0) { h *= -1 h = 360 - h } // Make a new color and return it - const color = new Color( l, c, h, 'lch' ) + const color = new Color(l, c, h, 'lch') return color } @@ -288,12 +269,12 @@ export default class Color { // Get the rgb values const { _a, _b, _c } = this.rgb() - const [ r, g, b ] = [ _a, _b, _c ].map( v => v / 255 ) + const [ r, g, b ] = [ _a, _b, _c ].map(v => v / 255) // Find the maximum and minimum values to get the lightness - const max = Math.max( r, g, b ) - const min = Math.min( r, g, b ) - const l = ( max + min ) / 2 + const max = Math.max(r, g, b) + const min = Math.min(r, g, b) + const l = (max + min) / 2 // If the r, g, v values are identical then we are grey const isGrey = max === min @@ -301,16 +282,16 @@ export default class Color { // Calculate the hue and saturation const delta = max - min const s = isGrey ? 0 - : l > 0.5 ? delta / ( 2 - max - min ) - : delta / ( max + min ) + : l > 0.5 ? delta / (2 - max - min) + : delta / (max + min) const h = isGrey ? 0 - : max === r ? ( ( g - b ) / delta + ( g < b ? 6 : 0 ) ) / 6 - : max === g ? ( ( b - r ) / delta + 2 ) / 6 - : max === b ? ( ( r - g ) / delta + 4 ) / 6 + : max === r ? ((g - b) / delta + (g < b ? 6 : 0)) / 6 + : max === g ? ((b - r) / delta + 2) / 6 + : max === b ? ((r - g) / delta + 4) / 6 : 0 // Construct and return the new color - const color = new Color( h, s, l, 'hsl' ) + const color = new Color(360 * h, 100 * s, 100 * l, 'hsl') return color } @@ -318,62 +299,26 @@ export default class Color { // Get the rgb values for the current color const { _a, _b, _c } = this.rgb() - const [ r, g, b ] = [ _a, _b, _c ].map( v => v / 255 ) + const [ r, g, b ] = [ _a, _b, _c ].map(v => v / 255) // Get the cmyk values in an unbounded format - const k = 100 * Math.min( 1 - r, 1 - g, 1 - b ) - const c = 100 * ( 1 - r - k ) / ( 1 - k ) - const m = 100 * ( 1 - g - k ) / ( 1 - k ) - const y = 100 * ( 1 - b - k ) / ( 1 - k ) + const k = Math.min(1 - r, 1 - g, 1 - b) + const c = (1 - r - k) / (1 - k) + const m = (1 - g - k) / (1 - k) + const y = (1 - b - k) / (1 - k) // Construct the new color - const color = new Color( c, m, y, k, 'cmyk' ) + const color = new Color(c, m, y, k, 'cmyk') return color } - /* - Modifying the color - */ - - brighten ( amount = 0.1 ) { - - } - - darken ( amount = 0.1 ) { - - } - - /* - Mixing methods - */ - - to ( otherColor, space ) { - - // Force both colors to the color of this space (or let the user decide) - space = space || this.space - - // Get the starting and ending colors - // let start = this[ space ]() - // let end = otherColor[ space ]() - - // Return a function that blends between the two colors - return function ( t ) { - - } - - } - - avearge ( otherColor, space ) { - - } - /* Input and Output methods */ hex () { let { _a, _b, _c } = this.rgb() - let [ r, g, b ] = [ _a, _b, _c ].map( componentHex ) + let [ r, g, b ] = [ _a, _b, _c ].map(componentHex) return `#${r}${g}${b}` } @@ -384,8 +329,8 @@ export default class Color { toRgb () { let { r, g, b } = this.rgb() let { max, min, round } = Math - let format = v => max( 0, min( round( v ), 255 ) ) - let [ rV, gV, bV ] = [ r, g, b ].map( format ) + let format = v => max(0, min(round(v), 255)) + let [ rV, gV, bV ] = [ r, g, b ].map(format) let string = `rgb(${rV},${gV},${bV})` return string } @@ -395,19 +340,11 @@ export default class Color { return [ _a, _b, _c, _d, space ] } - static fromArray ( array ) { - - let newColor = new Color( ...array ) - return newColor - - } - /* Generating random colors */ - static random ( mode = 'vibrant' ) { - + static random (mode = 'vibrant') { 'sine' 'pastel' 'vibrant' @@ -415,37 +352,27 @@ export default class Color { 'rgb' 'lab' 'grey' - } /* Constructing colors */ - static temperature ( kelvin ) {} - // Test if given value is a color string - static test ( color ) { - + static test (color) { color += '' - return isHex.test( color ) || isRgb.test( color ) - + return isHex.test(color) || isRgb.test(color) } // Test if given value is a rgb object - static isRgb ( color ) { - + static isRgb (color) { return color && typeof color.r === 'number' && typeof color.g === 'number' && typeof color.b === 'number' - } // Test if given value is a color - static isColor ( color ) { - - return this.isRgb( color ) || this.test( color ) - + static isColor (color) { + return this.isRgb(color) || this.test(color) } - } -- cgit v1.2.3 From 059058fbac867a270ceef34970f5ac04f58ec913 Mon Sep 17 00:00:00 2001 From: Ulrich-Matthias Schäfer Date: Sun, 25 Nov 2018 13:04:42 +0100 Subject: fix Morphable so that it works with color spaces. It prefers the `to` space over the `from` space - _d is initialized to 0 so toArray does not give you undefined - fix tests --- dist/svg.js | 17 +++++++++++------ spec/spec/morphing.js | 2 +- src/types/Color.js | 4 ++-- src/types/Morphable.js | 9 ++++++++- 4 files changed, 22 insertions(+), 10 deletions(-) (limited to 'spec') diff --git a/dist/svg.js b/dist/svg.js index 2b7a05e..bb7841f 100644 --- a/dist/svg.js +++ b/dist/svg.js @@ -6,7 +6,7 @@ * @copyright Wout Fierens * @license MIT * -* BUILT: Mon Nov 26 2018 00:15:11 GMT+1300 (New Zealand Daylight Time) +* BUILT: Sun Nov 25 2018 13:00:14 GMT+0100 (GMT+01:00) */; var SVG = (function () { 'use strict'; @@ -1091,11 +1091,10 @@ var SVG = (function () { var d = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; var space = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 'rgb'; - // If the user gave us an array, make the color from it if (typeof a === 'number') { // Allow for the case that we don't need d... space = typeof d === 'string' ? d : space; - d = typeof d === 'string' ? undefined : d; // Assign the values straight to the color + d = typeof d === 'string' ? 0 : d; // Assign the values straight to the color Object.assign(this, { _a: a, @@ -1103,7 +1102,7 @@ var SVG = (function () { _c: c, _d: d, space: space - }); + }); // If the user gave us an array, make the color from it } else if (a instanceof Array) { this.space = b || 'rgb'; Object.assign(this, { @@ -1195,7 +1194,7 @@ var SVG = (function () { this.opacity = _opacity; } /* - */ + */ }, { key: "brightness", @@ -4914,7 +4913,13 @@ var SVG = (function () { } } - var result = new this._type(value).toArray(); + var result = new this._type(value); + + if (this._type === Color) { + result = this._to ? result[this._to[4]]() : this._from ? result[this._from[4]]() : result; + } + + result = result.toArray(); this._morphObj = this._morphObj || new this._type(); this._context = this._context || Array.apply(null, Array(result.length)).map(Object); return result; diff --git a/spec/spec/morphing.js b/spec/spec/morphing.js index 7fc0c06..50d0ccb 100644 --- a/spec/spec/morphing.js +++ b/spec/spec/morphing.js @@ -44,7 +44,7 @@ describe('Morphing', function () { expect(morpher instanceof SVG.Morphable).toBe(true) expect(morpher.type()).toBe(SVG.Color) expect(morpher.at(0.5) instanceof SVG.Color).toBe(true) - expect(morpher.at(0.5).toHex()).toBe('#808080') + expect(morpher.at(0.5).hex()).toBe('#808080') }) it(`Creates a morphable out of an SVG.Box`, function () { diff --git a/src/types/Color.js b/src/types/Color.js index 8a44de1..3975aef 100644 --- a/src/types/Color.js +++ b/src/types/Color.js @@ -60,14 +60,14 @@ export default class Color { } init (a = 0, b = 0, c = 0, d = 0, space = 'rgb') { - // If the user gave us an array, make the color from it if (typeof a === 'number') { // Allow for the case that we don't need d... space = typeof d === 'string' ? d : space - d = typeof d === 'string' ? undefined : d + d = typeof d === 'string' ? 0 : d // Assign the values straight to the color Object.assign(this, { _a: a, _b: b, _c: c, _d: d, space }) + // If the user gave us an array, make the color from it } else if (a instanceof Array) { this.space = b || 'rgb' Object.assign(this, { _a: a[0], _b: a[1], _c: a[2], _d: a[3] }) diff --git a/src/types/Morphable.js b/src/types/Morphable.js index 240215b..e7af8c1 100644 --- a/src/types/Morphable.js +++ b/src/types/Morphable.js @@ -80,7 +80,14 @@ export default class Morphable { } } - var result = (new this._type(value)).toArray() + var result = (new this._type(value)) + if (this._type === Color) { + result = this._to ? result[this._to[4]]() + : this._from ? result[this._from[4]]() + : result + } + result = result.toArray() + this._morphObj = this._morphObj || new this._type() this._context = this._context || Array.apply(null, Array(result.length)).map(Object) -- cgit v1.2.3 From b5fc96a3637756e1c432464c18907f010311766e Mon Sep 17 00:00:00 2001 From: Ulrich-Matthias Schäfer Date: Sat, 1 Dec 2018 14:53:05 +0100 Subject: clamp values in toHex, tests, replace for of with for in --- spec/spec/color.js | 72 ++++++++++++++++++++++++++++++------------------------ src/types/Color.js | 19 ++++++++------ 2 files changed, 51 insertions(+), 40 deletions(-) (limited to 'spec') diff --git a/spec/spec/color.js b/spec/spec/color.js index 973e323..1f0dfbd 100644 --- a/spec/spec/color.js +++ b/spec/spec/color.js @@ -116,17 +116,19 @@ describe('Color', function() { }) it('handles black', () => { - let {r, g, b} = new SVG.Color(0, 0, 0).lab().rgb() - expect( r ).toBeCloseTo(0, 0) - expect( g ).toBeCloseTo(0, 0) - expect( b ).toBeCloseTo(0, 0) + let color = new SVG.Color(0, 0, 0).lab().rgb() + expect( color.r ).toBeCloseTo(0, 0) + expect( color.g ).toBeCloseTo(0, 0) + expect( color.b ).toBeCloseTo(0, 0) + expect( color.toHex() ).toBe('#000000') }) it('handles white', () => { - let {r, g, b} = new SVG.Color(255, 255, 255).lab().rgb() - expect( r ).toBeCloseTo(255, 0) - expect( g ).toBeCloseTo(255, 0) - expect( b ).toBeCloseTo(255, 0) + let color = new SVG.Color(255, 255, 255).lab().rgb() + expect( color.r ).toBeCloseTo(255, 0) + expect( color.g ).toBeCloseTo(255, 0) + expect( color.b ).toBeCloseTo(255, 0) + expect( color.toHex() ).toBe('#ffffff') }) }) @@ -157,17 +159,19 @@ describe('Color', function() { }) it('handles black', () => { - let {r, g, b} = new SVG.Color(0, 0, 0).lch().rgb() - expect( r ).toBeCloseTo(0, 0) - expect( g ).toBeCloseTo(0, 0) - expect( b ).toBeCloseTo(0, 0) + let color = new SVG.Color(0, 0, 0).lch().rgb() + expect( color.r ).toBeCloseTo(0, 0) + expect( color.g ).toBeCloseTo(0, 0) + expect( color.b ).toBeCloseTo(0, 0) + expect( color.toHex() ).toBe('#000000') }) it('handles white', () => { - let {r, g, b} = new SVG.Color(255, 255, 255).lch().rgb() - expect( r ).toBeCloseTo(255, 0) - expect( g ).toBeCloseTo(255, 0) - expect( b ).toBeCloseTo(255, 0) + let color = new SVG.Color(255, 255, 255).lch().rgb() + expect( color.r ).toBeCloseTo(255, 0) + expect( color.g ).toBeCloseTo(255, 0) + expect( color.b ).toBeCloseTo(255, 0) + expect( color.toHex() ).toBe('#ffffff') }) }) @@ -199,17 +203,19 @@ describe('Color', function() { }) it('handles black', () => { - let {r, g, b} = new SVG.Color(0, 0, 0).hsl().rgb() - expect( r ).toBeCloseTo(0, 0) - expect( g ).toBeCloseTo(0, 0) - expect( b ).toBeCloseTo(0, 0) + let color = new SVG.Color(0, 0, 0).hsl().rgb() + expect( color.r ).toBeCloseTo(0, 0) + expect( color.g ).toBeCloseTo(0, 0) + expect( color.b ).toBeCloseTo(0, 0) + expect( color.toHex() ).toBe('#000000') }) it('handles white', () => { - let {r, g, b} = new SVG.Color(255, 255, 255).hsl().rgb() - expect( r ).toBeCloseTo(255, 0) - expect( g ).toBeCloseTo(255, 0) - expect( b ).toBeCloseTo(255, 0) + let color = new SVG.Color(255, 255, 255).hsl().rgb() + expect( color.r ).toBeCloseTo(255, 0) + expect( color.g ).toBeCloseTo(255, 0) + expect( color.b ).toBeCloseTo(255, 0) + expect( color.toHex() ).toBe('#ffffff') }) }) @@ -242,17 +248,19 @@ describe('Color', function() { }) it('handles black', () => { - let {r, g, b} = new SVG.Color(0, 0, 0).cmyk().rgb() - expect( r ).toBeCloseTo(0, 0) - expect( g ).toBeCloseTo(0, 0) - expect( b ).toBeCloseTo(0, 0) + let color = new SVG.Color(0, 0, 0).cmyk().rgb() + expect( color.r ).toBeCloseTo(0, 0) + expect( color.g ).toBeCloseTo(0, 0) + expect( color.b ).toBeCloseTo(0, 0) + expect( color.toHex() ).toBe('#000000') }) it('handles white', () => { - let {r, g, b} = new SVG.Color(255, 255, 255).cmyk().rgb() - expect( r ).toBeCloseTo(255, 0) - expect( g ).toBeCloseTo(255, 0) - expect( b ).toBeCloseTo(255, 0) + let color = new SVG.Color(255, 255, 255).cmyk().rgb() + expect( color.r ).toBeCloseTo(255, 0) + expect( color.g ).toBeCloseTo(255, 0) + expect( color.b ).toBeCloseTo(255, 0) + expect( color.toHex() ).toBe('#ffffff') }) }) diff --git a/src/types/Color.js b/src/types/Color.js index b745bfd..ea9f674 100644 --- a/src/types/Color.js +++ b/src/types/Color.js @@ -18,8 +18,8 @@ function componentHex (component) { } function is (object, space) { - for (const key of space) { - if (object[key] == null) { + for (let i = space.length; i--;) { + if (object[space[i]] == null) { return false } } @@ -318,9 +318,15 @@ export default class Color { Input and Output methods */ - toHex () { + _clamped () { let { _a, _b, _c } = this.rgb() - let [ r, g, b ] = [ _a, _b, _c ].map(componentHex) + let { max, min, round } = Math + let format = v => max(0, min(round(v), 255)) + return [ _a, _b, _c ].map(format) + } + + toHex () { + let [ r, g, b ] = this._clamped().map(componentHex) return `#${r}${g}${b}` } @@ -329,10 +335,7 @@ export default class Color { } toRgb () { - let { r, g, b } = this.rgb() - let { max, min, round } = Math - let format = v => max(0, min(round(v), 255)) - let [ rV, gV, bV ] = [ r, g, b ].map(format) + let [ rV, gV, bV ] = this._clamped() let string = `rgb(${rV},${gV},${bV})` return string } -- cgit v1.2.3