Nếu bạn cần lấy màu trung bình của một khu vực hình chữ nhật, thay vì màu của một pixel, hãy xem câu hỏi khác sau:
👉 JavaScript - Nhận màu trung bình từ một vùng nhất định của hình ảnh
Dù sao, cả hai đều được thực hiện theo cách rất giống nhau:
🔍 Nhận màu sắc / giá trị của một điểm ảnh từ một hình ảnh hoặc canvas
Để có được màu sắc của một pixel, trước tiên bạn sẽ vẽ hình ảnh đó vào canvas, bạn đã thực hiện:
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);
Và sau đó lấy giá trị của một pixel như sau:
const data = context.getImageData(X, Y, 1, 1).data;
🚀 Tăng tốc độ bằng cách lấy tất cả dữ liệu hình ảnh cùng một lúc
Bạn cần sử dụng cùng CanvasRenderingContext2D.getImageData () này để nhận các giá trị của toàn bộ hình ảnh, bạn thực hiện bằng cách thay đổi tham số thứ ba và thứ tư của nó. Chữ ký của hàm đó là:
ImageData ctx.getImageData(sx, sy, sw, sh);
sx
: Tọa độ x của góc trên bên trái của hình chữ nhật mà từ đó ImageData sẽ được trích xuất.
sy
: Tọa độ y của góc trên bên trái của hình chữ nhật mà từ đó ImageData sẽ được trích xuất.
sw
: Chiều rộng của hình chữ nhật mà từ đó ImageData sẽ được trích xuất.
sh
: Chiều cao của hình chữ nhật mà từ đó ImageData sẽ được trích xuất.
Bạn có thể thấy nó trả về một ImageData
đối tượng, bất kể đó là gì . Phần quan trọng ở đây là đối tượng đó có thuộc .data
tính chứa tất cả các giá trị pixel của chúng ta.
Tuy nhiên, lưu ý rằng thuộc .data
tính là 1 chiều Uint8ClampedArray
, có nghĩa là tất cả các thành phần của pixel đã được làm phẳng, vì vậy bạn sẽ nhận được một cái gì đó giống như sau:
Giả sử bạn có một hình ảnh 2x2 như sau:
RED PIXEL | GREEN PIXEL
BLUE PIXEL | TRANSPARENT PIXEL
Sau đó, bạn sẽ nhận được chúng như thế này:
[ 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 |
Vì gọi getImageData
là một hoạt động chậm, bạn có thể gọi nó chỉ một lần để lấy dữ liệu của tất cả hình ảnh ( sw
= chiều rộng hình ảnh, sh
= chiều cao hình ảnh).
Sau đó, trong ví dụ trên, nếu bạn muốn truy cập các thành phần của TRANSPARENT PIXEL
, có nghĩa là, là ở vị trí x = 1, y = 1
của hình ảnh tưởng tượng này, bạn sẽ tìm thấy chỉ số đầu tiên của mình i
trong nó ImageData
's data
tài sản như sau:
const i = (y * imageData.width + x) * 4;
✨ Hãy xem nó trong hành động
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>
⚠️ Lưu ý rằng tôi đang sử dụng một URI dữ liệu nhỏ để tránh Cross-Origin
các sự cố nếu tôi bao gồm hình ảnh bên ngoài hoặc câu trả lời lớn hơn mức cho phép nếu tôi cố gắng sử dụng URI dữ liệu dài hơn.
🕵️ Những màu này trông thật lạ phải không?
Nếu bạn di chuyển con trỏ xung quanh các đường viền của hình hoa thị, bạn sẽ thấy đôi khi avgSolidColor
có màu đỏ, nhưng pixel bạn đang lấy mẫu trông có màu trắng. Đó là bởi vì mặc dù R
thành phần cho pixel đó có thể cao, nhưng kênh alpha lại thấp, vì vậy màu thực sự là một màu đỏ gần như trong suốt, nhưng hãy avgSolidColor
bỏ qua điều đó.
Mặt khác, avgAlphaColor
trông màu hồng. Chà, điều đó thực sự không đúng, nó chỉ trông có màu hồng bởi vì chúng tôi hiện đang sử dụng kênh alpha, kênh này làm cho nó nửa trong suốt và cho phép chúng tôi nhìn thấy nền của trang, trong trường hợp này là màu trắng.
🎨 Màu có trọng số alpha
Sau đó, chúng ta có thể làm gì để khắc phục điều này? Chà, hóa ra chúng ta chỉ cần sử dụng kênh alpha và nghịch đảo của nó làm trọng số để tính toán các thành phần của mẫu mới của chúng ta, trong trường hợp này là kết hợp nó với màu trắng, vì đó là màu chúng ta sử dụng làm nền.
Điều đó có nghĩa rằng nếu một pixel R, G, B, A
, nơi A
là trong khoảng thời gian [0, 1]
, chúng tôi sẽ tính toán nghịch đảo của kênh alpha, iA
và các thành phần của mẫu trọng như:
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;
Lưu ý rằng pixel càng trong suốt ( A
càng gần 0) thì màu càng nhạt.