Obtener color de píxeles del lienzo, en mousemove

85

¿Es posible obtener el píxel de valor RGB con el mouse? ¿Existe un ejemplo completo de esto? Esto es lo que tengo hasta ahora:

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

Respuestas:

150

Aquí hay un ejemplo completo e independiente. Primero, use el siguiente HTML:

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

Luego coloque algunos cuadrados en el lienzo con colores de fondo aleatorios:

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

E imprima cada color al pasar el mouse por encima:

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

El código anterior asume la presencia de jQuery y las siguientes funciones de utilidad:

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

Véalo en acción aquí:

Wayne
fuente
¿Puede analizar esta pregunta y ver si hay una solución para eso? Lo agradeceré mucho :)
Khawer Zeshan
El uso de offsetParents anidados es una forma realmente agradable de hacerlo. Nunca pensé en eso. Pero, ¿por qué no usas un whilebucle regular en lugar de un ify luego un do...while?
Jonathan Lam
esta respuesta me ayuda hoy (1 de septiembre de 2017) .so +1
Alive to Die
@AlivetoDie # 100! Gracias :)
Wayne
12

Sé que esta es una pregunta antigua, pero aquí hay una alternativa. Almacenaría esos datos de imagen en una matriz, luego, en el evento de movimiento del mouse sobre el lienzo:

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]

Mucho más fácil que obtener imageData cada vez.

Caio Vertematti
fuente
7

Combinando varias referencias encontradas aquí en StackOverflow (incluido el artículo anterior) y en otros sitios, lo hice usando javascript y 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>

Esta es mi solución completa. Aquí solo usé lienzo y una imagen, pero si necesita usar <map>sobre la imagen, también es posible.

ebragaparah
fuente
5

llamar a getImageData cada vez ralentizará el proceso ... para acelerar las cosas, recomiendo almacenar datos de imagen y luego puede obtener el valor de imagen fácil y rápidamente, así que haga algo como esto para un mejor rendimiento

// 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
fuente
Muy útil +1
Wayne
3

Respuesta rápida

context.getImageData(x, y, 1, 1).data;devuelve una matriz rgba. p.ej[50, 50, 50, 255]


Aquí hay una versión de la función rgbToHex de @ lwburk que toma la matriz rgba como argumento.

function rgbToHex(rgb){
  return '#' + ((rgb[0] << 16) | (rgb[1] << 8) | rgb[2]).toString(16);
};
Slamborne
fuente
Esto solo funciona con valores de rojo por encima de 16, por ejemplo, [10, 42, 67, 255]produce #a2a43un código de color hexadecimal válido / bien formateado.
Ti Hausmann
3

Si necesita obtener el color promedio de un área rectangular, en lugar del color de un solo píxel, eche un vistazo a esta otra pregunta:

👉 JavaScript: obtenga un color promedio de un área determinada de una imagen

De todos modos, ambos se hacen de forma muy similar:

🔍 Obtener el color / valor de un solo píxel de una imagen o lienzo

Para obtener el color de un solo píxel, primero debe dibujar esa imagen en un lienzo, lo que ya ha hecho:

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

Y luego obtenga el valor de un solo píxel como este:

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

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

🚀 Acelerar se reduce al obtener todos los ImageData a la vez

Necesita usar este mismo CanvasRenderingContext2D.getImageData () para obtener los valores de toda la imagen, lo que hace cambiando su tercer y cuarto parámetro. La firma de esa función es:

ImageData ctx.getImageData(sx, sy, sw, sh);
  • sx: La coordenada x de la esquina superior izquierda del rectángulo del que se extraerán los datos de imagen.
  • sy: La coordenada y de la esquina superior izquierda del rectángulo del que se extraerán los ImageData.
  • sw: El ancho del rectángulo del que se extraerán los ImageData.
  • sh: La altura del rectángulo del que se extraerán los datos de imagen.

Puede ver que devuelve un ImageDataobjeto, sea ​​lo que sea . La parte importante aquí es que ese objeto tiene una .datapropiedad que contiene todos nuestros valores de píxeles.

Sin embargo, tenga en cuenta que la .datapropiedad es de una dimensión Uint8ClampedArray, lo que significa que todos los componentes del píxel se han aplanado, por lo que obtiene algo que se parece a esto:

Digamos que tienes una imagen de 2x2 como esta:

 RED PIXEL |       GREEN PIXEL
BLUE PIXEL | TRANSPARENT PIXEL

Entonces, los obtendrás así:

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

Como llamar getImageDataes una operación lenta, puede llamarla solo una vez para obtener los datos de toda la imagen ( sw= ancho de imagen, sh= alto de imagen).

Entonces, en el ejemplo anterior, si desea acceder a los componentes de la TRANSPARENT PIXEL, es decir, el que está en la posición x = 1, y = 1de esta imagen imaginaria, que encontraría su primer índice ien su ImageData's datapropiedad como:

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

✨ Veámoslo en acción

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>

⚠️ Tenga en cuenta que estoy usando un URI de datos pequeño para evitar Cross-Originproblemas si incluyo una imagen externa o una respuesta más grande de lo permitido si trato de usar un URI de datos más largo.

🕵️ Estos colores se ven raros, ¿no?

Si mueve el cursor alrededor de los bordes de la forma de asterisco, verá que a veces avgSolidColores rojo, pero el píxel que está muestreando parece blanco. Esto se debe a que, aunque el Rcomponente de ese píxel puede ser alto, el canal alfa es bajo, por lo que el color es en realidad un tono rojo casi transparente, pero lo avgSolidColorignora.

Por otro lado, avgAlphaColorluce rosa. Bueno, eso en realidad no es cierto, solo se ve rosa porque ahora estamos usando el canal alfa, lo que lo hace semitransparente y nos permite ver el fondo de la página, que en este caso es blanco.

🎨 Color ponderado alfa

Entonces, ¿qué podemos hacer para solucionar este problema? Bueno, resulta que solo necesitamos usar el canal alfa y su inverso como pesos para calcular los componentes de nuestra nueva muestra, en este caso fusionándolo con el blanco, ya que ese es el color que usamos como fondo.

Eso significa que si un píxel es R, G, B, A, dónde Aestá en el intervalo [0, 1], calcularemos la inversa del canal alfa iA, y los componentes de la muestra ponderada como:

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;

Tenga en cuenta que cuanto más transparente es un píxel ( Amás cerca de 0), más claro es el color.

Danziger
fuente
1

Tengo un ejemplo práctico muy simple de cómo obtener el color de los píxeles del lienzo.

Primero algo de HTML básico:

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

Luego JS para dibujar algo en el Canvas y obtener color:

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

Aquí hay un ejemplo de trabajo , solo mire la consola.

Damjan Pavlica
fuente
0

La respuesta de @Wayne Burkett es buena. Si también quisiera extraer el valor alfa para obtener un color rgba, podríamos hacer esto:

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

Dividí el valor alfa por 255 porque el objeto ImageData lo almacena como un número entero entre 0-255, pero la mayoría de las aplicaciones (por ejemplo, CanvasRenderingContext2D.fillRect()) requieren que los colores estén en formato CSS válido, donde el valor alfa está entre 0 y 1.

(Recuerde también que si extrae un color transparente y luego lo vuelve a dibujar en el lienzo, se superpondrá al color que haya anteriormente. Por lo tanto, si dibujó el color rgba(0,0,0,0.1)sobre el mismo lugar 10 veces, sería negro).

Galen Long
fuente