2017-08-18 6 views
0

私はキャンバスでアンチエイリアス描画関数を実行しようとしています。 キャンバスとエイリアシングに関するこのサイトのすべてのスーパー回答に感謝します。ここ高速アンチエイリアスHTML5canvasの基本描画関数を作成する方法はありますか?

はデモです:https://jsfiddle.net/garciaVvV/eu34c8sy/12/

は、ここではラインのJSです:

function lineXY(mouseX, mouseY, mouseXX, mouseYY){ 
    var x0= mouseX; 
    var y0= mouseY; 
    var x1= mouseXX; 
    var y1= mouseYY; 

var coordinatesArray = []; 
// Translate coordinates 
// Define differences and error check 
var dx = Math.abs(x1 - x0); 
var dy = Math.abs(y1 - y0); 
var sx = (x0 < x1) ? 1 : -1; 
var sy = (y0 < y1) ? 1 : -1; 
var err = dx - dy; 
// Set first coordinates 
coordinatesArray.push([x0,y0]); 

// Main loop 
while (!((x0 == x1) && (y0 == y1))) { 
    var e2 = err << 1; 
    if (e2 > -dy) { 
    err -= dy; 
    x0 += sx; 
    } 
    if (e2 < dx) { 
    err += dx; 
    y0 += sy; 
    } 
    // Set coordinates 
coordinatesArray.push([x0,y0]); 
    // Return the result 
} 

for(var i=0;i<coordinatesArray.length;i++) { 
    aliasedCircle(ctx, coordinatesArray[i][0], coordinatesArray[i][1], 100); 
} 
} 

大きなペンで速い描きながらそれはぎくしゃくしますか?そしてそれを甘くする方法?

おかげ

+0

*ジャーキー*とは何ですか? – Kaiido

+0

かなり速く描こうとすると、瞬間を止めてラインを完成させます。 –

答えて

1

主な理由は、ピクセル当たりの円形経路Xの長さを再生ラインと第一の円とした後、パスのかなり多数が生成されることは勿論です。

  • 我々は画像として円をキャッシュし、ビットマップブラシとして使用することができます:

    は、私たちがこれを改善するために行うことができます物事のカップルがあります。これにより、ライン内の各点に対して円のすべての線を再生成する必要がなくなります。ブラシはサイズや色が変わったときに更新する必要があります。

  • 私たちはラインの各点を描画する必要はありません、我々は描画する必要がある前に、我々はスキップすることができますどのように多くのピクセルを計算する方法を見つけることができますが、より良いオプションです:

  • 我々はできますそれぞれの点に円を描くのではなく、最初と最後の点の間に太い線を描くことで「チート」します。

  • 最後に、各イベントの代わりに各フレームにマウスを登録して負荷を軽減することができます。

最初のポイントは十分に単純である:単にブラシ(直径)のサイズオフスクリーンキャンバスを作成し、描画のいずれかの色を変更するブラシを再生(または複合モードを使用し、その上に描画)する:。

// show brush 
 
document.body.appendChild(createBrush(150, "#09f")); 
 

 
function createBrush(radius, color) { 
 
    var ctx = document.createElement("canvas").getContext("2d"); 
 
    ctx.canvas.width = ctx.canvas.height = radius<<1; 
 
    ctx.fillStyle = color;    
 
    aliasedCircle(ctx, radius, radius, radius); 
 
    ctx.fill();     
 
    return ctx.canvas 
 
} 
 

 
function aliasedCircle(ctx, xc, yc, r) { // NOTE: for fill only! 
 
    var x = r, y = 0, cd = 0; 
 

 
    // middle line 
 
    ctx.rect(xc - x, yc, r<<1, 1); 
 

 
    while (x > y) { 
 
    cd -= (--x) - (++y); 
 
    if (cd < 0) cd += x++; 
 
    ctx.rect(xc - y, yc - x, y<<1, 1); // upper 1/4 
 
    ctx.rect(xc - x, yc - y, x<<1, 1); // upper 2/4 
 
    ctx.rect(xc - x, yc + y, x<<1, 1); // lower 3/4 
 
    ctx.rect(xc - y, yc + x, y<<1, 1); // lower 4/4 
 
    } 
 
}
今、私たちは線を描画する方法を見てすることができ、画像/ビットマップブラシを持っていること。我々は2つのアプローチを用いることができる。エイリアス化したいので、何とか妥協する必要があります。

Bresenhamを使用して線を描き、線を塗りつぶすことは、私たちが作業している状況で非常に遅くなる可能性があります。円を何回も描くのも遅いです。

第3の選択肢は、コンテキストの独自の行を使用してエッジを「ハック」することです(もちろん、すべてこれがバケツの塗りつぶしを改善するためのものであれば前の質問を参考にしてください)代わりにアルゴリズムを記入してください:))。

