รับสีพิกเซลจากแคนวาสบน mousemove


85

เป็นไปได้ไหมที่จะรับพิกเซลค่า RGB ใต้เมาส์? มีตัวอย่างทั้งหมดนี้หรือไม่? นี่คือสิ่งที่ฉันมีจนถึงตอนนี้:

function draw() {
      var ctx = document.getElementById('canvas').getContext('2d');
      var img = new Image();
      img.src = 'Your URL';

      img.onload = function(){
        ctx.drawImage(img,0,0);


      };

      canvas.onmousemove = function(e) {
            var mouseX, mouseY;

            if(e.offsetX) {
                mouseX = e.offsetX;
                mouseY = e.offsetY;
            }
            else if(e.layerX) {
                mouseX = e.layerX;
                mouseY = e.layerY;
            }
            var c = ctx.getImageData(mouseX, mouseY, 1, 1).data;
            
            $('#ttip').css({'left':mouseX+20, 'top':mouseY+20}).html(c[0]+'-'+c[1]+'-'+c[2]);
      };
    }

คำตอบ:


150

นี่คือตัวอย่างที่สมบูรณ์แบบในตัว ขั้นแรกให้ใช้ HTML ต่อไปนี้:

<canvas id="example" width="200" height="60"></canvas>
<div id="status"></div>

จากนั้นวางสี่เหลี่ยมบนผืนผ้าใบด้วยสีพื้นหลังแบบสุ่ม:

var example = document.getElementById('example');
var context = example.getContext('2d');
context.fillStyle = randomColor();
context.fillRect(0, 0, 50, 50);
context.fillStyle = randomColor();
context.fillRect(55, 0, 50, 50);
context.fillStyle = randomColor();
context.fillRect(110, 0, 50, 50);

และพิมพ์แต่ละสีเมื่อวางเมาส์เหนือ:

$('#example').mousemove(function(e) {
    var pos = findPos(this);
    var x = e.pageX - pos.x;
    var y = e.pageY - pos.y;
    var coord = "x=" + x + ", y=" + y;
    var c = this.getContext('2d');
    var p = c.getImageData(x, y, 1, 1).data; 
    var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
    $('#status').html(coord + "<br>" + hex);
});

โค้ดด้านบนถือว่ามี jQuery และฟังก์ชันยูทิลิตี้ต่อไปนี้:

function findPos(obj) {
    var curleft = 0, curtop = 0;
    if (obj.offsetParent) {
        do {
            curleft += obj.offsetLeft;
            curtop += obj.offsetTop;
        } while (obj = obj.offsetParent);
        return { x: curleft, y: curtop };
    }
    return undefined;
}

function rgbToHex(r, g, b) {
    if (r > 255 || g > 255 || b > 255)
        throw "Invalid color component";
    return ((r << 16) | (g << 8) | b).toString(16);
}

function randomInt(max) {
  return Math.floor(Math.random() * max);
}

function randomColor() {
    return `rgb(${randomInt(256)}, ${randomInt(256)}, ${randomInt(256)})`
}

ดูการใช้งานจริงที่นี่:


คุณช่วยตรวจสอบคำถามนี้และดูว่ามีวิธีแก้ไขหรือไม่ ฉันจะขอบคุณเป็นอย่างยิ่ง :)
Khawer Zeshan

การใช้offsetParents ที่ซ้อนกันของคุณเป็นวิธีที่ดีมากในการดำเนินการดังกล่าว ฉันไม่เคยคิดเรื่องนั้นมาก่อน แต่ทำไมคุณไม่ใช้whileลูปปกติแทนifa แล้วdo...whileล่ะ?
Jonathan Lam

คำตอบนี้ช่วยฉันได้ในวันนี้ (1 กันยายน 2017) ดังนั้น +1
Alive to Die

@AlivetoDie # 100! ขอบคุณ :)
Wayne

12

ฉันรู้ว่านี่เป็นคำถามเก่า แต่นี่เป็นทางเลือก ฉันจะเก็บข้อมูลรูปภาพนั้นไว้ในอาร์เรย์จากนั้นในเหตุการณ์เลื่อนเมาส์บนผืนผ้าใบ:

var index = (Math.floor(y) * canvasWidth + Math.floor(x)) * 4
var r = data[index]
var g = data[index + 1]
var b = data[index + 2]
var a = data[index + 3]

ง่ายกว่าการรับ imageData ทุกครั้ง


7

การรวมการอ้างอิงต่างๆที่พบที่นี่ใน StackOverflow (รวมถึงบทความด้านบน) และในไซต์อื่น ๆ ฉันทำเช่นนั้นโดยใช้ javascript และ JQuery:

<html>
<body>
<canvas id="myCanvas" width="400" height="400" style="border:1px solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
<script src="jquery.js"></script>
<script type="text/javascript">
    window.onload = function(){
        var canvas = document.getElementById('myCanvas');
        var context = canvas.getContext('2d');
        var img = new Image();
        img.src = 'photo_apple.jpg';
        context.drawImage(img, 0, 0);
    };

    function findPos(obj){
    var current_left = 0, current_top = 0;
    if (obj.offsetParent){
        do{
            current_left += obj.offsetLeft;
            current_top += obj.offsetTop;
        }while(obj = obj.offsetParent);
        return {x: current_left, y: current_top};
    }
    return undefined;
    }

    function rgbToHex(r, g, b){
    if (r > 255 || g > 255 || b > 255)
        throw "Invalid color component";
    return ((r << 16) | (g << 8) | b).toString(16);
    }

$('#myCanvas').click(function(e){
    var position = findPos(this);
    var x = e.pageX - position.x;
    var y = e.pageY - position.y;
    var coordinate = "x=" + x + ", y=" + y;
    var canvas = this.getContext('2d');
    var p = canvas.getImageData(x, y, 1, 1).data;
    var hex = "#" + ("000000" + rgbToHex(p[0], p[1], p[2])).slice(-6);
    alert("HEX: " + hex);
});
</script>
<img src="photo_apple.jpg"/>
</body>
</html>

นี่คือทางออกที่สมบูรณ์ของฉัน ที่นี่ฉันใช้ผ้าใบและภาพเดียวเท่านั้น แต่ถ้าคุณจำเป็นต้องใช้<map>กับรูปภาพก็เป็นไปได้


5

การเรียก getImageData ทุกครั้งจะทำให้กระบวนการช้าลง ... เพื่อเร่งความเร็วสิ่งต่างๆฉันแนะนำให้เก็บข้อมูลรูปภาพจากนั้นคุณจะได้รับค่า pix ได้อย่างง่ายดายและรวดเร็วดังนั้นทำสิ่งนี้เพื่อประสิทธิภาพที่ดีขึ้น

// keep it global
let imgData = false;  // initially no image data we have

// create some function block 
if(imgData === false){   
  // fetch once canvas data     
  var ctx = canvas.getContext("2d");
  imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
}
    // Prepare your X Y coordinates which you will be fetching from your mouse loc
    let x = 100;   // 
    let y = 100;
    // locate index of current pixel
    let index = (y * imgData.width + x) * 4;

        let red = imgData.data[index];
        let green = imgData.data[index+1];
        let blue = imgData.data[index+2];
        let alpha = imgData.data[index+3];
   // Output
   console.log('pix x ' + x +' y '+y+ ' index '+index +' COLOR '+red+','+green+','+blue+','+alpha);

มีประโยชน์มาก +1
Wayne

4

หากคุณต้องการให้ได้สีเฉลี่ยของพื้นที่สี่เหลี่ยมแทนที่จะเป็นสีของพิกเซลเดียวโปรดดูคำถามอื่นนี้:

👉 JavaScript - รับสีเฉลี่ยจากพื้นที่หนึ่งของภาพ

อย่างไรก็ตามทั้งสองจะทำในลักษณะที่คล้ายกันมาก:

