1. 状態とその保存と復元
このセクションを始める前に、まず状態とは何か、状態の保存と復元について理解する必要があります。 MFC プログラミングをプレイしたことがある人は、次のようなコードに遭遇することがよくあります:
pOldPen=pDC->SelectObject(pNewPen)
新しいブラシ オブジェクトを選択するときは、常に古いブラシ オブジェクトを保存する必要があります。これはなぜでしょうか。する?新しいブラシ オブジェクトは一時的にのみ使用されるため、使い切ってから元のブラシ構成を復元したい場合、古い構成を事前に保存しておかないと、これらの構成は失われ、復元する方法がありません。
HTML5描画では、ある瞬間の状態は、現時点でのコンテキストオブジェクトの一連のプロパティの設定値となりますが、色や太さなど、ブラシの状態を決定するプロパティは比較的少数です。 、線の種類など、コンテキストを決定します。次のような状態の属性があります:
1. 現在のコンテキスト オブジェクトの移動、回転、スケーリング設定
2. StrokeStyle、fillStyle、globalAlpha、lineWidth現在のコンテキスト オブジェクトの lineCap、lineJoin、miterLimit、shadowOffsetX、shadowOffsetY、shadowBlur、shadowColor、globalCompositeOperation 属性値
3. 上記の一連の設定は、現在のコンテキスト オブジェクトの状態を決定します。この瞬間には、
移動、回転、スケーリング、globalCompositeOperation (組み合わせ)、トリミングが含まれますこれについては、すぐに説明します。
2. 状態の保存と復元特定の瞬間の状態は非常に多くの属性によって決定されると述べましたが、現時点での状態を保存したい場合は、これらの属性値を保存する必要があります。それらを 1 つずつ元に戻すのは大変です。これは実際に当てはまるため、コンテキストは状態を保存および復元するための 2 つの単純なメソッドを提供します:
save( ) およびrestore( )
save メソッドとrestore メソッドはそれぞれ複数回呼び出すことができます。が呼び出されると、状態 (つまり、一連の属性値) がスタックにプッシュされます。
restore メソッドが呼び出されるたびに、最後の保存の状態が復元されます。つまり、スタックからポップされます。
弾倉を想像してください。最初に発射される弾丸は常に弾倉に押し込まれた最後の弾丸です。
3. バリエーション
1. 動き:
translate(dx,dy) このメソッドは、実際には、ある数学的な意味を含んでいます。座標系全体 移動が発生し、新しい座標系の任意の点 (x, y) が元の座標系の座標と等価になります:
x'=x+dx
y'=y+dy
If ctx.translate(5 ,8) を呼び出します。コンテキスト オブジェクトの座標系の状態を変更し、新しい状態で点 (3,2) に描画します。これは、点 (8,10) に描画される画像と同等です。元の状態、つまり
x'=5 +3=8
y'=5+2=10
おそらく、なぜそんなに面倒なことをするのかと疑問に思うかもしれませんが、(8 で直接比較しても大丈夫ですか) 、10)?たとえば、
ctx.translate(5,8)
ctx.drawImage(img,3,2)
を
ctx.drawImage(img,8,10) に変更する方が簡単で直接的ではないでしょうか。
?
動きが大きくなると、座標原点を適切に変更すると、グラフィックスの計算がよりわかりやすくなり、非常に便利になるということを理解しています。次のような線があるとします。セグメントは、x 軸の正の方向の小さなセグメントです。
y = 0 (1
x = 0 ( 1 <= y <= 3)
しかし、線分を使用する場合、毎回原点を円の中心として回転することはできません。 1 つの端点 (1,0) が円の中心として回転されます。回転した線分上の各点の座標値を取得するにはどうすればよいでしょうか?実際、このプロセスは 3 つのステップに分けることができます:
ステップ 1:
原点座標を (1,0) に移動します。新しい線分はまだ x 軸上にありますが、方程式は y = 0 になります。 (0 ステップ 2: 新しい座標系の原点を円の中心として を回転し、線分 x = 0 (0 の原点 を新しい座標系の (-1,0) に移動します。つまり、原点を元の位置に戻します。今回は: x = 1 (0 3 番目のステップで得られた線分が、描画する必要がある最後の線分です。このような単純な場合でも、座標原点を移動せずに回転した図形を直接計算するのは難しいことがこの例からわかります。
ヒント:
座標原点を移動する前に、描画後に状態を復元することを忘れないでください。 2. スケーリングscale(sx, sy) これも非常に簡単です。sx、sy は、スケーリング後の新しい座標系の点 (x, y) と等価になります。座標系: x' = x * sxy' = y * sy
同様に、座標系を変更するときは、状態を保存して復元することを忘れないでください
3. Rotaterotate(A)
angleは、回転後の任意の点(x,y)の回転角度です。新しい座標系は元の座標系と同等です 座標系の座標は次のとおりです:
x' = x cosA - y sinA
y' = x sinA + y cosA
同様に、常に保存と復元を忘れないでください座標系を変更するときの状態
4. Transform transform (m11, m12, m21, m22, dx, dy)
実際、前述の移動、スケーリング、および回転は、変換の特殊なケースです。変換メソッドの 6 つのパラメーターは、次のような変換行列を形成します
m12 | m22 | dy |
0 | 0 | 1 |
transform メソッドの呼び出しは、これらのパラメーターを使用してコンテキスト オブジェクトの新しい 変換行列 を設定することと同じです。変換行列の具体的な内容については、グラフィックス関連情報を参照してください。単純な特殊なケース: Move translation(dx,dy):transform(1,0,0,1,dx,dy)と同等 Scalescale(sx,xy):transform(sx,0)と同等,0,sy, 0,0) 回転rotate(A):transform(cosA,sinA,-sinA,cosA,0,0)と同等 (dx,dy)を参照点として使用して、角度 A を回転:transform( cosA, sinA, -sinA, cosA, dx(1-cosA) + dysinA, dy(1-cosA) - dxsinA) (dx,dy) を参照点として使用して ( sx,sy) scaling:transform(sx, 0, 0, sy, dx(1-sx), dy(1-sy)) 他にも多くのより複雑な変形があります。グラフィックス関連の情報を参照してください。 。 以下は基準点変形の例です。画像上の特定の点を押し続けると、その点を基準として画像が拡大縮小または回転されます。マウスを離すと画像が復元されます。ボタン。 ヒント: この拡大縮小と回転の例では、変形マトリックスを使用しませんが、4 つの単純な変形で構成されています。効果は次のとおりです:
4. 組み合わせ いわゆる組み合わせとは、1 つのグラフィックが描画されるときに何が起こるかです。別のグラフィックの上に。デフォルトでは、上のグラフィックが下のグラフィックを覆い、これをソースオーバーと呼びます。 コンテキスト オブジェクトには合計 12 の組み合わせタイプがあります。次のように、属性 globalCompositeOperation を使用して組み合わせタイプを設定します。 globalCompositeOperation = type type は、次の 12 の文字列値のいずれかです。 : 上記のすべての例では、青い四角形が最初に描画されます、つまり「既存のキャンバスコンテンツ」であり、赤い円が後で描画されます、つまり「新しい形状」です。 5. クリッピングパス 最初の記事では、コンテキスト オブジェクトの 2 つの主要なタイプの描画メソッド、つまり、線を描画するためのストローク シリーズのメソッドと、領域を塗りつぶすための fill シリーズのメソッドを紹介しました。実はコンテキストオブジェクトにはクリッピングクリップという描画メソッドもあります。 トリミングとは何ですか?不適切なたとえを使用すると、テレビの画面を布で覆います。この時点では、テレビの画面には何も変化が見られません。 ただし、布上の領域を切り取ると、少なくともその領域で画面が変化するのがわかります。 もちろんトリミングエリアの外側の画面も常に変化しています(つまり、再描画されています)が、それを見ることはできません 。これはトリミングと呼ばれ、画像を処理するときにこの必要性がよく発生します。 では、クリッピングパスとは何ですか?布上の領域を切り取る必要があるとありますが、この領域はどのようにして作成されたのでしょうか。 この領域は、切断アクションクリップ の前に描画パスによって設定され、正方形、円、五つ星の形状、またはその他の描画可能な輪郭形状にすることができます。 つまり、クリッピングパスは実際には描画パスですが、このパスは描画に使用されるのではなく、表示領域とオクルージョン領域の間に境界線を設定するために使用されます。描画パスがわからない方は、前回の記事「遊んで学ぶHTML5(2):基本的な描画」で紹介しています。 以下の例では、トリミングに 2 つの方法を使用しています。最初の方法は、前後に移動する円形のトリミング領域を表示します。一般的なプロセスは次のとおりです。1. キャンバスの中心位置を変更します3.新しい中心位置 4. キャンバス上に美しい画像を描画します新しい位置で切り抜き領域を設定し続けるため、切り抜き領域が移動していることがわかりますが、切り抜き領域の外側の画像は表示されませんクリップ方式を使用する トリミング領域を設定すると、その後描画されるグラフィックはトリミング領域の一部しか表示できず、キャンバスの背景色は常にトリミング領域の外側に表示されます。 例えば、トリミング範囲外の画像を完全に遮断したくない場合、トリミング範囲内の画像は完全に表示したいが、トリミング範囲外の画像は半透明で表示されます。私たちは何をすべきか? これには、上で述べた組み合わせの知識が必要です。 2 番目の方法は、半透明のオクルージョンを表示します。一般的なアイデアは次のとおりです。 1. キャンバスをクリアします 2. 透明度 0.9 のグレーを使用します 。円の中心位置4、在新的圆心位置处以 XOR 方式绘制圆,这样和圆形重叠的部分将被擦除掉。 5、 在第 4 步的基础上,以 destination-over 方式绘制美女图像,这时候美女图像将会出现在第 4 步图形效果的下方,想象一下,正好是我们想要的效果吧?! 代码 Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ --><canvas id="canvas1" width="250" height="300" onmousedown="trans.transform(event);" onmouseup="trans.init(event);" onmousemove="trans.translate(event);" style="background-color:black"> 你的浏览器不支持 <canvas>标签,请使用 Chrome 浏览器 或者 FireFox 浏览器 </canvas><br/> <input type="radio" name="r" id="r1" checked="checked">移动——在图像上按住鼠标并移动<br /> <input type="radio" name="r" id="r2">基准点缩放——在图像某点处按住鼠标<br /> <input type="radio" name="r" id="r3">基准点旋转——在图像某点处按住鼠标<br /> <input type="radio" name="r" id="r4">基准点缩放同时旋转——在图像某点处按住鼠标<br /> <canvas id="canvas3" width="250" height="300" style="background-color:black"> 你的浏览器不支持 <canvas>标签,请使用 Chrome 浏览器 或者 FireFox 浏览器 </canvas><br/> <input type="button" onclick="move(1);" value="移动裁剪区域"> <input type="button" onclick="move(2);" value="移动蒙版"> <input type="button" onclick="stop();" value="停止移动"><br /> <div> <table> <tr> <td><canvas id="tut0" width="125" height="125"></canvas><br/><label id="lab0"></label></td> <td><canvas id="tut1" width="125" height="125"></canvas><br/><label id="lab1"></label></td> <td><canvas id="tut2" width="125" height="125"></canvas><br/><label id="lab2"></label></td> <td><canvas id="tut3" width="125" height="125"></canvas><br/><label id="lab3"></label></td> </tr> <tr> <td><canvas id="tut4" width="125" height="125"></canvas><br/><label id="lab4"></label></td> <td><canvas id="tut5" width="125" height="125"></canvas><br/><label id="lab5"></label></td> <td><canvas id="tut6" width="125" height="125"></canvas><br/><label id="lab6"></label></td> <td><canvas id="tut7" width="125" height="125"></canvas><br/><label id="lab7"></label></td> </tr> <tr> <td><canvas id="tut8" width="125" height="125"></canvas><br/><label id="lab8"></label></td> <td><canvas id="tut9" width="125" height="125"></canvas><br/><label id="lab9"></label></td> <td><canvas id="tut10" width="125" height="125"></canvas><br/><label id="lab10"></label></td> <td><canvas id="tut11" width="125" height="125"></canvas><br/><label id="lab11"></label></td> </tr> </table> </div> <script type="text/javascript"> //美女图的 Base64 编码 IMG_SRC='data:image/gif;base64,/9j/4QDfRXhpZgAASUkqAAgAAAAFABIBAwA......';//省略四十字节 //========================================== //基准点变形类 //========================================== function Transform(){ //获取画布对象 this.ctx = document.getElementById("canvas1").getContext("2d"); //创建图像对象 this.img=new Image(); //指定图像源 this.img.src=IMG_SRC; this.interval = null; //鼠标按钮状态 this.pressed=false; this.init(); } //初始化图形 Transform.prototype.init=function(){ //鼠标按钮状态 this.pressed=false; //停止计时器 if(this.interval) clearInterval(this.interval); //变化值 this.delta = 0.06; //清空 this.ctx.clearRect(0,0,250,300); //重绘 this.paint(); } //绘制图像 Transform.prototype.paint=function(){ var that=this; var img=this.img if(img.complete) that.ctx.drawImage(img,0,0); else var interval = setInterval(function(){ if(img.complete){ that.ctx.drawImage(img,0,0); clearInterval(interval); } },300); } //鼠标按钮按下后,开始变形 Transform.prototype.transform = function(){ //获取基准点 this.dx=event.offsetX; this.dy=event.offsetY; //获取基准点 this.startx=event.offsetX; this.starty=event.offsetY; //初始缩放比例 this.sc=1; //初旋转角度 this.angle=0; var that=this; if(document.getElementById("r1").checked) //鼠标按钮状态 this.pressed=true; else if(document.getElementById("r2").checked) this.interval = setInterval(function(){that.scale()},50); else if((document.getElementById("r3").checked)) this.interval = setInterval(function(){that.rotate()},50); else this.interval = setInterval(function(){that.scaleAndRotate()},50); } //移动 Transform.prototype.translate = function(){ this.ddx=event.offsetX-this.startx; this.ddy=event.offsetY-this.starty; if(this.pressed){ //清空 this.ctx.clearRect(0,0,250,300); //保存状态 this.ctx.save(); //平移 this.ctx.translate(this.ddx,this.ddy); //重绘 this.paint(); //绘制基准点 this.ctx.fillStyle="red"; this.ctx.fillRect(this.dx-5,this.dy-5,10,10); //恢复状态 this.ctx.restore(); } } //缩放变形 Transform.prototype.scale = function(){ //清空 this.ctx.clearRect(0,0,250,300); //改变缩放比例 this.sc=this.sc - this.delta; if(this.sc<0.2 || this.sc>2) this.delta = -this.delta; //保存状态 this.ctx.save(); //以 (dx,dy) 为基准点进行 (sx,sy)比例缩放:transform(sx, 0, 0, sy, dx(1-sx), dy(1-sy)) this.ctx.transform(this.sc, 0, 0, this.sc, this.dx*(1-this.sc), this.dy*(1-this.sc)) //用新的变形矩阵重绘 this.paint(); //绘制基准点 this.ctx.fillStyle="red"; this.ctx.fillRect(this.dx-5,this.dy-5,10,10); //恢复状态 this.ctx.restore(); } //旋转变形 Transform.prototype.rotate = function(){ //清空 this.ctx.clearRect(0,0,250,300); //改变缩放比例 var PI = Math.PI; this.angle=this.angle + PI/60; //保存状态 this.ctx.save(); //以 (dx,dy) 为基准点旋转角度 A:transform(cosA, sinA, -sinA, cosA, dx(1-cosA) + dysinA, dy(1-cosA) - dxsinA) this.ctx.transform(Math.cos(this.angle), Math.sin(this.angle), -Math.sin(this.angle), Math.cos(this.angle), this.dx*(1-Math.cos(this.angle)) + this.dy*Math.sin(this.angle), this.dy*(1-Math.cos(this.angle)) - this.dx*Math.sin(this.angle)) //用新的变形矩阵重绘 this.paint(); //绘制基准点 this.ctx.fillStyle="red"; this.ctx.fillRect(this.dx-5,this.dy-5,10,10); //恢复状态 this.ctx.restore(); } //即缩放又旋转变形,没有使用变形矩阵 Transform.prototype.scaleAndRotate = function(){ //清空 this.ctx.clearRect(0,0,250,300); //改变缩放比例 this.sc=this.sc - this.delta; if(this.sc<0.2 || this.sc>2) this.delta = -this.delta; var PI = Math.PI; this.angle=this.angle + PI/60; //保存状态 this.ctx.save(); //先移动原点到基点 this.ctx.translate(this.dx,this.dy); this.ctx.scale(this.sc,this.sc); this.ctx.rotate(this.angle); this.ctx.translate(-this.dx,-this.dy); //用新的变形矩阵重绘 this.paint(); //绘制基准点 this.ctx.fillStyle="red"; this.ctx.fillRect(this.dx-5,this.dy-5,10,10); //恢复状态 this.ctx.restore(); } var trans = new Transform(); //========================================== function Clip(){ var canvas = document.getElementById("canvas3"); this.ctx = canvas.getContext("2d"); this.img=new Image(); this.img.src=IMG_SRC; //移动方向 this.delta=[3,3]; //起始点 this.pos_x = 225; this.pos_y = 120; //半径 this.radius = 40; //画布的长和宽 this.w = parseInt(canvas.getAttribute("width")); this.h = parseInt(canvas.getAttribute("height")); } Clip.prototype.draw1=function(){ //碰撞检测 if (this.pos_x < this.radius) { this.delta[0] = Math.random() % 4 + 5; } else if (this.pos_x > this.w - this.radius) { this.delta[0] = -(Math.random() % 4 + 5); } if (this.pos_y < this.radius) { this.delta[1] = Math.random() % 4 + 5; } else if (this.pos_y > this.h - this.radius) { this.delta[1] = -(Math.random() % 4 + 5); } this.pos_x += this.delta[0]; this.pos_y += this.delta[1]; this.ctx.clearRect(0, 0, this.w, this.h); //保存状态 this.ctx.save() //移动变形 this.ctx.translate(this.pos_x,this.pos_y); //设置裁剪区域 this.ctx.beginPath(); this.ctx.arc(0 ,0,this.radius,0,Math.PI*2,true); this.ctx.clip(); // 将图片画到画布上 this.ctx.drawImage(this.img, -this.pos_x, -this.pos_y,this.w, this.h); //恢复状态 this.ctx.restore(); } Clip.prototype.draw2=function(){ //碰撞检测 if (this.pos_x < this.radius) { this.delta[0] = Math.random() % 4 + 5; } else if (this.pos_x > this.w - this.radius) { this.delta[0] = -(Math.random() % 4 + 5); } if (this.pos_y < this.radius) { this.delta[1] = Math.random() % 4 + 5; } else if (this.pos_y > this.h - this.radius) { this.delta[1] = -(Math.random() % 4 + 5); } this.pos_x += this.delta[0]; this.pos_y += this.delta[1]; this.ctx.clearRect(0, 0, this.w, this.h); //绘制灰色的半透明蒙版 this.ctx.fillStyle="rgba(125,125,125,0.9)" this.ctx.fillRect(0, 0, this.w, this.h); //保存状态 this.ctx.save() //移动坐标 this.ctx.translate(this.pos_x,this.pos_y); //裁剪透明的圆形区域 this.ctx.globalCompositeOperation = "xor"; this.ctx.fillStyle="white" this.ctx.beginPath(); this.ctx.arc(0 ,0,this.radius,0,Math.PI*2,true); this.ctx.fill(); // 将图片画到蒙版的下面,即只露出透明区域 this.ctx.globalCompositeOperation = "destination-over"; this.ctx.drawImage(this.img, -this.pos_x, -this.pos_y,this.w, this.h); //恢复状态 this.ctx.restore(); } var cl=new Clip(); cl.interval=null; function move(id){ if(cl.interval) clearInterval(cl.interval) if(id==1){ cl.ctx.clearRect(0, 0, 450, 300); cl.interval=setInterval(function(){cl.draw1()},20); } else{ cl.ctx.clearRect(0, 0, 450, 300); cl.interval=setInterval(function(){cl.draw2()},20); } } function stop(){ clearInterval(cl.interval) } var compositeTypes = [ 'source-over','source-in','source-out','source-atop', 'destination-over','destination-in','destination-out','destination-atop', 'lighter','darker','copy','xor' ]; function drawComp(){ for (i=0;i<compositeTypes.length;i++){ var label = document.createTextNode(compositeTypes[i]); document.getElementById('lab'+i).appendChild(label); var ctx = document.getElementById('tut'+i).getContext('2d'); // draw rectangle ctx.fillStyle = "#09f"; ctx.fillRect(15,15,70,70); // set composite property ctx.globalCompositeOperation = compositeTypes[i]; // draw circle ctx.fillStyle = "#f30"; ctx.beginPath(); ctx.arc(75,75,35,0,Math.PI*2,true); ctx.fill(); } } drawComp(); </script> ログイン後にコピー |
以上が遊びながら学ぶHTML5 (6) ~オートボットと変身の詳しい紹介~の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。