Nhận màu pixel từ canvas, trên di chuột


85

Có thể lấy pixel giá trị RGB dưới chuột không? Có một ví dụ đầy đủ về điều này? Đây là những gì tôi có cho đến nay:

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]);
      };
    }

Câu trả lời:


150

Đây là một ví dụ hoàn chỉnh, khép kín. Đầu tiên, hãy sử dụng HTML sau:

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

Sau đó, đặt một số hình vuông trên canvas với màu nền ngẫu nhiên:

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);

Và in từng màu khi di chuột qua:

$('#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);
});

Đoạn mã trên giả định sự hiện diện của jQuery và các hàm tiện ích sau:

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)})`
}

Nhìn thấy nó trong hành động ở đây:


Bạn có thể vui lòng xem xét câu hỏi này và xem nếu có giải pháp cho điều đó. Tôi sẽ đánh giá cao điều đó :)
Khawer Zeshan

Việc bạn sử dụng các offsetParents lồng nhau là một cách thực sự tốt để thực hiện điều đó. Tôi chưa bao giờ nghĩ về điều đó. Nhưng tại sao bạn không sử dụng một whilevòng lặp thông thường thay vì một ifvà sau đó là a do...while?
Jonathan Lam

câu trả lời này giúp tôi ngày hôm nay (1-September-2017) .so 1
Alive to Die

@AlivetoDie # 100! Cảm ơn :)
Wayne

12

Tôi biết đây là một câu hỏi cũ, nhưng đây là một câu hỏi thay thế. Tôi sẽ lưu trữ dữ liệu hình ảnh đó trong một mảng, sau đó, với sự kiện di chuyển chuột trên canvas:

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]

Dễ dàng hơn rất nhiều so với việc lấy imageData mọi lúc.


7

Hợp nhất các tài liệu tham khảo khác nhau được tìm thấy ở đây trong StackOverflow (bao gồm cả bài viết ở trên) và trong các trang web khác, tôi đã làm như vậy bằng cách sử dụng javascript và 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>

Đây là giải pháp hoàn chỉnh của tôi. Ở đây tôi chỉ sử dụng canvas và một hình ảnh, nhưng nếu bạn cần sử dụng <map>trên hình ảnh, nó cũng có thể.


5

gọi getImageData mỗi lần sẽ làm chậm quá trình ... để tăng tốc mọi thứ, tôi khuyên bạn nên lưu trữ dữ liệu hình ảnh và sau đó bạn có thể nhận giá trị pix một cách dễ dàng và nhanh chóng, vì vậy hãy làm điều gì đó như thế này để có hiệu suất tốt hơn

// 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);

Rất hữu ích +1
Wayne

3

Câu trả lời nhanh

context.getImageData(x, y, 1, 1).data;trả về một mảng rgba. ví dụ[50, 50, 50, 255]


Đây là phiên bản của hàm rgbToHex của @ lwburk lấy mảng rgba làm đối số.

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

Điều này chỉ hoạt động với các giá trị màu đỏ trên 16, chẳng hạn như [10, 42, 67, 255]tạo ra #a2a43không phải là mã màu hex hợp lệ / được định dạng tốt.
Ti Hausmann

3

Nếu bạn cần lấy màu trung bình của một khu vực hình chữ nhật, thay vì màu của một pixel, hãy xem câu hỏi khác sau:

👉 JavaScript - Nhận màu trung bình từ một vùng nhất định của hình ảnh

Dù sao, cả hai đều được thực hiện theo cách rất giống nhau:

🔍 Nhận màu sắc / giá trị của một điểm ảnh từ một hình ảnh hoặc canvas

Để có được màu sắc của một pixel, trước tiên bạn sẽ vẽ hình ảnh đó vào canvas, bạn đã thực hiện:

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);

Và sau đó lấy giá trị của một pixel như sau:

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

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

🚀 Tăng tốc độ bằng cách lấy tất cả dữ liệu hình ảnh cùng một lúc

Bạn cần sử dụng cùng CanvasRenderingContext2D.getImageData () này để nhận các giá trị của toàn bộ hình ảnh, bạn thực hiện bằng cách thay đổi tham số thứ ba và thứ tư của nó. Chữ ký của hàm đó là:

ImageData ctx.getImageData(sx, sy, sw, sh);
  • sx: Tọa độ x của góc trên bên trái của hình chữ nhật mà từ đó ImageData sẽ được trích xuất.
  • sy: Tọa độ y của góc trên bên trái của hình chữ nhật mà từ đó ImageData sẽ được trích xuất.
  • sw: Chiều rộng của hình chữ nhật mà từ đó ImageData sẽ được trích xuất.
  • sh: Chiều cao của hình chữ nhật mà từ đó ImageData sẽ được trích xuất.

Bạn có thể thấy nó trả về một ImageDatađối tượng, bất kể đó là gì . Phần quan trọng ở đây là đối tượng đó có thuộc .datatính chứa tất cả các giá trị pixel của chúng ta.

Tuy nhiên, lưu ý rằng thuộc .datatính là 1 chiều Uint8ClampedArray, có nghĩa là tất cả các thành phần của pixel đã được làm phẳng, vì vậy bạn sẽ nhận được một cái gì đó giống như sau:

Giả sử bạn có một hình ảnh 2x2 như sau:

 RED PIXEL |       GREEN PIXEL
BLUE PIXEL | TRANSPARENT PIXEL

Sau đó, bạn sẽ nhận được chúng như thế này:

[ 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 | 

Vì gọi getImageDatalà một hoạt động chậm, bạn có thể gọi nó chỉ một lần để lấy dữ liệu của tất cả hình ảnh ( sw= chiều rộng hình ảnh, sh= chiều cao hình ảnh).

Sau đó, trong ví dụ trên, nếu bạn muốn truy cập các thành phần của TRANSPARENT PIXEL, có nghĩa là, là ở vị trí x = 1, y = 1của hình ảnh tưởng tượng này, bạn sẽ tìm thấy chỉ số đầu tiên của mình itrong nó ImageData's datatài sản như sau:

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

✨ Hãy xem nó trong hành động

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>

⚠️ Lưu ý rằng tôi đang sử dụng một URI dữ liệu nhỏ để tránh Cross-Origincác sự cố nếu tôi bao gồm hình ảnh bên ngoài hoặc câu trả lời lớn hơn mức cho phép nếu tôi cố gắng sử dụng URI dữ liệu dài hơn.

🕵️ Những màu này trông thật lạ phải không?

Nếu bạn di chuyển con trỏ xung quanh các đường viền của hình hoa thị, bạn sẽ thấy đôi khi avgSolidColorcó màu đỏ, nhưng pixel bạn đang lấy mẫu trông có màu trắng. Đó là bởi vì mặc dù Rthành phần cho pixel đó có thể cao, nhưng kênh alpha lại thấp, vì vậy màu thực sự là một màu đỏ gần như trong suốt, nhưng hãy avgSolidColorbỏ qua điều đó.

Mặt khác, avgAlphaColortrông màu hồng. Chà, điều đó thực sự không đúng, nó chỉ trông có màu hồng bởi vì chúng tôi hiện đang sử dụng kênh alpha, kênh này làm cho nó nửa trong suốt và cho phép chúng tôi nhìn thấy nền của trang, trong trường hợp này là màu trắng.

🎨 Màu có trọng số alpha

Sau đó, chúng ta có thể làm gì để khắc phục điều này? Chà, hóa ra chúng ta chỉ cần sử dụng kênh alpha và nghịch đảo của nó làm trọng số để tính toán các thành phần của mẫu mới của chúng ta, trong trường hợp này là kết hợp nó với màu trắng, vì đó là màu chúng ta sử dụng làm nền.

Điều đó có nghĩa rằng nếu một pixel R, G, B, A, nơi Alà trong khoảng thời gian [0, 1], chúng tôi sẽ tính toán nghịch đảo của kênh alpha, iAvà các thành phần của mẫu trọng như:

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;

Lưu ý rằng pixel càng trong suốt ( Acàng gần 0) thì màu càng nhạt.



1

Tôi có một ví dụ làm việc rất đơn giản về lấy màu pixel từ canvas.

Đầu tiên một số HTML cơ bản:

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

Sau đó, JS để vẽ thứ gì đó trên Canvas và lấy màu:

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);  
}

Đây là một ví dụ hoạt động , chỉ cần nhìn vào bảng điều khiển.


0

Câu trả lời của @Wayne Burkett là tốt. Nếu bạn cũng muốn trích xuất giá trị alpha để có màu rgba, chúng tôi có thể làm như sau:

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

Tôi chia giá trị alpha cho 255 vì đối tượng ImageData lưu trữ nó dưới dạng số nguyên từ 0 - 255, nhưng hầu hết các ứng dụng (ví dụ CanvasRenderingContext2D.fillRect():) đều yêu cầu màu ở định dạng CSS hợp lệ, trong đó giá trị alpha nằm trong khoảng từ 0 đến 1.

(Cũng nên nhớ rằng nếu bạn trích xuất một màu trong suốt và sau đó vẽ nó trở lại canvas, nó sẽ phủ lên bất kỳ màu nào có trước đó. Vì vậy, nếu bạn vẽ màu rgba(0,0,0,0.1)trên cùng một điểm 10 lần, nó sẽ là màu đen.)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.