Рисование осуществляется путем динамического создания BMP изображения из линейного буфера, в который собственно и осуществляется рисование. BMP создаем при помощи протокола 'data:' (так называемого data URL, позволяющего вставлять в HTML код графику, JavaScript и т.д.) с указанием типа контента 'image/bmp'. Используем URL encoding.
Описываемая техника работает в Opera, Mozilla(и его производных), Safari, но не работает в Intrnet Explorer. Internet Explorer, начиная с версии 8 поддерживает этот формат, но в ограниченном размере (до 32768 знаков). Тут описан способ при помощи которого можно использовать data URI в IE, соединяя mhtml и data URI. Но он работает в статических данных вствленных в код. И нам не подходит.
BMP изображение и будет нашим Canvas для отображения.
'data:image/bmp;'
Далее наполняется заголовок bmp в соответствии с форматом. Для простоты будем создавать 8-битную картинку.
Все данные передаются в шестнадцатеричном формате. Для этого нам понадобится процедура преобразования байта в шестнадцатеричную строку с символом '%' спереди (URL encoding).
Преобразование байта в шестнадцатеричный вид. Приведу 2 способа преобразования.
var hexChars='0123456789ABCDEF';
function dec2hex(i){
return hexChars.charAt(i>>4) + hexChars.charAt(i&15);
}
function dec2hex(i){
var l = i.toString(16);
return (i <= 0xf)?'0'+l : l;
}
Для ускорения работы и уменьшения нагрузки на браузер сделаем табличку с преобразованными значениями от 0 до 255 и для преобразования будем обращаться к ней.
var D2H = new Array(256);
for(var i = 0;i < 256;i++) D2H[i] = dec2hex(i);
BMP - изображение состоит из заголовков BITMAPFILEHEADER и BITMAPINFOHEADER, палитры и данных.
BITMAPFILEHEADER:
2 байта ='BM'
4 байта = размер файла в байтах
4 байта = 0 резерв
4 байта = 1078 смещение до начала данных от начала структуры BITMAPFILEHEADER.
14 байт
BITMAPINFOHEADER:
4 байта = 40 размер структуры
4 байта = ширина
4 байта = высота
2 байта = 1
2 байта = 8 - количество бит на пиксель (у нас 8 бит)
4 байта = 0 - не сжатое изображение
4 байта = 0 - размер изображение, не нужно в нашем случае
4 байта = 0 - горизонтальное разрешение
4 байта = 0 - вертикальное разрешение
4 байта = 0 - количество цветовых индексов в палитре, 0 - максимально возможное
4 байта = 0 - количество необходимых цветовых индексов
40 байт
Порядок байтов little endian, т.е. младший байт идет первым. Заголовоки будут подготавливаться функцией bmp_head(w, h), где w - ширина, h - высота изображения в пикселях. Размер файла расчитывается слудующим образом: 14 + 40 байт заголовки = 54 + 256 * 4 = 1024 байта палитра + ширина * высота.
function bmp_head(w, h)
{
size = 54 + 1024 + w * h;
return 'data:image/bmp,BM'+ D2H[size & 255] + D2H[(size>>8) & 255] +
D2H[(size >> 16) & 255] + D2H[(size >> 24) & 255] + '%00%00%00%00%' + '36%04%00%00'+
'%28%00%00%00'+
D2H[w & 255] + D2H[(w >> 8) & 255] + D2H[(w >> 16) & 255] + D2H[(w >> 24) & 255]+
D2H[h & 255] + D2H[(h >> 8) & 255] + D2H[(h >> 16) & 255] + D2H[(h >> 24) & 255]+
'%01%00%'+'08%00'+'%00%00%00%00'+'%00%00%00%00'+'%00%00%00%00'+'%00%00%00%00'+
'%00%00%00%00'+'%00%00%00%00';
}
После заголовка устанавливается палитра. Она может содержать 256 цветов по 4 байта на цвет: 3 байта составляющие цвета r g b . Четвертый байт не используется, его установим в 0.
Для генерации палитры воспользуемся функцией rgb2h(r,g,b), где r, g, b - красная, зеленая и синяя компоненты цвета
Компоненты в BMP файле идут от синей к красной. И операция r & 255, побитовое "И" компоненты цвета и числом 255, обеспечит нам попадание в отрезок от 0 до 255 цветов.
function rgb2h(r,g,b)
{
return D2H[b & 255] + D2H[g & 255] + D2H[b & 255] + '%00';
}
function gen_pal(){
var pal = "";
for(i = 0;i < 256;i++)
pal += rgb2h(i, i, i);
return pal;
}
//палитра для огня
function gen_fire_pal(){
var pal="";
for(i = 0;i < 256 - 8 - 32 - 16 - 32;i++)//черный
pal += rgb2h(0, 0, 0);
for(i = 0;i < 32;i++)красный в черный
pal += rgb2h((i-32)<<3, 0, 0);
for(i = 0;i < 16;i++)//желтый в красный
pal += rgb2h(255, i<<4, 0);
for(i = 0;i < 32;i++)//белый в желтый
pal += rgb2h(255, 255, (i-32)<<3);
for(i = 0;i < 8;i++)//белый
pal += rgb2h(255, 255, 255);
return pal;
}
Например для создания изображения 100 х 100 пикселей
var scr = Array(100*100);
bmp = bmp_head(100,100) + gen_fire_pal();
// производим отрисовку в буфер scr
for(var i = 0;i < 100*100;i++)
scr[i] = i & 255;
// добавляем заполнение байтов данных изображения
for(var i = 0;i < 100*100;i++)
bmp += D2H[ scr[i] ];
// и выводим изображение как источник для какого-либо элемента
document.getElemenById('canvas').src = bmp;
// если необходимо устанавливаем таймоут для вызова функции выполняющей обновление изображения
setTimeoot(function(){refresh_func();},50);
Вот что у нас получилось:
Теперь попробуем применить данную технику для эффекта огня. Палитру для огня мы описали ранее. Огонь реализуется как частный случай блюра.
Ну или вот реализовать вращающийся 3d кубик. И для пущего эффекта поблюрим его немного, с той же палитрой, что и у огня. Т.е. наш кубик будет немного гореть.
Для sin и cos просчитываются таблички значений. Для отрисовки линий используется алгоритм Брезенхема. Про реализацию эффектов пока детально рассказыать не буду, т.к. планируются цикл статей посвященных графическим эффектам и демо дизайну.
Исходники можно скачать здесь