🔍การรับสี / มูลค่าของพิกเซลเดียวจากรูปภาพหรือผ้าใบ

เพื่อให้ได้สีของพิกเซลเดียวก่อนอื่นคุณต้องวาดภาพนั้นลงบนผืนผ้าใบซึ่งคุณได้ทำไปแล้ว:

const image = document.getElementById('image');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const width = image.width;
const height = image.height;

canvas.width = width;
canvas.height = height;

context.drawImage(image, 0, 0, width, height);

จากนั้นรับค่าของพิกเซลเดียวดังนี้:

const data = context.getImageData(X, Y, 1, 1).data;

// RED   = data[0]
// GREEN = data[1]
// BLUE  = data[2]
// ALPHA = data[3]

🚀เร่งความเร็วโดยรับ ImageData ทั้งหมดในครั้งเดียว

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

ImageData ctx.getImageData(sx, sy, sw, sh);
  • sx: พิกัด x ของมุมบนซ้ายของสี่เหลี่ยมผืนผ้าที่จะดึง ImageData
  • sy: พิกัด y ของมุมบนซ้ายของสี่เหลี่ยมผืนผ้าที่จะดึง ImageData
  • sw: ความกว้างของรูปสี่เหลี่ยมผืนผ้าที่จะดึง ImageData
  • sh: ความสูงของรูปสี่เหลี่ยมผืนผ้าที่จะดึง ImageData

คุณสามารถเห็นมันส่งกลับImageDataวัตถุสิ่งที่เป็น ส่วนสำคัญที่นี่คือวัตถุนั้นมี.dataคุณสมบัติที่มีค่าพิกเซลทั้งหมดของเรา

อย่างไรก็ตามโปรดทราบว่า.dataคุณสมบัติคือ 1 มิติUint8ClampedArrayซึ่งหมายความว่าส่วนประกอบทั้งหมดของพิกเซลถูกแบนดังนั้นคุณจะได้รับสิ่งที่มีลักษณะดังนี้:

สมมติว่าคุณมีภาพ 2x2 ดังนี้:

 RED PIXEL |       GREEN PIXEL
BLUE PIXEL | TRANSPARENT PIXEL

จากนั้นคุณจะได้รับสิ่งนี้:

[ 255, 0, 0, 255,    0, 255, 0, 255,    0, 0, 255, 255,    0, 0, 0, 0          ]
|   RED PIXEL   |    GREEN PIXEL   |     BLUE PIXEL   |    TRANSPAERENT  PIXEL |
|   1ST PIXEL   |      2ND PIXEL   |      3RD PIXEL   |             4TH  PIXEL | 

เนื่องจากการโทรgetImageDataเป็นการดำเนินการที่ช้าคุณสามารถเรียกใช้เพียงครั้งเดียวเพื่อรับข้อมูลของภาพทั้งหมด ( sw= ความกว้างของภาพ, sh= ความสูงของภาพ)

จากนั้นในตัวอย่างข้างต้นถ้าคุณต้องการที่จะเข้าถึงส่วนประกอบของTRANSPARENT PIXEL, ที่อยู่, หนึ่งที่ตำแหน่งx = 1, y = 1ของภาพจินตนาการนี้คุณจะพบว่าดัชนีครั้งแรกiในของมันImageData's dataคุณสมบัติดังนี้

const i = (y * imageData.width + x) * 4;

✨มาดูกันเลย

const solidColor = document.getElementById('solidColor');
const alphaColor = document.getElementById('alphaColor');
const solidWeighted = document.getElementById('solidWeighted');

const solidColorCode = document.getElementById('solidColorCode');
const alphaColorCode = document.getElementById('alphaColorCode');
const solidWeightedCOde = document.getElementById('solidWeightedCode');

const brush = document.getElementById('brush');
const image = document.getElementById('image');
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const width = image.width;
const height = image.height;

