Графика в JavaScript. (Использование протокола data URI)


Рисование осуществляется путем динамического создания 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 просчитываются таблички значений. Для отрисовки линий используется алгоритм Брезенхема. Про реализацию эффектов пока детально рассказыать не буду, т.к. планируются цикл статей посвященных графическим эффектам и демо дизайну.

Старт | Стоп
Все остальное, что заинтересует смотрите в исходниках.

Исходники можно скачать здесь



evg 17.01.2010 Rambler's Top100