HTML5游戏开发(九)
一、图像绘制
HTML5的Canvas元素提供了极为丰富的图像支持。我们可以在绘制的时候缩放或保持原样,可以将图片绘制在canvas中的任何地主,也可以操作每个像素的颜色及透明度。
图像绘制
方法 | 描述 |
---|---|
drawImage() | 向画布上绘制图像、画布或视频 |
像素操作
属性 | 描述 |
---|---|
width | 返回 ImageData 对象的宽度 |
height | 返回 ImageData 对象的高度 |
data | 返回一个对象,其包含指定的 ImageData 对象的图像数据 |
方法 | 描述 |
---|---|
createImageData() | 创建新的、空白的 ImageData 对象 |
getImageData() | 返回 ImageData 对象,该对象为画布上指定的矩形复制像素数据 |
putImageData() | 把图像数据(从指定的 ImageData 对象)放回画布上 |
1.图像绘制
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图像绘制</title>
<style> #canvas { margin-left: 20px; margin-right: 0; margin-top: 10px; margin-bottom: 20px; border: thin solid #aaaaaa; cursor: crosshair; padding: 0; } </style>
</head>
<body>
<canvas id='canvas' width='600' height='317'>
</canvas>
<script src='js/drawing.js'></script>
</body>
</html>
JS脚本
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
//创建图像对象
image = new Image();
image.src = 'img/xueshan.png';
image.onload = function(e) {
context.drawImage(image, 0, 0);
};
2.图像的缩放
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
//创建图像对象
image = new Image();
image.src = 'img/xueshan.png';
image.onload = function(e) {
//原图为600x317将其缩放为500X300
context.drawImage(image, 0, 0,500,300);
};
显示效果:
3.离屏
离屏canvas经常用来存放临时性的图像信息。使用离屏可以提高绘图效率。
(1)图像加载
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图像绘制</title>
<style> #canvas { margin-left: 20px; margin-right: 0; margin-top: 10px; margin-bottom: 20px; border: thin solid #aaaaaa; padding: 0; } </style>
</head>
<body>
<div id='controls'>
<output id='scaleOutput'>1.0</output>
<input id='scaleSlider' type='range' min='1' max='3.0' step='0.01' value='1.0' />
</div>
<canvas id='canvas' width='600' height='317'>
</canvas>
<script src='js/drawing.js'></script>
</body>
</html>
JS脚本:
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
//创建离屏对象
offscreenCanvas = document.createElement('canvas');
offscreenContext = offscreenCanvas.getContext('2d'),
image = new Image(),
//获取输出对象
scaleOutput = document.getElementById('scaleOutput'),
//缩放比例对象
scaleSlider = document.getElementById('scaleSlider'),
scale = scaleSlider.value,
scale = 1.0,
MINIMUM_SCALE = 1.0,
MAXIMUM_SCALE = 3.0;
image.src = 'img/xueshan.png';
//离屏的大小设定
offscreenCanvas.width = canvas.width;
offscreenCanvas.height = canvas.height;
//图像加载
image.onload = function(e) {
context.drawImage(image, 0, 0, canvas.width, canvas.height);
//使用离屏---给离屏对象一份数据
offscreenContext.drawImage(image, 0, 0,
canvas.width, canvas.height);
//初始绘制
drawWatermark(context);
//绘制水印--使用离屏绘制
drawWatermark(offscreenContext);
//图像缩放
drawScaleText(scaleSlider.value);
};
(2)图像缩放
//图像缩放
function drawScaled() {
var w = canvas.width,
h = canvas.height,
sw = w * scale,
sh = h * scale;
//使用离屏图像绘制
context.drawImage(offscreenCanvas, 0, 0,
offscreenCanvas.width, offscreenCanvas.height,
-sw/2 + w/2, -sh/2 + h/2, sw, sh);
}
//设置缩放文本值
function drawScaleText(value) {
var text = parseFloat(value).toFixed(2);
//计算比例
var percent = parseFloat(value - MINIMUM_SCALE) /
parseFloat(MAXIMUM_SCALE - MINIMUM_SCALE);
//输出文本
scaleOutput.innerText = text;
percent = percent < 0.35 ? 0.35 : percent;
//输出字体大小
scaleOutput.style.fontSize = percent*MAXIMUM_SCALE/1.5 + 'em';
}
(3)绘制水印
//绘制水印
function drawWatermark(context) {
var lineOne = 'Copyright',
lineTwo = 'marquis',
textMetrics = null,
FONT_HEIGHT = 80;
context.save();
context.fillStyle = 'rgba(100,140,230,0.5);';
context.strokeStyle = 'yellow';
context.shadowColor = 'rgba(50, 50, 50, 1.0)';
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 10;
context.font = FONT_HEIGHT + 'px Arial';
//绘制第一行文本
textMetrics = context.measureText(lineOne);
context.translate(canvas.width/2, canvas.height/2);
context.fillText(lineOne, -textMetrics.width/2, 0);
context.strokeText(lineOne, -textMetrics.width/2, 0);
//绘制第二行文本
textMetrics = context.measureText(lineTwo);
context.fillText(lineTwo, -textMetrics.width/2, FONT_HEIGHT);
context.strokeText(lineTwo, -textMetrics.width/2, FONT_HEIGHT);
context.restore();
}
(4)事件处理
//-----------------------事件处理
scaleSlider.onchange = function(e) {
scale = e.target.value;
if (scale < MINIMUM_SCALE) scale = MINIMUM_SCALE;
else if (scale > MAXIMUM_SCALE) scale = MAXIMUM_SCALE;
//图像缩放
drawScaled();
//文本缩放
drawScaleText(scale);
}
显示效果:
二、图像的操作
1.底片效果
<!DOCTYPE html>
<html>
<head>
<title>底片滤镜</title>
<meta charset="utf-8" />
<style> body { background: rgba(100, 145, 250, 0.3); } #canvas { margin-left: 20px; margin-right: 0; margin-bottom: 20px; border: thin solid #aaaaaa; cursor: crosshair; } #controls { margin: 20px 0px 20px 20px; } a { font: 18px Times Roman; text-decoration: none; margin-right: 15px; } </style>
</head>
<body>
<div id='controls'>
<input type='button' id='negativeButton' value='底片'/>
</div>
<canvas id='canvas' width='600' height='317'>
</canvas>
<script src='js/negative.js'></script>
</body>
</html>
JS脚本
var image = new Image(),
canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
negativeButton = document.getElementById('negativeButton');
negativeButton.onclick = function() {
//获取图像数据
var imagedata = context.getImageData(0, 0, canvas.width, canvas.height),
//图像数据处理,注意:一定要在服务中运行,否则会有跨域问题
data = imagedata.data;
//底片滤镜,计算出平均值,将这一平均值设置回去
for(i=0; i <= data.length - 4; i+=4) {
data[i] = 255 - data[i]
data[i+1] = 255 - data[i+1];
data[i+2] = 255 - data[i+2];
}
context.putImageData(imagedata, 0, 0);
};
//加载图片
image.src = 'img/xueshan.png';
image.onload = function() {
context.drawImage(image, 0, 0,
image.width, image.height, 0, 0,
context.canvas.width, context.canvas.height);
};
显示效果:
错误解决:
example.js:98 Uncaught SecurityError: Failed to execute ‘getImageData’ on ‘CanvasRenderingContext2D’: The canvas has been tainted by cross-origin data.
原因:
getImageData此方法不允许操作非此域名外的图片资源,即使是子域也不行。
解决:
可以使用base64编码方式。
或:
在response添加 Access-Control-Allow-Origin
或使用chrom命令:
–allow-file-access-from-files
2.使用工作线程处理图像
(1)基本功能
<!DOCTYPE html>
<html>
<head>
<title>凸透镜r</title>
<meta charset="utf-8" />
<style> body { background: rgba(100, 145, 250, 0.3); } #canvas { margin-left: 20px; margin-right: 0; margin-bottom: 20px; border: thin solid #aaaaaa; } #controls { margin: 20px 0px 20px 20px; } a { font: 18px Times Roman; text-decoration: none; margin-right: 15px; } </style>
</head>
<body>
<div id='controls'>
<input type='button' id='sunglassButton' value='凸透镜'/>
</div>
<canvas id='canvas' width='600' height='317'>
</canvas>
<script src='js/sunglass.js'></script>
</body>
</html>
JS脚本
var image = new Image(),
canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
sunglassButton = document.getElementById('sunglassButton'),
sunglassFilter,
sunglassesOn = false;
//凸透镜函数
function putSunglassesOn() {
//主线程向工作线程发送消息
sunglassFilter.postMessage(context.getImageData(0, 0, canvas.width, canvas.height));
//工作线程成功完成工作后的回调函数
sunglassFilter.onmessage = function (event) {
context.putImageData(event.data, 0, 0);
};
}
//绘制原图
function drawOriginalImage() {
context.drawImage(image, 0, 0,
image.width, image.height, 0, 0,
canvas.width, canvas.height);
}
//转换事件
sunglassButton.onclick = function() {
if (sunglassesOn) {
sunglassButton.value = '凸透镜';
drawOriginalImage();
sunglassesOn = false;
}
else {
sunglassButton.value = '原图';
//绘制凸透镜效果
putSunglassesOn();
sunglassesOn = true;
}
};
//加载图像
image.src = 'img/xueshan.png';
image.onload = function() {
//创建一个工作线程,这个线程在主线程外执行
//注意:js文件引用路径,以html引用路径为准
//只能用一个js文件创建工作线程,而不能用函数。
//因为规定工作线程不能访问DOM,如果向Worker构造
//函数传入一个函数,该函数可能包含DOM或主JavaScript代码
//的引用,就违反规则。因此工作线程的设计者选择的做法是只
//能传递一个js文件的URL
sunglassFilter = new Worker('js/sunglassFilter.js');
drawOriginalImage();
};
(2)工作线程 sunglassFilter.js
//定义一个message事件
onmessage = function (event) {
console.log(event)
var imagedata = event.data,
data = imagedata.data,
length = data.length,
width = imagedata.width;
for (i=0; i < length; ++i) {
if ((i+1) % 4 != 0) {
if ((i+4) % (width*4) == 0) { //最后一个像素
data[i] = data[i-4];
data[i+1] = data[i-3];
data[i+2] = data[i-2];
data[i+3] = data[i-1];
i+=4;
}
else {
data[i] = 2*data[i] - data[i+4] - 0.5*data[i+4];
}
}
}
//调用postMessage方法,window.postMessage() 方法可以安全地实现跨源通信。
postMessage(imagedata);
};
显示效果:
四、视频
研究Canvas视频功能的最终目标是为了实现即时视频处理。
1.播放视频
requestNextAnimationFrame函数,将视频中的当前视频帧绘制到Canvas中,以实现视频播放。
(1)Ployfill函数定义
/** * ployfill函数定义 */
window.requestNextAnimationFrame =
(function() {
var originalWebkitRequestAnimationFrame = undefined,
wrapper = undefined,
callback = undefined,
geckoVersion = 0,
userAgent = navigator.userAgent,
index = 0,
self = this;
if(window.webkitRequestAnimationFrame) {
// Define the wrapper
wrapper = function(time) {
if(time === undefined) {
time = +new Date();
}
self.callback(time);
};
originalWebkitRequestAnimationFrame = window.webkitRequestAnimationFrame;
window.webkitRequestAnimationFrame = function(callback, element) {
self.callback = callback;
originalWebkitRequestAnimationFrame(wrapper, element);
}
}
if(window.mozRequestAnimationFrame) {
index = userAgent.indexOf('rv:');
if(userAgent.indexOf('Gecko') != -1) {
geckoVersion = userAgent.substr(index + 3, 3);
if(geckoVersion === '2.0') {
window.mozRequestAnimationFrame = undefined;
}
}
}
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback, element) {
var start,
finish;
window.setTimeout(function() {
start = +new Date();
callback(start);
finish = +new Date();
self.timeout = 1000 / 60 - (finish - start);
}, self.timeout);
};
})();
(2)视频播放
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>视频播放</title>
<style> body { background: #dddddd; } #canvas { background: #ffffff; border: thin solid darkgray; } #video { /*将视频设置为不显示*/ display: none; } </style>
</head>
<body>
<!--添加视频标记-->
<video id='video' width="320" height="240" controls="controls">
<source src='video/movie.ogg' />
</video>
<canvas id='canvas' width='600' height='405'>
</canvas>
<script src='js/video.js'></script>
</body>
</html>
JS脚本
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
video = document.getElementById('video');
//播放视频
function animate() {
if(!video.ended) {
context.drawImage(video, 0, 0, canvas.width, canvas.height);
window.requestNextAnimationFrame(animate);
}
}
//播放视视
video.play();
//将播放的视频放入canvas中
window.requestNextAnimationFrame(animate);
显示效果:
2.视频处理
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>视频处理</title>
<style> body { background: #dddddd; } .floatingControls { position: absolute; left: 175px; top: 290px; } #canvas { background: #ffffff; border: thin solid #aaaaaa; margin: 10px; } </style>
</head>
<body>
<video id='video' controls src='video/movie.ogg'></video>
<canvas id='canvas' width='320' height='240'>
</canvas>
<div id='controls' class='floatingControls'>
<input id='controlButton' type='button' value='播放' />
<input id='colorCheckbox' type='checkbox' checked> <span style='font-size:1.15em'>颜色</span>
<input id='flipCheckbox' type='checkbox'> <span style='font-size:1.15em'>翻转</span>
</div>
<script src='js/videoclip.js'></script>
</body>
</html>
JS脚本
/** * ployfill函数定义 */
window.requestNextAnimationFrame =
(function() {
var originalWebkitRequestAnimationFrame = undefined,
wrapper = undefined,
callback = undefined,
geckoVersion = 0,
userAgent = navigator.userAgent,
index = 0,
self = this;
if(window.webkitRequestAnimationFrame) {
// Define the wrapper
wrapper = function(time) {
if(time === undefined) {
time = +new Date();
}
self.callback(time);
};
originalWebkitRequestAnimationFrame = window.webkitRequestAnimationFrame;
window.webkitRequestAnimationFrame = function(callback, element) {
self.callback = callback;
originalWebkitRequestAnimationFrame(wrapper, element);
}
}
if(window.mozRequestAnimationFrame) {
index = userAgent.indexOf('rv:');
if(userAgent.indexOf('Gecko') != -1) {
geckoVersion = userAgent.substr(index + 3, 3);
if(geckoVersion === '2.0') {
window.mozRequestAnimationFrame = undefined;
}
}
}
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback, element) {
var start,
finish;
window.setTimeout(function() {
start = +new Date();
callback(start);
finish = +new Date();
self.timeout = 1000 / 60 - (finish - start);
}, self.timeout);
};
})();
var canvas = document.getElementById('canvas'),
offscreenCanvas = document.createElement('canvas'),
offscreenContext = offscreenCanvas.getContext('2d'),
context = canvas.getContext('2d'),
video = document.getElementById('video'),
controlButton = document.getElementById('controlButton'),
flipCheckbox = document.getElementById('flipCheckbox'),
colorCheckbox = document.getElementById('colorCheckbox'),
imageData,
poster = new Image();
//-----------------------------移除颜色
//移除颜色
function removeColor() {
var data,
width,
average;
//获取离屏数据
imageData = offscreenContext.getImageData(0, 0,
offscreenCanvas.width, offscreenCanvas.height);
data = imageData.data;
width = data.width;
for(i = 0; i < data.length - 4; i += 4) {
average = (data[i] + data[i + 1] + data[i + 2]) / 3;
data[i] = average;
data[i + 1] = average;
data[i + 2] = average;
}
offscreenContext.putImageData(imageData, 0, 0);
}
//-----------------------------视频翻转
function drawFlipped() {
context.save();
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(Math.PI);
context.translate(-canvas.width / 2, -canvas.height / 2);
context.drawImage(offscreenCanvas, 0, 0);
context.restore();
}
//下一个视频
function nextVideoFrame() {
//如果视频已到达结尾时,将按钮重置为播放
if(video.ended) {
controlButton.value = '播放';
} else {
//获取数据到离屏
offscreenContext.drawImage(video, 0, 0);
//如果颜色选中,则去色
if(!colorCheckbox.checked)
removeColor();
//如果反转选中,则进行视每帧反转
if(flipCheckbox.checked)
//反转
drawFlipped();
else
context.drawImage(offscreenCanvas, 0, 0);
//转变后的进行播放
requestNextAnimationFrame(nextVideoFrame);
}
}
//播发
function startPlaying() {
//将视频播放传为Canvas
requestNextAnimationFrame(nextVideoFrame);
if(video.ended) {
video.load();
}
//播发
video.play();
}
//暂停
function stopPlaying() {
//暂停
video.pause();
}
//-----------------------------事件处理
controlButton.onclick = function(e) {
if(controlButton.value === '播放') {
console.log("播放")
//开始播发
startPlaying();
controlButton.value = '暂停';
} else {
//停止播放
stopPlaying();
controlButton.value = '播放';
}
}
//poster为video的属性,规定视频下载时显示的图像,或者在用户点击播放按钮前显示的图像。
poster.onload = function() {
//这里显示一张雪山图
context.drawImage(poster, 0, 0);
};
//-------------------1、初始化
poster.src = 'img/xueshan.png';
//离屏显示
offscreenCanvas.width = canvas.width;
offscreenCanvas.height = canvas.height;
显示效果: