Uzyskaj kolor piksela z płótna, na myszy

85

Czy można uzyskać piksel wartości RGB pod myszką? Czy jest na to pełny przykład? Oto, co mam do tej pory:

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
źródło

Odpowiedzi:

150

Oto kompletny, samodzielny przykład. Najpierw użyj następującego kodu HTML:

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

Następnie umieść na płótnie kilka kwadratów z losowymi kolorami tła:

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

I wydrukuj każdy kolor po najechaniu myszą:

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

Powyższy kod zakłada obecność jQuery i następujących funkcji narzędziowych:

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

Zobacz to w akcji tutaj:

Wayne
źródło
Czy możesz przyjrzeć się temu pytaniu i sprawdzić, czy jest na to rozwiązanie. Będę bardzo wdzięczny :)
Khawer Zeshan
Twoje użycie zagnieżdżonych offsetParents to naprawdę dobry sposób na zrobienie tego. Nigdy o tym nie myślałem. Ale dlaczego nie użyjesz zwykłej whilepętli zamiast ifa potem a do...while?
Jonathan Lam
ta odpowiedź pomaga mi dzisiaj (1 września 2017 r.). więc +1
Alive to Die
@AlivetoDie # 100! Dzięki :)
Wayne
12

Wiem, że to stare pytanie, ale mam alternatywę. Przechowałbym dane obrazu w tablicy, a następnie zdarzenie ruchu myszy nad płótnem:

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]

O wiele łatwiejsze niż pobieranie imageData za każdym razem.

Caio Vertematti
źródło
7

Łącząc różne odniesienia znalezione tutaj w StackOverflow (w tym artykuł powyżej) i na innych stronach, zrobiłem to za pomocą javascript i 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>

To jest moje kompletne rozwiązanie. Tutaj użyłem tylko płótna i jednego obrazu, ale jeśli chcesz użyć <map>go na obrazie, jest to również możliwe.

ebragaparah
źródło
5

wywoływanie getImageData za każdym razem spowolni proces ... aby przyspieszyć działanie, zalecam przechowywanie danych obrazu, a następnie łatwo i szybko uzyskasz wartość pix, więc zrób coś takiego dla lepszej wydajności

// 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
źródło
Bardzo pomocne +1
Wayne
4

Jeśli chcesz uzyskać średni kolor prostokątnego obszaru, a nie kolor pojedynczego piksela, spójrz na to drugie pytanie:

👉 JavaScript - uzyskaj średni kolor z określonego obszaru obrazu

Zresztą oba są wykonywane w bardzo podobny sposób:

🔍 Pobieranie koloru / wartości pojedynczego piksela z obrazu lub płótna

Aby uzyskać kolor pojedynczego piksela, najpierw narysowałbyś ten obraz na płótnie, co już zrobiłeś:

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

A następnie uzyskaj wartość pojedynczego piksela w następujący sposób:

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

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

🚀 Przyspieszenie tempa dzięki jednoczesnemu pobieraniu wszystkich danych obrazu

Musisz użyć tej samej CanvasRenderingContext2D.getImageData (), aby uzyskać wartości całego obrazu, co robisz, zmieniając jego trzeci i czwarty parametr. Podpis tej funkcji to:

ImageData ctx.getImageData(sx, sy, sw, sh);
  • sx: Współrzędna x lewego górnego rogu prostokąta, z którego zostaną wyodrębnione ImageData.
  • sy: Współrzędna y lewego górnego rogu prostokąta, z którego zostaną wyodrębnione ImageData.
  • sw: Szerokość prostokąta, z którego zostaną wyodrębnione ImageData.
  • sh: Wysokość prostokąta, z którego zostaną wyodrębnione ImageData.

Możesz zobaczyć, że zwraca ImageDataobiekt, cokolwiek to jest . Ważną częścią jest to, że ten obiekt ma .datawłaściwość, która zawiera wszystkie nasze wartości pikseli.

Należy jednak pamiętać, że .datawłaściwość jest 1-wymiarem Uint8ClampedArray, co oznacza, że ​​wszystkie komponenty piksela zostały spłaszczone, więc otrzymujesz coś, co wygląda tak:

Powiedzmy, że masz taki obraz 2x2:

 RED PIXEL |       GREEN PIXEL
BLUE PIXEL | TRANSPARENT PIXEL

