@@ -948,6 +948,80 @@ export class Image {
948948 return this ;
949949 }
950950
951+ /**
952+ * Creates a multi-point gradient generator
953+ * @param {Object<number, number> } colors The gradient points to use (e.g. `{0: 0xff0000ff, 1: 0x00ff00ff}`)
954+ * @return {(function(number): number) } The gradient generator. The function argument is the position in the gradient (0..1).
955+ */
956+ static gradient ( colors ) {
957+ const entries = Object . entries ( colors ) . sort ( ( a , b ) => a [ 0 ] - b [ 0 ] ) ;
958+ const positions = entries . map ( e => parseFloat ( e [ 0 ] ) ) ;
959+ const values = entries . map ( e => e [ 1 ] ) ;
960+
961+ if ( positions . length === 0 ) throw new RangeError ( 'Invalid gradient point count' ) ;
962+ else if ( positions . length === 1 ) {
963+ return ( ) => values [ 0 ] ;
964+ } else if ( positions . length === 2 ) {
965+ const gradient = this . __gradient__ ( values [ 0 ] , values [ 1 ] ) ;
966+ return position => {
967+ if ( position <= positions [ 0 ] ) return values [ 0 ] ;
968+ if ( position >= positions [ 1 ] ) return values [ 1 ] ;
969+ return gradient ( ( position - positions [ 0 ] ) / ( positions [ 1 ] - positions [ 0 ] ) ) ;
970+ } ;
971+ }
972+
973+ const minDef = Math . min ( ...positions ) ;
974+ const maxDef = Math . max ( ...positions ) ;
975+ let gradients = [ ] ;
976+
977+ for ( let i = 0 ; i < positions . length ; i ++ ) {
978+ let minPos = positions [ i - 1 ] ;
979+ if ( minPos === undefined ) continue ;
980+
981+ let maxPos = positions [ i ] ;
982+
983+ let minVal = values [ i - 1 ] ;
984+ if ( minVal === undefined ) minVal = values [ i ] ;
985+
986+ const maxVal = values [ i ] ;
987+ const gradient = this . __gradient__ ( minVal , maxVal ) ;
988+
989+ gradients . push ( { min : minPos , max : maxPos , gradient} ) ;
990+ }
991+
992+ return position => {
993+ if ( position <= minDef ) return gradients [ 0 ] . gradient ( 0 ) ;
994+ if ( position >= maxDef ) return gradients [ gradients . length - 1 ] . gradient ( 1 ) ;
995+
996+ for ( const gradient of gradients )
997+ if ( position >= gradient . min && position <= gradient . max )
998+ return gradient . gradient ( ( position - gradient . min ) / ( gradient . max - gradient . min ) ) ;
999+ throw new RangeError ( `Invalid gradient position: ${ position } ` ) ;
1000+ } ;
1001+ }
1002+
1003+ /**
1004+ * @private
1005+ */
1006+ static __gradient__ ( startColor , endColor ) {
1007+ const sr = startColor >>> 24 ;
1008+ const sg = startColor >> 16 & 0xff ;
1009+ const sb = startColor >> 8 & 0xff ;
1010+ const sa = startColor & 0xff ;
1011+ const er = ( endColor >>> 24 ) - sr ;
1012+ const eg = ( endColor >> 16 & 0xff ) - sg ;
1013+ const eb = ( endColor >> 8 & 0xff ) - sb ;
1014+ const ea = ( endColor & 0xff ) - sa ;
1015+
1016+ return position => {
1017+ const r = sr + position * er ;
1018+ const g = sg + position * eg ;
1019+ const b = sb + position * eb ;
1020+ const a = sa + position * ea ;
1021+ return ( ( ( r & 0xff ) << 24 ) | ( ( g & 0xff ) << 16 ) | ( ( b & 0xff ) << 8 ) | ( a & 0xff ) ) ;
1022+ } ;
1023+ }
1024+
9511025 /**
9521026 * Encodes the image into a PNG
9531027 * @param {number } compression The compression level to use (0-3)
0 commit comments