ฉันสามารถปิดการลบรอยหยักในองค์ประกอบ <canvas> HTML ได้หรือไม่


88

ฉันกำลังเล่นกับ<canvas>องค์ประกอบวาดเส้นและอื่น ๆ

ฉันสังเกตว่าเส้นทแยงมุมของฉันมีการต่อต้านการดัด ฉันชอบรูปลักษณ์ที่ดูหยาบสำหรับสิ่งที่ฉันกำลังทำ - มีวิธีใดในการปิดคุณสมบัตินี้หรือไม่?


ฉันคิดว่าค่อนข้างเกี่ยวข้องกับเบราว์เซอร์ ข้อมูลเพิ่มเติมเกี่ยวกับซอฟต์แวร์ที่คุณใช้อาจเป็นประโยชน์
Tomalak

ฉันต้องการวิธีการข้ามเบราว์เซอร์ แต่วิธีที่ใช้ได้กับเบราว์เซอร์เดียวก็ยังน่าสนใจสำหรับฉัน
Blorgbeard ออก

ฉันแค่อยากจะดูว่ามีการเปลี่ยนแปลงในหัวข้อนี้หรือไม่?
vternal3


มีการอัปเดตเกี่ยวกับเรื่องนี้หรือไม่?
Roland

คำตอบ:


64

สำหรับภาพตอนนี้ context.imageSmoothingEnabled= falseสำหรับภาพที่มีในขณะนี้

อย่างไรก็ตามไม่มีสิ่งใดที่ควบคุมการวาดเส้นอย่างชัดเจน คุณอาจจำเป็นต้องวาดเส้นของคุณเอง ( วิธีที่ยาก ) โดยใช้และgetImageDataputImageData


1
ฉันสงสัยเกี่ยวกับการทำงานของอัลกอริธึมบรรทัดจาวาสคริปต์ .. อาจทำให้เบรสเซนแฮมไปในบางจุด
Blorgbeard ออก

ผู้จำหน่ายเบราว์เซอร์กำลังโน้มน้าวเอ็นจิ้น JS ความเร็วสูงใหม่เมื่อเร็ว ๆ นี้ดังนั้นในที่สุดก็จะมีประโยชน์สำหรับมัน
Kornel

1
ใช้งานได้จริงหรือ? ฉันวาดเส้นโดยใช้putImageData แต่มันยังคงใช้นามแฝงของพิกเซลที่อยู่ใกล้เคียง
Pacerier

ถ้าฉันวาดลงบนผืนผ้าใบที่เล็กกว่า (แคช) แล้ววาดภาพไปยังผืนผ้าใบอื่นโดยปิดการตั้งค่านั้นจะทำงานได้ตามที่ตั้งใจไว้หรือไม่
SparK

69

วาดของคุณเส้นบนพิกัดเช่น1-pixel ctx.lineTo(10.5, 10.5)การวาดเส้นหนึ่งพิกเซลเหนือจุดหมาย(10, 10)ความว่า1พิกเซลนี้ที่ตำแหน่งนั้นถึงจาก9.5ถึง10.5ซึ่งส่งผลให้ทั้งสองสายที่ได้รับการวาดบนผืนผ้าใบ

เคล็ดลับที่ดีที่ไม่จำเป็นต้องเพิ่ม0.5พิกัดจริงที่คุณต้องการวาดทับเสมอไปหากคุณมีเส้นพิกเซลเดียวจำนวนมากคือctx.translate(0.5, 0.5)ผืนผ้าใบทั้งหมดของคุณในตอนเริ่มต้น


อืมฉันมีปัญหาในการกำจัดการลบรอยหยักโดยใช้เทคนิคนี้ บางทีฉันไม่เข้าใจอะไรบางอย่าง? คุณช่วยโพสต์ตัวอย่างได้ไหม?
Xavi

