Если вам нужно получить средний цвет прямоугольной области, а не цвет одного пикселя, взгляните на другой вопрос:
👉 JavaScript - получить средний цвет из определенной области изображения
Во всяком случае, оба сделаны очень похоже:
🔍 Получение цвета / значения одного пикселя из изображения или холста
Чтобы получить цвет одного пикселя, вы сначала должны нарисовать это изображение на холсте, что вы уже сделали:
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);
А затем получите значение одного пикселя следующим образом:
const data = context.getImageData(X, Y, 1, 1).data;
🚀 Ускорение работы за счет одновременного получения всех данных ImageData
Вам нужно использовать тот же CanvasRenderingContext2D.getImageData (), чтобы получить значения всего изображения, что вы делаете, изменяя его третий и четвертый параметры. Сигнатура этой функции:
ImageData ctx.getImageData(sx, sy, sw, sh);
sx: Координата x левого верхнего угла прямоугольника, из которого будут извлечены данные ImageData.
sy: Координата Y верхнего левого угла прямоугольника, из которого будут извлечены данные ImageData.
sw: Ширина прямоугольника, из которого будут извлечены данные ImageData.
sh: Высота прямоугольника, из которого будут извлечены данные ImageData.
Вы можете увидеть , он возвращает ImageDataобъект, что бы это . Важная часть здесь заключается в том, что этот объект имеет.data свойство, которое содержит все наши значения пикселей.
Однако обратите внимание, что .dataсвойство является одномерным Uint8ClampedArray, что означает, что все компоненты пикселя сглажены, поэтому вы получаете что-то похожее на это:
Допустим, у вас есть изображение 2x2, подобное этому:
RED PIXEL | GREEN PIXEL
BLUE PIXEL | TRANSPARENT PIXEL
Тогда вы получите их вот так:
[ 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 |
Поскольку вызов getImageDataявляется медленной операцией, вы можете вызвать его только один раз, чтобы получить данные всего изображения ( sw= ширина изображения, sh= высота изображения).
Затем, в приведенном выше примере, если вы хотите получить доступ к компонентам TRANSPARENT PIXEL, то есть, один в положении x = 1, y = 1этого воображаемого образом, вы найдете свой первый индекс iв его ImageData«сек dataсобственности как:
const i = (y * imageData.width + x) * 4;
✨ Посмотрим на это в действии
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>
⚠️ Обратите внимание, что я использую небольшой URI данных, чтобы избежать Cross-Originпроблем, если я включаю внешнее изображение или ответ, размер которого превышает допустимый, если я пытаюсь использовать более длинный URI данных.
🕵️ Эти цвета выглядят странно, правда?
Если вы переместите курсор вокруг границ звездочки, вы иногда увидите avgSolidColor он красный, но пиксель, который вы выбираете, выглядит белым. Это потому, что даже если Rкомпонент для этого пикселя может быть высоким, альфа-канал низкий, поэтому цвет на самом деле почти прозрачный оттенок красного, ноavgSolidColor игнорирует это.
С другой стороны, avgAlphaColor выглядит розовым. Ну, на самом деле это неправда, он просто выглядит розовым, потому что теперь мы используем альфа-канал, что делает его полупрозрачным и позволяет нам видеть фон страницы, который в данном случае белый.
🎨 Альфа-взвешенный цвет
Тогда что мы можем сделать, чтобы это исправить? Что ж, оказывается, нам просто нужно использовать альфа-канал и его инверсию в качестве весов для вычисления компонентов нашего нового образца, в данном случае объединяя его с белым, так как этот цвет мы используем в качестве фона.
Это означает, что если пиксель равен R, G, B, A, где Aнаходится в интервале [0, 1], мы будем вычислять инверсию альфа-канала iA, и компоненты взвешенной выборки как:
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;
Обратите внимание: чем прозрачнее пиксель ( Aближе к 0), тем светлее цвет.