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;
🚀 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 ImageData
Objekt zurückgibt , was auch immer das ist . Der wichtige Teil hier ist, dass dieses Objekt eine .data
Eigenschaft hat, die alle unsere Pixelwerte enthält.
Beachten Sie jedoch, dass die .data
Eigenschaft eine 1-Dimension Uint8ClampedArray
ist. 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 getImageData
ein 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 = 1
dieses imaginären Bildes, würden Sie ihr erstes Index finden i
in seiner ImageData
‚s - data
Eigenschaft 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;
const R = imageDataData[i];
const G = imageDataData[i + 1];
const B = imageDataData[i + 2];
const A = imageDataData[i + 3] / 255;
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;
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-Origin
Probleme 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 avgSolidColor
rot angezeigt, aber das Pixel, das Sie abtasten, sieht weiß aus. Das liegt daran, R
dass 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, avgSolidColor
ignoriert dies jedoch.
Auf der anderen Seite avgAlphaColor
sieht 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 A
in dem Intervall [0, 1]
, werden wir die Inverse des Alphakanal berechnen, iA
und 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 A
die Farbe umso heller ist, je transparenter ein Pixel ist ( näher an 0).