7
วิธีนี้ไม่ได้กำจัดการลบรอยหยัก แต่จะทำให้เส้นลดรอยหยักดูดีขึ้นมากเช่นการกำจัดเส้นแนวนอนหรือแนวตั้งที่น่าอายที่มีความหนาสองพิกเซลเมื่อคุณต้องการหนึ่งพิกเซลจริงๆ
David Given

1
@porneL: ไม่เส้นถูกลากระหว่างมุมของพิกเซล เมื่อเส้นของคุณกว้าง 1 พิกเซลจะขยายครึ่งพิกเซลในทิศทางใดทิศทางหนึ่ง
Eric

การเพิ่ม +0.5 ใช้ได้ผลกับฉัน แต่ctx.translate(0.5,0.5)ไม่ได้ผล เมื่อ FF39.0
Paulo Bueno

ขอบคุณมาก! ฉันไม่อยากจะเชื่อเลยว่าฉันมีเส้น 1px สำหรับการเปลี่ยนแปลง!
Chunky Chunk

24

สามารถทำได้ใน Mozilla Firefox เพิ่มสิ่งนี้ในรหัสของคุณ:

contextXYZ.mozImageSmoothingEnabled = false;

ใน Opera ปัจจุบันเป็นคำขอคุณสมบัติ แต่หวังว่าจะมีการเพิ่มเร็ว ๆ นี้


เย็น. +1 สำหรับการมีส่วนร่วมของคุณ ฉันสงสัยว่าการปิดใช้งาน AA จะเพิ่มความเร็วในการ
วาดเส้นหรือไม่

7
OP ต้องการยกเลิกการต่อต้านนามแฝง แต่ใช้ได้กับรูปภาพเท่านั้น ตามสเป็คจะกำหนด"whether pattern fills and the drawImage() method will attempt to smooth images if their pixels don't line up exactly with the display, when scaling images up"
rvighne

13

จะต้องมีกราฟิกแบบเวกเตอร์ของ antialias

จำเป็นต้องมีการลบรอยหยักสำหรับการพล็อตกราฟิกเวกเตอร์ที่ถูกต้องซึ่งเกี่ยวข้องกับพิกัดที่ไม่ใช่จำนวนเต็ม (0.4, 0.4) ซึ่งมีไคลเอนต์ทั้งหมด แต่มีเพียงไม่กี่รายเท่านั้น

เมื่อกำหนดพิกัดที่ไม่ใช่จำนวนเต็มผืนผ้าใบมีสองตัวเลือก:

  • Antialias - วาดพิกเซลรอบ ๆ พิกัดตามระยะทางของพิกัดจำนวนเต็มจากจำนวนเต็มหนึ่งที่ไม่ใช่จำนวนเต็ม (กล่าวคือข้อผิดพลาดในการปัดเศษ)
  • Round - ใช้ฟังก์ชันการปัดเศษบางส่วนกับพิกัดที่ไม่ใช่จำนวนเต็ม (ดังนั้น 1.4 จะกลายเป็น 1 เป็นต้น)

กลยุทธ์ในภายหลังจะใช้ได้กับกราฟิกแบบคงที่แม้ว่าเส้นโค้งขนาดเล็ก (วงกลมที่มีรัศมี 2) จะแสดงขั้นตอนที่ชัดเจนแทนที่จะเป็นเส้นโค้งที่ราบรื่น

ปัญหาที่แท้จริงคือเมื่อแปลกราฟิก (ย้าย) - การกระโดดระหว่างพิกเซลหนึ่งกับอีกพิกเซลหนึ่ง (1.6 => 2, 1.4 => 1) หมายความว่าจุดเริ่มต้นของรูปร่างอาจกระโดดโดยสัมพันธ์กับคอนเทนเนอร์หลัก (ขยับตลอดเวลา 1 พิกเซลขึ้น / ลงและซ้าย / ขวา)

เคล็ดลับบางประการ