const BRUSH_SIZE = brush.offsetWidth;
const BRUSH_CENTER = BRUSH_SIZE / 2;
const MIN_X = image.offsetLeft + 4;
const MAX_X = MIN_X + width - 1;
const MIN_Y = image.offsetTop + 4;
const MAX_Y = MIN_Y + height - 1;

canvas.width = width;
canvas.height = height;

context.drawImage(image, 0, 0, width, height);

const imageDataData = context.getImageData(0, 0, width, height).data;

function sampleColor(clientX, clientY) {
  if (clientX < MIN_X || clientX > MAX_X || clientY < MIN_Y || clientY > MAX_Y) {
    requestAnimationFrame(() => {
      brush.style.transform = `translate(${ clientX }px, ${ clientY }px)`;
      solidColorCode.innerText = solidColor.style.background = 'rgb(0, 0, 0)';
      alphaColorCode.innerText = alphaColor.style.background = 'rgba(0, 0, 0, 0.00)';
      solidWeightedCode.innerText = solidWeighted.style.background = 'rgb(0, 0, 0)';
    });
    
    return;
  }
  
  const imageX = clientX - MIN_X;
  const imageY = clientY - MIN_Y;
  
  const i = (imageY * width + imageX) * 4;

  // A single pixel (R, G, B, A) will take 4 positions in the array:
  const R = imageDataData[i];
  const G = imageDataData[i + 1];
  const B = imageDataData[i + 2];
  const A = imageDataData[i + 3] / 255;
  const iA = 1 - A;

  // Alpha-weighted color:
  const wR = (R * A + 255 * iA) | 0;
  const wG = (G * A + 255 * iA) | 0;
  const wB = (B * A + 255 * iA) | 0;

  // Update UI:
  
  requestAnimationFrame(() => {
    brush.style.transform = `translate(${ clientX }px, ${ clientY }px)`;

    solidColorCode.innerText = solidColor.style.background
      = `rgb(${ R }, ${ G }, ${ B })`;

    alphaColorCode.innerText = alphaColor.style.background
      = `rgba(${ R }, ${ G }, ${ B }, ${ A.toFixed(2) })`;

    solidWeightedCode.innerText = solidWeighted.style.background
      = `rgb(${ wR }, ${ wG }, ${ wB })`;
  });
}

document.onmousemove = (e) => sampleColor(e.clientX, e.clientY);
  
sampleColor(MIN_X, MIN_Y);
body {
  margin: 0;
  height: 100vh;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  cursor: none;
  font-family: monospace;
  overflow: hidden;
}

#image {
  border: 4px solid white;
  border-radius: 2px;
  box-shadow: 0 0 32px 0 rgba(0, 0, 0, .25);
  width: 150px;
  box-sizing: border-box;
}

#brush {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  width: 1px;
  height: 1px;
  mix-blend-mode: exclusion;
  border-radius: 100%;
}

#brush::before,
#brush::after {
  content: '';
  position: absolute;
  background: magenta;
}

#brush::before {
  top: -16px;
  left: 0;
  height: 33px;
  width: 100%;
}

#brush::after {
  left: -16px;
  top: 0;
  width: 33px;
  height: 100%;
}

#samples {
  position: relative;
  list-style: none;
  padding: 0;
  width: 250px;
}

#samples::before {
  content: '';
  position: absolute;
  top: 0;
  left: 27px;
  width: 2px;
  height: 100%;
  background: black;
  border-radius: 1px;
}

#samples > li {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-left: 56px;
}

#samples > li + li {
  margin-top: 8px;
}

.sample {
  position: absolute;
  top: 50%;
  left: 16px;
  transform: translate(0, -50%);
  display: block;
  width: 24px;
  height: 24px;
  border-radius: 100%;
  box-shadow: 0 0 16px 4px rgba(0, 0, 0, .25);  
  margin-right: 8px;
}

.sampleLabel {
  font-weight: bold;
  margin-bottom: 8px;
}

