Canvas "на коленке"
12.05.2013 15:25
0Подходит для быстрого прототипирования каких-либо графических эффектов или алгоритмов.
Пожалуйста, не используйте ее для продакшена, и вообще для чего-либо серьезного - для этого есть много готовых, быстрых и хорошо поддерживаемых библиотек.
Класс CANVAS
function CANVAS( IdOrObj, w, h ) { this.c = null; if ( IdOrObj && typeof( IdOrObj ) == "object" ) this.cDOM = IdOrObj; else { this.cDOM = document.createElement( "CANVAS" ); if ( IdOrObj ) this.cDOM.setAttribute( "id", IdOrObj ); } if ( !w || !h ) { w = Number( this.cDOM.getAttribute( "width" ) ); h = Number( this.cDOM.getAttribute( "height" ) ); } if ( !w || !h || w < 32 || h < 32 ) { w = 32; h = 32; } if ( Number( this.cDOM.getAttribute( "width" ) ) != w || Number( this.cDOM.getAttribute( "height" ) != h ) ) { this.cDOM.setAttribute( "width", w ); this.cDOM.setAttribute( "height", h ); } this.w = w; this.h = h; this.maxAddr = this.w * this.h * this.bpp - 1; this.c = this.cDOM.getContext( "2d" ); if ( !this.c ) { this.cDOM = null; this.c = null; } } CANVAS.prototype = { cDOM: null, c: null, w: 0, h: 0, setSize: function( w, h ) { this.w = w; this.h = h; this.cDOM.setAttribute( "width", w ); this.cDOM.setAttribute( "height", h ); this.maxAddr = this.w * this.h * this.bpp - 1; this.c = this.cDOM.getContext( "2d" ); }, get: function() { return { c: this.c, dom: this.cDOM, w: this.w, h: this.h }; }, clear: function() { this.c.save(); this.c.setTransform( 1, 0, 0, 1, 0, 0 ); this.c.clearRect( 0, 0, this.w, this.h ); this.c.restore(); //this.c.clearRect( 0, 0, this.w, this.h ); }, requestRedraw: function( renderer ) { if ( window.requestAnimationFrame ) return window.requestAnimationFrame( renderer ); else if ( window.webkitRequestAnimationFrame ) return window.webkitRequestAnimationFrame( renderer ); else if ( window.mozRequestAnimationFrame ) return window.mozRequestAnimationFrame( renderer ); else if ( window.oRequestAnimationFrame ) return window.oRequestAnimationFrame( renderer ); else if ( window.msRequestAnimationFrame ) return window.msRequestAnimationFrame( renderer ); else return window.setTimeout( function() { renderer }, 1000 / 60 ); }, cancelRedraw: function( id ) { if ( !id ) return; if ( window.cancelAnimationFrame ) window.cancelAnimationFrame( id ); else if ( window.webkitCancelAnimationFrame ) window.webkitCancelRequestAnimationFrame( id ); else if ( window.mozCancelAnimationFrame ) window.mozCancelAnimationFrame( id ); else if ( window.oCancelAnimationFrame ) window.oCancelAnimationFrame( id ); else if ( window.msCancelAnimationFrame ) window.msCancelAnimationFrame( id ); else window.clearTimeout( id ); } }; function DBUF( canvas, w, h ) { this.reset(); this.setup( canvas, w, h ); }; DBUF.prototype = { canvas: null, buf: null, w: 0, h: 0, bpp: 4, maxAddr: 0, reset: function() { this.canvas = null; this.buf = null; this.w = this.h = this.maxAddr = 0; }, setup: function( canvas, w, h ) { if ( canvas instanceof( CANVAS ) ) this.canvas = canvas; else { if ( typeof( canvas ) == "string" && document.getElementById( canvas ) ) this.canvas = new CANVAS( document.getElementById( canvas ), w, h ); else this.canvas = new CANVAS( canvas, w, h ); } this.w = w ? w : this.canvas.w; this.h = h ? h : this.canvas.h; this.create(); this.maxAddr = this.w * this.h * this.bpp - 1; }, create: function() { if ( !this.canvas ) return; this.buf = this.canvas.c.createImageData( this.w, this.h ); }, getCanvas: function() { return this.canvas; }, setSize: function( w, h ) { this.canvas.setSize( w, h ); var t = this.canvas.get(); this.w = t.w; this.h = t.h; this.buf = t.c.createImageData( t.w, t.h ); this.maxAddr = this.w * this.h * this.bpp - 1; }, get: function( merge ) { if ( !this.canvas ) return; if ( merge ) { var b = this.canvas.c.getImageData( 0, 0, this.w, this.h ); for ( var i = 0; i < this.maxAddr; i+= this.bpp ) { var sa = b.data[i + 3] / 255; var nsa = 1 - sa; var sr = b.data[i] * sa; var sg = b.data[i + 1] * sa; var sb = b.data[i + 2] * sa; var da = this.buf.data[i + 3] * nsa / 255; var dr = this.buf.data[i] * nsa; var dg = this.buf.data[i + 1] * nsa; var db = this.buf.data[i + 2] * nsa; this.buf.data[i] = ( sr + dr ) & 255; this.buf.data[i + 1] = ( sg + dg ) & 255; this.buf.data[i + 2] = ( sb + db ) & 255; this.buf.data[i + 3] = ( ( sa + da ) * 255 ) & 255; } b = undefined; } else this.buf = this.canvas.c.getImageData( 0, 0, this.w, this.h ); }, put: function( x, y ) { if ( !this.canvas ) return; if ( !x || !y ) { x = 0; y = 0; } this.canvas.c.putImageData( this.buf, x, y ); }, clear: function( r, g, b, a ) { if ( r == undefined ) r = 0; if ( g == undefined ) g = 0; if ( b == undefined ) b = 0; if ( a == undefined ) a = 255; for ( var i = 0; i <= this.maxAddr; i += 4 ) { this.buf.data[i] = r; this.buf.data[i + 1] = g; this.buf.data[i + 2] = b; this.buf.data[i + 3] = a; } }, requestRedraw: function( renderer ) { return this.canvas.requestRedraw( renderer ); }, cancelRedraw: function( id ) { this.canvas.cancelRedraw( id ); }, getPixel: function( x, y ) { return this.getPixelA( ( y * this.w + x ) * this.bpp ); }, getPixelA: function( addr ) { if ( addr > this.maxAddr ) return null; return { "r": this.buf.data[addr], "g": this.buf.data[addr + 1], "b": this.buf.data[addr + 2], "a": this.buf.data[addr + 3] }; }, setPixel: function( x, y, r, g, b, a ) { this.setPixelA( ( y * this.w + x ) * this.bpp, r, g, b, a ); }, setPixelA: function( addr, r, g, b, a ) { if ( !this.buf || addr < 0 || addr > this.maxAddr ) return; if ( a == undefined ) a = 255; this.buf.data[addr] = r; this.buf.data[addr + 1] = g; this.buf.data[addr + 2] = b; this.buf.data[addr + 3] = a; }, mergePixel: function( x, y, r, g, b, a ) { this.mergePixelA( ( y * this.w + x ) * this.bpp, r, g, b, a ); }, mergePixelA: function( addr, r, g, b, a ) { if ( a == undefined || a == 255 ) { return this.setPixelA( addr, r, g, b, 255 ); } if ( !this.buf || addr < 0 || addr > this.maxAddr ) return; a = a / 255; var na = 1 - a; r *= a; g *= a; b *= a; var da = this.buf.data[addr + 3] * na / 255; var dr = this.buf.data[addr] * na; var dg = this.buf.data[addr + 1] * na; var db = this.buf.data[addr + 2] * na; this.buf.data[addr] = ( r + dr ) & 255; this.buf.data[addr + 1] = ( g + dg ) & 255; this.buf.data[addr + 2] = ( b + db ) & 255; this.buf.data[addr + 3] = ( ( a + da ) * 255 ) & 255; }, drawImage: function( img, x, y ) { if ( !this.canvas ) return; if ( !x ) x = 0; if ( !y ) y = 0; this.canvas.c.drawImage( img, 0, 0 ); this.get(); }, blur: function() { for ( var x = 1; x < this.w - 1; x++ ) { for ( var y = 1; y < this.h - 1; y++ ) { var p1 = this.getPixel( x - 1, y ); var p2 = this.getPixel( x + 1, y ); var p3 = this.getPixel( x, y - 1 ); var p4 = this.getPixel( x, y + 1 ); var r = ( ( p1.r + p2.r + p3.r + p4.r ) / 4 ) & 255; var g = ( ( p1.g + p2.g + p3.g + p4.g ) / 4 ) & 255; var b = ( ( p1.b + p2.b + p3.b + p4.b ) / 4 ) & 255; this.setPixel( x, y, r, g, b, 255 ) } } } };
Класс CANVAS
Класс CANVAS это основа для работы с канвасом.Создание |
строку | тогда создастся элемент canvas с таким ID, элемент будет свободным - т.е.ни куда не добавится |
указатель на DOM объект canvas | тогда класс "привяжется" к нему |
Если заданы размеры - они будут установлены для данного canvas.
Если же нет - то в случае существующего элемента - возьмутся у него, или же будут заданы по-умолчанию, равными 32px.
Методы |
get | вернет массив с параметрами canvas: c - context 2d, dom - элемент canvas, w и h - его размеры |
clear | очистить canvas используя текущую кисть |
requestRedraw | в качестве аргумента передается функция, которая будет рендерить картинку для канваса, когда браузер будет готов к этому, но в то же время не меньше 60 кадров в секунду, возвращает дескриптор, который может быть использован в методе cancelRedraw для отмены запроса на перерисовку |
cancelRedraw( id ) | в качестве аргумента принимает значение, возвращаемое методом requestRedraw и отменяет запрос на перерисовку |
Класс DBUF
DBUF (dynamic buffer) - это класс для создания так называемых backbuffers в которых обычно и происходит вся отрисовка, и только затем она показывается.Они нужны для избавления от артефактов типа "мерцания" и "дрожания" которые часто бывают при прямой отрисовке, а так-же позволяют добиться более плавной смены изображения.
Кроме того у одного canvas может быть несколько таких буферов, в каждом из которых может происходить своя отрисовка, а затем они все "сводятся" в canvas используя например альфа-канал, либо же по разным координатам (их размер может быть больше или меньше размера canvas).
Создание |
строка | создастся внутренний экземпляр класса CANVAS с таким ID, элемент будет свободным, а DBUF "привяжется" к нему |
указатель на DOM объект элемента canvas | создастся внутренний экземпляр класса CANVAS, а DBUF "привяжется" к нему |
указатель на объект класса CANVAS | DBUF "привяжется" к нему |
Если CANVAS будет создан согласно аргументам, то размеры будут заданы аналогично созданию класса CANVAS.
Иначе они будут установлены только для данного DBUF следующим образом - если заданы - будут использованы, если нет - возьмутся у CANVAS.
Методы |
getCanvas() | вернуть указатель на CANVAS, привязанный к данному DBUF |
get( [merge] ) | заполнить DBUF содержимым CANVAS, если merge = false - содержимое CANVAS просто скопируется в DBUF, если merge = true - содержимое CANVAS объеденится при копировании с содержимым DBUF, используя альфа-канал |
put( x, y ) | скопировать содержимое DBUF в CANVAS по координатам x и y |
clear( [r, g, b, a] ) | заполнить DBUF цветом r, g, b, a, если задано, или 0, 0, 0, 255 если нет |
requestRedraw( renderer ) | аналогично requestRedraw у CANVAS |
cancelRedraw( id ) | аналогично cancelRedraw у CANVAS |
getPixel( x, y ) | вернуть значение пикселя из DBUF по координатам x, y в формате { r: ..., g: ..., b: ..., a: ... } |
getPixelA( addr ) | вернуть значение пикселя из DBUF по адресу addr в формате { r: ..., g: ..., b: ..., a: ... } |
setPixel( x, y, r, g, b, [a] ) | установить значение пикселя по координатам x, y и цветом r, g, b, a |
setPixelA( addr, r, g, b, [a] ) | установить значение пикселя по адресу addr и цветом r, g, b, a |
mergePixel( x, y, r, g, b, [a] ) | комбинировать значение пикселя по координатам x, y и цветом r, g, b, a через альфа-канал |
mergePixelA( addr, r, g, b, [a] ) | комбинировать значение пикселя по адресу addr и цветом r, g, b, a через альфа-канал |
drawImage( img, x, y ) | скопировать картинку img (new Image(...)) в DBUF по координатам x и y |
blur | размыть изображение в DBUF |
Диапазон адресов 0 - (w * h * 4).
Диапазон цветов 0 - 255.
Диапазон альфа-канала 0 (полностью прозрачный) - 255 (полностью непрозрачный).
У setPixel / setPixelA можно опускать значение альфа канала - тогда будет использовано 255;
Пример работы
Создаем html, в body добавляем вот этот скрипт:( function() { var c = new CANVAS( "c", 320, 240 ); document.body.appendChild( c.get().dom ); var b = new DBUF( c, 320, 240 ); b.clear(); for ( var i = 1; i <= 318 ; i++ ) b.setPixel( i, 1, 255, 0, 255 ); b.put(); } )();
Тоже самое можно получить так:
( function() { var b = new DBUF( "c", 320, 240 ); document.body.appendChild( b.getCanvas().get().dom ); b.clear(); for ( var i = 1; i <= 318; i++ ) b.setPixel( i, 1, 255, 0, 255 ); b.put(); } )();
Он сам создаст элемент canvas размером 320 на 240 пикселей, добавит его в body, и нарисует на нем линию розового цвета.
12.05.2013, Protocoder