Obtenha a cor do pixel da tela, no movimento do mouse

85

É possível obter o pixel de valor RGB sob o mouse? Existe um exemplo completo disso? Aqui está o que tenho até agora:

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
fonte

Respostas:

150

Aqui está um exemplo completo e independente. Primeiro, use o seguinte HTML:

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

Em seguida, coloque alguns quadrados na tela com cores de fundo aleatórias:

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 cor ao passar o mouse:

$('#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 código acima pressupõe a presença de jQuery e as seguintes funções de utilitário:

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

Veja em ação aqui:

Wayne
fonte
Você pode, por favor, olhar para esta questão e ver se há solução para isso. Eu aprecio muito isso :)
Khawer Zeshan
O uso de offsetParents aninhados é uma maneira muito boa de fazer isso. Nunca pensei nisso. Mas por que você não usa um whileloop regular em vez de um ife depois um do...while?
Jonathan Lam
esta resposta me ajuda hoje (1 de setembro de 2017) .so +1
Alive to Die
@AlivetoDie # 100! Obrigado :)
Wayne
12

Eu sei que esta é uma pergunta antiga, mas aqui está uma alternativa. Eu armazenaria os dados da imagem em uma matriz e, em seguida, no evento de movimento do mouse sobre a tela:

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]

Muito mais fácil do que obter o imageData sempre.

Caio Vertematti
fonte
7

Mesclando várias referências encontradas aqui no StackOverflow (incluindo o artigo acima) e em outros sites, fiz isso usando javascript e 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 é minha solução completa. Aqui usei apenas canvas e uma imagem, mas se precisar usar <map>sobre a imagem também é possível.

Ebragaparah
fonte
5

chamar getImageData todas as vezes tornará o processo mais lento ... para acelerar coisas, eu recomendo armazenar dados de imagem e então você pode obter valor de pix facilmente e rapidamente, então faça algo assim para melhor desempenho

// 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
fonte
Muito útil +1
Wayne
4

Se você precisar obter a cor média de uma área retangular, em vez da cor de um único pixel, dê uma olhada nesta outra questão:

👉 JavaScript - Obtenha a cor média de uma determinada área de uma imagem

De qualquer forma, ambos são feitos de maneira muito semelhante:

🔍 Obtendo a cor / valor de um único pixel de uma imagem ou tela

Para obter a cor de um único pixel, você deve primeiro desenhar essa imagem em uma tela, o que você já fez:

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

E então obtenha o valor de um único pixel como este:

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

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

🚀 Acelerar afina obtendo todos os ImageData de uma só vez

Você precisa usar este mesmo CanvasRenderingContext2D.getImageData () para obter os valores de toda a imagem, o que você faz alterando seu terceiro e quarto parâmetros. A assinatura dessa função é:

ImageData ctx.getImageData(sx, sy, sw, sh);
  • sx: A coordenada x do canto superior esquerdo do retângulo do qual os ImageData serão extraídos.
  • sy: A coordenada y do canto superior esquerdo do retângulo do qual o ImageData será extraído.
  • sw: A largura do retângulo do qual o ImageData será extraído.
  • sh: A altura do retângulo do qual o ImageData será extraído.

Você pode ver que ele retorna um ImageDataobjeto, seja ele qual for . A parte importante aqui é que esse objeto tem um.data propriedade que contém todos os nossos valores de pixel.

No entanto, observe que a .datapropriedade é de 1 dimensão Uint8ClampedArray, o que significa que todos os componentes do pixel foram achatados, então você está obtendo algo parecido com isto:

Digamos que você tenha uma imagem 2x2 como esta:

 RED PIXEL |       GREEN PIXEL
BLUE PIXEL | TRANSPARENT PIXEL

Então, você os obterá assim:

[ 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 a chamada getImageDataé uma operação lenta, você pode chamá-la apenas uma vez para obter os dados de toda a imagem ( sw= largura da imagem, sh= altura da imagem).

Então, no exemplo acima, se você quiser acessar os componentes do TRANSPARENT PIXEL, isto é, aquele em posição x = 1, y = 1desta imagem imaginário, você iria encontrar o seu primeiro índice iem seu ImageData's datapropriedade como:

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

✨ Vamos ver em ação

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>

⚠️ Observação Estou usando um URI de dados pequeno para evitar Cross-Originproblemas se incluir uma imagem externa ou uma resposta maior do que o permitido se eu tentar usar um URI de dados mais longo.

🕵️ Essas cores parecem estranhas, não é?

Se você mover o cursor ao redor das bordas da forma de asterisco, verá que às vezes avgSolidColoré vermelho, mas o pixel da amostra parece branco. Isso porque, embora o Rcomponente para aquele pixel possa ser alto, o canal alfa é baixo, então a cor é na verdade um tom quase transparente de vermelho, masavgSolidColor ignora isso.

Por outro lado, avgAlphaColorparece rosa. Bem, isso não é verdade, só parece rosa porque agora estamos usando o canal alfa, o que o torna semitransparente e nos permite ver o fundo da página, que neste caso é branco.

🎨 Cor alfa ponderada

Então, o que podemos fazer para consertar isso? Bem, acontece que só precisamos usar o canal alfa e seu inverso como pesos para calcular os componentes de nossa nova amostra, neste caso mesclando-o com o branco, já que essa é a cor que usamos como fundo.

Isso significa que se um pixel está R, G, B, A, onde Aestá no intervalo [0, 1], calcularemos o inverso do canal alfa iA, e os componentes da amostra 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;

Observe como quanto mais transparente é um pixel ( Amais próximo de 0), mais clara é a cor.

Danziger
fonte
3

Resposta rápida

context.getImageData(x, y, 1, 1).data;retorna uma matriz rgba. por exemplo[50, 50, 50, 255]


Aqui está uma versão da função rgbToHex de @lwburk que recebe o array rgba como argumento.

function rgbToHex(rgb){
  return '#' + ((rgb[0] << 16) | (rgb[1] << 8) | rgb[2]).toString(16);
};
Slamborne
fonte
Isso só funciona com valores de vermelho acima de 16, por exemplo, [10, 42, 67, 255]produz #a2a43que não é um código de cor hexadecimal válido / bem formatado.
Ti Hausmann
2

Você pode experimentar o classificador de cores . É uma maneira fácil de escolher a cor em uma tela. Veja a demonstração .

emn178
fonte
1

Eu tenho um exemplo de trabalho muito simples de obter a cor de pixel da tela.

Primeiro, algum HTML básico:

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

Em seguida, JS para desenhar algo na tela e obter cor:

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

Aqui está um exemplo prático , basta olhar para o console.

Damjan Pavlica
fonte
0

A resposta de @Wayne Burkett é boa. Se você quiser também extrair o valor alfa para obter uma cor rgba, poderíamos fazer isso:

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

Dividi o valor alfa por 255 porque o objeto ImageData o armazena como um número inteiro entre 0 e 255, mas a maioria dos aplicativos (por exemplo, CanvasRenderingContext2D.fillRect()) exige que as cores estejam em formato CSS válido, em que o valor alfa está entre 0 e 1.

(Lembre-se também de que se você extrair uma cor transparente e, em seguida, desenhá-la de volta na tela, ela se sobreporá a qualquer cor que esteja lá anteriormente. Portanto, se você desenhou a cor rgba(0,0,0,0.1)sobre o mesmo ponto 10 vezes, seria preto.)

Galen Long
fonte