Holen Sie sich Pixelfarbe von der Leinwand auf Mausbewegung


85

Ist es möglich, das RGB-Wertpixel unter die Maus zu bekommen? Gibt es ein vollständiges Beispiel dafür? Folgendes habe ich bisher:

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

Antworten:


150

Hier ist ein vollständiges, in sich geschlossenes Beispiel. Verwenden Sie zunächst den folgenden HTML-Code:

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

Dann lege ein paar Quadrate mit zufälligen Hintergrundfarben auf die Leinwand:

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

Und drucken Sie jede Farbe per Mouseover aus:

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

Der obige Code setzt das Vorhandensein von jQuery und den folgenden Dienstprogrammfunktionen voraus:

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

Sehen Sie es hier in Aktion:


Können Sie bitte diese Frage untersuchen und herausfinden, ob es dafür eine Lösung gibt? Ich werde das sehr schätzen :)
Khawer Zeshan

Ihre Verwendung von verschachtelten offsetParents ist ein wirklich guter Weg, um dies zu erreichen. Daran habe ich noch nie gedacht. Aber warum verwenden Sie nicht eine reguläre whileSchleife anstelle von ifund dann a do...while?
Jonathan Lam

Diese Antwort hilft mir heute (1. September 2017) .so +1
Lebendig zu sterben

@AlivetoDie # 100! Danke :)
Wayne

12

Ich weiß, dass dies eine alte Frage ist, aber hier ist eine Alternative. Ich würde diese Bilddaten dann in einem Array speichern, wenn ich das Ereignis mit der Maus über die Leinwand bewege:

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]

Viel einfacher als jedes Mal die imageData abzurufen.


7

Durch das Zusammenführen verschiedener Referenzen, die hier in StackOverflow (einschließlich des obigen Artikels) und auf anderen Websites gefunden wurden, habe ich Javascript und JQuery verwendet:

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

Dies ist meine Komplettlösung. Hier habe ich nur Leinwand und ein Bild verwendet, aber wenn Sie <map>über dem Bild verwenden müssen, ist es auch möglich.


5

Wenn Sie getImageData jedes Mal aufrufen, wird der Prozess verlangsamt. Um die Dinge zu beschleunigen, empfehle ich, Bilddaten zu speichern. Dann können Sie den Pix-Wert einfach und schnell abrufen. Tun Sie also etwas Ähnliches, um eine bessere Leistung zu erzielen

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

Sehr hilfreich +1
Wayne

3

Schnelle Antwort

context.getImageData(x, y, 1, 1).data;Gibt ein RGBA-Array zurück. z.B[50, 50, 50, 255]


Hier ist eine Version der rgbToHex-Funktion von @ lwburk, die das rgba-Array als Argument verwendet.

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

Dies funktioniert nur mit rot-Werten über 16, zum Beispiel [10, 42, 67, 255]erzeugt , #a2a43die kein gültiger / gut formatierte hex Farbcode ist.
Ti Hausmann

3

Wenn Sie die durchschnittliche Farbe eines rechteckigen Bereichs anstelle der Farbe eines einzelnen Pixels erhalten möchten, werfen Sie bitte einen Blick auf diese andere Frage:

👉 JavaScript - Ermittelt die durchschnittliche Farbe aus einem bestimmten Bereich eines Bildes

Wie auch immer, beide werden auf sehr ähnliche Weise gemacht:

🔍 Abrufen der Farbe / des Werts eines einzelnen Pixels aus einem Bild oder einer Leinwand

Um die Farbe eines einzelnen Pixels zu erhalten, zeichnen Sie dieses Bild zunächst auf eine Leinwand, was Sie bereits getan haben:

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

Und dann erhalten Sie den Wert eines einzelnen Pixels wie folgt:

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

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

🚀 Die Geschwindigkeit wird verringert, indem alle ImageData auf einmal abgerufen werden

Sie müssen dasselbe CanvasRenderingContext2D.getImageData () verwenden , um die Werte des gesamten Bilds abzurufen. Dies geschieht durch Ändern der dritten und vierten Parameter. Die Signatur dieser Funktion lautet:

ImageData ctx.getImageData(sx, sy, sw, sh);
  • sx: Die x-Koordinate der oberen linken Ecke des Rechtecks, aus dem die ImageData extrahiert werden.
  • sy: Die y-Koordinate der oberen linken Ecke des Rechtecks, aus dem die ImageData extrahiert werden.
  • sw: Die Breite des Rechtecks, aus dem die ImageData extrahiert werden.
  • sh: Die Höhe des Rechtecks, aus dem die ImageData extrahiert werden.

