diff --git a/README.md b/README.md index 7d16414..f88a1f4 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ Procedural Texture Generator ```javascript var texture = new TG.Texture( 256, 256 ) .add( new TG.XOR().tint( 1, 0.5, 0.7 ) ) - .add( new TG.SinX().frequency( 0.004 ).tint( 0.5, 0, 0 ) ) - .mul( new TG.SinY().frequency( 0.004 ).tint( 0.5, 0, 0 ) ) - .add( new TG.SinX().frequency( 0.0065 ).tint( 0.1, 0.5, 0.2 ) ) - .add( new TG.SinY().frequency( 0.0065 ).tint( 0.5, 0.5, 0.5 ) ) + .add( new TG.SinX().frequency( 500 ).tint( 0.5, 0, 0 ) ) + .mul( new TG.SinY().frequency( 500 ).tint( 0.5, 0, 0 ) ) + .add( new TG.SinX().frequency( 2 / 0.0065 ).tint( 0.1, 0.5, 0.2 ) ) + .add( new TG.SinY().frequency( 2 / 0.0065 ).tint( 0.5, 0.5, 0.5 ) ) .add( new TG.Noise().tint( 0.1, 0.1, 0.2 ) ) .toCanvas(); diff --git a/examples/animated.html b/examples/animated.html index 9363b8e..253023a 100644 --- a/examples/animated.html +++ b/examples/animated.html @@ -30,12 +30,12 @@ function render() { var texture = new TG.Texture( size, size ) - .add( new TG.SinX().frequency( 0.05+i/1000 ) ) - .mul( new TG.SinX().frequency( 0.08-i/2000 ) ) - .add( new TG.SinY().frequency( 0.05-i/1000 ) ) - .mul( new TG.SinY().frequency( 0.08+i/2000 ) ) - .div( new TG.Number().tint( 1, 2, 1 ) ) - .add( new TG.SinX().frequency( 0.003 ).tint( 0.5, 0, 0 ) ) + .add( new TG.SinX().frequency( 2/(0.05+i/1000) ) ) + .mul( new TG.SinX().frequency( 2/(0.08-i/2000) ) ) + .add( new TG.SinY().frequency( 2/(0.05-i/1000) ) ) + .mul( new TG.SinY().frequency( 2/(0.08+i/2000) ) ) + .div( new TG.Fill().tint( 1, 2, 1 ) ) + .add( new TG.SinX().frequency( 2/0.003 ).tint( 0.5, 0, 0 ) ) .toImageData(context); context.putImageData( texture, 0, 0 ); @@ -43,11 +43,11 @@ // var texture = new TG.Texture( size, size ) - .add( new TG.SinX().frequency( 0.066 + 0.05*Math.sin(i/100) ) ) - .add( new TG.SinY().frequency( 0.066 + 0.05*Math.sin(i/100) ) ) - .mul( new TG.SinX().offset( 32 ).frequency( 0.044 + 0.09*Math.sin(i/100) ).tint( 2, 2, 2 ) ) - .mul( new TG.SinY().offset( 16 ).frequency( 0.044 + 0.09*Math.sin(i/100) ).tint( 2, 2, 2 ) ) - .sub( new TG.Number().tint( 0.5, 2, 4 ) ) + .add( new TG.SinX().frequency( 2/(0.066 + 0.05*Math.sin(i/100)) ) ) + .add( new TG.SinY().frequency( 2/(0.066 + 0.05*Math.sin(i/100)) ) ) + .mul( new TG.SinX().offset( 32 ).frequency( 2/(0.044 + 0.09*Math.sin(i/100)) ).tint( 2, 2, 2 ) ) + .mul( new TG.SinY().offset( 16 ).frequency( 2/(0.044 + 0.09*Math.sin(i/100)) ).tint( 2, 2, 2 ) ) + .sub( new TG.Fill().tint( 0.5, 2, 4 ) ) .toImageData(context); context.putImageData( texture, size, 0 ); @@ -55,10 +55,10 @@ // var texture = new TG.Texture( size, size ) - .add( new TG.SinX().frequency( 0.004 + 0.002*Math.sin(i/100)) ) - .mul( new TG.SinY().frequency( 0.004 + 0.002*Math.sin(i/100)) ) - .mul( new TG.SinY().offset( 32 ).frequency( 0.04 + 0.02*Math.sin(i/100) ) ) - .div( new TG.SinX().frequency( 0.02 ).tint( 8, 5, 4 ) ) + .add( new TG.SinX().frequency( 2/(0.004 + 0.002*Math.sin(i/100))) ) + .mul( new TG.SinY().frequency( 2/(0.004 + 0.002*Math.sin(i/100))) ) + .mul( new TG.SinY().offset( 32 ).frequency( 2/(0.04 + 0.02*Math.sin(i/100)) ) ) + .div( new TG.SinX().frequency( 2/0.02 ).tint( 8, 5, 4 ) ) .add( new TG.Noise().tint( 0.1, 0, 0 ) ) .add( new TG.Noise().tint( 0, 0.1, 0 ) ) .add( new TG.Noise().tint( 0, 0, 0.1 ) ) diff --git a/examples/index.html b/examples/index.html index 8d718f6..7626766 100644 --- a/examples/index.html +++ b/examples/index.html @@ -1,491 +1,434 @@ - - - texgen.js - - - - -

Change size: 128x128 256x256 512x512 custom...

+ + + texgen.js + + + + + + + +

Change size: 128x128 256x256 512x512 custom...

+ +
+
-
-
- - - - - + + + + diff --git a/examples/noise.html b/examples/noise.html index 4c63ce6..bd44e54 100644 --- a/examples/noise.html +++ b/examples/noise.html @@ -190,21 +190,21 @@
new TG.Texture(size, size) .set(new TG.FractalNoise().baseFrequency(64).octaves(6).step(2).interpolation(2) ) .sub(new TG.Noise().tint(0, 0.1, 0) ) - .min(new TG.Number().tint(0, 0.6, 0) ) - .sub(new TG.Number().tint(0, 0.5, 0) ) - .mul(new TG.Number().tint(0, 5, 0) ) + .min(new TG.Fill().tint(0, 0.6, 0) ) + .sub(new TG.Fill().tint(0, 0.5, 0) ) + .mul(new TG.Fill().tint(0, 5, 0) ) .sub(new TG.SineDistort().sines(2, 2).amplitude(8, 8).tint(0, 0.75, 0) ) - .add(new TG.Number().tint(0.06, 0, 0.085) ) + .add(new TG.Fill().tint(0.06, 0, 0.085) ) .addToPage("Example 2"); new TG.Texture(size, size) .set(new TG.FractalNoise().baseFrequency(128).octaves(6).step(2).interpolation(2) ) .sub(new TG.Noise().tint(0, 0.1, 0) ) - .min(new TG.Number().tint(0, 0.63, 0) ) - .sub(new TG.Number().tint(0, 0.53, 0) ) - .mul(new TG.Number().tint(0, 6, 0) ) + .min(new TG.Fill().tint(0, 0.63, 0) ) + .sub(new TG.Fill().tint(0, 0.53, 0) ) + .mul(new TG.Fill().tint(0, 6, 0) ) .sub(new TG.SineDistort().sines(2, 2).amplitude(4, 4).tint(0, 0.75, 0) ) - .add(new TG.Number().tint(0.065, 0, 0.095) ) + .add(new TG.Fill().tint(0.065, 0, 0.095) ) .addToPage("Example 2 (less distortion)"); line = document.createElement("br"); diff --git a/src/TexGen.js b/src/TexGen.js index f1ca8a0..f47fe3a 100644 --- a/src/TexGen.js +++ b/src/TexGen.js @@ -2,635 +2,511 @@ * @author mrdoob / http://mrdoob.com/ */ -var TG = { - OP: { - SET: function ( x, y ) { return y; }, - ADD: function ( x, y ) { return x + y; }, - SUB: function ( x, y ) { return x - y; }, - MUL: function ( x, y ) { return x * y; }, - DIV: function ( x, y ) { return x / y; }, - AND: function ( x, y ) { return x & y; }, - XOR: function ( x, y ) { return x ^ y; }, - MIN: function ( x, y ) { return Math.min( x, y ); }, - MAX: function ( x, y ) { return Math.max( x, y ); } - } -}; - -TG.Texture = function ( width, height ) { +var TG = {}; - this.color = new Float32Array( 4 ); +// - this.buffer = new TG.Buffer( width, height ); - this.bufferCopy = new TG.Buffer( width, height ); +TG.Utils = { -}; + globalSeed: Date.now(), // used as a somewhat random seed, increased on each use so that each default seed is different -TG.Texture.prototype = { + smoothStep: function ( edge0, edge1, x ) { - constructor: TG.Texture, + // Scale, bias and saturate x to 0..1 range + x = TG.Utils.clamp( ( x - edge0 ) / ( edge1 - edge0 ), 0, 1 ); - set: function ( program, operation ) { + // Evaluate polynomial + return x * x * ( 3 - 2 * x ); - if ( operation === undefined ) operation = TG.OP.SET; + }, - this.bufferCopy.copy( this.buffer ); + mixColors: function( c1, c2, delta ) { - var string = [ - 'var x = 0, y = 0;', - 'var array = dst.array;', - 'var width = dst.width, height = dst.height;', - 'for ( var i = 0, il = array.length; i < il; i += 4 ) {', - ' ' + program.getSource(), - ' array[ i ] = op( array[ i ], color[ 0 ] * tint[ 0 ] );', - ' array[ i + 1 ] = op( array[ i + 1 ], color[ 1 ] * tint[ 1 ] );', - ' array[ i + 2 ] = op( array[ i + 2 ], color[ 2 ] * tint[ 2 ] );', - ' if ( ++x === width ) { x = 0; y ++; }', - '}' - ].join( '\n' ); + return [ + c1[ 0 ] * ( 1 - delta ) + c2[ 0 ] * delta, + c1[ 1 ] * ( 1 - delta ) + c2[ 1 ] * delta, + c1[ 2 ] * ( 1 - delta ) + c2[ 2 ] * delta, + c1[ 3 ] * ( 1 - delta ) + c2[ 3 ] * delta, + ]; + }, - new Function( 'op, dst, src, color, params, tint', string )( operation, this.buffer, this.bufferCopy, this.color, program.getParams(), program.getTint() ); + distance: function( x0, y0, x1, y1 ) { - return this; + var dx = x1 - x0, dy = y1 - y0; + return Math.sqrt( dx * dx + dy * dy ); }, - add: function ( program ) { return this.set( program, TG.OP.ADD ); }, + clamp: function( value, min, max ) { - sub: function ( program ) { return this.set( program, TG.OP.SUB ); }, + return Math.min( Math.max( value, min ), max ); - mul: function ( program ) { return this.set( program, TG.OP.MUL ); }, + }, - div: function ( program ) { return this.set( program, TG.OP.DIV ); }, + wrap: function ( value, min, max ) { + var v = value - min; + var r = max - min; - and: function ( program ) { return this.set( program, TG.OP.AND ); }, + return ( ( r + v % r ) % r ) + min; + }, - xor: function ( program ) { return this.set( program, TG.OP.XOR ); }, + mirroredWrap: function ( value, min, max ) { + var v = value - min; + var r = ( max - min ) * 2; - min: function ( program ) { return this.set( program, TG.OP.MIN ); }, + v = ( r + v % r ) % r; - max: function ( program ) { return this.set( program, TG.OP.MAX ); }, + if ( v > max - min ) { + return ( r - v ) + min; + } else { + return v + min; + } + }, - toImageData: function ( context ) { + deg2rad: function ( deg ) { - var buffer = this.buffer; - var array = buffer.array; + return deg * Math.PI / 180; - var imagedata = context.createImageData( buffer.width, buffer.height ); - var data = imagedata.data; + }, - for ( var i = 0, il = array.length; i < il; i += 4 ) { + hashRNG: function ( seed, x, y ) { + seed = Math.abs(seed % 0x7FFFFFFF) + 1; - data[ i ] = array[ i ] * 255; - data[ i + 1 ] = array[ i + 1 ] * 255; - data[ i + 2 ] = array[ i + 2 ] * 255; - data[ i + 3 ] = 255; + var a = ( seed * ( ( x ^ seed * 777 ) || 556 ) * ( ( y ^ seed * 314 ) || 989 ) + seed * 123 ) % 0x7FFFFFFF; + a = (a ^ 61) ^ (a >> 16); + a = a + (a << 3); + a = a ^ (a >> 4); + a = a * 0x27d4eb2d; + a = a ^ (a >> 15); + a = a / 0x7FFFFFFF; - } + return a; + }, - return imagedata; + cellNoiseBase: function ( x, y, seed, density, weightRange ) { + var qx, qy, rx, ry, w, px, py, dx, dy; + var dist, value; + var shortest = Infinity; + density = Math.abs( density ); - }, + for ( var sx = -2; sx <= 2; sx++ ) { + for ( var sy = -2; sy <= 2; sy++ ) { + qx = Math.ceil( x / density ) + sx; + qy = Math.ceil( y / density ) + sy; - toCanvas: function ( canvas ) { + rx = TG.Utils.hashRNG( seed, qx, qy ); + ry = TG.Utils.hashRNG( seed * 2, qx, qy ); + w = ( weightRange > 0 ) ? 1 + TG.Utils.hashRNG( seed * 3, qx, qy ) * weightRange : 1; - if ( canvas === undefined ) canvas = document.createElement( 'canvas' ); - canvas.width = this.buffer.width; - canvas.height = this.buffer.height; + px = ( rx + qx ) * density; + py = ( ry + qy ) * density; - var context = canvas.getContext( '2d' ); - var imagedata = this.toImageData( context ); + dx = Math.abs( px - x ); + dy = Math.abs( py - y ); - context.putImageData( imagedata, 0, 0 ); + dist = ( dx * dx + dy * dy ) * w; - return canvas; + if ( dist < shortest ) { + shortest = dist; + value = rx; + } + } + } + return { dist: Math.sqrt( shortest ), value: value }; } }; -// +TG.ColorInterpolatorMethod = { + STEP: 0, + LINEAR: 1, + SPLINE: 2, + COSINE: 3, +}; -TG.Program = function ( object ) { +TG.ColorInterpolator = function( method ) { - var tint = new Float32Array( [ 1, 1, 1 ] ); + this.points = []; // points must be a set pair (point, color): [{ pos-n: [r,g,b,a] } , ..., { pos-N: [r,g,b,a] } ] + this.low = 0; + this.high = 0; + this.interpolation = ( typeof( method ) == 'undefined' ) ? TG.ColorInterpolatorMethod.LINEAR : method; + this.repeat = false; - object.tint = function ( r, g, b ) { - tint[ 0 ] = r; - tint[ 1 ] = g; - tint[ 2 ] = b; - return this; - }; + return this; +}; - object.getTint = function () { - return tint; - }; +TG.ColorInterpolator.prototype = { - return object; + set: function ( points ) { -}; + this.points = points; + this.points.sort( function( a, b ) { + return a.pos - b.pos; + }); -TG.Number = function () { + this.low = this.points[ 0 ].pos; + this.high = this.points[ this.points.length - 1 ].pos; - return new TG.Program( { - getParams: function () {}, - getSource: function () { - return [ - 'color[ 0 ] = 1;', - 'color[ 1 ] = 1;', - 'color[ 2 ] = 1;' - ].join('\n'); + return this; + + }, + + addPoint: function ( position, r, g, b ) { + if ( Array.isArray( r ) ) { + b = r[ 2 ]; g = r[ 1 ]; r = r[ 0 ]; } - } ); + if ( typeof g == 'undefined' ) g = r; + if ( typeof b == 'undefined' ) b = r; -}; + this.points.push( { pos: position, color: [ r, g, b ] } ); + this.points.sort( function( a, b ) { + return a.pos - b.pos; + }); -TG.SinX = function () { + this.low = this.points[ 0 ].pos; + this.high = this.points[ this.points.length - 1 ].pos; - var params = { - frequency: 1, - offset: 0 - }; + return this; - return new TG.Program( { - frequency: function ( value ) { - params.frequency = value * Math.PI; - return this; - }, - offset: function ( value ) { - params.offset = value; - return this; - }, - getParams: function () { - return params; - }, - getSource: function () { - return [ - 'var value = Math.sin( ( x + params.offset ) * params.frequency );', - 'color[ 0 ] = value;', - 'color[ 1 ] = value;', - 'color[ 2 ] = value;' - ].join('\n'); - } - } ); + }, -}; + setRepeat: function ( value ) { -TG.SinY = function () { + this.repeat = value; + return this; - var params = { - frequency: 1, - offset: 0 - }; + }, - return new TG.Program( { - frequency: function ( value ) { - params.frequency = value * Math.PI; - return this; - }, - offset: function ( value ) { - params.offset = value; - return this; - }, - getParams: function () { - return params; - }, - getSource: function () { - return [ - 'var value = Math.sin( ( y + params.offset ) * params.frequency );', - 'color[ 0 ] = value;', - 'color[ 1 ] = value;', - 'color[ 2 ] = value;' - ].join('\n'); - } - } ); + setInterpolation: function ( value ) { -}; + this.interpolation = value; + return this; -TG.OR = function () { + }, - return new TG.Program( { - getParams: function () {}, - getSource: function () { - return [ - 'var value = ( x | y ) / width;', - 'color[ 0 ] = value;', - 'color[ 1 ] = value;', - 'color[ 2 ] = value;' - ].join('\n'); - } - } ); + getColorAt: function ( pos ) { -}; + if ( this.repeat == 2 ) pos = TG.Utils.mirroredWrap( pos, this.low, this.high ); + else if ( this.repeat ) pos = TG.Utils.wrap( pos, this.low, this.high ); + else pos = TG.Utils.clamp( pos, this.low, this.high ); -TG.XOR = function () { + var i = 0, points = this.points; - return new TG.Program( { - getParams: function () {}, - getSource: function () { - return [ - 'var value = ( x ^ y ) / width;', - 'color[ 0 ] = value;', - 'color[ 1 ] = value;', - 'color[ 2 ] = value;' - ].join('\n'); - } - } ); + while ( points[ i + 1 ].pos < pos ) i ++; -}; + var p1 = points[ i ]; + var p2 = points[ i + 1 ]; -TG.Noise = function () { + var delta = ( pos - p1.pos ) / ( p2.pos - p1.pos ); - var params = { - seed: Date.now() - }; + if ( this.interpolation == TG.ColorInterpolatorMethod.STEP ) { - return new TG.Program( { - seed: function ( value ) { - params.seed = value; - return this; - }, - getParams: function () { - return params; - }, - getSource: function () { - return [ - 'var value = TG.Utils.hashRNG( params.seed, x, y );', - 'color[ 0 ] = value;', - 'color[ 1 ] = value;', - 'color[ 2 ] = value;' - ].join('\n'); - } - } ); + return p1.color; -}; + } else if ( this.interpolation == TG.ColorInterpolatorMethod.LINEAR ) { -TG.FractalNoise = function () { + return TG.Utils.mixColors( p1.color, p2.color, delta ); - var params = { - interpolator: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.STEP ), - seed: Date.now(), - baseFrequency: 0.03125, - amplitude: 0.4, - persistence: 0.72, - octaves: 4, - step: 4 - }; + } else if ( this.interpolation == TG.ColorInterpolatorMethod.SPLINE ) { - return new TG.Program( { - seed: function ( value ) { - params.seed = value; - return this; - }, - baseFrequency: function ( value ) { - params.baseFrequency = 1 / value; - return this; - }, - amplitude: function ( value ) { - params.amplitude = value; - return this; - }, - persistence: function ( value ) { - params.persistence = value; - return this; - }, - octaves: function ( value ) { - params.octaves = Math.max( 1, value ); - return this; - }, - step: function ( value ) { - params.step = Math.max( 1, value ); - return this; - }, - interpolation: function ( value ) { - params.interpolator.setInterpolation( value ); - return this; - }, - getParams: function () { - return params; - }, - getSource: function () { - return [ - 'var value = 0;', - 'var amp = params.amplitude;', - 'var freq = params.baseFrequency;', - 'var x1, y1, dx, dy;', - 'var v1, v2, v3, v4;', - 'var i1, i2;', + var ar = 2 * p1.color[ 0 ] - 2 * p2.color[ 0 ]; + var br = -3 * p1.color[ 0 ] + 3 * p2.color[ 0 ]; + var dr = p1.color[ 0 ]; - 'for ( var j = 1; j <= params.octaves; j++ ) {', - 'x1 = Math.floor( x * freq ), y1 = Math.floor( y * freq );', + var ag = 2 * p1.color[ 1 ] - 2 * p2.color[ 1 ]; + var bg = -3 * p1.color[ 1 ] + 3 * p2.color[ 1 ]; + var dg = p1.color[ 1 ]; - 'if ( params.interpolator.interpolation == TG.ColorInterpolatorMethod.STEP ) {', - 'value += TG.Utils.hashRNG( params.seed * j, x1, y1 ) * amp;', - '} else {', - 'dx = ( x * freq ) - x1, dy = ( y * freq ) - y1;', + var ab = 2 * p1.color[ 2 ] - 2 * p2.color[ 2 ]; + var bb = -3 * p1.color[ 2 ] + 3 * p2.color[ 2 ]; + var db = p1.color[ 2 ]; - 'v1 = TG.Utils.hashRNG( params.seed * j, x1 , y1 );', - 'v2 = TG.Utils.hashRNG( params.seed * j, x1 + 1, y1 );', - 'v3 = TG.Utils.hashRNG( params.seed * j, x1 , y1 + 1 );', - 'v4 = TG.Utils.hashRNG( params.seed * j, x1 + 1, y1 + 1 );', + var delta2 = delta * delta; + var delta3 = delta2 * delta; - 'params.interpolator.set( [', - '{ pos: 0, color: [ v1 ] },', - '{ pos: 1, color: [ v2 ] }', - '] );', + return [ + ar * delta3 + br * delta2 + dr, + ag * delta3 + bg * delta2 + dg, + ab * delta3 + bb * delta2 + db + ]; - 'i1 = params.interpolator.getColorAt( dx );', + } else if ( this.interpolation == TG.ColorInterpolatorMethod.COSINE ) { + var cos = (1 - Math.cos(delta * Math.PI)) / 2; - 'params.interpolator.set( [', - '{ pos: 0, color: [ v3 ] },', - '{ pos: 1, color: [ v4 ] }', - '] );', + return [ + ( p1.color[ 0 ] * ( 1 - cos ) ) + ( p2.color[ 0 ] * cos ), + ( p1.color[ 1 ] * ( 1 - cos ) ) + ( p2.color[ 1 ] * cos ), + ( p1.color[ 2 ] * ( 1 - cos ) ) + ( p2.color[ 2 ] * cos ), + ]; - 'i2 = params.interpolator.getColorAt( dx );', + } - 'params.interpolator.set( [', - '{ pos: 0, color: [ i1[ 0 ] ] },', - '{ pos: 1, color: [ i2[ 0 ] ] }', - '] );', + } - 'value += params.interpolator.getColorAt( dy )[ 0 ] * amp;', - '}', +}; - 'freq *= params.step;', - 'amp *= params.persistence;', - '}', +// - 'color[ 0 ] = value;', - 'color[ 1 ] = value;', - 'color[ 2 ] = value;', - ].join('\n'); - } - } ); +TG.Buffer = function ( width, height ) { -}; + this.width = width; + this.height = height; -TG.CellularNoise = function () { + this.array = new Float32Array( width * height * 4 ); + this.color = new Float32Array( 4 ); - var params = { - seed: Date.now(), - density: 32, - weightRange: 0 - }; +}; - return new TG.Program( { - seed: function ( value ) { - params.seed = value; - return this; - }, - density: function ( value ) { - params.density = value; - return this; - }, - weightRange: function ( value ) { - params.weightRange = Math.max( 0, value ); - return this; - }, - getParams: function () { - return params; - }, - getSource: function () { - return [ - 'var p = TG.Utils.cellNoiseBase( x, y, params.seed, params.density, params.weightRange );', +TG.Buffer.prototype = { - 'var value = 1 - ( p.dist / params.density );', - 'if ( params.density < 0 ) value -= 1;', + constructor: TG.Buffer, - 'color[ 0 ] = value;', - 'color[ 1 ] = value;', - 'color[ 2 ] = value;' - ].join('\n'); - } - } ); + copy: function ( buffer ) { -}; + this.array.set( buffer.array ); -TG.VoronoiNoise = function () { + }, - var params = { - seed: Date.now(), - density: 32, - weightRange: 0 - }; + getPixelNearest: function ( x, y ) { - return new TG.Program( { - seed: function ( value ) { - params.seed = value; - return this; - }, - density: function ( value ) { - params.density = value; - return this; - }, - weightRange: function ( value ) { - params.weightRange = Math.max( 0, value ); - return this; - }, - getParams: function () { - return params; - }, - getSource: function () { - return [ - 'var p = TG.Utils.cellNoiseBase( x, y, params.seed, params.density, params.weightRange );', + if ( y >= this.height ) y -= this.height; + if ( y < 0 ) y += this.height; + if ( x >= this.width ) x -= this.width; + if ( x < 0 ) x += this.width; - 'color[ 0 ] = p.value;', - 'color[ 1 ] = p.value;', - 'color[ 2 ] = p.value;' - ].join('\n'); - } - } ); + var array = this.array; + var color = this.color; + var offset = Math.round( y ) * this.width * 4 + Math.round( x ) * 4; -}; + color[ 0 ] = array[ offset ]; + color[ 1 ] = array[ offset + 1 ]; + color[ 2 ] = array[ offset + 2 ]; -TG.CellularFractal = function () { + return this.color; - var params = { - seed: Date.now(), - weightRange: 0, - baseDensity: 64, - amplitude: 0.7, - persistence: 0.45, - octaves: 4, - step: 2 - }; + }, - return new TG.Program( { - seed: function ( value ) { - params.seed = value; - return this; - }, - baseDensity: function ( value ) { - params.baseDensity = value; - return this; - }, - weightRange: function ( value ) { - params.weightRange = Math.max( 0, value ); - return this; - }, - amplitude: function ( value ) { - params.amplitude = value; - return this; - }, - persistence: function ( value ) { - params.persistence = value; - return this; - }, - octaves: function ( value ) { - params.octaves = Math.max( 1, value ); - return this; - }, - step: function ( value ) { - params.step = Math.max( 1, value ); - return this; - }, - getParams: function () { - return params; - }, - getSource: function () { - return [ - 'var p;', - 'var value = 0;', - 'var amp = params.amplitude;', - 'var dens = params.baseDensity;', + getPixelBilinear: function ( x, y ) { - 'for ( var j = 1; j <= params.octaves; j++ ) {', - 'p = TG.Utils.cellNoiseBase( x, y, params.seed * j, dens, params.weightRange );', + var px = Math.floor( x ); + var py = Math.floor( y ); + var p0 = px + py * this.width; - 'p.dist = 1 - ( p.dist / dens );', - 'if ( dens < 0 ) p.dist -= 1;', + var array = this.array; + var color = this.color; - 'value += p.dist * amp;', - 'dens /= params.step;', - 'amp *= params.persistence;', - '}', + // Calculate the weights for each pixel + var fx = x - px; + var fy = y - py; + var fx1 = 1 - fx; + var fy1 = 1 - fy; - 'color[ 0 ] = value;', - 'color[ 1 ] = value;', - 'color[ 2 ] = value;', - ].join('\n'); - } - } ); + var w1 = fx1 * fy1; + var w2 = fx * fy1; + var w3 = fx1 * fy ; + var w4 = fx * fy ; + + var p1 = p0 * 4; // 0 + 0 * w + var p2 = ( 1 + p0 ) * 4; // 1 + 0 * w + var p3 = ( 1 * this.width + p0 ) * 4; // 0 + 1 * w + var p4 = ( 1 + 1 * this.width + p0 ) * 4; // 1 + 1 * w + + var len = this.width * this.height * 4; + + if ( p1 >= len ) p1 -= len; + if ( p1 < 0 ) p1 += len; + if ( p2 >= len ) p2 -= len; + if ( p2 < 0 ) p2 += len; + if ( p3 >= len ) p3 -= len; + if ( p3 < 0 ) p3 += len; + if ( p4 >= len ) p4 -= len; + if ( p4 < 0 ) p4 += len; + + // Calculate the weighted sum of pixels (for each color channel) + color[ 0 ] = array[ p1 + 0 ] * w1 + array[ p2 + 0 ] * w2 + array[ p3 + 0 ] * w3 + array[ p4 + 0 ] * w4; + color[ 1 ] = array[ p1 + 1 ] * w1 + array[ p2 + 1 ] * w2 + array[ p3 + 1 ] * w3 + array[ p4 + 1 ] * w4; + color[ 2 ] = array[ p1 + 2 ] * w1 + array[ p2 + 2 ] * w2 + array[ p3 + 2 ] * w3 + array[ p4 + 2 ] * w4; + color[ 3 ] = array[ p1 + 3 ] * w1 + array[ p2 + 3 ] * w2 + array[ p3 + 3 ] * w3 + array[ p4 + 3 ] * w4; + + return this.color; + }, + + getPixelOffset: function ( offset ) { + + var array = this.array; + var color = this.color; + + offset = parseInt( offset * 4 ); + + color[ 0 ] = array[ offset ]; + color[ 1 ] = array[ offset + 1 ]; + color[ 2 ] = array[ offset + 2 ]; + color[ 3 ] = array[ offset + 3 ]; + + return this.color; + }, }; -TG.VoronoiFractal = function () { +TG.OP = { + SET: function ( x, y ) { return y; }, + ADD: function ( x, y ) { return x + y; }, + SUB: function ( x, y ) { return x - y; }, + MUL: function ( x, y ) { return x * y; }, + DIV: function ( x, y ) { return x / y; }, + AND: function ( x, y ) { return x & y; }, + XOR: function ( x, y ) { return x ^ y; }, + MIN: function ( x, y ) { return Math.min( x, y ); }, + MAX: function ( x, y ) { return Math.max( x, y ); }, + POW: function ( x, y ) { return Math.pow( x, y ); } +}; - var params = { - seed: Date.now(), - weightRange: 0, - baseDensity: 64, - amplitude: 0.6, - persistence: 0.6, - octaves: 4, - step: 2 - }; +TG.Texture = function ( width, height ) { - return new TG.Program( { - seed: function ( value ) { - params.seed = value; - return this; - }, - baseDensity: function ( value ) { - params.baseDensity = value; - return this; - }, - weightRange: function ( value ) { - params.weightRange = Math.max( 0, value ); - return this; - }, - amplitude: function ( value ) { - params.amplitude = value; - return this; - }, - persistence: function ( value ) { - params.persistence = value; - return this; - }, - octaves: function ( value ) { - params.octaves = Math.max( 1, value ); - return this; - }, - step: function ( value ) { - params.step = Math.max( 1, value ); - return this; - }, - getParams: function () { - return params; - }, - getSource: function () { - return [ - 'var p;', - 'var value = 0;', - 'var amp = params.amplitude;', - 'var dens = params.baseDensity;', + this.color = new Float32Array( 4 ); - 'for ( var j = 1; j <= params.octaves; j++ ) {', - 'p = TG.Utils.cellNoiseBase( x, y, params.seed * j, dens, params.weightRange );', + this.buffer = new TG.Buffer( width, height ); + this.bufferCopy = new TG.Buffer( width, height ); - 'value += p.value * amp;', - 'dens /= params.step;', - 'amp *= params.persistence;', - '}', +}; + +TG.Texture.prototype = { + + constructor: TG.Texture, + + set: function ( program, operation ) { + + if ( operation === undefined ) operation = TG.OP.SET; + + this.bufferCopy.copy( this.buffer ); + + var string = [ + 'var x = 0, y = 0;', + 'var array = dst.array;', + 'var width = dst.width, height = dst.height;', + ( typeof program.getInit == 'function' ) ? program.getInit() : '', + 'for ( var i = 0, il = array.length; i < il; i += 4 ) {', + ' ' + program.getSource(), + ' array[ i ] = op( array[ i ], color[ 0 ] * tint[ 0 ] );', + ' array[ i + 1 ] = op( array[ i + 1 ], color[ 1 ] * tint[ 1 ] );', + ' array[ i + 2 ] = op( array[ i + 2 ], color[ 2 ] * tint[ 2 ] );', + ' if ( ++x === width ) { x = 0; y ++; }', + '}' + ].join( '\n' ); + + new Function( 'op, dst, src, color, params, tint', string )( operation, this.buffer, this.bufferCopy, this.color, program.getParams(), program.getTint() ); + + return this; + + }, + + add: function ( program ) { return this.set( program, TG.OP.ADD ); }, + + sub: function ( program ) { return this.set( program, TG.OP.SUB ); }, + + mul: function ( program ) { return this.set( program, TG.OP.MUL ); }, + + div: function ( program ) { return this.set( program, TG.OP.DIV ); }, + + and: function ( program ) { return this.set( program, TG.OP.AND ); }, + + xor: function ( program ) { return this.set( program, TG.OP.XOR ); }, + + min: function ( program ) { return this.set( program, TG.OP.MIN ); }, + + max: function ( program ) { return this.set( program, TG.OP.MAX ); }, + + pow: function ( program ) { return this.set( program, TG.OP.POW ); }, + + toImageData: function ( context ) { + + var buffer = this.buffer; + var array = buffer.array; + + var imagedata = context.createImageData( buffer.width, buffer.height ); + var data = imagedata.data; + + for ( var i = 0, il = array.length; i < il; i += 4 ) { + + data[ i ] = array[ i ] * 255; + data[ i + 1 ] = array[ i + 1 ] * 255; + data[ i + 2 ] = array[ i + 2 ] * 255; + data[ i + 3 ] = 255; - 'color[ 0 ] = value;', - 'color[ 1 ] = value;', - 'color[ 2 ] = value;', - ].join('\n'); } - } ); + + return imagedata; + + }, + + toCanvas: function ( canvas ) { + + if ( canvas === undefined ) canvas = document.createElement( 'canvas' ); + canvas.width = this.buffer.width; + canvas.height = this.buffer.height; + + var context = canvas.getContext( '2d' ); + var imagedata = this.toImageData( context ); + + context.putImageData( imagedata, 0, 0 ); + + return canvas; + + } }; -TG.CheckerBoard = function () { +TG.Program = function ( object ) { - var params = { - size: [ 32, 32 ], - offset: [ 0, 0 ], - rowShift: 0 + var tint = new Float32Array( [ 1, 1, 1 ] ); + + object.tint = function ( r, g, b ) { // multiplies each color channel of the generated image by 'r', 'g' and 'b' respectively + tint[ 0 ] = r; + tint[ 1 ] = ( typeof g == 'undefined' ) ? r : g; + tint[ 2 ] = ( typeof b == 'undefined' ) ? r : b; + return this; + }; + + object.getTint = function () { + return tint; }; + return object; + +}; + +// --- Generators --- + +TG.Fill = function () { // every pixel set to 1; together with tint can be used to do basic calculations (e.g. .mul( TG.Fill().tint( 2 ) ) -> double the value of each pixel) + return new TG.Program( { - size: function ( x, y ) { - params.size = [ x, y ]; - return this; - }, - offset: function ( x, y ) { - params.offset = [ x, y ]; - return this; - }, - rowShift: function ( value ) { - params.rowShift = value; - return this; - }, - getParams: function () { - return params; - }, + getParams: function () {}, getSource: function () { return [ - 'var value = ( ( ( y + params.offset[ 1 ] ) / params.size[ 1 ] ) & 1 ) ^ ( ( ( x + params.offset[ 0 ] + parseInt( y / params.size[ 1 ] ) * params.rowShift ) / params.size[ 0 ] ) & 1 ) ? 0 : 1', - 'color[ 0 ] = value;', - 'color[ 1 ] = value;', - 'color[ 2 ] = value;' + 'color[ 0 ] = 1;', + 'color[ 1 ] = 1;', + 'color[ 2 ] = 1;' ].join('\n'); } } ); }; -TG.Rect = function () { +TG.SinX = function () { // generates a sine wave on the x and value (brightness) axes var params = { - position: [ 0, 0 ], - size: [ 32, 32 ] + frequency: 1, + offset: 0 }; return new TG.Program( { - position: function ( x, y ) { - params.position = [ x, y ]; + frequency: function ( value ) { // sets the 'width' of the sine wave in pixels + params.frequency = ( 2 / value ) * Math.PI; return this; }, - size: function ( x, y ) { - params.size = [ x, y ]; + offset: function ( value ) { // moves the wave by 'value' pixels + params.offset = -value; return this; }, getParams: function () { @@ -638,7 +514,7 @@ TG.Rect = function () { }, getSource: function () { return [ - 'var value = ( x >= params.position[ 0 ] && x <= ( params.position[ 0 ] + params.size[ 0 ] ) && y <= ( params.position[ 1 ] + params.size[ 1 ] ) && y >= params.position[ 1 ] ) ? 1 : 0;', + 'var value = Math.sin( ( x + params.offset ) * params.frequency );', 'color[ 0 ] = value;', 'color[ 1 ] = value;', 'color[ 2 ] = value;' @@ -648,25 +524,20 @@ TG.Rect = function () { }; -TG.Circle = function () { +TG.SinY = function () { // generates a sine wave on the y and value (brightness) axes var params = { - position: [ 0, 0 ], - radius: 50, - delta: 1 + frequency: 1, + offset: 0 }; return new TG.Program( { - delta: function ( value ) { - params.delta = value; + frequency: function ( value ) { // sets the 'width' of the sine wave in pixels + params.frequency = ( 2 / value ) * Math.PI; return this; }, - position: function ( x, y ) { - params.position = [ x, y ]; - return this; - }, - radius: function ( value ) { - params.radius = value; + offset: function ( value ) { // moves the wave by 'value' pixels + params.offset = -value; return this; }, getParams: function () { @@ -674,8 +545,7 @@ TG.Circle = function () { }, getSource: function () { return [ - 'var dist = TG.Utils.distance( x, y, params.position[ 0 ], params.position[ 1 ] );', - 'var value = 1 - TG.Utils.smoothStep( params.radius - params.delta, params.radius, dist );', + 'var value = Math.sin( ( y + params.offset ) * params.frequency );', 'color[ 0 ] = value;', 'color[ 1 ] = value;', 'color[ 2 ] = value;' @@ -685,85 +555,52 @@ TG.Circle = function () { }; -TG.PutTexture = function ( texture ) { - - var params = { - offset: [ 0, 0 ], - repeat: false, - srcTex: texture.buffer - }; +TG.OR = function () { // generates a pattern using bitwise OR on the x and y coordinates return new TG.Program( { - offset: function ( x, y ) { - params.offset = [ x, y ]; - return this; - }, - repeat: function ( value ) { - params.repeat = value; - return this; - }, - getParams: function () { - return params; - }, + getParams: function () {}, getSource: function () { return [ - 'var texWidth = params.srcTex.width;', - 'var texHeight = params.srcTex.height;', - - 'var texX = Math.floor( x - params.offset[ 0 ] );', - 'var texY = Math.floor( y - params.offset[ 1 ] );', + 'var value = ( x | y ) / width;', + 'color[ 0 ] = value;', + 'color[ 1 ] = value;', + 'color[ 2 ] = value;' + ].join('\n'); + } + } ); - 'if ( texX >= texWidth || texY >= texHeight || texX < 0 || texY < 0 ) {', - 'if ( params.repeat ) {', - 'var nx, ny;', - 'var rangeX = texWidth - 1;', - 'var rangeY = texHeight - 1;', +}; - 'if ( params.repeat == 1 ) {', - 'nx = TG.Utils.wrap( texX, 0, texWidth );', - 'ny = TG.Utils.wrap( texY, 0, texHeight );', - '} else if ( params.repeat == 2 ) {', - 'nx = TG.Utils.mirroredWrap( texX, 0, rangeX );', - 'ny = TG.Utils.mirroredWrap( texY, 0, rangeY );', - '} else if ( params.repeat == 3 ) {', - 'nx = TG.Utils.clamp( texX, 0, rangeX );', - 'ny = TG.Utils.clamp( texY, 0, rangeY );', - '}', +TG.XOR = function () { // generates a pattern using bitwise XOR on the x and y coordinates - 'color = params.srcTex.getPixelNearest( nx, ny );', - '} else {', - 'color[ 0 ] = 0;', - 'color[ 1 ] = 0;', - 'color[ 2 ] = 0;', - '}', - '} else color = params.srcTex.getPixelNearest( texX, texY );', - ].join( '\n' ); + return new TG.Program( { + getParams: function () {}, + getSource: function () { + return [ + 'var value = ( x ^ y ) / width;', + 'color[ 0 ] = value;', + 'color[ 1 ] = value;', + 'color[ 2 ] = value;' + ].join('\n'); } } ); }; -// Filters - -TG.SineDistort = function () { +TG.Noise = function () { // generates a random noise pattern var params = { - sines: [ 4, 4 ], - offset: [ 0, 0 ], - amplitude: [ 16, 16 ] + seed: TG.Utils.globalSeed++, + size: 1 }; return new TG.Program( { - sines: function ( x, y ) { - params.sines = [ x, y ]; - return this; - }, - offset: function ( x, y ) { - params.offset = [ x, y ]; + seed: function ( value ) { // the same seed always results in the same noise pattern + params.seed = value; return this; }, - amplitude: function ( x, y ) { - params.amplitude = [ x, y ]; + size: function ( value ) { // sets the size of the pixel grid for the noise function + params.size = value; return this; }, getParams: function () { @@ -771,34 +608,55 @@ TG.SineDistort = function () { }, getSource: function () { return [ - 'var s = Math.sin( params.sines[ 0 ] / 100 * y + params.offset[ 0 ] ) * params.amplitude[ 0 ] + x;', - 'var t = Math.sin( params.sines[ 1 ] / 100 * x + params.offset[ 1 ] ) * params.amplitude[ 1 ] + y;', - 'color.set( src.getPixelBilinear( s, t ) );', - ].join( '\n' ); + 'var value = TG.Utils.hashRNG( params.seed, Math.floor( x / params.size ), Math.floor( y / params.size ) );', + 'color[ 0 ] = value;', + 'color[ 1 ] = value;', + 'color[ 2 ] = value;' + ].join('\n'); } } ); }; -TG.Twirl = function () { +TG.FractalNoise = function () { // generates a noise pattern with extra 'depth' by overlaying noise of different sizes var params = { - strength: 0, - radius: 120, - position: [ 128, 128 ] + interpolator: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.STEP ), + seed: TG.Utils.globalSeed++, + baseFrequency: 0.03125, + amplitude: 0.4, + persistence: 0.72, + octaves: 4, + step: 4 }; return new TG.Program( { - strength: function ( value ) { - params.strength = value / 100.0; + seed: function ( value ) { // the same seed always results in the same noise pattern + params.seed = value; return this; }, - radius: function ( value ) { - params.radius = value; + baseFrequency: function ( value ) { // sets the size of the noise for the first octave in pixels + params.baseFrequency = 1 / value; return this; }, - position: function ( x, y ) { - params.position = [ x, y ]; + amplitude: function ( value ) { // sets how much 'contrast' the noise should initially have; gets decreased with each octave + params.amplitude = value; + return this; + }, + persistence: function ( value ) { // how much the amplitude should be decreased with each octave + params.persistence = value; + return this; + }, + octaves: function ( value ) { // how many different noise patterns are layered on top of each other + params.octaves = Math.max( 1, value ); + return this; + }, + step: function ( value ) { // how much the frequency gets decreased with each octave (e.g. a value of 2 halves the size each time) + params.step = Math.max( 0, value ); + return this; + }, + interpolation: function ( value ) { // which interpolation algorithm should be used for non-whole coordinates -> should the noise be smooth or rough? (see TG.ColorInterpolator below) + params.interpolator.setInterpolation( value ); return this; }, getParams: function () { @@ -806,113 +664,80 @@ TG.Twirl = function () { }, getSource: function () { return [ - 'var dist = TG.Utils.distance( x, y, params.position[ 0 ], params.position[ 1 ] );', - - // no distortion if outside of whirl radius. - 'if (dist < params.radius) {', - 'dist = Math.pow(params.radius - dist, 2) / params.radius;', + 'var value = 0;', + 'var amp = params.amplitude;', + 'var freq = params.baseFrequency;', + 'var x1, y1, dx, dy;', + 'var v1, v2, v3, v4;', + 'var i1, i2;', - 'var angle = 2.0 * Math.PI * (dist / (params.radius / params.strength));', - 'var s = (((x - params.position[ 0 ]) * Math.cos(angle)) - ((y - params.position[ 0 ]) * Math.sin(angle)) + params.position[ 0 ] + 0.5);', - 'var t = (((y - params.position[ 1 ]) * Math.cos(angle)) + ((x - params.position[ 1 ]) * Math.sin(angle)) + params.position[ 1 ] + 0.5);', - '} else {', - 'var s = x;', - 'var t = y;', - '}', + 'for ( var j = 1; j <= params.octaves; j++ ) {', + 'x1 = Math.floor( x * freq ), y1 = Math.floor( y * freq );', - 'color.set( src.getPixelBilinear( s, t ) );', - ].join( '\n' ); - } - } ); + 'if ( params.interpolator.interpolation == TG.ColorInterpolatorMethod.STEP ) {', + 'value += TG.Utils.hashRNG( params.seed * j, x1, y1 ) * amp;', + '} else {', + 'dx = ( x * freq ) - x1, dy = ( y * freq ) - y1;', -}; + 'v1 = TG.Utils.hashRNG( params.seed * j, x1 , y1 );', + 'v2 = TG.Utils.hashRNG( params.seed * j, x1 + 1, y1 );', + 'v3 = TG.Utils.hashRNG( params.seed * j, x1 , y1 + 1 );', + 'v4 = TG.Utils.hashRNG( params.seed * j, x1 + 1, y1 + 1 );', -TG.Transform = function () { + 'params.interpolator.set( [', + '{ pos: 0, color: [ v1 ] },', + '{ pos: 1, color: [ v2 ] }', + '] );', - var params = { - offset: [ 0, 0 ], - angle: 0, - scale: [ 1, 1 ] - }; + 'i1 = params.interpolator.getColorAt( dx )[ 0 ];', - return new TG.Program( { - offset: function ( x, y ) { - params.offset = [ x, y ]; - return this; - }, - angle: function ( value ) { - params.angle = TG.Utils.deg2rad( value ); - return this; - }, - scale: function ( x, y ) { - if ( x === 0 || y === 0 ) return; - params.scale = [ x, y ]; - return this; - }, - getParams: function () { - return params; - }, - getSource: function () { - return [ - 'var x2 = x - width / 2;', - 'var y2 = y - height / 2;', - - 'var s = x2 * ( Math.cos( params.angle ) / params.scale[ 0 ] ) + y2 * -( Math.sin( params.angle ) / params.scale[ 0 ] );', - 'var t = x2 * ( Math.sin( params.angle ) / params.scale[ 1 ] ) + y2 * ( Math.cos( params.angle ) / params.scale[ 1 ] );', - - 's += params.offset[ 0 ] + width / 2;', - 't += params.offset[ 1 ] + height / 2;', - - 'color.set( src.getPixelBilinear( s, t ) );', - ].join( '\n' ); - } - } ); + 'params.interpolator.set( [', + '{ pos: 0, color: [ v3 ] },', + '{ pos: 1, color: [ v4 ] }', + '] );', -}; + 'i2 = params.interpolator.getColorAt( dx )[ 0 ];', -TG.Pixelate = function () { + 'params.interpolator.set( [', + '{ pos: 0, color: [ i1 ] },', + '{ pos: 1, color: [ i2 ] }', + '] );', - var params = { - size: [ 1, 1 ] - }; + 'value += params.interpolator.getColorAt( dy )[ 0 ] * amp;', + '}', - return new TG.Program( { - size: function ( x, y ) { - params.size = [ x, y ]; - return this; - }, - getParams: function () { - return params; - }, - getSource: function () { - return [ - 'var s = params.size[ 0 ] * Math.floor(x/params.size[ 0 ]);', - 'var t = params.size[ 1 ] * Math.floor(y/params.size[ 1 ]);', + 'freq *= params.step;', + 'amp *= params.persistence;', + '}', - 'color.set( src.getPixelNearest( s, t ) );' - ].join( '\n' ); + 'color[ 0 ] = value;', + 'color[ 1 ] = value;', + 'color[ 2 ] = value;', + ].join('\n'); } } ); }; -TG.GradientMap = function () { +TG.CellularNoise = function () { // noise based on the distance of randomly distributed points on the xy-plane var params = { - gradient: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.LINEAR ) + seed: TG.Utils.globalSeed++, + density: 32, + weightRange: 0 }; return new TG.Program( { - repeat: function ( value ) { - params.gradient.setRepeat( value ); + seed: function ( value ) { // the same seed always results in the same noise pattern + params.seed = value; return this; }, - interpolation: function ( value ) { - params.gradient.setInterpolation( value ); + density: function ( value ) { // the average distance betweeen each point in pixels; negative values invert the pattern + params.density = value; return this; }, - point: function ( position, color ) { - params.gradient.addPoint( position, color ); + weightRange: function ( value ) { // gives some points more or less 'influence' making them bigger or smaller; too high values can break the point finding algorithm! + params.weightRange = Math.max( 0, value ); return this; }, getParams: function () { @@ -920,67 +745,96 @@ TG.GradientMap = function () { }, getSource: function () { return [ - 'var v = src.getPixelNearest( x, y );', + 'var p = TG.Utils.cellNoiseBase( x, y, params.seed, params.density, params.weightRange );', - 'var r = params.gradient.getColorAt( v[ 0 ] )[ 0 ];', - 'var g = params.gradient.getColorAt( v[ 1 ] )[ 1 ];', - 'var b = params.gradient.getColorAt( v[ 2 ] )[ 2 ];', + 'var value = 1 - ( p.dist / params.density );', + 'if ( params.density < 0 ) value -= 1;', - 'color[ 0 ] = r;', - 'color[ 1 ] = g;', - 'color[ 2 ] = b;' + 'color[ 0 ] = value;', + 'color[ 1 ] = value;', + 'color[ 2 ] = value;' ].join('\n'); } } ); + }; -TG.Normalize = function () { +TG.VoronoiNoise = function () { // noise based on voronoi diagrams of randomly distributed points on the xy-plane var params = { - multiplier: 0, - offset: 0 + seed: TG.Utils.globalSeed++, + density: 32, + weightRange: 0 }; return new TG.Program( { + seed: function ( value ) { // the same seed always results in the same noise pattern + params.seed = value; + return this; + }, + density: function ( value ) { // the average distance betweeen each point in pixels + params.density = value; + return this; + }, + weightRange: function ( value ) { // gives some points more or less 'influence'; too high values can break the point finding algorithm! + params.weightRange = Math.max( 0, value ); + return this; + }, getParams: function () { return params; }, getSource: function () { return [ - 'if ( !params.init ) {', - 'var high = -Infinity;', - 'var low = Infinity;', - - 'for ( var j = 0, len = src.array.length; j < len; j++ ) {', - 'if ( j % 4 == 3 ) continue;', - - 'high = ( src.array[ j ] > high ) ? src.array[ j ] : high;', - 'low = ( src.array[ j ] < low ) ? src.array[ j ] : low;', - '}', - - 'params.offset = -low;', - 'params.multiplier = 1 / ( high - low );', - 'params.init = true;', - '}', + 'var p = TG.Utils.cellNoiseBase( x, y, params.seed, params.density, params.weightRange );', - 'var v = src.getPixelNearest( x, y );', - 'color[ 0 ] = ( v[ 0 ] + params.offset ) * params.multiplier;', - 'color[ 1 ] = ( v[ 1 ] + params.offset ) * params.multiplier;', - 'color[ 2 ] = ( v[ 2 ] + params.offset ) * params.multiplier;' - ].join( '\n' ); + 'color[ 0 ] = p.value;', + 'color[ 1 ] = p.value;', + 'color[ 2 ] = p.value;' + ].join('\n'); } } ); + }; -TG.Posterize = function () { +TG.CellularFractal = function () { // generates a noise pattern with extra 'depth' by overlaying cellular noise with different densities var params = { + seed: TG.Utils.globalSeed++, + weightRange: 0, + baseDensity: 64, + amplitude: 0.7, + persistence: 0.45, + octaves: 4, step: 2 }; return new TG.Program( { - step: function ( value ) { - params.step = Math.max( value, 2 ) + seed: function ( value ) { // the same seed always results in the same noise pattern + params.seed = value; + return this; + }, + baseDensity: function ( value ) { // sets the density for the first octave + params.baseDensity = value; + return this; + }, + weightRange: function ( value ) { // sets the weightRange for the cellular noise; see TG.CellularNoise above + params.weightRange = Math.max( 0, value ); + return this; + }, + amplitude: function ( value ) { // sets how much 'contrast' the noise should initially have; gets decreased with each octave + params.amplitude = value; + return this; + }, + persistence: function ( value ) { // how much the amplitude should be decreased with each octave + params.persistence = value; + return this; + }, + octaves: function ( value ) { // how many different noise patterns are layered on top of each other + params.octaves = Math.max( 1, value ); + return this; + }, + step: function ( value ) { // how much the density gets decreased with each octave (e.g. a value of 2 halves the density each time) + params.step = Math.max( 1, value ); return this; }, getParams: function () { @@ -988,237 +842,264 @@ TG.Posterize = function () { }, getSource: function () { return [ - 'var v = src.getPixelNearest( x, y );', - 'color[ 0 ] = Math.floor( Math.floor( v[ 0 ] * 255 / ( 255 / params.step ) ) * 255 / ( params.step - 1 ) ) / 255;', - 'color[ 1 ] = Math.floor( Math.floor( v[ 1 ] * 255 / ( 255 / params.step ) ) * 255 / ( params.step - 1 ) ) / 255;', - 'color[ 2 ] = Math.floor( Math.floor( v[ 2 ] * 255 / ( 255 / params.step ) ) * 255 / ( params.step - 1 ) ) / 255;' - ].join( '\n' ); - } - } ); - -}; + 'var p;', + 'var value = 0;', + 'var amp = params.amplitude;', + 'var dens = params.baseDensity;', -// Buffer + 'for ( var j = 1; j <= params.octaves; j++ ) {', + 'p = TG.Utils.cellNoiseBase( x, y, params.seed * j, dens, params.weightRange );', -TG.Buffer = function ( width, height ) { + 'p.dist = 1 - ( p.dist / dens );', + 'if ( dens < 0 ) p.dist -= 1;', - this.width = width; - this.height = height; + 'value += p.dist * amp;', + 'dens /= params.step;', + 'amp *= params.persistence;', + '}', - this.array = new Float32Array( width * height * 4 ); - this.color = new Float32Array( 4 ); + 'color[ 0 ] = value;', + 'color[ 1 ] = value;', + 'color[ 2 ] = value;', + ].join('\n'); + } + } ); }; -TG.Buffer.prototype = { - - constructor: TG.Buffer, - - copy: function ( buffer ) { - - this.array.set( buffer.array ); +TG.VoronoiFractal = function () { // generates a noise pattern with extra 'depth' by overlaying voronoi noise with different densities - }, - - getPixelNearest: function ( x, y ) { - - if ( y >= this.height ) y -= this.height; - if ( y < 0 ) y += this.height; - if ( x >= this.width ) x -= this.width; - if ( x < 0 ) x += this.width; - - var array = this.array; - var color = this.color; - var offset = Math.round( y ) * this.width * 4 + Math.round( x ) * 4; - - color[ 0 ] = array[ offset ]; - color[ 1 ] = array[ offset + 1 ]; - color[ 2 ] = array[ offset + 2 ]; - - return this.color; - - }, - - getPixelBilinear: function ( x, y ) { - - var px = Math.floor( x ); - var py = Math.floor( y ); - var p0 = px + py * this.width; - - var array = this.array; - var color = this.color; - - // Calculate the weights for each pixel - var fx = x - px; - var fy = y - py; - var fx1 = 1 - fx; - var fy1 = 1 - fy; - - var w1 = fx1 * fy1; - var w2 = fx * fy1; - var w3 = fx1 * fy ; - var w4 = fx * fy ; - - var p1 = p0 * 4; // 0 + 0 * w - var p2 = ( 1 + p0 ) * 4; // 1 + 0 * w - var p3 = ( 1 * this.width + p0 ) * 4; // 0 + 1 * w - var p4 = ( 1 + 1 * this.width + p0 ) * 4; // 1 + 1 * w - - var len = this.width * this.height * 4; - - if ( p1 >= len ) p1 -= len; - if ( p1 < 0 ) p1 += len; - if ( p2 >= len ) p2 -= len; - if ( p2 < 0 ) p2 += len; - if ( p3 >= len ) p3 -= len; - if ( p3 < 0 ) p3 += len; - if ( p4 >= len ) p4 -= len; - if ( p4 < 0 ) p4 += len; - - // Calculate the weighted sum of pixels (for each color channel) - color[ 0 ] = array[ p1 + 0 ] * w1 + array[ p2 + 0 ] * w2 + array[ p3 + 0 ] * w3 + array[ p4 + 0 ] * w4; - color[ 1 ] = array[ p1 + 1 ] * w1 + array[ p2 + 1 ] * w2 + array[ p3 + 1 ] * w3 + array[ p4 + 1 ] * w4; - color[ 2 ] = array[ p1 + 2 ] * w1 + array[ p2 + 2 ] * w2 + array[ p3 + 2 ] * w3 + array[ p4 + 2 ] * w4; - color[ 3 ] = array[ p1 + 3 ] * w1 + array[ p2 + 3 ] * w2 + array[ p3 + 3 ] * w3 + array[ p4 + 3 ] * w4; - - return this.color; - }, - - getPixelOffset: function ( offset ) { + var params = { + seed: TG.Utils.globalSeed++, + weightRange: 0, + baseDensity: 64, + amplitude: 0.6, + persistence: 0.6, + octaves: 4, + step: 2 + }; - var array = this.array; - var color = this.color; + return new TG.Program( { + seed: function ( value ) { // the same seed always results in the same noise pattern + params.seed = value; + return this; + }, + baseDensity: function ( value ) { // sets the density for the first octave + params.baseDensity = value; + return this; + }, + weightRange: function ( value ) { // sets the weightRange for the voronoi noise; see TG.VoronoiNoise above + params.weightRange = Math.max( 0, value ); + return this; + }, + amplitude: function ( value ) { // sets how much 'contrast' the noise should initially have; gets decreased with each octave + params.amplitude = value; + return this; + }, + persistence: function ( value ) { // how much the amplitude should be decreased with each octave (values over 1 increase the amplitude instead) + params.persistence = value; + return this; + }, + octaves: function ( value ) { // how many different noise patterns are layered on top of each other + params.octaves = Math.max( 1, value ); + return this; + }, + step: function ( value ) { // how much the density gets decreased with each octave (e.g. a value of 2 halves the density each time) + params.step = Math.max( 1, value ); + return this; + }, + getParams: function () { + return params; + }, + getSource: function () { + return [ + 'var p;', + 'var value = 0;', + 'var amp = params.amplitude;', + 'var dens = params.baseDensity;', - offset = parseInt( offset * 4 ); + 'for ( var j = 1; j <= params.octaves; j++ ) {', + 'p = TG.Utils.cellNoiseBase( x, y, params.seed * j, dens, params.weightRange );', - color[ 0 ] = array[ offset ]; - color[ 1 ] = array[ offset + 1 ]; - color[ 2 ] = array[ offset + 2 ]; - color[ 3 ] = array[ offset + 3 ]; + 'value += p.value * amp;', + 'dens /= params.step;', + 'amp *= params.persistence;', + '}', - return this.color; - }, + 'color[ 0 ] = value;', + 'color[ 1 ] = value;', + 'color[ 2 ] = value;', + ].join('\n'); + } + } ); }; -// +TG.CheckerBoard = function () { // generates a grid pattern of alternating black and white cells -TG.ColorInterpolatorMethod = { - STEP: 0, - LINEAR: 1, - SPLINE: 2, -}; - -// points must be a set pair (point, color): -// [{ pos-n: [r,g,b,a] } , ..., { pos-N: [r,g,b,a] } ] -TG.ColorInterpolator = function( method ) { + var params = { + size: [ 32, 32 ], + offset: [ 0, 0 ], + rowShift: 0 + }; - this.points = []; - this.low = 0; - this.high = 0; - this.interpolation = ( typeof( method ) == 'undefined' ) ? TG.ColorInterpolatorMethod.LINEAR : method; - this.repeat = false; + return new TG.Program( { + size: function ( x, y ) { // sets the width and height of each square in pixels + if ( typeof y == "undefined" ) y = x; + params.size = [ x, y ]; + return this; + }, + offset: function ( x, y ) { // moves the origin of the pattern to 'x' and 'y' in pixels + params.offset = [ -x, -y ]; + return this; + }, + rowShift: function ( value ) { // offsets each row by 'value' pixels + params.rowShift = value; + return this; + }, + getParams: function () { + return params; + }, + getSource: function () { + return [ + 'var value = ( ( ( y + params.offset[ 1 ] ) / params.size[ 1 ] ) & 1 ) ^ ( ( ( x + params.offset[ 0 ] + parseInt( y / params.size[ 1 ] ) * params.rowShift ) / params.size[ 0 ] ) & 1 ) ? 0 : 1', + 'color[ 0 ] = value;', + 'color[ 1 ] = value;', + 'color[ 2 ] = value;' + ].join('\n'); + } + } ); - return this; }; -TG.ColorInterpolator.prototype = { - - set: function ( points ) { - - this.points = points; - this.points.sort( function( a, b ) { - return a.pos - b.pos; - }); - - this.low = this.points[ 0 ].pos; - this.high = this.points[ this.points.length - 1 ].pos; - - return this; - - }, - - addPoint: function ( position, color ) { - - this.points.push( { pos: position, color: color } ); - this.points.sort( function( a, b ) { - return a.pos - b.pos; - }); - - this.low = this.points[ 0 ].pos; - this.high = this.points[ this.points.length - 1 ].pos; - - return this; - - }, - - setRepeat: function ( value ) { +TG.Rect = function () { // generates a basic white rectangle - this.repeat = value; - return this; - - }, - - setInterpolation: function ( value ) { - - this.interpolation = value; - return this; - - }, - - getColorAt: function ( pos ) { - - if ( this.repeat == 2 ) pos = TG.Utils.mirroredWrap( pos, this.low, this.high ); - else if ( this.repeat ) pos = TG.Utils.wrap( pos, this.low, this.high ); - else pos = TG.Utils.clamp( pos, this.low, this.high ); - - var i = 0, points = this.points; - - while ( points[ i + 1 ].pos < pos ) i ++; + var params = { + position: [ 0, 0 ], + size: [ 32, 32 ] + }; - var p1 = points[ i ]; - var p2 = points[ i + 1 ]; + return new TG.Program( { + position: function ( x, y ) { // sets the coordinates of the top-leftmost point of the rectangle + params.position = [ x, y ]; + return this; + }, + size: function ( x, y ) { // sets the width and height of the rectangle + if ( typeof y == "undefined" ) y = x; + params.size = [ x, y ]; + return this; + }, + getParams: function () { + return params; + }, + getSource: function () { + return [ + 'var value = ( x >= params.position[ 0 ] && x <= ( params.position[ 0 ] + params.size[ 0 ] ) && y <= ( params.position[ 1 ] + params.size[ 1 ] ) && y >= params.position[ 1 ] ) ? 1 : 0;', + 'color[ 0 ] = value;', + 'color[ 1 ] = value;', + 'color[ 2 ] = value;' + ].join('\n'); + } + } ); - var delta = ( pos - p1.pos ) / ( p2.pos - p1.pos ); +}; - if ( this.interpolation == TG.ColorInterpolatorMethod.STEP ) { +TG.Circle = function () { // generates a basic white circle - return p1.color; + var params = { + position: [ 0, 0 ], + radius: 50, + delta: 1 + }; - } else if ( this.interpolation == TG.ColorInterpolatorMethod.LINEAR ) { + return new TG.Program( { + delta: function ( value ) { // sets how many pixels from the edge of the circle the brightness should gradually decrease; 0 results in a completely crisp circle + params.delta = value; + return this; + }, + position: function ( x, y ) { // sets the coordinates of the center of the circle + params.position = [ x, y ]; + return this; + }, + radius: function ( value ) { // sets the radius of the circle in pixels + params.radius = value; + return this; + }, + getParams: function () { + return params; + }, + getSource: function () { + return [ + 'var dist = TG.Utils.distance( x, y, params.position[ 0 ], params.position[ 1 ] );', + 'var value = 1 - TG.Utils.smoothStep( params.radius - params.delta, params.radius, dist );', + 'color[ 0 ] = value;', + 'color[ 1 ] = value;', + 'color[ 2 ] = value;' + ].join('\n'); + } + } ); - return TG.Utils.mixColors( p1.color, p2.color, delta ); +}; - } else if ( this.interpolation == TG.ColorInterpolatorMethod.SPLINE ) { +TG.PutTexture = function ( texture ) { // puts an already existing texture onto another one - var ar = 2 * p1.color[ 0 ] - 2 * p2.color[ 0 ]; - var br = -3 * p1.color[ 0 ] + 3 * p2.color[ 0 ]; - var dr = p1.color[ 0 ]; + var params = { + offset: [ 0, 0 ], + repeat: false, + srcTex: texture.buffer + }; - var ag = 2 * p1.color[ 1 ] - 2 * p2.color[ 1 ]; - var bg = -3 * p1.color[ 1 ] + 3 * p2.color[ 1 ]; - var dg = p1.color[ 1 ]; + return new TG.Program( { + offset: function ( x, y ) { // sets the coordinates of the top-leftmost point + params.offset = [ x, y ]; + return this; + }, + repeat: function ( value ) { // which algorithm should be used on coordinates outside of the texture; 1 = wrap around, 2 = wrap around but mirrored, 3 = extend the last pixel + params.repeat = value; + return this; + }, + getParams: function () { + return params; + }, + getSource: function () { + return [ + 'var texWidth = params.srcTex.width;', + 'var texHeight = params.srcTex.height;', - var ab = 2 * p1.color[ 2 ] - 2 * p2.color[ 2 ]; - var bb = -3 * p1.color[ 2 ] + 3 * p2.color[ 2 ]; - var db = p1.color[ 2 ]; + 'var texX = Math.floor( x - params.offset[ 0 ] );', + 'var texY = Math.floor( y - params.offset[ 1 ] );', - var delta2 = delta * delta; - var delta3 = delta2 * delta; + 'if ( texX >= texWidth || texY >= texHeight || texX < 0 || texY < 0 ) {', + 'if ( params.repeat ) {', + 'var nx, ny;', + 'var rangeX = texWidth - 1;', + 'var rangeY = texHeight - 1;', - return [ - ar * delta3 + br * delta2 + dr, - ag * delta3 + bg * delta2 + dg, - ab * delta3 + bb * delta2 + db - ]; + 'if ( params.repeat == 1 ) {', + 'nx = TG.Utils.wrap( texX, 0, texWidth );', + 'ny = TG.Utils.wrap( texY, 0, texHeight );', + '} else if ( params.repeat == 2 ) {', + 'nx = TG.Utils.mirroredWrap( texX, 0, rangeX );', + 'ny = TG.Utils.mirroredWrap( texY, 0, rangeY );', + '} else if ( params.repeat == 3 ) {', + 'nx = TG.Utils.clamp( texX, 0, rangeX );', + 'ny = TG.Utils.clamp( texY, 0, rangeY );', + '}', + 'color = params.srcTex.getPixelNearest( nx, ny );', + '} else {', + 'color[ 0 ] = 0;', + 'color[ 1 ] = 0;', + 'color[ 2 ] = 0;', + '}', + '} else color = params.srcTex.getPixelNearest( texX, texY );', + ].join( '\n' ); } - - } + } ); }; -TG.RadialGradient = function () { +TG.RadialGradient = function () { // generates a circular gradient around a point var params = { gradient: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.LINEAR ), @@ -1227,29 +1108,29 @@ TG.RadialGradient = function () { }; return new TG.Program( { - repeat: function ( value ) { + repeat: function ( value ) { // sets how the gradient should repeat if outside of the range of the added points; 0 = clamp to the last point, 1 = wrap around, 2 = wrap around but mirrored params.gradient.setRepeat( value ); return this; }, - radius: function ( value ) { + radius: function ( value ) { // sets how far from the center the last point (i.e. position 1) of the gradient is params.radius = value; return this; }, - interpolation: function ( value ) { + interpolation: function ( value ) { // sets the interpolation method of the gradient; 0 = step -> do not interpolate, 1 = linear-, 2 = spline-, 3 = cosine-interpolation params.gradient.setInterpolation( value ); return this; }, - center: function ( x, y ) { + center: function ( x, y ) { // sets the center around which the gradient is generated params.center = [ x, y ]; return this; }, + point: function ( position, r, g, b ) { // adds a point to the gradient; position 0 is the center and 1 is the radius + params.gradient.addPoint( position, r, g, b ); + return this; + }, getParams: function () { return params; }, - point: function ( position, color ) { - params.gradient.addPoint( position, color ); - return this; - }, getSource: function () { return [ @@ -1262,28 +1143,28 @@ TG.RadialGradient = function () { }; -TG.LinearGradient = function () { +TG.LinearGradient = function () { // generates a gradient across the whole texture var params = { gradient: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.LINEAR ) }; return new TG.Program( { - repeat: function ( value ) { + repeat: function ( value ) { // sets how the gradient should repeat if outside of the range of the added points; 0 = clamp to the last point, 1 = wrap around, 2 = wrap around but mirrored params.gradient.setRepeat( value ); return this; }, - interpolation: function ( value ) { + interpolation: function ( value ) { // sets the interpolation method of the gradient; 0 = step -> do not interpolate, 1 = linear-, 2 = spline-, 3 = cosine-interpolation params.gradient.setInterpolation( value ); return this; }, + point: function ( position, r, g, b ) { // adds a point to the gradient; position 0 is x0 and 1 is the width of the texture + params.gradient.addPoint( position, r, g, b ); + return this; + }, getParams: function () { return params; }, - point: function ( position, color ) { - params.gradient.addPoint( position, color ); - return this; - }, getSource: function () { return [ @@ -1295,115 +1176,258 @@ TG.LinearGradient = function () { }; +// --- Filters --- -// +TG.SineDistort = function () { // warps the texture in a wavy pattern -TG.Utils = { + var params = { + sines: [ 4, 4 ], + offset: [ 0, 0 ], + amplitude: [ 16, 16 ] + }; - smoothStep: function ( edge0, edge1, x ) { + return new TG.Program( { + sines: function ( x, y ) { // sets the width of the sine waves on the x and y axis respectively + if ( typeof y == "undefined" ) y = x; + params.sines = [ x, y ]; + return this; + }, + offset: function ( x, y ) { // shifts the 'phase' of each wave + params.offset = [ x, y ]; + return this; + }, + amplitude: function ( x, y ) { // sets the 'amplitude' of each wave (or intensity of the filter) + if ( typeof y == "undefined" ) y = x; + params.amplitude = [ x, y ]; + return this; + }, + getParams: function () { + return params; + }, + getSource: function () { + return [ + 'var s = Math.sin( params.sines[ 0 ] / 100 * y + params.offset[ 0 ] ) * params.amplitude[ 0 ] + x;', + 'var t = Math.sin( params.sines[ 1 ] / 100 * x + params.offset[ 1 ] ) * params.amplitude[ 1 ] + y;', + 'color.set( src.getPixelBilinear( s, t ) );', + ].join( '\n' ); + } + } ); - // Scale, bias and saturate x to 0..1 range - x = TG.Utils.clamp( ( x - edge0 ) / ( edge1 - edge0 ), 0, 1 ); +}; - // Evaluate polynomial - return x * x * ( 3 - 2 * x ); +TG.Twirl = function () { // distorts the texture into a vortex around a point - }, + var params = { + strength: 0, + radius: 120, + position: [ 128, 128 ] + }; - mixColors: function( c1, c2, delta ) { + return new TG.Program( { + strength: function ( value ) { // sets how much the texture should be rotated + params.strength = value / 100.0; + return this; + }, + radius: function ( value ) { // sets the radius of influence + params.radius = value; + return this; + }, + position: function ( x, y ) { // sets the coordinates of the center of the swirl + params.position = [ x, y ]; + return this; + }, + getParams: function () { + return params; + }, + getSource: function () { + return [ + 'var dist = TG.Utils.distance( x, y, params.position[ 0 ], params.position[ 1 ] );', - return [ - c1[ 0 ] * ( 1 - delta ) + c2[ 0 ] * delta, - c1[ 1 ] * ( 1 - delta ) + c2[ 1 ] * delta, - c1[ 2 ] * ( 1 - delta ) + c2[ 2 ] * delta, - c1[ 3 ] * ( 1 - delta ) + c2[ 3 ] * delta, - ]; - }, + // no distortion if outside of whirl radius. + 'if (dist < params.radius) {', + 'dist = Math.pow(params.radius - dist, 2) / params.radius;', - distance: function( x0, y0, x1, y1 ) { + 'var angle = 2.0 * Math.PI * (dist / (params.radius / params.strength));', + 'var s = (((x - params.position[ 0 ]) * Math.cos(angle)) - ((y - params.position[ 0 ]) * Math.sin(angle)) + params.position[ 0 ] + 0.5);', + 'var t = (((y - params.position[ 1 ]) * Math.cos(angle)) + ((x - params.position[ 1 ]) * Math.sin(angle)) + params.position[ 1 ] + 0.5);', + '} else {', + 'var s = x;', + 'var t = y;', + '}', - var dx = x1 - x0, dy = y1 - y0; - return Math.sqrt( dx * dx + dy * dy ); + 'color.set( src.getPixelBilinear( s, t ) );', + ].join( '\n' ); + } + } ); - }, +}; - clamp: function( value, min, max ) { +TG.Transform = function () { // moves, rotates or scales the texture - return Math.min( Math.max( value, min ), max ); + var params = { + offset: [ 0, 0 ], + angle: 0, + scale: [ 1, 1 ] + }; - }, - - wrap: function ( value, min, max ) { - var v = value - min; - var r = max - min; + return new TG.Program( { + offset: function ( x, y ) { // moves the texture by 'x' and 'y' pixels + params.offset = [ -x, -y ]; + return this; + }, + angle: function ( value ) { // rotates the texture by 'value' degrees around the origin (x: 0, y: 0) + params.angle = TG.Utils.deg2rad( value ); + return this; + }, + scale: function ( x, y ) { // scales the texture by 'x' and 'y' (e.g. 2 doubles the size) + x = x || 1; + y = y || x; - return ( ( r + v % r ) % r ) + min; - }, + params.scale = [ x, y ]; + return this; + }, + getParams: function () { + return params; + }, + getSource: function () { + return [ + 'var x2 = x - width / 2;', + 'var y2 = y - height / 2;', - mirroredWrap: function ( value, min, max ) { - var v = value - min; - var r = ( max - min ) * 2; + 'var s = x2 * ( Math.cos( params.angle ) / params.scale[ 0 ] ) + y2 * -( Math.sin( params.angle ) / params.scale[ 0 ] );', + 'var t = x2 * ( Math.sin( params.angle ) / params.scale[ 1 ] ) + y2 * ( Math.cos( params.angle ) / params.scale[ 1 ] );', - v = ( r + v % r ) % r; + 's += params.offset[ 0 ] + width / 2;', + 't += params.offset[ 1 ] + height / 2;', - if ( v > max - min ) { - return ( r - v ) + min; - } else { - return v + min; + 'color.set( src.getPixelBilinear( s, t ) );', + ].join( '\n' ); } - }, + } ); - deg2rad: function ( deg ) { +}; - return deg * Math.PI / 180; +TG.Pixelate = function () { // divides the texture into 'pixels' - }, + var params = { + size: [ 1, 1 ] + }; - hashRNG: function ( seed, x, y ) { - seed = ( Math.abs( seed % 2147483648 ) == 0 ) ? 1 : seed; + return new TG.Program( { + size: function ( x, y ) { // set the width and height of each 'pixel' + if ( typeof y == "undefined" ) y = x; + params.size = [ x, y ]; + return this; + }, + getParams: function () { + return params; + }, + getSource: function () { + return [ + 'var s = params.size[ 0 ] * Math.floor(x/params.size[ 0 ]);', + 'var t = params.size[ 1 ] * Math.floor(y/params.size[ 1 ]);', - var a = ( ( seed * ( x + 1 ) * 777 ) ^ ( seed * ( y + 1 ) * 123 ) ) % 2147483647; - a = (a ^ 61) ^ (a >> 16); - a = a + (a << 3); - a = a ^ (a >> 4); - a = a * 0x27d4eb2d; - a = a ^ (a >> 15); - a = a / 2147483647; + 'color.set( src.getPixelNearest( s, t ) );' + ].join( '\n' ); + } + } ); - return a; - }, - - cellNoiseBase: function ( x, y, seed, density, weightRange ) { - var qx, qy, rx, ry, w, px, py, dx, dy; - var dist, value; - var shortest = Infinity; - density = Math.abs( density ); +}; - for ( var sx = -2; sx <= 2; sx++ ) { - for ( var sy = -2; sy <= 2; sy++ ) { - qx = Math.ceil( x / density ) + sx; - qy = Math.ceil( y / density ) + sy; +TG.GradientMap = function () { // takes the value of each pixel and maps it to a color in a gradient; best used on a grayscale image - rx = TG.Utils.hashRNG( seed, qx, qy ); - ry = TG.Utils.hashRNG( seed * 2, qx, qy ); - w = ( weightRange > 0 ) ? 1 + TG.Utils.hashRNG( seed * 3, qx, qy ) * weightRange : 1; + var params = { + gradient: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.LINEAR ) + }; - px = ( rx + qx ) * density; - py = ( ry + qy ) * density; + return new TG.Program( { + repeat: function ( value ) { // how to map values that are out of range onto the gradient; 0 = clamp to the last (first) point of the gradient, 1 = wrap around, 2 = wrap around but mirrored + params.gradient.setRepeat( value ); + return this; + }, + interpolation: function ( value ) { // set the interpolation method for the gradient; 0 = step -> do not interpolate, 1 = linear-, 2 = spline-, 3 = cosine-interpolation + params.gradient.setInterpolation( value ); + return this; + }, + point: function ( position, r, g, b ) { // add a point to the gradient; position 0 is black and 1 is white in the original texture + params.gradient.addPoint( position, r, g, b ); + return this; + }, + getParams: function () { + return params; + }, + getSource: function () { + return [ + 'var v = src.getPixelNearest( x, y );', - dx = Math.abs( px - x ); - dy = Math.abs( py - y ); + 'var r = params.gradient.getColorAt( v[ 0 ] )[ 0 ];', + 'var g = params.gradient.getColorAt( v[ 1 ] )[ 1 ];', + 'var b = params.gradient.getColorAt( v[ 2 ] )[ 2 ];', - dist = ( dx * dx + dy * dy ) * w; + 'color[ 0 ] = r;', + 'color[ 1 ] = g;', + 'color[ 2 ] = b;' + ].join('\n'); + } + } ); - if ( dist < shortest ) { - shortest = dist; - value = rx; - } - } +}; + +TG.Normalize = function () { // adjusts the whole texture so that every pixel is in the visible range (0 - 1) + + return new TG.Program( { + getParams: function () {}, + getInit: function () { + return [ + 'var high = -Infinity;', + 'var low = Infinity;', + + 'for ( var j = 0, len = src.array.length; j < len; j++ ) {', + 'if ( j % 4 == 3 ) continue;', + + 'high = ( src.array[ j ] > high ) ? src.array[ j ] : high;', + 'low = ( src.array[ j ] < low ) ? src.array[ j ] : low;', + '}', + + 'var offset = -low;', + 'var multiplier = 1 / ( high - low );', + ].join( '\n' ); + }, + getSource: function () { + return [ + 'var v = src.getPixelNearest( x, y );', + 'color[ 0 ] = ( v[ 0 ] + offset ) * multiplier;', + 'color[ 1 ] = ( v[ 1 ] + offset ) * multiplier;', + 'color[ 2 ] = ( v[ 2 ] + offset ) * multiplier;' + ].join( '\n' ); } + } ); - return { dist: Math.sqrt( shortest ), value: value }; - } +}; + +TG.Posterize = function () { // reduces the amount of colors in a texture + + var params = { + step: 2 + }; + + return new TG.Program( { + step: function ( value ) { // sets how many possible values each color channel is divided into (2 means possible values are 0 and 1, 3 means 0, 0.5 and 1 etc.) + params.step = Math.max( value, 2 ); + return this; + }, + getParams: function () { + return params; + }, + getSource: function () { + return [ + 'var v = src.getPixelNearest( x, y );', + 'color[ 0 ] = Math.floor( v[ 0 ] / ( 1 / params.step ) ) / ( params.step - 1 );', + 'color[ 1 ] = Math.floor( v[ 1 ] / ( 1 / params.step ) ) / ( params.step - 1 );', + 'color[ 2 ] = Math.floor( v[ 2 ] / ( 1 / params.step ) ) / ( params.step - 1 );' + ].join( '\n' ); + } + } ); }; +