ฉันสามารถปิดการลบรอยหยักในองค์ประกอบ <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.