HTML5 캔버스에서 단일 픽셀을 설정하는 가장 좋은 방법은 무엇입니까?
HTML5 Canvas에는 단일 픽셀을 명시 적으로 설정하는 방법이 없습니다.
매우 짧은 선을 사용하여 픽셀을 설정할 수는 있지만 앤티 앨리어싱 및 선 캡이 방해 될 수 있습니다.
또 다른 방법은 작은 ImageData객체 를 만들고 다음을 사용하는 것입니다 .
context.putImageData(data, x, y)
그것을 제자리에 두십시오.
누구나 효율적이고 신뢰할 수있는 방법을 설명 할 수 있습니까?
두 가지 최고의 경쟁자가 있습니다.
1 × 1 이미지 데이터를 생성하고 색상을 설정
putImageData하고 위치를 지정하십시오.var id = myContext.createImageData(1,1); // only do this once per page var d = id.data; // only do this once per page d[0] = r; d[1] = g; d[2] = b; d[3] = a; myContext.putImageData( id, x, y );fillRect()픽셀을 그리는 데 사용 합니다 (별칭 문제가 없어야 함).ctx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")"; ctx.fillRect( x, y, 1, 1 );
http://jsperf.com/setting-canvas-pixel/9 또는 여기 https://www.measurethat.net/Benchmarks/Show/1664/1 에서 속도를 테스트 할 수 있습니다.
최대 속도를 원하는 브라우저에 대해 테스트하는 것이 좋습니다. 2017 년 7 월 현재 fillRect()Firefox v54 및 Chrome v59 (Win7x64)에서 5-6 배 더 빠릅니다.
다른 대안은 다음과 같습니다.
사용하여
getImageData()/putImageData()전체 캔버스에; 다른 옵션보다 약 100 배 느립니다.데이터 URL을 사용하여 사용자 지정 이미지를 만들고
drawImage()이를 사용 하여 표시 :var img = new Image; img.src = "data:image/png;base64," + myPNGEncoder(r,g,b,a); // Writing the PNGEncoder is left as an exercise for the reader원하는 모든 픽셀로 채워진 다른 이미지 또는 캔버스를 만들고 원하는 픽셀
drawImage()만 블리 팅 하는 데 사용 합니다. 아마도 매우 빠를 것이지만 필요한 픽셀을 미리 계산해야하는 한계가 있습니다.
내 테스트는 캔버스 컨텍스트를 저장 및 복원하지 않습니다 fillStyle. fillRect()성능 이 저하 될 수 있습니다. 또한 깨끗한 슬레이트로 시작하거나 각 테스트에 대해 정확히 동일한 픽셀 세트를 테스트하지 않습니다.
나는 고려하지 fillRect()않았지만, 대답은 그것을 벤치마킹하도록 자극했다 putImage().
(오래된) MacBook Pro에서 Chrome 9.0.597.84를 사용하여 임의의 위치에 임의의 색상으로 100,000 개의 픽셀을 배치하는 데는 100ms 미만 putImage()이지만을 사용하면 거의 900ms가 걸립니다 fillRect(). ( http://pastebin.com/4ijVKJcC의 벤치 마크 코드 ).
대신 루프 외부에서 단일 색상을 선택하고 임의의 위치에 해당 색상을 플롯하면에 putImage()대해 59ms 대 102ms가 걸립니다 fillRect().
구문에서 CSS 색상 사양을 생성하고 파싱하는 오버 헤드가 rgb(...)대부분의 차이를 담당 하는 것으로 보입니다 .
ImageData반면에 원시 RGB 값을 블록에 똑바로 넣으면 문자열 처리 또는 구문 분석이 필요하지 않습니다.
function setPixel(imageData, x, y, r, g, b, a) {
index = (x + y * imageData.width) * 4;
imageData.data[index+0] = r;
imageData.data[index+1] = g;
imageData.data[index+2] = b;
imageData.data[index+3] = a;
}
브라우저마다 다른 방법을 선호하는 것 같으므로 로딩 프로세스의 일부로 세 가지 방법 모두로 더 작은 테스트를 수행하여 어느 것이 가장 적합한 지 알아 낸 다음 응용 프로그램 전체에서 사용하는 것이 합리적 일 수 있습니다.
언급되지 않은 한 가지 방법은 getImageData를 사용한 다음 putImageData를 사용하는 것입니다.
이 방법은 한 번에 많이 그리기를 원할 때 유용합니다.
http://next.plnkr.co/edit/mfNyalsAR2MWkccr
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
var id = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
var pixels = id.data;
var x = Math.floor(Math.random() * canvasWidth);
var y = Math.floor(Math.random() * canvasHeight);
var r = Math.floor(Math.random() * 256);
var g = Math.floor(Math.random() * 256);
var b = Math.floor(Math.random() * 256);
var off = (y * id.width + x) * 4;
pixels[off] = r;
pixels[off + 1] = g;
pixels[off + 2] = b;
pixels[off + 3] = 255;
ctx.putImageData(id, 0, 0);
사각형은 어떻습니까? 그것은 ImageData객체를 만드는 것보다 더 효율적이어야 합니다.
이상하게 보이지만 HTML5는 선, 원, 사각형 및 기타 여러 기본 도형 그리기를 지원하지만 기본 점을 그리기에 적합한 것은 없습니다. 그렇게하는 유일한 방법은 당신이 가진 것을 가지고 포인트를 시뮬레이션하는 것입니다.
따라서 기본적으로 3 가지 가능한 솔루션이 있습니다.
- 점을 선으로 그리다
- 다각형으로 점을 그립니다
- 점을 원으로 그리다
그들 각각은 단점이 있습니다
선
function point(x, y, canvas){
canvas.beginPath();
canvas.moveTo(x, y);
canvas.lineTo(x+1, y+1);
canvas.stroke();
}
우리는 남동쪽 방향으로 그림을 그리며, 이것이 가장자리라면 문제가있을 수 있습니다. 그러나 다른 방향으로 그릴 수도 있습니다.
직사각형
function point(x, y, canvas){
canvas.strokeRect(x,y,1,1);
}
또는 렌더링 엔진이 한 픽셀 만 채울 수 있기 때문에 fillRect를 사용하는 더 빠른 방법으로
function point(x, y, canvas){
canvas.fillRect(x,y,1,1);
}
원
서클의 문제 중 하나는 엔진이 렌더링하기가 어렵다는 것입니다.
function point(x, y, canvas){
canvas.beginPath();
canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
canvas.stroke();
}
채우기로 달성 할 수있는 사각형과 동일한 아이디어.
function point(x, y, canvas){
canvas.beginPath();
canvas.arc(x, y, 1, 0, 2 * Math.PI, true);
canvas.fill();
}
이 모든 솔루션의 문제점 :
- 그리려는 모든 점을 추적하기는 어렵습니다.
- 확대하면보기 흉하게 보입니다.
" 점을 그리는 가장 좋은 방법 은 무엇입니까 ? "라는 궁금한 점이 있으면 채워진 사각형으로 갈 것입니다. 비교 테스트와 함께 내 jsperf 를 볼 수 있습니다 .
sdleihssirhc와 같은 사각형을 그립니다!
ctx.fillRect (10, 10, 1, 1);
^-x : 10, y : 10에 1x1 직사각형을 그려야합니다.
흠, 길이가 1 픽셀 인 1 픽셀 너비의 선을 만들고 방향이 단일 축을 따라 움직일 수 있습니다.
ctx.beginPath();
ctx.lineWidth = 1; // one pixel wide
ctx.strokeStyle = rgba(...);
ctx.moveTo(50,25); // positioned at 50,25
ctx.lineTo(51,25); // one pixel long
ctx.stroke();
전체 Phrogz 아주 철저한 대답에, 사이에 중요한 차이가 fillRect()하고 putImageData().
제 용도 컨텍스트 그릴 위에 의해 가산 은 USING 직사각형 (NOT 화소)를 fillStyle에서는 된 알파 값 컨텍스트 globalAlpha 상기 변환 행렬 , 라인 캡 등
번째 대체하는 전체 픽셀의 세트 (아마 하나지만 왜 ?)
결과는 jsperf에서 볼 수 있듯이 다릅니다 .
한 번에 한 픽셀 씩 설정하고 싶지 않습니다 (화면에 그림을 그리는 것을 의미). 그렇기 때문에 특정 API가없는 이유가 바로 그런 것입니다.
성능면에서 목표가 그림 (예 : 광선 추적 소프트웨어)을 생성하는 것이라면 항상getImageData() 최적화 된 Uint8Array 인 배열을 사용하려고합니다 . 그런 putImageData()다음를 사용하여 ONCE 또는 초당 몇 번 전화 setTimeout/seTInterval합니다.
빠른 HTML 데모 코드 : SFML C ++ 그래픽 라이브러리에 대해 알고있는 내용을 기반으로합니다.
UTF-8 인코딩으로 HTML 파일로 저장하고 실행하십시오. 리팩토링을 자유롭게하십시오. 간결하고 공간을 많이 차지하지 않기 때문에 일본어 변수를 사용하는 것을 좋아합니다.
드물게 하나의 임의의 픽셀을 설정하여 화면에 표시하려고합니다. 따라서
PutPix(x,y, r,g,b,a)
백 버퍼에 수많은 임의의 픽셀을 그리는 방법. (싼 전화)
그런 다음 보여줄 준비가되면
Apply()
변경 사항을 표시하는 방법입니다. (고가의 전화)
아래 전체 .HTML 파일 코드 :
<!DOCTYPE HTML >
<html lang="en">
<head>
<title> back-buffer demo </title>
</head>
<body>
</body>
<script>
//Main function to execute once
//all script is loaded:
function main(){
//Create a canvas:
var canvas;
canvas = attachCanvasToDom();
//Do the pixel setting test:
var test_type = FAST_TEST;
backBufferTest(canvas, test_type);
}
//Constants:
var SLOW_TEST = 1;
var FAST_TEST = 2;
function attachCanvasToDom(){
//Canvas Creation:
//cccccccccccccccccccccccccccccccccccccccccc//
//Create Canvas and append to body:
var can = document.createElement('canvas');
document.body.appendChild(can);
//Make canvas non-zero in size,
//so we can see it:
can.width = 800;
can.height= 600;
//Get the context, fill canvas to get visual:
var ctx = can.getContext("2d");
ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
ctx.fillRect(0,0,can.width-1, can.height-1);
//cccccccccccccccccccccccccccccccccccccccccc//
//Return the canvas that was created:
return can;
}
//THIS OBJECT IS SLOOOOOWW!
// 筆 == "pen"
//T筆 == "Type:Pen"
function T筆(canvas){
//Publicly Exposed Functions
//PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE//
this.PutPix = _putPix;
//PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE//
if(!canvas){
throw("[NilCanvasGivenToPenConstruct]");
}
var _ctx = canvas.getContext("2d");
//Pixel Setting Test:
// only do this once per page
//絵 =="image"
//資 =="data"
//絵資=="image data"
//筆 =="pen"
var _絵資 = _ctx.createImageData(1,1);
// only do this once per page
var _筆 = _絵資.data;
function _putPix(x,y, r,g,b,a){
_筆[0] = r;
_筆[1] = g;
_筆[2] = b;
_筆[3] = a;
_ctx.putImageData( _絵資, x, y );
}
}
//Back-buffer object, for fast pixel setting:
//尻 =="butt,rear" using to mean "back-buffer"
//T尻=="type: back-buffer"
function T尻(canvas){
//Publicly Exposed Functions
//PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE//
this.PutPix = _putPix;
this.Apply = _apply;
//PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE//
if(!canvas){
throw("[NilCanvasGivenToPenConstruct]");
}
var _can = canvas;
var _ctx = canvas.getContext("2d");
//Pixel Setting Test:
// only do this once per page
//絵 =="image"
//資 =="data"
//絵資=="image data"
//筆 =="pen"
var _w = _can.width;
var _h = _can.height;
var _絵資 = _ctx.createImageData(_w,_h);
// only do this once per page
var _筆 = _絵資.data;
function _putPix(x,y, r,g,b,a){
//Convert XY to index:
var dex = ( (y*4) *_w) + (x*4);
_筆[dex+0] = r;
_筆[dex+1] = g;
_筆[dex+2] = b;
_筆[dex+3] = a;
}
function _apply(){
_ctx.putImageData( _絵資, 0,0 );
}
}
function backBufferTest(canvas_input, test_type){
var can = canvas_input; //shorthand var.
if(test_type==SLOW_TEST){
var t筆 = new T筆( can );
//Iterate over entire canvas,
//and set pixels:
var x0 = 0;
var x1 = can.width - 1;
var y0 = 0;
var y1 = can.height -1;
for(var x = x0; x <= x1; x++){
for(var y = y0; y <= y1; y++){
t筆.PutPix(
x,y,
x%256, y%256,(x+y)%256, 255
);
}}//next X/Y
}else
if(test_type==FAST_TEST){
var t尻 = new T尻( can );
//Iterate over entire canvas,
//and set pixels:
var x0 = 0;
var x1 = can.width - 1;
var y0 = 0;
var y1 = can.height -1;
for(var x = x0; x <= x1; x++){
for(var y = y0; y <= y1; y++){
t尻.PutPix(
x,y,
x%256, y%256,(x+y)%256, 255
);
}}//next X/Y
//When done setting arbitrary pixels,
//use the apply method to show them
//on screen:
t尻.Apply();
}
}
main();
</script>
</html>
putImageData아마도 fillRect기본적으로 더 빠를 것 입니다. 다섯 번째 매개 변수는 해석 해야하는 문자열을 사용하여 다른 방법 (사각형 색상)을 할당 할 수 있기 때문에 이것을 생각합니다.
그렇게하고 있다고 가정 해보십시오.
context.fillRect(x, y, 1, 1, "#fff")
context.fillRect(x, y, 1, 1, "rgba(255, 255, 255, 0.5)")`
context.fillRect(x, y, 1, 1, "rgb(255,255,255)")`
context.fillRect(x, y, 1, 1, "blue")`
그래서 라인
context.fillRect(x, y, 1, 1, "rgba(255, 255, 255, 0.5)")`
모두 사이에서 가장 무겁습니다. fillRect호출 의 다섯 번째 인수 는 조금 더 긴 문자열입니다.
속도가 걱정된다면 WebGL을 고려할 수도 있습니다.
핸디 및 풋 픽셀 (pp) 기능 (ES6)의 제안 (읽기 픽셀 여기 ) :
let pp= ((s='.myCanvas',c=document.querySelector(s),ctx=c.getContext('2d'),id=ctx.createImageData(1,1)) => (x,y,r=0,g=0,b=0,a=255)=>(id.data.set([r,g,b,a]),ctx.putImageData(id, x, y),c))()
pp(10,30,0,0,255,255); // x,y,r,g,b,a ; return canvas object
let pp= ((s='.myCanvas',c=document.querySelector(s),ctx=c.getContext('2d'),id=ctx.createImageData(1,1)) => (x,y,r=0,g=0,b=0,a=255)=>(id.data.set([r,g,b,a]),ctx.putImageData(id, x, y),c))()
// draw shape
for(let i=0; i<800; i++) {
let a = Math.PI*4*i/800;
let x=16*Math.sin(a)**3;
let y=13*Math.cos(a)-5*Math.cos(2*a)-2*Math.cos(3*a)-Math.cos(4*a);
pp(100+2.5*x,45-2.5*y,255,0,0,255)
}
<canvas class="myCanvas" width=200 height=100 style="background: black"></canvas>
This function use putImageData and has initialisation part (first long line). At the begining instead s='.myCanvas' use you CSS selector to your canvas.
I you wanna to normalize parameters to value from 0-1 you should change default value a=255 to a=1 and line with: id.data.set([r,g,b,a]),ctx.putImageData(id, x, y) to id.data.set([r*255,g*255,b*255,a*255]),ctx.putImageData(id, x*c.width, y*c.height)
The handy code above is good to ad-hoc test graphics algorithms or make proof of concept, but it is not good to use in production where code should be readable and clear.
'Programming' 카테고리의 다른 글
| IList 사용시기 및 List 사용시기 (0) | 2020.05.24 |
|---|---|
| 찾기 및 바꾸기-캐리지 리턴 또는 줄 바꾸기 추가 (0) | 2020.05.24 |
| Rust에서 사용하지 않는 코드 경고를 비활성화하는 방법은 무엇입니까? (0) | 2020.05.24 |
| 브라우저가 끌어서 놓기 파일을로드하지 못하도록 방지 (0) | 2020.05.24 |
| Vim 또는 Linux에서 공백을 탭으로 변환하는 방법은 무엇입니까? (0) | 2020.05.24 |