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;
🚀 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;
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>
⚠️ 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.