.sampleCode {
  
}
<img id="image" src="data:image/gif;base64,R0lGODlhSwBLAPEAACMfIO0cJAAAAAAAACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf4jUmVzaXplZCBvbiBodHRwczovL2V6Z2lmLmNvbS9yZXNpemUAIfkEBQAAAgAsAAAAAEsASwAAAv+Uj6mb4A+QY7TaKxvch+MPKpC0eeUUptdomOzJqnLUvnFcl7J6Pzn9I+l2IdfII8DZiCnYsYdK4qRTptAZwQKRVK71CusOgx2nFRrlhMu+33o2NEalC6S9zQvfi3Mlnm9WxeQ396F2+HcQsMjYGEBRVbhy5yOp6OgIeVIHpEnZyYCZ6cklKBJX+Kgg2riqKoayOWl2+VrLmtDqBptIOjZ6K4qAeSrL8PcmHExsgMs2dpyIxPpKvdhM/YxaTMW2PGr9GP76BN3VHTMurh7eoU14jsc+P845Vn6OTb/P/I68iYOfwGv+JOmRNHBfsV5ujA1LqM4eKDoNvXyDqItTxYX/DC9irKBlIhkKGPtFw1JDiMeS7CqWqySPZcKGHH/JHGgIpb6bCl1O0LmT57yCOqoI5UcU0YKjPXmFjMm0ZQ4NIVdGBdZRi9WrjLxJNMY1Yr4dYeuNxWApl1ALHb+KDHrTV1owlriedJgSr4Cybu/9dFiWYAagsqAGVkkzaZTAuqD9ywKWMUG9dCO3u2zWpVzIhpW122utZlrHnTN+Bq2Mqrlnqh8CQ+0Mrq3Kc++q7eo6dlB3rLuh3abPVbbbI2mxBdhWdsZhid8cr0oy9F08q0k5FXSadiyL1mF5z51a8VsQOp3/LlodkBfzmzWf2bOrtfzr48k/1hupDaLa9rUbO+zlwndfaOCURAXRNaCBqBT2BncJakWfTzSYkmCEFr60RX0V8sKaHOltCBJ1tAAFYhHaVVbig3jxp0IBADs=" >

<div id="brush"></div>

<ul id="samples">
  <li>
    <span class="sample" id="solidColor"></span>
    <div class="sampleLabel">solidColor</div>
    <div class="sampleCode" id="solidColorCode">rgb(0, 0, 0)</div>
  </li>
  <li>
    <span class="sample" id="alphaColor"></span>
    <div class="sampleLabel">alphaColor</div>
    <div class="sampleCode" id="alphaColorCode">rgba(0, 0, 0, 0.00)</div>
  </li>
  <li>
    <span class="sample" id="solidWeighted"></span>
    <div class="sampleLabel">solidWeighted (with white)</div>
    <div class="sampleCode" id="solidWeightedCode">rgb(0, 0, 0)</div>
  </li>
</ul>

⚠️หมายเหตุฉันกำลังใช้ URI ข้อมูลขนาดเล็กเพื่อหลีกเลี่ยงCross-Originปัญหาหากฉันใส่รูปภาพภายนอกหรือคำตอบที่ใหญ่กว่าที่อนุญาตหากฉันพยายามใช้ URI ข้อมูลที่ยาวขึ้น

🕵️สีเหล่านี้ดูแปลก ๆ ใช่ไหม?

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

ในทางกลับกันavgAlphaColorดูเป็นสีชมพู นั่นไม่ใช่ความจริงมันดูเป็นสีชมพูเพราะตอนนี้เราใช้ช่องอัลฟาซึ่งทำให้เป็นแบบกึ่งโปร่งใสและทำให้เราเห็นพื้นหลังของหน้าซึ่งในกรณีนี้จะเป็นสีขาว

🎨สีอัลฟ่าถ่วงน้ำหนัก

