Получение цвета пикселя с холста при перемещении мыши

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]);
      };
    }
винс83000
источник
Дубликат stackoverflow.com/questions/1936021/…
bcoughlan

Ответы:

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цикл вместо ifа do...while?
Джонатан Лам
этот ответ помогает мне сегодня (1 сентября 2017 г.) .so +1
Alive to Die
@AlivetoDie # 100! Спасибо :)
Уэйн
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>поверх изображения, это тоже возможно.

Ebragaparah
источник
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);
user889030
источник
Очень полезно +1
Уэйн
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свойство является одномерным 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«сек 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);
};
Slamborne
источник
Это работает только с красными значениями выше 16, например, [10, 42, 67, 255]производит, #a2a43которое не является допустимым / хорошо отформатированным шестнадцатеричным цветовым кодом.
Ti Hausmann
1

У меня есть очень простой рабочий пример получения цвета пикселя с холста.

Сначала немного базового HTML:

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

Затем JS, чтобы нарисовать что-то на холсте и получить цвет:

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 Беркитта ответ хорош. Если вы хотите также извлечь альфа-значение, чтобы получить цвет 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 раз, он будет черным.)

Гален Лонг
источник