Sie können sehen, dass es ein ImageDataObjekt zurückgibt , was auch immer das ist . Der wichtige Teil hier ist, dass dieses Objekt eine .dataEigenschaft hat, die alle unsere Pixelwerte enthält.

Beachten Sie jedoch, dass die .dataEigenschaft eine 1-Dimension Uint8ClampedArrayist. Dies bedeutet, dass alle Komponenten des Pixels abgeflacht wurden, sodass Sie Folgendes erhalten:

Angenommen, Sie haben ein 2x2-Bild wie dieses:

 RED PIXEL |       GREEN PIXEL
BLUE PIXEL | TRANSPARENT PIXEL

Dann erhalten Sie sie wie folgt:

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

Da das Aufrufen getImageDataein langsamer Vorgang ist, können Sie ihn nur einmal aufrufen, um die Daten aller Bilder abzurufen ( sw= Bildbreite, sh= Bildhöhe).

Dann im obigen Beispiel, wenn Sie die Komponenten des zugreifen wollen TRANSPARENT PIXEL, das heißt, die man an der Position x = 1, y = 1dieses imaginären Bildes, würden Sie ihr erstes Index finden iin seiner ImageData‚s - dataEigenschaft als:

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

✨ Lassen Sie es uns in Aktion sehen

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>

⚠️ Hinweis: Ich verwende einen URI für kleine Daten, um Cross-OriginProbleme zu vermeiden , wenn ich ein externes Bild oder eine Antwort einbinde , die größer als zulässig ist, wenn ich versuche, einen URI für längere Daten zu verwenden.

🕵️ Diese Farben sehen komisch aus, nicht wahr?

Wenn Sie den Cursor um die Ränder der Sternchenform bewegen, wird manchmal avgSolidColorrot angezeigt, aber das Pixel, das Sie abtasten, sieht weiß aus. Das liegt daran, Rdass der Alpha-Kanal niedrig ist, obwohl die Komponente für dieses Pixel möglicherweise hoch ist. Die Farbe ist also tatsächlich ein fast transparenter Rotton, avgSolidColorignoriert dies jedoch.

Auf der anderen Seite avgAlphaColorsieht rosa aus. Nun, das stimmt eigentlich nicht, es sieht nur rosa aus, weil wir jetzt den Alphakanal verwenden, der halbtransparent ist und es uns ermöglicht, den Hintergrund der Seite zu sehen, der in diesem Fall weiß ist.

🎨 Alpha-gewichtete Farbe

Was können wir dann tun, um dies zu beheben? Nun, es stellt sich heraus, dass wir nur den Alphakanal und seine Umkehrung als Gewichtung verwenden müssen, um die Komponenten unserer neuen Stichprobe zu berechnen. In diesem Fall wird sie mit Weiß zusammengeführt, da dies die Farbe ist, die wir als Hintergrund verwenden.

Das heißt, wenn ein Bildpunkt ist R, G, B, A, wo Ain dem Intervall [0, 1], werden wir die Inverse des Alphakanal berechnen, iAund die Komponenten des gewichteten Probe als:

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;

Beachten Sie, dass Adie Farbe umso heller ist, je transparenter ein Pixel ist ( näher an 0).


2

Sie können den Farb-Sampler ausprobieren . Es ist eine einfache Möglichkeit, Farbe auf einer Leinwand auszuwählen. Siehe Demo .


1

Ich habe ein sehr einfaches Arbeitsbeispiel für das Abrufen von Pixelfarben von der Leinwand.

Zuerst einige grundlegende HTML:

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

Dann JS, um etwas auf die Leinwand zu zeichnen und Farbe zu bekommen:

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

Hier ist ein funktionierendes Beispiel , schauen Sie sich einfach die Konsole an.


0

@ Wayne Burketts Antwort ist gut. Wenn Sie auch den Alpha-Wert extrahieren möchten, um eine RGBA-Farbe zu erhalten, können Sie dies tun:

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

Ich habe den Alpha-Wert durch 255 geteilt, da das ImageData-Objekt ihn als Ganzzahl zwischen 0 und 255 speichert. In den meisten Anwendungen (zum Beispiel CanvasRenderingContext2D.fillRect()) müssen Farben jedoch im gültigen CSS-Format vorliegen, wobei der Alpha-Wert zwischen 0 und 1 liegt.

(Denken Sie auch daran, dass beim Extrahieren einer transparenten Farbe und anschließendes Zurückzeichnen auf die Leinwand die zuvor vorhandene Farbe überlagert wird. Wenn Sie die Farbe also rgba(0,0,0,0.1)zehnmal über denselben Punkt gezeichnet haben, ist sie schwarz.)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.