แล้วเราจะแก้ไขปัญหานี้ได้อย่างไร? ปรากฎว่าเราต้องใช้ช่องอัลฟาและผกผันเป็นน้ำหนักในการคำนวณส่วนประกอบของตัวอย่างใหม่ของเราในกรณีนี้จะรวมเข้ากับสีขาวเนื่องจากเป็นสีที่เราใช้เป็นพื้นหลัง

นั่นหมายความว่าถ้าพิกเซลอยู่R, G, B, Aที่ไหนAในช่วงเวลา[0, 1]เราจะคำนวณค่าผกผันของช่องอัลฟาiAและส่วนประกอบของตัวอย่างที่ถ่วงน้ำหนักเป็น:

const iA = 1 - A;
const wR = (R * A + 255 * iA) | 0;
const wG = (G * A + 255 * iA) | 0;
const wB = (B * A + 255 * iA) | 0;

สังเกตว่าพิกเซลมีความโปร่งใสมากเพียงใด ( Aใกล้ 0) สีก็จะยิ่งอ่อนลง


3

คำตอบที่รวดเร็ว

context.getImageData(x, y, 1, 1).data;ส่งคืนอาร์เรย์ rgba เช่น[50, 50, 50, 255]


นี่คือเวอร์ชันของฟังก์ชัน rgbToHex ของ @lwburk ที่ใช้อาร์เรย์ rgba เป็นอาร์กิวเมนต์

function rgbToHex(rgb){
  return '#' + ((rgb[0] << 16) | (rgb[1] << 8) | rgb[2]).toString(16);
};

สิ่งนี้ใช้ได้กับค่าสีแดงที่สูงกว่า 16 เท่านั้นเช่น[10, 42, 67, 255]สร้าง#a2a43รหัสสีฐานสิบหกที่ไม่ถูกต้อง / จัดรูปแบบได้ดี
Ti Hausmann

2

คุณสามารถลองสีตัวอย่าง เป็นวิธีง่ายๆในการเลือกสีในผืนผ้าใบ ดูการสาธิต


1

ฉันมีตัวอย่างการทำงานที่ง่ายมากในการรับสีพิกเซลจากผ้าใบ

ขั้นแรก HTML พื้นฐาน:

<canvas id="myCanvas" width="400" height="250" style="background:red;" onmouseover="echoColor(event)">
</canvas>

จากนั้น JS เพื่อวาดบางสิ่งบน Canvas และเพื่อให้ได้สี:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(10, 10, 50, 50);

function echoColor(e){
    var imgData = ctx.getImageData(e.pageX, e.pageX, 1, 1);
    red = imgData.data[0];
    green = imgData.data[1];
    blue = imgData.data[2];
    alpha = imgData.data[3];
    console.log(red + " " + green + " " + blue + " " + alpha);  
}

นี่คือตัวอย่างการทำงานเพียงแค่ดูที่คอนโซล


0

คำตอบของ @Wayne Burkett นั้นดี หากคุณต้องการแยกค่าอัลฟาเพื่อให้ได้สี rgba เราสามารถทำได้:

var r = p[0], g = p[1], b = p[2], a = p[3] / 255;
var rgba = "rgb(" + r + "," + g + "," + b + "," + a + ")";

ฉันแบ่งค่าอัลฟาด้วย 255 เนื่องจากออบเจ็กต์ ImageData เก็บเป็นจำนวนเต็มระหว่าง 0-255 แต่แอปพลิเคชันส่วนใหญ่ (เช่นCanvasRenderingContext2D.fillRect()) ต้องการให้สีอยู่ในรูปแบบ CSS ที่ถูกต้องโดยที่ค่าอัลฟาอยู่ระหว่าง 0 ถึง 1

(โปรดจำไว้ว่าถ้าคุณดึงสีโปร่งใสแล้ววาดกลับลงบนผืนผ้าใบมันจะซ้อนทับสีใดก็ได้ที่มีอยู่ก่อนหน้านี้ดังนั้นหากคุณวาดสีrgba(0,0,0,0.1)ทับจุดเดิม 10 ครั้งมันจะเป็นสีดำ)

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