I'm pretty new to canvas and haven't worked with it before but I thought it would be a good fit for the following task. While working on it I got doubts and I still don't know if the task is even possible to implement using canvas.
Exemplary graphic of the masks and images and the result that I want to achieve (and the actual results that I got).
The outlines are just there to better illustrate the images
dimensions.
The masks are SVG images which are preloaded using promises before
they are drawn and they change per iteration. So on the first
iteration it's mask A for image 1 and on the second iteration mask
B for image 2.
Simplified pseudo code example:
const items = [1, 2];
for (let i = 0; i < items.length; i++) {
ctx.drawImage(preloadedMask[i], x, y, canvasWidth, canvasHeight);
ctx.globalCompositeOperation = 'source-in';
img[i] = new Image();
img[i].onload = () => {
ctx.drawImage(img[i], 0, 0, canvasWidth, canvasHeight);
ctx.globalCompositeOperation = 'source-over';
//ctx.globalCompositeOperation = 'source-out';
};
img[i].src = `images/${i+1}.jpg`;
}
When I remove the globalCompositeOperation and the images the masks are perfectly drawn next to each other like I expected.
But as soon as I add a globalCompositeOperation it gets complicated and I am super confused to be honest.
I tried every possible globalCompositeOperation value in the onload callback - but it doesn't change much. I think I have to change the globalCompositeOperation after the mask is drawn for each iteration to a different value - but I am out of ideas.
Is there any way to achieve my desired output as described in the graphic or should I ditch canvas for this task?
What you're trying to achieve isn't that easy unfortunately - at least if you're using SVGs which are treated as images and directly drawn to the canvas.
Suppose we have the following svg masks and images
If we take the first mask and the first image and use the following code:
context.drawImage(maskA,0,0,width,height);
context.globalCompositeOperation = "source-in";
context.drawImage(imageA,0,0,width,height);
we get the desired output:
If we repeat the process and do the same for the second mask:
context.drawImage(maskB,0,0,width,height);
context.globalCompositeOperation = "source-in";
context.drawImage(imageB,0,0,width,height);
we'll just see an empty canvas. Why? We set globalCompositeOperation to 'source-in' and the previous canvas and the second mask (maskB) don't have any overlapping regions. That means we're effectively erasing the canvas.
If we try to compensate and either save/restore the context or reset globalCompositeOperation to it's initial state
context.save();
context.drawImage(maskA,0,0,width,height);
context.globalCompositeOperation = "source-in";
context.drawImage(imageA,0,0,width,height);
context.restore();
context.drawImage(maskB,0,0,width,height);
context.globalCompositeOperation = "source-in";
context.drawImage(imageB,0,0,width,height);
we still don't succeed:
So the trick here is this:
make sure both the svgs and images to be masked are fully loaded
create a new empty canvas the size of your target canvas
draw the first mask onto the new canvas
set it's globalCompositeOperation to 'source-in'
draw the first image onto the new canvas
draw the new canvas to the target canvas
erase the new canvas and repeat the previous steps to compose your final image
Here's an example (just click 'Run code snippet'):
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
let imagesLoaded = 0;
let imageA = document.getElementById("imageA");
let imageB = document.getElementById("imageB");
let width = canvas.width;
let height = canvas.height;
function loaded() {
imagesLoaded++;
if (imagesLoaded == 4) {
let tempCanvas = document.createElement("canvas");
let tempContext = tempCanvas.getContext("2d");
tempCanvas.width = width;
tempCanvas.height = height;
tempContext.save();
tempContext.drawImage(document.getElementById("semiCircleA"), 0, 0, width, height);
tempContext.globalCompositeOperation = "source-in";
tempContext.drawImage(imageA, 0, 0, width, 160);
ctx.drawImage(tempCanvas, 0, 0, width, height);
tempContext.restore();
tempContext.clearRect(0, 0, width, height);
tempContext.drawImage(document.getElementById("semiCircleB"), 0, 0, width, height);
tempContext.globalCompositeOperation = "source-in";
tempContext.drawImage(imageB, 0, 0, width, height);
ctx.drawImage(tempCanvas, 0, 0, width, height);
}
}
document.getElementById("semiCircleA").onload = loaded;
document.getElementById("semiCircleB").onload = loaded;
imageA.onload = loaded;
imageA.src = "https://picsum.photos/id/237/160/160";
imageB.onload = loaded;
imageB.src = "https://picsum.photos/id/137/160/160";
<h1>Final Canvas</h1>
<canvas id="canvas" width=160 height=160>
</canvas>
<br>
<h1>Sources</h1>
<img id="semiCircleA" src='data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="160px" height="160px">
<path d="M80,0 A80,80 0 0,0 80,160"/>
</svg>'>
<img id="semiCircleB" src='data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="160px" height="160px">
<path d="M80,0 A80,80 0 0,1 80,160"/>
</svg>'>
<img id="imageA">
<img id="imageB">
A canvas can be a layer
The canvas like any element is easy to create and can be treated like an image, or if you are familiar with photoshop, a canvas can be a layer.
To create a blank canvas
// Returns the renderable image (canvas)
function CreateImage(width, height) {
return Object.assign(document.createElement("canvas"), {width, height});
}
To copy a canvas or image like object
// Image can be any image like element including canvas. Returns the renderable image
function CopyImage(img, width = img.width, height = img.height, smooth = true) {
const can = createImage(width, height});
can.ctx = can.getContext("2d");
can.ctx.imageSmoothingEnabled = smooth;
can.ctx.drawImage(img, 0, 0, width, height);
return can;
}
Loading
Never load images in a render loop. The image onload event will not respect the order you assign the src. Thus the rendering of images in onload will not always be in the order you wish.
Load all images and wait before rendering.
An example of loading a set of images. The function loadImages returns a promise that will resolve when all images have loaded.
const images = {
maskA: "imageUrl",
maskB: "imageUrl",
imgA: "imageUrl",
imgB: "imageUrl",
};
function loadImages(imgList, data) {
return new Promise((done, loadingError) => {
var count = 0;
const imgs = Object.entries();
for (const [name, src] of imgs) {
imgList[name] = new Image;
imgList[name].src = src;
count ++;
imgList[name].addEventListener("load", () => {
count--;
if (count === 0) { done({imgs: imgList, data}) }
}, {once, true)
);
imgList[name].addEventListener("error", () => {
for (const [name, src] of imgs) { imgList[name] = src }
loadingError(new Error("Could not load all images"));
}, {once, true)
);
}
});
}
Rendering
It is best to create functions to do repeated tasks. One task you are repeating is masking, the following function uses a canvas as a destination, an image, and a mask
function maskImage(ctx, img, mask, x = 0, y = 0, w = ctx.canvas.height, h = ctx.canvas.width, clear = true) {
ctx.globalCompositeOperation = "source-over";
clear && ctx.clearRect(0, 0, ctx.canvas.height, ctx.canvas.width);
ctx.drawImage(img, x, y, w, h);
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(mask, 0, 0, w, h);
return ctx.canvas; // return the renderable image
}
Once you have some utilities set up to help coordinate the loading and rendering you can composite your finial result
// assumes ctx is the context to render to
loadImages(images, {ctx}).then(({imgs, {ctx}} => {
const w = ctx.canvas.width, h = ctx.canvas.height;
ctx.clearRect(0, 0, w, h);
const layer = copyImage(ctx.canvas);
ctx.drawImage(maskImage(layer.ctx, imgs.imgA, imgs.maskA), 0, 0, w, h);
ctx.drawImage(maskImage(layer.ctx, imgs.imgB, imgs.maskB), 0, 0, w, h);
// if you no longer need the images always remove them from memory to avoid hogging
// client's resources.
imgs = {}; // de-reference images so that GC can clean up.
}
You can now layer as many masked images as needed. Because functions where created for each sub task it is easy to create more complicated rendering without needing to write verbose and repeated code, in both this project and future projects.
Related
In a html canvas, I am trying to generate a drop shadow on an image with transparent pieces in it. This image is generated by code and then drawn to the canvas using: ctx.putImageData(dst, 0, 0)
The problem is that the following code is not generating any shadow:
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 15;
ctx.shadowColor = 'rgba(0,0,0,1)';
ctx.putImageData(dst, 0, 0);
Any help would be appreciated
ctx.putImageData() will replace the pixels in your context with the ones contained in the ImageData that you puts.
There is no context's property like shadowBlur, nor filter, nor globalCompositeOperation, nor even matrix tranforms that will affect it. Even transparent pixels in your ImageData will be transparent in the context.
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'salmon';
ctx.fillRect(0,0,300,150);
ctx.translate(120, 50);
ctx.rotate(Math.PI/3);
ctx.translate(-25, -25);
ctx.filter = 'blur(5px)';
ctx.globalCompositeOperation = 'lighter';
ctx.fillStyle = '#0000FF';
ctx.fillRect(0,0,50,50);
setTimeout(() => {
// at this time, all previous filters, transform, gCO are still active
const bluerect = ctx.createImageData(50,50);
const data = new Uint32Array(bluerect.data.buffer);
data.fill(0xFFFF0000); // blue
ctx.putImageData(bluerect, 0, 0); // same as our previous fillRect();
// a transparent ImageData (smaller)
const transrect = ctx.createImageData(25, 25);
ctx.putImageData(transrect, 170, 50); // push a bit farther;
}, 1500);
body {
background: lightblue;
}
<canvas id="canvas"></canvas>
So, how to deal with an ImageData and still be able to apply the context's properties on it?
Go through a second off-screen canvas, on which you will put your ImageData, and that you will then draw on your main canvas. drawImage accepts an HTMLCanvasElement as source, and it is affected by context properties like shadowBlur:
const ctx = canvas.getContext('2d');
ctx.shadowBlur = 12;
ctx.shadowColor = "red";
// our ImageData
const bluerect = ctx.createImageData(50,50);
const data = new Uint32Array(bluerect.data.buffer);
data.fill(0xFFFF0000); // blue
// create a new canvas, the size of our ImageData
const offscreen = document.createElement('canvas');
offscreen.width = bluerect.width;
offscreen.height = bluerect.height;
// put our ImageData on it
offscreen.getContext('2d')
.putImageData(bluerect, 0, 0);
// draw it on main canvas
ctx.drawImage(offscreen, 50, 50);
<canvas id="canvas"></canvas>
Now, new browsers have also the ability to do it without the use of a second browser, by generating an ImageBitmap from the ImageData, but this operation is asynchronous, so you may still prefer the old way.
const ctx = canvas.getContext('2d');
ctx.shadowBlur = 12;
ctx.shadowColor = "red";
// our ImageData
const bluerect = ctx.createImageData(50,50);
const data = new Uint32Array(bluerect.data.buffer);
data.fill(0xFFFF0000); // blue
// create an ImageBitmap from our ImageData
createImageBitmap(bluerect)
.then(bitmap => { // later
ctx.drawImage(bitmap, 50, 50);
});
<canvas id="canvas"></canvas>
I'm writing a "TV filter" (you know the kind, RGB bars as it zooms in), for a video file and I've been having a look at some ways of shrinking the image that retains as much detail as possible.
For testing I'm drawing the sampled image back to the screen to see the quality - in the actual filter, I'll just be sampling pixels and getting the RGB values of the resultant computed color.
I've tried three, and the Hermite filter looks good, but compared to the speed "hardware" nearest neighbour version, it's not going to be suitable for processing video.
Is there any "tricks" in JavaScript that can be used to get accelerated image shrinking like 2, but with a quality like 1 or 3?
1: Brute force: http://codepen.io/SarahC/pen/VpvWvb?editors=1010
2: Internal nearest neighbour: http://codepen.io/SarahC/pen/ryeQgN?editors=1010
3: Hermite filter: http://codepen.io/SarahC/pen/ryMNWZ?editors=1010
Here's the "hardware"? version:
function processResize(percent) {
var size = percent * 0.01;
var sw = canvas.width * size;
var sh = canvas.height * size;
ctx.drawImage(canvas2, 0, 0, sw, sh);
ctx.drawImage(canvas, 0, 0, sw, sh, 0, 0, w, h);
}
I am not entirely sure from the description what you try to achieve, but from the codepens it seems as you try to create a mosaic effect.
You can use the built-in interpolation setting of the canvas context to use nearest-neighbor by turning image smoothing off, then draw the image to a small size representing how many "blocks" you want. Then draw back that version to full size again:
// blocks = initial number of pixels (video aspect is usually 16:9 so you may want
// to calculate a separate values for height.
var blocks = 24;
// draw initial size representing "blocks"
ctx.drawImage(video, 0, 0, blocks, blocks);
// turn off image smoothing (see below for prefixing)
// This uses nearest neighbor
ctx.imageSmoothingEnabled = false;
// enlarge the mosaic back to full size
ctx.drawImage(c, 0, 0, blocks, blocks, 0, 0, c.width, c.height);
Video Example
(the video may take a few seconds to load...)
var ctx = null;
var blocks = 24;
var video = document.createElement("video");
video.preload = "auto"; video.muted = video.autoplay = video.loop = true;
video.oncanplay = function() { // initialize for demo
if (!ctx) {
c.width = this.videoWidth;
c.height = this.videoHeight;
ctx = c.getContext("2d");
document.querySelector("input").oninput = function() {blocks = +this.value};
requestAnimationFrame(loop);
}
}
video.src = "//media.w3.org/2010/05/sintel/trailer.mp4";
function smoothing(state) {
ctx.oImageSmoothingEnabled = ctx.msImageSmoothingEnabled =
ctx.mozImageSmoothingEnabled = ctx.webkitImageSmoothingEnabled =
ctx.imageSmoothingEnabled = state;
}
function loop() {
smoothing(true); // improve quality of first step
ctx.drawImage(video, 0, 0, blocks, blocks);
smoothing(false); // mosaic step
ctx.drawImage(c, 0, 0, blocks, blocks, 0, 0, c.width, c.height);
// loop and throttle to 30 fps
requestAnimationFrame(function() {requestAnimationFrame(loop)});
}
<label>Blocks: <input type=range min=8 max=128 value=24></label><br>
<canvas id=c></canvas>
I am new to canvas, I have an image myimg.jpg, I have converted this image into canvas and i am trying to apply some pattern image for heel.
I am not able to do it. Here is my screenshot:
How can I get it done.
<div id="myId">
<canvas id="canvaswrapper" width="660" height="540"></canvas>
</div>
function drawImage(){
var ctx = $("canvas")[0].getContext("2d"),
img = new Image();
img.onload = function(){
ctx.drawImage(img, 0, 0, 500, 500);
ctx.beginPath();
var img2= new Image();
var w;
var h;
img2.src = "http://www.gravatar.com/avatar/e555bd971bc2f4910893cd5b785c30ff?s=128&d=identicon&r=PG";
var pattern = ctx.createPattern(img2, "repeat");
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, w, h);
ctx.arc(300,305,50,0,2*Math.PI);
ctx.fill();
ctx.stroke();
};
img.src = "myimg.jpg";
}
drawImage();
You can define the area you want to fill using an image mask that fits on top of your image - this step is something for Photoshop/GIMP.
For example, having your shoe as-is:
Create a mask for it leaving the heal in the original position (it makes it easier to draw it back in - you can always crop it and draw it using an offset instead). Important: background must be transparent:
Then super-impose the pattern using these steps:
Load the pattern and define is as a fill-pattern
Draw the mask into the empty canvas
Optional step: Adjust transformations if needed (translate, scale)
Choose composite mode "source-atop"
Fill the canvas
Choose composite mode "destination-atop"
Draw the main image on top (which will show behind the mask/pattern)
Optional step: draw in original mask image using blending mode "multiply" to add shadow and highlights (does not work in IE). This will help creating an illusion of depth. For IE, drawing it on top using a reduced alpha or a separate image only containing shadows etc. can be an option
Result
Example
var iShoe = new Image, iMask = new Image, iPatt = new Image, count = 3;
iShoe.onload = iMask.onload = iPatt.onload = loader;
iShoe.src = "http://i.stack.imgur.com/hqL1C.png";
iMask.src = "http://i.stack.imgur.com/k5XWN.png";
iPatt.src = "http://i.stack.imgur.com/CEQ10.png";
function loader() {
if (--count) return; // wait until all images has loaded
var ctx = document.querySelector("canvas").getContext("2d"),
pattern = ctx.createPattern(iPatt, "repeat");
// draw in mask
ctx.drawImage(iMask, 0, 0);
// change comp mode
ctx.globalCompositeOperation = "source-atop";
// fill mask
ctx.scale(0.5, 0.5); // scale: 0.5
ctx.fillStyle = pattern; // remember to double the area to fill:
ctx.fillRect(0, 0, ctx.canvas.width*2, ctx.canvas.height*2);
ctx.setTransform(1,0,0,1,0,0); // reset transform
// draw shoe behind mask
ctx.globalCompositeOperation = "destination-atop";
ctx.drawImage(iShoe, 0, 0);
// to make it more realistic, add mask in blend mode (does not work in IE):
ctx.globalCompositeOperation = "multiply";
if (ctx.globalCompositeOperation === "multiply") {
ctx.drawImage(iMask, 0, 0);
}
}
<canvas width=281 height=340></canvas>
Here's what I'm trying to do:
Get image A, and image B. Image B is a black and white mask image.
Replace image A's alpha channel with image B's red channel.
Draw image C on the canvas.
Draw image A on top of image C.
Everything seems ok until step 4. Image C isn't visible at all and where image A should be transparent there's white color.
cx.putImageData(imageA, 0, 0);
var resultData = cx.getImageData(0, 0, view.width, view.height);
for (var h=0; h<resultData.data.length; h+=4) {
resultData.data[h+3] = imageB.data[h];
}
cx.putImageData(imageC, 0, 0);
cx.putImageData(resultData, 0, 0);
Simon is right: the putImageData method does not pay any attention to compositing; it merely copies pixel values. In order to get compositing, we need to use drawing operations.
We need to mess with the channels (turn red into alpha) with the pixel data, put that changed pixel data into an image, and then use a composite operation to get the desired masking.
//copy from one channel to another
var assignChannel = function(imageData, channelTo, channelFrom) {
if(channelTo < 0 || channelTo > 3 || channelFrom < 0 || channelFrom > 3) {
throw new Error("bad channel number");
}
if(channelTo == channelFrom)
return;
var px = imageData.data;
for(var i = 0; i < px.length; i += 4) {
px[i + channelTo] = px[i + channelFrom];
}
};
/**============================================================================
* this function uses 3 or 4 canvases for clarity / pedagogical reasons:
* redCanvas has our mask image;
* maskCanvas will be used to store the alpha channel conversion of redCanvas' image;
* imageCanvas contains the image to be masked;
* ctx is the context of the canvas to which the masked image will be drawn.
============================================================================**/
var drawOnTopOfRed = function(redCanvas, maskCanvas, imageCanvas, ctx) {
var redImageData = redCanvas.getContext("2d").getImageData(0, 0, w, h);
//assign the alpha channel
assignChannel(redImageData, 3, 0);
//write the mask image
maskCanvas.getContext("2d").putImageData(redImageData, 0, 0);
ctx.save();
//draw the mask
ctx.globalCompositeOperation = "copy";
ctx.drawImage(maskCanvas, 0, 0);
//draw the image to be masked, but only where both it
//and the mask are opaque; see http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#compositing for details.
ctx.globalCompositeOperation = "source-in";
ctx.drawImage(imageCanvas, 0, 0);
ctx.restore();
};
jsfiddle example
A doodle with the example:
Because in step 4 you are using putImageData which perfectly replaces pixels. You want to draw image A on top of image C, so you can't do this. Instead you will want to use drawImage()
So do:
cx.putImageData(imageC, 0, 0); // step 3
// create a new canvas and new context,
// call that new context ctx2 and canvas can2:
var can2 = document.createElement('canvas');
// set can2's width and height, get the context etc...
ctx2.putImageData(resultData, 0, 0);
cx.drawImage(can2, 0, 0); // step 4 using drawImage instead of putting image data
Is there any way to check if a selected (x,y) point of a PNG image is transparent?
Building on Jeff's answer, your first step would be to create a canvas representation of your PNG. The following creates an off-screen canvas that is the same width and height as your image and has the image drawn on it.
var img = document.getElementById('my-image');
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
After that, when a user clicks, use event.offsetX and event.offsetY to get the position. This can then be used to acquire the pixel:
var pixelData = canvas.getContext('2d').getImageData(event.offsetX, event.offsetY, 1, 1).data;
Because you are only grabbing one pixel, pixelData is a four entry array containing the pixel's R, G, B, and A values. For alpha, anything less than 255 represents some level of transparency with 0 being fully transparent.
Here is a jsFiddle example: http://jsfiddle.net/thirtydot/9SEMf/869/ I used jQuery for convenience in all of this, but it is by no means required.
Note: getImageData falls under the browser's same-origin policy to prevent data leaks, meaning this technique will fail if you dirty the canvas with an image from another domain or (I believe, but some browsers may have solved this) SVG from any domain. This protects against cases where a site serves up a custom image asset for a logged in user and an attacker wants to read the image to get information. You can solve the problem by either serving the image from the same server or implementing Cross-origin resource sharing.
Canvas would be a great way to do this, as #pst said above. Check out this answer for a good example:
getPixel from HTML Canvas?
Some code that would serve you specifically as well:
var imgd = context.getImageData(x, y, width, height);
var pix = imgd.data;
for (var i = 0, n = pix.length; i < n; i += 4) {
console.log pix[i+3]
}
This will go row by row, so you'd need to convert that into an x,y and either convert the for loop to a direct check or run a conditional inside.
Reading your question again, it looks like you want to be able to get the point that the person clicks on. This can be done pretty easily with jquery's click event. Just run the above code inside a click handler as such:
$('el').click(function(e){
console.log(e.clientX, e.clientY)
}
Those should grab your x and y values.
The two previous answers demonstrate how to use Canvas and ImageData. I would like to propose an answer with runnable example and using an image processing framework, so you don't need to handle the pixel data manually.
MarvinJ provides the method image.getAlphaComponent(x,y) which simply returns the transparency value for the pixel in x,y coordinate. If this value is 0, pixel is totally transparent, values between 1 and 254 are transparency levels, finally 255 is opaque.
For demonstrating I've used the image below (300x300) with transparent background and two pixels at coordinates (0,0) and (150,150).
Console output:
(0,0): TRANSPARENT
(150,150): NOT_TRANSPARENT
image = new MarvinImage();
image.load("https://i.imgur.com/eLZVbQG.png", imageLoaded);
function imageLoaded(){
console.log("(0,0): "+(image.getAlphaComponent(0,0) > 0 ? "NOT_TRANSPARENT" : "TRANSPARENT"));
console.log("(150,150): "+(image.getAlphaComponent(150,150) > 0 ? "NOT_TRANSPARENT" : "TRANSPARENT"));
}
<script src="https://www.marvinj.org/releases/marvinj-0.7.js"></script>
Building on Brian Nickel's answer, only the wanted single pixel of the source image is drawn onto a 1*1 pixel canvas, which is more efficient than drawing the entire image just to get a single pixel:
function getPixel(img, x, y) {
let canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
canvas.getContext('2d').drawImage(img, x, y, 1, 1, 0, 0, 1, 1);;
let pixelData = canvas.getContext('2d').getImageData(0, 0, 1, 1).data;
return pixelData;
}
With : i << 2
const pixels = context.getImageData(x, y, width, height).data;
for (let i = 0, dx = 0; dx < data.length; i++, dx = i << 2)
{
if (pixels[dx+3] <= 8) { console.log("transparent x= " + i); }
}
Here's a consolidation of a few answers into a runnable snippet that lets you upload a file, hover to preview the RGB value of each pixel, then click to put the RGB in a div.
Pertinent to the original question, the last value (alpha) is the transparency. 0 is fully transparent and 255 is fully opaque.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const input = document
.querySelector('input[type="file"]');
input.addEventListener("change", e => {
const image = new Image();
image.addEventListener("load", e => {
const {width, height} = image;
canvas.width = width;
canvas.height = height;
ctx.drawImage(image, 0, 0);
const {data} = ctx.getImageData(
0, 0, width, height
);
const rgb = (x, y) => {
const i = (x + y * width) * 4;
return data.slice(i, i + 4).join(", ");
};
canvas.addEventListener("mousemove", event => {
const {offsetX: x, offsetY: y} = event;
console.log(rgb(x, y));
});
canvas.addEventListener("click", event => {
const {offsetX: x, offsetY: y} = event;
document.querySelector("div")
.textContent = rgb(x, y);
});
});
image.addEventListener("error", () =>
console.error("failed")
);
image.src = URL
.createObjectURL(event.target.files[0]);
});
.as-console-wrapper {
height: 21px !important;
}
<div>
Upload image and mouseover to preview RGB. Click to select a value.
</div>
<form>
<input type="file">
</form>
<canvas></canvas>
References:
HTML5 Canvas - How to get adjacent pixels position from the linearized imagedata Uint8ClampedArray?
How to upload image into HTML5 canvas