HTML5游戏开发(九)

时间:2022-11-17 15:27:28

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);
};

显示效果:
HTML5游戏开发(九)

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);
}

显示效果:
HTML5游戏开发(九)

二、图像的操作

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);
};

显示效果:
HTML5游戏开发(九)

错误解决:
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
HTML5游戏开发(九)

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);
};

显示效果:
HTML5游戏开发(九)

四、视频

  研究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);

显示效果:
HTML5游戏开发(九)

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;

显示效果:
HTML5游戏开发(九)