เคล็ดลับ # 1 : คุณสามารถทำให้การลบรอยหยัก (หรือทำให้แข็ง) อ่อนลงได้โดยการปรับขนาดผ้าใบ (พูดด้วย x) จากนั้นใช้มาตราส่วนซึ่งกันและกัน (1 / x) กับรูปทรงเรขาคณิตด้วยตัวคุณเอง (ไม่ใช้ผ้าใบ)

เปรียบเทียบ (ไม่มีมาตราส่วน):

A few rectangles

ด้วย (ขนาดผ้าใบ: 0.75; มาตราส่วนด้วยตนเอง: 1.33):

Same rectangles with softer edges

และ (ขนาดผ้าใบ: 1.33; มาตราส่วนแบบแมนนวล: 0.75):

Same rectangles with darker edges

เคล็ดลับ # 2 : หากรูปลักษณ์ที่ดูหยาบกร้านเป็นสิ่งที่คุณต้องการจริงๆลองวาดแต่ละรูปร่างสองสามครั้ง (โดยไม่ต้องลบ) เมื่อวาดแต่ละครั้งพิกเซลลดรอยหยักจะเข้มขึ้น

เปรียบเทียบ. หลังจากวาดครั้งเดียว:

A few paths

หลังจากวาดสามครั้ง:

Same paths but darker and no visible antialiasing.


