Difference between revisions of "Widget:Logo"
(created initial logo widget) |
m (now ''really'' centered it) |
||
(70 intermediate revisions by the same user not shown) | |||
Line 8: | Line 8: | ||
<nowiki>{{#widget:</nowiki>{{PAGENAME}}<nowiki> | <nowiki>{{#widget:</nowiki>{{PAGENAME}}<nowiki> | ||
− | |image= | + | |image=/w/images/e/e9/ACKsmass_logo.png |
− | |width= | + | |width=600px |
− | |height= | + | |height=200px |
|padding=8px | |padding=8px | ||
|float=right | |float=right | ||
Line 17: | Line 17: | ||
This will give the following result:<br/> | This will give the following result:<br/> | ||
{{#widget:{{PAGENAME}} | {{#widget:{{PAGENAME}} | ||
− | |image= | + | |image=/w/images/e/e9/ACKsmass_logo.png |
− | |width= | + | |width=600px |
− | |height= | + | |height=200px |
|padding=8px | |padding=8px | ||
|float=right | |float=right | ||
}}<br/> | }}<br/> | ||
− | + | '''Notes''' | |
+ | * it will display snow in December - March and will show Christmas lights between 7 December and 7 January | ||
+ | * '''image''' is mandatory, the rest is optional. | ||
+ | *: it also must be written without protocol since colon (''':''') is not allowed, and may be relative, for example: ''//ackspace.nl/w/images/e/e9/ACKsmass_logo.png'' or ''/w/images/e/e9/ACKsmass_logo.png'' | ||
+ | * You must provide a unit for the sizes (i.e. px, %, etc.) | ||
+ | |||
+ | Also note that there is a chain of browser extensions and apps within the [[GitHub::https://github.com/ACKspace/espixelflut|espixelflut github repo]]: | ||
+ | * [https://github.com/ACKspace/espixelflut/blob/master/chrome/FlutLogo%20app%20connector%20extension.crx FlutLogo app connector extension.crx]: the extension that listens to the UDP connector app and drives the logo "LEDs" ([https://github.com/ACKspace/espixelflut/tree/master/chrome/FlutLogo%20app%20connector%20extension source available], but it needs a static ID for the connector app) | ||
+ | * [https://github.com/ACKspace/espixelflut/tree/master/chrome/FlutLogo%20UDP%20connector%20app FlutLogo UDP connector app] the app that listens to UDP ART-net packets and connects to the browser extension | ||
+ | * [https://github.com/ACKspace/espixelflut/tree/master/chrome/FlutArt FlutArt]: an ART-net app that can be used to control lights (which also works on the browser extension) | ||
+ | |||
+ | Note that if you want to use the [[LED sleeve]] [[GitHub::https://github.com/AlbertVos/bitlair-ohm2013-ledstrip-contol|python code from bitlair]], you have to provide a different listening port to avoid conflicts, like this: | ||
+ | <code>./fire2.py port=0 addr='[("127.0.0.1",6454)]'</code> | ||
== Copy to your site == | == Copy to your site == | ||
To use this widget on your site, just install [http://www.mediawiki.org/wiki/Extension:Widgets MediaWiki Widgets extension] and copy [{{fullurl:{{FULLPAGENAME}}|action=edit}} full source code] of this page to your wiki as '''{{FULLPAGENAME}}''' article. | To use this widget on your site, just install [http://www.mediawiki.org/wiki/Extension:Widgets MediaWiki Widgets extension] and copy [{{fullurl:{{FULLPAGENAME}}|action=edit}} full source code] of this page to your wiki as '''{{FULLPAGENAME}}''' article. | ||
− | + | ||
− | </noinclude><includeonly><script type="text/javascript"> | + | </noinclude><includeonly> |
+ | <canvas id="logo" width="<!--{$width|escape:html|default:auto}-->" height="<!--{$height|escape:html|default:auto}-->" style="float:<!--{$float|escape:html|default:none}-->"><img id="img" src="<!--{$image|escape:urlpathinfo}-->"/></canvas> | ||
+ | <script type="text/javascript"> | ||
(function( ) | (function( ) | ||
{ | { | ||
"use strict"; | "use strict"; | ||
− | + | var timer = null; | |
+ | var start = null; | ||
+ | var oldTimestamp = null; | ||
+ | var maxFlakes = 200; | ||
+ | var snowFlakes = []; | ||
+ | var dynamicImage = null; | ||
+ | var width = null; | ||
+ | var height = null; | ||
+ | var ctx = null; | ||
+ | var offsetY = 50; | ||
+ | var alphaDir = 0.01; | ||
+ | var alpha = 0; | ||
+ | var buffer; | ||
+ | var bufferContext; | ||
+ | var hashParams = location.hash.split("#").slice(1); | ||
+ | var debug = ( hashParams.indexOf("debug") !== -1 ); | ||
+ | //mw.config.set("debug", true) | ||
− | + | function blink() | |
− | + | { | |
− | + | treeLights[ nToggle ].on = !treeLights[ nToggle ].on; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | nToggle++; | |
− | + | if ( nToggle >= treeLights.length ) | |
− | + | nToggle = 0; | |
+ | } | ||
− | nToggle | + | var nToggle = 0; |
− | + | var treeLights = [ | |
− | + | { x: 84, y: 16, c: [0xff,0x00,0x00], on: false }, | |
− | + | { x: 101, y: 40, c: [0xff,0xa5,0x00], on: false }, | |
+ | { x: 87, y: 60, c: [0xff,0xc0,0xcb], on: false }, | ||
+ | { x: 64, y: 79, c: [0x33,0x66,0xff], on: false }, | ||
+ | { x: 119, y: 91, c: [0xff,0xff,0x00], on: false } | ||
+ | ]; | ||
+ | var snow = [255,255,255]; | ||
+ | var ackbit = [255,0,0]; | ||
− | + | var oldTimestamp = null; | |
− | + | function snowFall( _timestamp ) | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | var oldTimestamp = null; | ||
− | function snowFall( _timestamp | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
{ | { | ||
− | // | + | // Smooth progress |
− | + | if ( !start ) | |
− | var | + | start = _timestamp; |
+ | var progress = _timestamp - start; | ||
− | + | if ( ( progress / 500 ) > snowFlakes.length && ( _timestamp - oldTimestamp < 50 ) ) | |
+ | snowFlakes.push( { x: ( Math.random( ) * width ) | 0, y: 0, c:snow.slice() } ); | ||
oldTimestamp = _timestamp; | oldTimestamp = _timestamp; | ||
− | + | if ( snowFlakes.length ) | |
{ | { | ||
− | // | + | // Fetch a random flake |
− | + | var pixelPos; | |
− | + | var nFlakeNot = ( Math.random( ) * snowFlakes.length ) | 0; | |
+ | |||
+ | var delta = _timestamp - oldTimestamp; | ||
+ | oldTimestamp = _timestamp; | ||
− | var | + | for ( var nFlake = 0; nFlake < snowFlakes.length; nFlake++ ) |
− | |||
− | |||
− | |||
{ | { | ||
− | // Fixate flake | + | // Skip some flakes |
− | + | if ( Math.random() > 0.8 ) | |
− | + | continue; | |
− | + | ||
− | + | var flake = snowFlakes[ nFlake ]; | |
− | + | // Calculate the new position of the flake. | |
+ | // If it can't move anywhere down (3 positions): fixate it in the dynamic image and reset the flake | ||
+ | if ( !updateFlake( flake, dynamicImage ) ) | ||
+ | { | ||
+ | // Fixate flake | ||
+ | pixelPos = ( flake.x + width * flake.y ) * 4; | ||
+ | dynamicImage.data[ pixelPos + 0 ] = flake.c[0]; | ||
+ | dynamicImage.data[ pixelPos + 1 ] = flake.c[1]; | ||
+ | dynamicImage.data[ pixelPos + 2 ] = flake.c[2]; | ||
+ | dynamicImage.data[ pixelPos + 3 ] = 254; | ||
+ | |||
+ | // Generate new flake | ||
+ | flake = { x: ( Math.random( ) * width ) | 0, y: 0, c:snow.slice() }; | ||
+ | } | ||
+ | |||
+ | // Store the updated or newly created flake. | ||
+ | snowFlakes[ nFlake ] = flake; | ||
− | |||
− | |||
} | } | ||
− | // | + | // Clear the current frame |
− | |||
// Draw the dynamic image | // Draw the dynamic image | ||
ctx.putImageData( dynamicImage, 0, 0 ); | ctx.putImageData( dynamicImage, 0, 0 ); | ||
+ | |||
+ | // Draw all flakes | ||
+ | ctx.fillStyle = 'white'; | ||
+ | ctx.shadowBlur = 0; | ||
+ | |||
+ | for ( nFlake = 0; nFlake < snowFlakes.length; nFlake++ ) | ||
+ | { | ||
+ | flake = snowFlakes[ nFlake ]; | ||
+ | ctx.fillStyle = 'rgb(' + flake.c[0] + ',' + flake.c[1] + ',' + flake.c[2] + ')'; | ||
+ | ctx.fillRect( flake.x, flake.y, 1, 1 ); | ||
+ | } | ||
+ | |||
+ | // Draw all tree lights (only during holiday season | ||
+ | if ( is_winter_holiday() ) | ||
+ | { | ||
+ | for ( var t = 0; t < treeLights.length; t++ ) | ||
+ | drawTreeLight( treeLights[t], false ); | ||
+ | } | ||
+ | |||
+ | // Draw the glowing ACK bit | ||
+ | ctx.strokeStyle = 'rgb(' + ackbit[0] + ',' + ackbit[1] + ',' + ackbit[2] + ')'; | ||
+ | ctx.shadowColor = 'rgba(' + ackbit[0] + ',' + ackbit[1] + ',' + ackbit[2] + ',' + (0.00130 * ackbit[0] + 0.00196 * ackbit[1] + 0.00063 * ackbit[2]) + ')'; | ||
+ | ctx.globalAlpha = 1; | ||
+ | ctx.lineWidth = 10; | ||
+ | ctx.shadowBlur = 70; | ||
+ | for ( var n = 0; n < 10; n++ ) | ||
+ | { | ||
+ | ctx.beginPath(); | ||
+ | ctx.rect( 287, 88 + offsetY, 8, 7 ); | ||
+ | ctx.stroke(); | ||
+ | } | ||
+ | |||
+ | // Draw all tree lights overlay: either on or off | ||
+ | ctx.shadowBlur = 0; | ||
+ | // Draw all tree lights (only during holiday season | ||
+ | if ( is_winter_holiday() ) | ||
+ | { | ||
+ | for ( var t = 0; t < treeLights.length; t++ ) | ||
+ | drawTreeLight( treeLights[t], true ); | ||
+ | } | ||
} | } | ||
− | + | window.requestAnimationFrame( snowFall ); | |
− | + | } | |
− | |||
− | + | function getRndColor() | |
+ | { | ||
+ | // Thanks to Adafruit's NeoPixel lib | ||
+ | var wheelPos = Math.random() * 255 | 0; | ||
+ | if( wheelPos < 85 ) | ||
{ | { | ||
− | + | return [ (255 - wheelPos * 2), 85, (85 + wheelPos * 2) ]; | |
− | |||
} | } | ||
− | + | if( wheelPos < 170 ) | |
− | + | { | |
− | + | wheelPos -= 85; | |
+ | return [ 85, (85 + wheelPos * 2), (255 - wheelPos * 2) ]; | ||
+ | } | ||
+ | wheelPos -= 170; | ||
+ | return [ (85 + wheelPos * 2), (255 - wheelPos * 2), 85 ]; | ||
+ | } | ||
+ | |||
+ | function drawTreeLight( _treeLight, _glow ) | ||
+ | { | ||
+ | ctx.strokeStyle = ctx.shadowColor = 'rgb(' + _treeLight.c[0] + ',' + _treeLight.c[1] + ',' + _treeLight.c[2] + ')'; | ||
− | + | if ( !_glow ) | |
− | |||
− | |||
− | |||
− | |||
− | |||
{ | { | ||
+ | // Draw the light | ||
+ | ctx.lineWidth = 3; | ||
+ | ctx.globalAlpha = 1; | ||
ctx.beginPath(); | ctx.beginPath(); | ||
− | ctx. | + | ctx.arc( _treeLight.x, _treeLight.y + offsetY, 2, 0, 2 * Math.PI ); |
ctx.stroke(); | ctx.stroke(); | ||
+ | } | ||
+ | else if ( _treeLight.on ) | ||
+ | { | ||
+ | // Let it glow! | ||
+ | ctx.lineWidth = 1; | ||
+ | for ( var n = 0; n < 20; n++ ) | ||
+ | { | ||
+ | ctx.globalAlpha = (n / 30) * (0.00130 * _treeLight.c[0] + 0.00196 * _treeLight.c[1] + 0.00063 * _treeLight.c[2]); | ||
+ | ctx.beginPath(); | ||
+ | ctx.arc( _treeLight.x, _treeLight.y + offsetY, 22 - n, 0, 2 * Math.PI ); | ||
+ | ctx.stroke(); | ||
+ | } | ||
+ | ctx.globalAlpha = 1; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | // Add dark 'off' mask | ||
+ | ctx.strokeStyle = ctx.shadowColor = "rgba( 0,0,0,0.3)"; | ||
+ | ctx.lineWidth = 3; | ||
+ | ctx.globalAlpha = 1; | ||
+ | ctx.beginPath(); | ||
+ | ctx.arc( _treeLight.x, _treeLight.y + offsetY, 2, 0, 2 * Math.PI ); | ||
+ | ctx.stroke(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | function updateFlake( _flake, _dynamicImage ) | ||
+ | { | ||
+ | var newFlake; | ||
+ | |||
+ | // Check each next possible 'y' pixel, and add it to the list of possibilities | ||
+ | var possibilities = []; | ||
+ | |||
+ | // Try three flake possibilities | ||
+ | for ( var n = -1; n <= 1; n++ ) | ||
+ | { | ||
+ | newFlake = tryCreateFlake( _flake, n, 1, _dynamicImage ); | ||
+ | if ( newFlake ) | ||
+ | possibilities.push( newFlake ); | ||
} | } | ||
− | + | if ( !possibilities.length ) | |
− | + | { | |
− | + | newFlake = tryCreateFlake( _flake, -2, 1, _dynamicImage ); | |
− | + | if ( newFlake ) | |
− | + | possibilities.push( newFlake ); | |
+ | |||
+ | newFlake = tryCreateFlake( _flake, 2, 1, _dynamicImage ); | ||
+ | if ( newFlake ) | ||
+ | possibilities.push( newFlake ); | ||
+ | } | ||
− | + | // No possibilities means, no update: will fixate this flake | |
− | + | if ( !possibilities.length ) | |
+ | return false; | ||
− | + | // Pick a flake | |
− | + | var newFlake = possibilities[ ( Math.random( ) * possibilities.length ) | 0 ]; | |
− | + | _flake.x = newFlake.x; | |
+ | _flake.y = newFlake.y; | ||
− | + | // Flake updated | |
− | + | return true; | |
− | // | ||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
− | + | ||
+ | function tryCreateFlake( _flake, _x, _y, _dynamicImage ) | ||
{ | { | ||
− | // | + | // Don't create flake if we're off the bottom of the screen |
− | + | if ( ( _flake.y + _y ) >= height ) | |
− | + | return null; | |
+ | |||
+ | // Don't allow to go off the sides of the screen | ||
+ | if ( ( _flake.x + _x ) < 0 ) | ||
+ | return null; | ||
+ | |||
+ | if ( ( _flake.x + _x ) >= width ) | ||
+ | return null; | ||
+ | |||
+ | if ( debug ) | ||
{ | { | ||
− | + | // Make sure we're not colliding with other flakes | |
− | + | for ( var nFlake = 0; nFlake < snowFlakes.length; ++nFlake ) | |
− | + | { | |
− | + | var f = snowFlakes[ nFlake ]; | |
+ | if ( f.x === _x && f.y === _y ) | ||
+ | return null; | ||
+ | } | ||
} | } | ||
+ | |||
+ | var newFlake = { x: _flake.x + _x, y: _flake.y + _y, c: _flake.c }; | ||
+ | |||
+ | // Check the alpha channel (apparently, our logo has opacity < 250) | ||
+ | if ( _dynamicImage.data[ ( newFlake.x + width * newFlake.y ) * 4 + 3 ] > 220 ) | ||
+ | return null; | ||
+ | |||
+ | // All test cases valid, return the resulting flake | ||
+ | return newFlake; | ||
} | } | ||
− | + | ||
+ | function is_winter() | ||
{ | { | ||
− | + | return (new Date( "1 april " + (new Date() ).getFullYear() )).valueOf() > Date.now() || (new Date( "1 december " + (new Date() ).getFullYear() )).valueOf() < Date.now(); | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | function is_winter_holiday() | |
− | |||
{ | { | ||
− | + | return (new Date( "7 january " + (new Date() ).getFullYear() )).valueOf() > Date.now() || (new Date( "7 december " + (new Date() ).getFullYear() )).valueOf() < Date.now(); | |
− | |||
− | |||
} | } | ||
− | + | function window_load() | |
− | + | { | |
− | + | var logo = document.getElementById( "logo" ); | |
+ | ctx = logo.getContext("2d"); | ||
− | + | buffer = document.createElement('canvas'); | |
− | + | buffer.width = logo.width; | |
− | + | buffer.height = logo.height; | |
− | + | bufferContext = buffer.getContext('2d'); | |
− | + | var img = document.getElementById("img"); | |
− | + | ctx.drawImage( img, 0, offsetY ); | |
− | |||
− | + | bufferContext.drawImage( img, 0, offsetY ); | |
− | + | bufferContext.fillStyle = 'white'; | |
− | + | bufferContext.shadowBlur = 0; | |
− | |||
− | |||
− | + | width = logo.width; | |
− | + | height = logo.height; | |
− | + | dynamicImage = ctx.getImageData( 0, 0, width, height ); | |
+ | if ( debug ) | ||
+ | { | ||
+ | console && console.log( "winter:", is_winter() ); | ||
+ | console && console.log( "winter holiday:", is_winter_holiday() ); | ||
+ | window.snowBump = function( _n ) | ||
+ | { | ||
+ | for (;--_n;) | ||
+ | snowFlakes.push( { x: ( Math.random( ) * width ) | 0, y: 0, c:[255,255,255] } ); | ||
+ | }; | ||
− | + | logo.addEventListener( "touchdown", function(e){ e.preventDefault(); e.stopPropagation(); }, { passive: false }); | |
− | + | logo.addEventListener( "touchmove", function(e){ e.preventDefault(); e.stopPropagation(); }, { passive: false }); | |
+ | logo.addEventListener( "mousemove", function( _event ) | ||
+ | { | ||
+ | if ( _event.buttons !== 1 || _event.which !== 1 ) | ||
+ | return; | ||
− | + | if ( window.x === null ) | |
+ | { | ||
+ | window.x = _event.offsetX; | ||
+ | window.y = _event.offsetY; | ||
+ | } | ||
− | + | // horizontal? | |
− | + | if ( Math.abs(window.x - _event.offsetX) > Math.abs(window.y - _event.offsetY) ) | |
− | + | { | |
+ | var yInc = (_event.offsetY - window.y) / Math.abs(window.x - _event.offsetX); | ||
+ | for ( ; window.x !== _event.offsetX; window.x += (_event.offsetX > window.x ? 1 : -1) ) | ||
+ | { | ||
+ | window.y += yInc; | ||
+ | drawSnow( window.x, Math.round( window.y ) ) | ||
+ | } | ||
+ | window.y = Math.round( window.y ); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | var xInc = (_event.offsetX - window.x) / Math.abs(window.y - _event.offsetY); | ||
+ | for ( ; window.y !== _event.offsetY; window.y += (_event.offsetY > window.y ? 1 : -1) ) | ||
+ | { | ||
+ | window.x += xInc; | ||
+ | drawSnow( Math.round( window.x ), window.y ) | ||
+ | } | ||
+ | window.x = Math.round( window.x ); | ||
+ | } | ||
+ | }, false ); | ||
+ | } | ||
− | + | if ( is_winter() ) | |
− | + | { | |
− | } | + | window.requestAnimationFrame( snowFall ); |
+ | timer = setInterval( blink, 1500 ); | ||
+ | } | ||
+ | } | ||
− | + | function drawSnow( _x, _y ) | |
− | + | { | |
− | + | if ( dynamicImage.data[ ( _x + width * _y ) * 4 + 0 ] === 255 | |
− | + | && dynamicImage.data[ ( _x + width * _y ) * 4 + 1 ] === 255 | |
− | + | && dynamicImage.data[ ( _x + width * _y ) * 4 + 2 ] === 255 | |
− | + | && dynamicImage.data[ ( _x + width * _y ) * 4 + 3 ] === 254 ) | |
− | + | { | |
− | + | dynamicImage.data[ ( _x + width * _y ) * 4 + 2 ] = 0; | |
− | + | dynamicImage.data[ ( _x + width * _y ) * 4 + 3 ] = 220; | |
+ | } | ||
+ | } | ||
+ | document.addEventListener("data", function( e ) | ||
+ | { | ||
+ | if ( timer ) | ||
+ | { | ||
+ | clearInterval( timer ); | ||
+ | timer = null; | ||
+ | } | ||
− | + | if ( !e.detail || !e.detail.length ) | |
+ | return; | ||
+ | var d = e.detail; | ||
+ | var l = d.length; | ||
+ | var m = treeLights.length; | ||
+ | var o = 74; | ||
+ | for ( var n=0; n < m; ++n ) | ||
+ | { | ||
+ | treeLights[n].c = d.slice(3*(n+o),3+3*(n+o)); | ||
+ | treeLights[n].on = true; | ||
+ | } | ||
+ | m = (m+o)*3; | ||
+ | if ( l > m ) | ||
+ | ackbit = d.slice(m,m+=3); | ||
+ | if ( debug && l == 255 ) | ||
+ | snow = d.slice(252,255); | ||
+ | }); | ||
+ | window.addEventListener( "load", window_load, false ); | ||
+ | window.x = window.y = null; | ||
}( )); | }( )); | ||
</script> | </script> | ||
− | |||
</includeonly> | </includeonly> |
Latest revision as of 13:24, 28 November 2021
This widget creates an animated themed ACKspace logo.
Created by xopr
Using this widget
To insert this widget, use the following code:
{{#widget:Logo |image=/w/images/e/e9/ACKsmass_logo.png |width=600px |height=200px |padding=8px |float=right }}
This will give the following result:
Notes
- it will display snow in December - March and will show Christmas lights between 7 December and 7 January
- image is mandatory, the rest is optional.
- it also must be written without protocol since colon (:) is not allowed, and may be relative, for example: //ackspace.nl/w/images/e/e9/ACKsmass_logo.png or /w/images/e/e9/ACKsmass_logo.png
- You must provide a unit for the sizes (i.e. px, %, etc.)
Also note that there is a chain of browser extensions and apps within the espixelflut github repo:
- FlutLogo app connector extension.crx: the extension that listens to the UDP connector app and drives the logo "LEDs" (source available, but it needs a static ID for the connector app)
- FlutLogo UDP connector app the app that listens to UDP ART-net packets and connects to the browser extension
- FlutArt: an ART-net app that can be used to control lights (which also works on the browser extension)
Note that if you want to use the LED sleeve python code from bitlair, you have to provide a different listening port to avoid conflicts, like this:
./fire2.py port=0 addr='[("127.0.0.1",6454)]'
Copy to your site
To use this widget on your site, just install MediaWiki Widgets extension and copy full source code of this page to your wiki as Widget:Logo article.