したがって、3番目のオプションを試してみましょう。 Bresenhamだけでなく、内部の仕組みも必要です。課題は、ブレーゼンハムが正確にエッジをカバーするようにすることです。(今、これはすべてのケースで完璧とオフセットではないかもしれない - EFLAとエッジを一致させよう:

var ctx = c.getContext("2d"); 
 

 
drawLine(ctx, 60, 60, 250, 210, 50); 
 
ctx.stroke(); 
 

 
function drawLine(ctx, x1, y1, x2, y2, radius) { 
 
    ctx.moveTo(x1, y1); 
 
    ctx.lineTo(x2, y2); 
 
    ctx.lineWidth = radius<<1; 
 
    ctx.lineCap = "butt"; 
 
}
<canvas id=c height=300></canvas>

は、実際には、より高速なラインアルゴリズムを使用することができますブレゼンハムを追加できますまたは本来の描画操作の線幅)を調整する必要があります。

また、両側の角度に対して90°のオフセットを計算する必要があります。 90°の加算と減算の代わりに、代わりにcos/sinを切り替えることができます。

var ctx = c.getContext("2d"); 
 
var x1 = 60, y1 = 60, x2 = 250, y2 = 210, r = 50; 
 

 
ctx.globalAlpha = 0.25; 
 
drawLine(ctx, x1, y1, x2, y2, r); 
 
ctx.stroke(); 
 
ctx.beginPath(); 
 
ctx.globalAlpha = 1; 
 

 
// calc angle 
 
var diffX = x2 - x1, 
 
    diffY = y2 - y1, 
 
    angle = Math.atan2(diffY, diffX); 
 

 
// two edge lines offset per angle 
 
var lx1 = x1 - r * Math.sin(angle), 
 
    ly1 = y1 + r * Math.cos(angle), 
 
    lx2 = x2 - r * Math.sin(angle), 
 
    ly2 = y2 + r * Math.cos(angle), 
 
    rx1 = x1 + r * Math.sin(angle), 
 
    ry1 = y1 - r * Math.cos(angle), 
 
    rx2 = x2 + r * Math.sin(angle), 
 
    ry2 = y2 - r * Math.cos(angle); 
 

 
fastLine(ctx, lx1|0, ly1|0, lx2|0, ly2|0); 
 
fastLine(ctx, rx1|0, ry1|0, rx2|0, ry2|0); 
 
ctx.fill(); 
 

 
function drawLine(ctx, x1, y1, x2, y2, radius) { 
 
    ctx.moveTo(x1, y1); 
 
    ctx.lineTo(x2, y2); 
 
    ctx.lineWidth = radius<<1; 
 
    ctx.lineCap = "butt"; 
 
} 
 

 
function fastLine(ctx, x1, y1, x2, y2) { 
 
    var dlt, mul, 
 
     sl = y2 - y1, 
 
     ll = x2 - x1, 
 
     yl = false, 
 
     lls = ll >> 31, 
 
     sls = sl >> 31, 
 
     i; 
 

 
    if ((sl^sls) - sls > (ll^lls) - lls) { 
 
    sl ^= ll; 
 
    ll ^= sl; 
 
    sl ^= ll; 
 
    yl = true 
 
    } 
 

 
    dlt = ll < 0 ? -1 : 1; 
 
    mul = (ll === 0) ? sl : sl/ll; 
 

 
    if (yl) { 
 
    x1 += 0.5; 
 
    for (i = 0; i !== ll; i += dlt) 
 
     ctx.rect((x1 + i * mul)|0, y1 + i, 1, 1) 
 
    } 
 
    else { 
 
    y1 += 0.5; 
 
    for (i = 0; i !== ll; i += dlt) 
 
     ctx.rect(x1 + i, (y1 + i * mul)|0, 1, 1) 
 
    } 
 
}
<canvas id=c height=300></canvas>

そして、我々は、コンポーネントを統合し、少しリファクタリング場合は、最後に、我々はこれらのアプローチを利用してきちんとしたエイリアス線画のメカニズムを取得:

var ctx = c.getContext("2d"); 
 
var x1 = 0, y1 = 0, r = 90; 
 
var brush = createBrush(r, "#000"); 
 

 
document.querySelector("button").onclick = function() { 
 
    ctx.beginPath(); 
 
    ctx.clearRect(0,0,c.width,c.height); 
 
}; 
 

 
// mouse move handler using rAF. 
 