@vanowm รู้สึกอิสระที่จะโคลนและเล่นกับ: github.com/Izhaki/gefri ภาพทั้งหมดเป็นภาพหน้าจอจากโฟลเดอร์ / demo (มีการแก้ไขโค้ดเล็กน้อยสำหรับเคล็ดลับ # 2) ฉันแน่ใจว่าคุณจะพบว่าง่ายต่อการแนะนำการปัดเศษจำนวนเต็มให้กับตัวเลขที่วาด (ใช้เวลา 4 นาที) จากนั้นลากเพื่อดูเอฟเฟกต์
Izhaki

มันอาจจะดูเหมือนไม่น่าเชื่อ แต่ก็เป็นสถานการณ์ที่หายากที่คุณต้องการ antialias ปิด ตามความเป็นจริงฉันเพิ่งตั้งโปรแกรมเกมที่ผู้คนต้องวาดภาพพื้นที่บนผืนผ้าใบและฉันต้องการให้มีเพียง 4 สีและฉันก็ติดเพราะแอนตี้เลีย การทำซ้ำรูปแบบสามครั้งไม่สามารถแก้ปัญหาได้ (ฉันขึ้นไป 200) และยังมีพิกเซลของสีที่ไม่ถูกต้อง
Arnaud

9

ฉันจะวาดทุกอย่างโดยใช้อัลกอริทึมเส้นที่กำหนดเองเช่นอัลกอริทึมเส้นของ Bresenham ตรวจสอบการใช้งานจาวาสคริปต์นี้: http://members.chello.at/easyfilter/canvas.html

ฉันคิดว่านี่จะช่วยแก้ปัญหาของคุณได้อย่างแน่นอน


2
สิ่งที่ฉันต้องการสิ่งเดียวที่ฉันจะเพิ่มคือการที่คุณจำเป็นต้องใช้setPixel(x, y); ฉันใช้คำตอบที่ยอมรับที่นี่: stackoverflow.com/questions/4899799/…
Tina Vall

8

ฉันต้องการเพิ่มว่าฉันมีปัญหาในการลดขนาดภาพและวาดภาพบนผืนผ้าใบ แต่ก็ยังใช้การปรับให้เรียบแม้ว่าจะไม่ได้ใช้ในการลดขนาดก็ตาม

ฉันแก้ไขโดยใช้สิ่งนี้:

function setpixelated(context){
    context['imageSmoothingEnabled'] = false;       /* standard */
    context['mozImageSmoothingEnabled'] = false;    /* Firefox */
    context['oImageSmoothingEnabled'] = false;      /* Opera */
    context['webkitImageSmoothingEnabled'] = false; /* Safari */
    context['msImageSmoothingEnabled'] = false;     /* IE */
}

คุณสามารถใช้ฟังก์ชันนี้ดังนี้:

var canvas = document.getElementById('mycanvas')
setpixelated(canvas.getContext('2d'))

บางทีนี่อาจเป็นประโยชน์สำหรับใครบางคน


ทำไมไม่ context.imageSmoothingEnabled = false?
Martijn Scheffer

สิ่งนี้ใช้ไม่ได้ในขณะที่ฉันเขียนคำตอบ ตอนนี้ใช้งานได้หรือไม่
eri0o

1
มันก็เหมือนกันทุกประการในจาวาสคริปต์เขียน obj ['name'] หรือ obj.name มาตลอดและจะเหมือนกันเสมออ็อบเจกต์คือคอลเล็กชันของค่าที่ตั้งชื่อ (ทูเปิล) โดยใช้สิ่งที่คล้ายกับ ตารางแฮชสัญกรณ์ทั้งสองจะได้รับการปฏิบัติในลักษณะเดียวกันไม่มีเหตุผลใด ๆ เลยที่โค้ดของคุณจะไม่ทำงานมาก่อนที่แย่ที่สุดคือกำหนดค่าที่ไม่มีผลกระทบ (เนื่องจากมีไว้สำหรับเบราว์เซอร์อื่นตัวอย่างง่ายๆ: เขียน obj = {a: 123}; console.log (obj ['a'] === obj.a? "yes its true": "no it's not")
Martijn Scheffer

ฉันคิดว่าคุณหมายถึงทำไมมีสิ่งอื่น ๆ ทั้งหมดสิ่งที่ฉันหมายถึงกับความคิดเห็นของฉันก็คือในเวลานั้นเบราว์เซอร์ต้องการคุณสมบัติที่แตกต่างกัน
eri0o

ตกลงใช่แน่นอน :) ฉันกำลังพูดถึงไวยากรณ์ไม่ใช่ความถูกต้องของรหัสเอง (ใช้งานได้)
Martijn Scheffer

6
ctx.translate(0.5, 0.5);
ctx.lineWidth = .5;

ด้วยคำสั่งผสมนี้ฉันสามารถวาดเส้นบาง ๆ ขนาด 1px ที่สวยงามได้


6
คุณไม่จำเป็นต้องตั้งค่า lineWidth เป็น. 5 ... ซึ่งจะ (หรือควร) ทำให้มีความทึบเพียงครึ่งเดียว
aaaidan

4

สังเกตเห็นเคล็ดลับที่ จำกัด มาก หากคุณต้องการสร้างภาพ 2 สีคุณสามารถวาดรูปร่างใดก็ได้ที่คุณต้องการด้วยสี # 010101 บนพื้นหลังที่มีสี # 000000 เมื่อเสร็จแล้วคุณสามารถทดสอบแต่ละพิกเซลใน imageData.data [] และตั้งค่าเป็น 0xFF ว่าค่าใดที่ไม่ใช่ 0x00:

imageData = context2d.getImageData (0, 0, g.width, g.height);
for (i = 0; i != imageData.data.length; i ++) {
    if (imageData.data[i] != 0x00)
        imageData.data[i] = 0xFF;
}
context2d.putImageData (imageData, 0, 0);

ผลลัพธ์จะเป็นภาพขาวดำที่ไม่ได้ลบรอยหยัก สิ่งนี้จะไม่สมบูรณ์แบบเนื่องจากการลบรอยหยักจะเกิดขึ้น แต่การลบรอยหยักนี้จะมีข้อ จำกัด มากสีของรูปทรงจะเหมือนกับสีของพื้นหลังมาก


1

สำหรับผู้ที่ยังคงมองหาคำตอบ นี่คือทางออกของฉัน

ภาพสมมติคือ 1 ช่องสีเทา ฉันเพิ่งขีด จำกัด หลัง ctx.stroke ()

ctx.beginPath();
ctx.moveTo(some_x, some_y);
ctx.lineTo(some_x, some_y);
...
ctx.closePath();
ctx.fill();
ctx.stroke();

let image = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height)
for(let x=0; x < ctx.canvas.width; x++) {
  for(let y=0; y < ctx.canvas.height; y++) {
    if(image.data[x*image.height + y] < 128) {
      image.data[x*image.height + y] = 0;
    } else {
      image.data[x*image.height + y] = 255;
    }
  }
}