Następnie otrzymasz je w ten sposób:

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

Ponieważ wywołanie getImageDatajest powolną operacją, możesz wywołać je tylko raz, aby uzyskać dane całego obrazu ( sw= szerokość obrazu, sh= wysokość obrazu).

Następnie, w powyższym przykładzie, jeśli chcesz uzyskać dostęp do części składowych TRANSPARENT PIXEL, czyli jeden na pozycji x = 1, y = 1tego wyimaginowanego obrazu, to znajdzie swój pierwszy indeks iw swojej ImageData„s datanieruchomości jako:

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

✨ Zobaczmy to w akcji

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>

⚠️ Uwaga Używam małego identyfikatora URI danych, aby uniknąć Cross-Originproblemów, jeśli dołączę obraz zewnętrzny lub odpowiedź, która jest większa niż dozwolona, ​​jeśli próbuję użyć dłuższego identyfikatora URI danych.

🕵️ Te kolory wyglądają dziwnie, prawda?

Jeśli przesuniesz kursor wokół krawędzi kształtu gwiazdki, czasami zobaczysz, że avgSolidColorjest czerwony, ale piksel, który próbkujesz, wygląda na biały. Dzieje się tak, ponieważ nawet jeśli Rskładnik tego piksela może być wysoki, kanał alfa jest niski, więc kolor jest w rzeczywistości prawie przezroczystym odcieniem czerwieni, ale avgSolidColorignoruje to.

Z drugiej strony avgAlphaColorwygląda na różowo. Cóż, to właściwie nieprawda, wygląda po prostu na różowo, ponieważ teraz używamy kanału alfa, co sprawia, że ​​jest półprzezroczysty i pozwala nam zobaczyć tło strony, które w tym przypadku jest białe.

🎨 Kolor ważony alfa

Co w takim razie możemy zrobić, aby to naprawić? Cóż, okazuje się, że musimy użyć kanału alfa i jego odwrotności jako wag do obliczenia składników naszej nowej próbki, w tym przypadku scalając ją z bielą, ponieważ tego koloru używamy jako tła.

Oznacza to, że jeśli piksel jest R, G, B, A, gdzie Ajest w interwale [0, 1], obliczymy odwrotność kanału alfa iA, a składowe ważonej próbki jako:

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;

Zwróć uwagę, że im bardziej przezroczysty piksel ( Abliżej 0), tym jaśniejszy kolor.

Danziger
źródło
3

Szybka odpowiedź

context.getImageData(x, y, 1, 1).data;zwraca tablicę rgba. na przykład[50, 50, 50, 255]


Oto wersja funkcji rgbToHex @ lwburka, która przyjmuje tablicę rgba jako argument.

function rgbToHex(rgb){
  return '#' + ((rgb[0] << 16) | (rgb[1] << 8) | rgb[2]).toString(16);
};
slamborne
źródło
Działa to tylko z czerwonymi wartościami powyżej 16, na przykład [10, 42, 67, 255]tworzy, #a2a43który nie jest prawidłowym / dobrze sformatowanym kodem szesnastkowym.
Ti Hausmann
1

Mam bardzo prosty przykład działania pobierania koloru piksela z płótna.

Najpierw trochę podstawowego HTML:

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

Następnie JS, aby narysować coś na płótnie i uzyskać kolor:

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

Oto działający przykład , wystarczy spojrzeć na konsolę.

Damjan Pavlica
źródło
0

@Wayne Burkitta odpowiedź jest dobra. Jeśli chcesz również wyodrębnić wartość alfa, aby uzyskać kolor rgba, możemy zrobić to:

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

Podzieliłem wartość alfa przez 255, ponieważ obiekt ImageData przechowuje ją jako liczbę całkowitą od 0 do 255, ale większość aplikacji (na przykład CanvasRenderingContext2D.fillRect()) wymaga , aby kolory były w prawidłowym formacie CSS, gdzie wartość alfa wynosi od 0 do 1.

(Pamiętaj też, że jeśli wyodrębnisz przezroczysty kolor, a następnie narysujesz go z powrotem na płótnie, pokryje on dowolny kolor, który był tam wcześniej. Więc jeśli narysujesz kolor rgba(0,0,0,0.1)na tym samym miejscu 10 razy, będzie on czarny).

Galen Long
źródło