c.onmousemove = function(e) { 
 
    requestAnimationFrame(function() { 
 
    var x2 = e.clientX|0, y2=e.clientY|0; 
 
    aliasedLine(ctx, x1, y1, x2, y2, r); 
 
    x1 = x2; 
 
    y1 = y2; 
 
    }) 
 
}; 
 

 
function aliasedLine(ctx, x1, y1, x2, y2, radius) { 
 
    // calc angle 
 
    var diffX = x2 - x1, 
 
     diffY = y2 - y1, 
 
     angle = Math.atan2(diffY, diffX), 
 

 
     // two edge lines offset per angle 
 
     lx1 = x1 - radius * Math.sin(angle), 
 
     ly1 = y1 + radius * Math.cos(angle), 
 
     lx2 = x2 - radius * Math.sin(angle), 
 
     ly2 = y2 + radius * Math.cos(angle), 
 
     rx1 = x1 + radius * Math.sin(angle), 
 
     ry1 = y1 - radius * Math.cos(angle), 
 
     rx2 = x2 + radius * Math.sin(angle), 
 
     ry2 = y2 - radius * Math.cos(angle); 
 

 
    // main line 
 
    ctx.beginPath(); 
 
    drawLine(ctx, x1, y1, x2, y2, radius); 
 
    ctx.stroke(); 
 
    
 
    // aliased edges 
 
    ctx.beginPath(); 
 
    fastLine(ctx, lx1|0, ly1|0, lx2|0, ly2|0); 
 
    fastLine(ctx, rx1|0, ry1|0, rx2|0, ry2|0); 
 
    ctx.fill(); 
 

 
    // caps 
 
    ctx.drawImage(brush, x1 - radius, y1 - radius) 
 
    ctx.drawImage(brush, x2 - radius, y2 - radius) 
 
} 
 

 

 
function createBrush(radius, color) { 
 
    var ctx = document.createElement("canvas").getContext("2d"); 
 
    ctx.canvas.width = ctx.canvas.height = 1 + radius<<1; 
 
    ctx.fillStyle = color;    
 
    aliasedCircle(ctx, radius, radius, radius); 
 
    ctx.fill();     
 
    return ctx.canvas 
 
} 
 

 
function aliasedCircle(ctx, xc, yc, r) { // NOTE: for fill only! 
 
    var x = r, y = 0, cd = 0; 
 

 
    // middle line 
 
    ctx.rect(xc - x, yc, r<<1, 1); 
 

 
    while (x > y) { 
 
    cd -= (--x) - (++y); 
 
    if (cd < 0) cd += x++; 
 
    ctx.rect(xc - y, yc - x, y<<1, 1); // upper 1/4 
 
    ctx.rect(xc - x, yc - y, x<<1, 1); // upper 2/4 
 
    ctx.rect(xc - x, yc + y, x<<1, 1); // lower 3/4 
 
    ctx.rect(xc - y, yc + x, y<<1, 1); // lower 4/4 
 
    } 
 
} 
 

 
function drawLine(ctx, x1, y1, x2, y2, radius) { 
 
    ctx.moveTo(x1, y1); 
 
    ctx.lineTo(x2, y2); 
 
    ctx.lineWidth = radius<<1; 
 
} 
 

 
function fastLine(ctx, x1, y1, x2, y2) { 
 
    var dlt, mul, 
 
     sl = y2 - y1, 
 
     ll = x2 - x1, 
 
     yl = false, 
 
     lls = ll >> 31, 
 
     sls = sl >> 31, 
 
     i; 
 

 
    if ((sl^sls) - sls > (ll^lls) - lls) { 
 
    sl ^= ll; 
 
    ll ^= sl; 
 
    sl ^= ll; 
 
    yl = true 
 
    } 
 

 
    dlt = ll < 0 ? -1 : 1; 
 
    mul = (ll === 0) ? sl : sl/ll; 
 

 
    if (yl) { 
 
    x1 += 0.5; 
 
    for (i = 0; i !== ll; i += dlt) 
 
     ctx.rect((x1 + i * mul)|0, y1 + i, 1, 1) 
 
    } 
 
    else { 
 
    y1 += 0.5; 
 
    for (i = 0; i !== ll; i += dlt) 
 
     ctx.rect(x1 + i, (y1 + i * mul)|0, 1, 1) 
 
    } 
 
}
#c {background:#aaa}
<canvas id=c width=1200 height=800></canvas> 
 
<br><button>Clear</button>

いくつかの最終ノート:ちょうどそれがpでないかもしれないことに注意してくださいエイリアス、エイリアス、特にほぼ0/90度の線で表示されます。これは、サンプル数のために、EFLAラインがその単一のピクセルポイントでカバーすることができない、細かい段階的ラインを作る多くの点を置くことができるからです。

代替方法の1つは、ポリゴン塗りつぶし(scanlineなど)を実装することです。これはもう少し数学的なステップであり、許容される性能で実行可能です。

+0

不完全さは20ピクセル以下の半径でかなり見えますが、それでも非常に便利です:)。私はこのキャンバス絵のゲームを見つけた、それは問題が完全にここで修正されたように見える。私は理解していません:(もしあなたが興味があれば:http://skribbl.io –

+0

@garciaventureええ、ラインはアンチエイリアスですが、どちらかは改良されたバケツを使っています。 (コードを見ていない)。コンポジットモードを使用する方法の1つは、https://stackoverflow.com/a/45710008/1693593です(これも[this](https://stackoverflow.com/)が含まれています)。 a/28574510/1693593) – K3N

+0

ストロークとそのポイントをブラシ情報と一緒に保存することができますので、後で任意の色で再描画することができます。 – K3N

関連する問題