หากช่องภาพของคุณคือ 3 หรือ 4 คุณต้องแก้ไขดัชนีอาร์เรย์เช่น

x*image.height*number_channel + y*number_channel + channel

0

เพียงสองบันทึกเกี่ยวกับคำตอบของ StashOfCode:

  1. ใช้งานได้กับผ้าใบสีเทาทึบแสงเท่านั้น (เติมสีขาวแล้ววาดด้วยสีดำหรือกลับด้าน)
  2. อาจล้มเหลวเมื่อเส้นบาง (ความกว้างของเส้นประมาณ 1px)

ควรทำสิ่งนี้แทน:

ขีดเส้นแล้วเติม#FFFFFFจากนั้นทำสิ่งนี้:

imageData.data[i] = (imageData.data[i] >> 7) * 0xFF

ซึ่งแก้ได้สำหรับเส้นที่มีความกว้าง 1px

นอกเหนือจากนั้นโซลูชันของ StashOfCode นั้นสมบูรณ์แบบเพราะไม่จำเป็นต้องเขียนฟังก์ชันแรสเตอร์ของคุณเอง (คิดว่าไม่ใช่แค่เส้น แต่เป็นเบซิเออร์, ส่วนโค้งวงกลม, รูปหลายเหลี่ยมที่เต็มไปด้วยรู ฯลฯ ... )


0

นี่คือการใช้อัลกอริทึมพื้นฐานของ Bresenham ใน JavaScript โดยอ้างอิงจากเวอร์ชันเลขคณิตจำนวนเต็มที่อธิบายไว้ในบทความวิกิพีเดียนี้: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm

    function range(f=0, l) {
        var list = [];
        const lower = Math.min(f, l);
        const higher = Math.max(f, l);
        for (var i = lower; i <= higher; i++) {
            list.push(i);
        }
        return list;
    }

    //Don't ask me.
    //https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
    function bresenhamLinePoints(start, end) {

        let points = [];

        if(start.x === end.x) {
            return range(f=start.y, l=end.y)
                        .map(yIdx => {
                            return {x: start.x, y: yIdx};
                        });
        } else if (start.y === end.y) {
            return range(f=start.x, l=end.x)
                        .map(xIdx => {
                            return {x: xIdx, y: start.y};
                        });
        }

        let dx = Math.abs(end.x - start.x);
        let sx = start.x < end.x ? 1 : -1;
        let dy = -1*Math.abs(end.y - start.y);
        let sy = start.y < end.y ? 1 : - 1;
        let err = dx + dy;

        let currX = start.x;
        let currY = start.y;

        while(true) {
            points.push({x: currX, y: currY});
            if(currX === end.x && currY === end.y) break;
            let e2 = 2*err;
            if (e2 >= dy) {
                err += dy;
                currX += sx;
            }
            if(e2 <= dx) {
                err += dx;
                currY += sy;
            }
        }

        return points;

    }

0

canvas { image-rendering: pixelated; }ลองสิ่งที่ต้องการ

วิธีนี้อาจไม่ได้ผลหากคุณพยายามทำให้บรรทัดเดียวไม่ใช่การลบรอยหยัก

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

ctx.fillRect(4, 4, 2, 2);
canvas {
  image-rendering: pixelated;
  width: 100px;
  height: 100px; /* Scale 10x */
}
<html>
  <head></head>
  <body>
    <canvas width="10" height="10">Canvas unsupported</canvas>
  </body>
</html>

ฉันยังไม่ได้ทดสอบสิ่งนี้กับเบราว์เซอร์จำนวนมาก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.