Why won't my grayscale function affect the entire canvas? - javascript

I'm a new programmer, and I've been trying to create a grayscale function through JS for practice.
My code:
<canvas width='400' height='400'></canvas>
<script>
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var image = new Image();
image.onload = function() {
ctx.drawImage(image, 0, 0);
grayscale();
}
image.src = 'images/fry.jpg';
function grayscale () {
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data = imageData.data;
var pixelCount = data.length / 4;
for (var i = 0; i < pixelCount; i++) {
var gray = (data[i] * 0.3) + (data[i+1] * 0.59) + (data[i+2] * 0.11);
data[i] = gray;
data[i+1] = gray;
data[i+2] = gray;
}
ctx.putImageData(imageData, 0, 0);
}
</script>
But when I run the code in Safari and Firefox, this is what happens: grayscale only partly affects image
But, I noticed that if I change the canvas.height dimension in imageData to dimensions significantly larger than the canvas (such as 2000), then the entire image is grayscaled. The fry.jpg file only has dimensions of 387x315 on my computer.
What am I doing wrong?

You are only looping through 1/4 of the image when you divide the imageData.data array by 4.
Each pixel takes 4 values, one each for each channel red, green, blue, and alpha.
The quickest way to iterate them all is
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data = imageData.data;
var channelCount = data.length; // Total number of channel data
var i = 0; // counter
var gray; // computered Gray scale
while(i < channelCount){ // while not done all
gray = (data[i] * 0.3) + (data[i+1] * 0.59) + (data[i+2] * 0.11);
data[i++] = data[i++] = data[i++] = gray; // assign pixel RGB
i++; // skip the alpha
}
// put it back onto the canvas.
ctx.putImageData(imageData, 0, 0);

Related

How can I convert a canvas to an image with transparency?

I am currently trying to take a weather radar image that has a black background, and make the background transparent. I am using canvas to do this. When I display the image, the background that should be transparent now looks like a red and black checkerboard pattern. See here:
The code being used is here:
function removeBlack(img) {
// Create canvas and draw image
let tmpCanvas = document.createElement('canvas');
tmpCanvas.width = img.width;
tmpCanvas.height = img.height;
let tmpCtx = tmpCanvas.getContext('2d');
tmpCtx.drawImage(img, 0, 0, tmpCanvas.width, tmpCanvas.height);
// Get image data and add opacity to black pixels
let imgData = tmpCtx.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height);
let data = imgData.data;
for (var i = 0; i < data.length; i += 4) {
let r = data[i],
g = data[i+1],
b = data[i+2];
if (r === 0 && g === 0 && b === 0) data[i + 4] = 255;
}
tmpCtx.putImageData(imgData, 0, 0);
imgData = tmpCanvas.toDataURL("image/png");
let image = document.createElement('img');
image.src = imgData;
img.remove();
tmpCanvas.remove();
return image;
}
Should be data[i + 3] = 255 in your loop
data[i + 4] will go into the next 32-bit colour word and set the red byte to maximum
Ps: you'll also have to test if the alpha value should be 0 or 255 - 255 might be fully opaque rather than transparent

Multi color image into single color image in canvas JavaScript [duplicate]

I want to change the background color of this image while keeping the form, the effects and the contour of
the image.
<canvas id="canvas01" width="1200" height="800"></canvas>
<script>
function drawImage(imageObj,x, y, width, height){
var canvas = document.getElementById('canvas01');
var context = canvas.getContext('2d');
context.drawImage(imageObj, x, y, width, height);
}
var image = new Image();
image.onload = function(){
drawImage(this, 400, 100, 320, 450);
};
image.src ="images/658FFBC6.png";
</script>
Luma preservation
At the risk of looking similar to the existing answer, I would like to point out a small but important difference using a slightly different approach.
The key is to preserve the luma component in an image (ie. shadow details, wrinkles etc. in this case) so two steps are needed to control the look using blending modes via globalCompositeOperation (or alternatively, a manual approach using conversion between RGB and the HSL color-space if older browsers must be supported):
"saturation": will alter the chroma (intensity, saturation) from the next drawn element and apply it to the existing content on the canvas, but preserve luma and hue.
"hue": will grab the chroma and luma from the source but alter the hue, or color if you will, based on the next drawn element.
As these are blending modes (ignoring the alpha channel) we will also need to clip the result using composition as a last step.
The color blending mode can be used too but it will alter luma which may or may not be desirable. The difference can be subtle in many cases, but also very obvious depending on target chroma and hue where luma/shadow definition is lost.
So, to achieve a good quality result preserving both luma and chroma, these are more or less the main steps (assumes an empty canvas):
// step 1: draw in original image
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, 0, 0);
// step 2: adjust saturation (chroma, intensity)
ctx.globalCompositeOperation = "saturation";
ctx.fillStyle = "hsl(0," + sat + "%, 50%)"; // hue doesn't matter here
ctx.fillRect(0, 0);
// step 3: adjust hue, preserve luma and chroma
ctx.globalCompositeOperation = "hue";
ctx.fillStyle = "hsl(" + hue + ",1%, 50%)"; // sat must be > 0, otherwise won't matter
ctx.fillRect(0, 0, c.width, c.height);
// step 4: in our case, we need to clip as we filled the entire area
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(img, 0, 0);
// step 5: reset comp mode to default
ctx.globalCompositeOperation = "source-over";
50% lightness (L) will keep the original luma value.
Live Example
Click the checkbox to see the effect on the result. Then test with different chroma and hue settings.
var ctx = c.getContext("2d");
var img = new Image(); img.onload = demo; img.src = "//i.stack.imgur.com/Kk1qd.png";
function demo() {c.width = this.width>>1; c.height = this.height>>1; render()}
function render() {
var hue = +rHue.value, sat = +rSat.value, l = +rL.value;
ctx.clearRect(0, 0, c.width, c.height);
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, 0, 0, c.width, c.height);
if (!!cColor.checked) {
// use color blending mode
ctx.globalCompositeOperation = "color";
ctx.fillStyle = "hsl(" + hue + "," + sat + "%, 50%)";
ctx.fillRect(0, 0, c.width, c.height);
}
else {
// adjust "lightness"
ctx.globalCompositeOperation = l < 100 ? "color-burn" : "color-dodge";
// for common slider, to produce a valid value for both directions
l = l >= 100 ? l - 100 : 100 - (100 - l);
ctx.fillStyle = "hsl(0, 50%, " + l + "%)";
ctx.fillRect(0, 0, c.width, c.height);
// adjust saturation
ctx.globalCompositeOperation = "saturation";
ctx.fillStyle = "hsl(0," + sat + "%, 50%)";
ctx.fillRect(0, 0, c.width, c.height);
// adjust hue
ctx.globalCompositeOperation = "hue";
ctx.fillStyle = "hsl(" + hue + ",1%, 50%)";
ctx.fillRect(0, 0, c.width, c.height);
}
// clip
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(img, 0, 0, c.width, c.height);
// reset comp. mode to default
ctx.globalCompositeOperation = "source-over";
}
rHue.oninput = rSat.oninput = rL.oninput = cColor.onchange = render;
body {font:16px sans-serif}
<div>
<label>Hue: <input type=range id=rHue max=359 value=0></label>
<label>Saturation: <input type=range id=rSat value=100></label>
<label>Lightness: <input type=range id=rL max=200 value=100></label>
<label>Use "color" instead: <input type=checkbox id=cColor></label>
</div>
<canvas id=c></canvas>
Global composite operations
The 2D context property ctx.globalCompositeOperation is very useful for a wide range of image processing tasks. For more on globalCompositeOperation at MDN
You can convert the image into a canvas, that way you can edit it.
function imageToCanvas(image){
const c = document.createElement("canvas");
c.width = image.width;
c.height = image.height;
c.ctx = c.getContext("2d"); // attach context to the canvas for eaasy reference
c.ctx.drawImage(image,0,0);
return c;
}
You can use the globalCompositeOperation = "color" to colour the image
function colorImage(image,color){ // image is a canvas image
image.ctx.fillStyle = color;
image.ctx.globalCompositeOperation = "color";
image.ctx.fillRect(0,0,image.width,image.height);
image.ctx.globalCompositeOperation = "source-over";
return image;
}
Unfortunately this also overwrites the alpha pixels so you need to use the original image as a mask to restore the alpha pixels.
function maskImage(dest,source){
dest.ctx.globalCompositeOperation = "destination-in";
dest.ctx.drawImage(source,0,0);
dest.ctx.globalCompositeOperation = "source-over";
return dest;
}
And then you have a coloured image
Example.
In he example I colour the image in a range of colours and added a function to restore the canvas copy of the image back to the original. If you get the image from the page as an element then use naturalWidth and naturalHeight as the width and height properties may not match the image resolution.
const ctx = canvas.getContext("2d");
const image = new Image;
var colCopy;
image.src = "https://i.stack.imgur.com/Kk1qd.png";
image.onload = () => {
colCopy = imageToCanvas(image);
const scale = canvas.height / image.naturalHeight;
ctx.scale(scale, scale);
ctx.drawImage(colCopy, 0, 0);
for (var i = 32; i < 360; i += 32) {
restoreImage(colCopy, image);
colorImage(colCopy, "hsl(" + i + ",100%,50%)");
maskImage(colCopy, image);
ctx.drawImage(colCopy, 150 * i / 16, 0);
}
}
function imageToCanvas(image) {
const c = document.createElement("canvas");
c.width = image.naturalWidth;
c.height = image.naturalHeight;
c.ctx = c.getContext("2d"); // attach context to the canvas for easy reference
c.ctx.drawImage(image, 0, 0);
return c;
}
function restoreImage(dest, source) {
dest.ctx.clearRect(0, 0, dest.width, dest.height);
dest.ctx.drawImage(source, 0, 0);
return dest;
}
function colorImage(dest, color) { // image is a canvas image
dest.ctx.fillStyle = color;
dest.ctx.globalCompositeOperation = "color";
dest.ctx.fillRect(0, 0, dest.width, dest.height);
dest.ctx.globalCompositeOperation = "source-over";
return dest;
}
function maskImage(dest, source) {
dest.ctx.globalCompositeOperation = "destination-in";
dest.ctx.drawImage(source, 0, 0);
dest.ctx.globalCompositeOperation = "source-over";
return dest;
}
canvas {
border: 2px solid black;
}
<canvas id="canvas" width=600></canvas>
The image can get a little washed out in some situations, you can convert the image to a higher contrast black and white image using composite operations similar to shown above, and use the high contrast image as the template to colour.
Using Filters
Most of the common browsers now support canvas filters which has a hue shift filter. You can use that to shift the hue to the value you want, though first you will need to know what the image original hue is. (see below example on how to find HUE)
See Canvas filters at MDN for compatibility and how to use canvas filters.
The following function will preserve the saturation and just shift the hue.
// dest canvas to hold the resulting image
// source the original image
// hue The hue to set the dest image to
// sourceHue the hue reference point of the original image.
function colorImage(dest,source, hue , sourceHue) { // image is a canvas image
dest.ctx.clearRect(0,0,dest.width, dest.height);
dest.ctx.filter="hue-rotate("+((hue - sourceHue) | 0)+"deg)";
dest.ctx.drawImage(source,0, 0, dest.width, dest.height);
return dest;
}
Filters example.
The following uses ctx.filter = "hue-rotate(30deg)" to rotate the hue. I have not included any code to find the image original hue so manually set it by eye to 120.
const ctx = canvas.getContext("2d");
const image = new Image;
var colCopy;
const sourceHue = 120;
image.src = "https://i.stack.imgur.com/Kk1qd.png";
image.onload = () => {
colCopy = imageToCanvas(image);
const scale = canvas.height / image.naturalHeight;
ctx.scale(scale, scale);
ctx.drawImage(colCopy, 0, 0);
for (var i = 32; i < 360; i += 32) {
colorImage(colCopy,image,i,sourceHue);
ctx.drawImage(colCopy, 150 * i / 16, 0);
}
}
function imageToCanvas(image) {
const c = document.createElement("canvas");
c.width = image.naturalWidth;
c.height = image.naturalHeight;
c.ctx = c.getContext("2d"); // attach context to the canvas for easy reference
c.ctx.drawImage(image, 0, 0);
return c;
}
function colorImage(dest,source, hueRotate , sourceHue) { // image is a canvas image
dest.ctx.clearRect(0,0,dest.width, dest.height);
dest.ctx.filter="hue-rotate("+((hueRotate - sourceHue) | 0)+"deg)";
dest.ctx.drawImage(source,0, 0, dest.width, dest.height);
return dest;
}
canvas {
border: 2px solid black;
}
<canvas id="canvas" width=600></canvas>
RGB to Hue
There are plenty of answers to help find the hue of a pixel here on SO. Here is a particularly detailed one RGB to HSL conversion.
Filters example White.
The following uses ctx.filter = "grayscale(100%)" to remove saturation and then ctx.filter = "brightness(amount%)" to change the brightness. This gives a range of gray colours from black to white. You can also do the same with the colour, by reducing the grayscale amount.
const ctx = canvas.getContext("2d");
const image = new Image;
var colCopy;
const sourceHue = 120;
image.src = "https://i.stack.imgur.com/Kk1qd.png";
image.onload = () => {
colCopy = imageToCanvas(image);
const scale = canvas.height / image.naturalHeight;
ctx.scale(scale, scale);
ctx.drawImage(colCopy, 0, 0);
for (var i = 40; i < 240; i += 20) {
grayImage(colCopy,image,i);
ctx.drawImage(colCopy, 150 * ((i-40) / 12), 0);
}
}
function imageToCanvas(image) {
const c = document.createElement("canvas");
c.width = image.naturalWidth;
c.height = image.naturalHeight;
c.ctx = c.getContext("2d"); // attach context to the canvas for easy reference
c.ctx.drawImage(image, 0, 0);
return c;
}
function grayImage(dest,source, brightness) { // image is a canvas image
dest.ctx.clearRect(0,0,dest.width, dest.height);
dest.ctx.filter = "grayscale(100%)";
dest.ctx.drawImage(source,0, 0, dest.width, dest.height);
dest.ctx.filter = "brightness(" + brightness +"%)";
dest.ctx.drawImage(dest,0, 0, dest.width, dest.height);
return dest;
}
canvas {
border: 2px solid black;
}
<canvas id="canvas" width=600></canvas>
You can combine filters on a single line of code before performing your draw operation, like this:
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const image = document.getElementById('source');
ctx.filter = 'hue-rotate(120deg) grayscale(10%) brightness(150%)';
ctx.drawImage(image, 10, 10, 180, 120);
<canvas id="canvas"></canvas>
<div style="display:none;">
<img id="source"
src="https://interactive-examples.mdn.mozilla.net/media/examples/gecko-320-213.jpg">
</div>

How to draw a monochrome image onto a canvas, but use a different color?

I have an image which is black and white. If necessary, I can transform it into transparent and white.
How can I draw this image onto a canvas, replacing white with an arbitrary color?
I want to be able to draw text onto a canvas using a font like the below, using different colors. I don't want to have multiple copies of the image.
There are two ways you can do this.
Offscreen
Create an off-screen canvas, render the the text to that canvas and then use ctx.globalCompositeOperation = "color" (update my bad that should be ctx.globalCompositeOperation = "destination-in") draw the color first then draw the text over the color. (see example)
Example of above method
canvas.width = 430;
canvas.height = 16;
const ctx = canvas.getContext("2d");
// off screen canvas
const text = document.createElement("canvas");
text.width = 512;
text.height = 16;
text.ctx = text.getContext("2d");
const font = new Image;
font.src = "https://i.stack.imgur.com/VXaVG.png"
font.addEventListener("load", () => {
drawColorString("000Black text#F00 Red text#FF0 Yellow#0F0 Green#0FF Cyan#00F Blue#F0F Magenta", 10, 4);
});
const grad = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
grad.addColorStop(0,"#FFF");
grad.addColorStop(1,"#000");
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
function drawColorString(str, xpos, ypos) {
const parts = str.split("#");
var x = 0;
for (const part of parts) {
const color = part.slice(0,3);
const chars = part.slice(3);
for (const char of chars) {
drawChar(char, color, x);
x += 8;
}
}
colorText();
ctx.drawImage(text, xpos, ypos);
}
function colorText() {
text.ctx.globalCompositeOperation = "destination-in";
text.ctx.drawImage(text, 0, 8, text.width, 8, 0, 0, text.width, 8);
text.ctx.globalCompositeOperation = "source-over";
}
function drawChar(char, color, xpos) {
const c = char.charCodeAt(0);
const x = (c % 32) * 8;
const y = (c / 32 | 0) * 8;
text.ctx.fillStyle = "#" + color;
text.ctx.fillRect(xpos, 0, 8, 8);
text.ctx.drawImage(font, x, y, 8, 8, xpos, 8, 8, 8);
}
canvas {
border:1px solid black;
}
<canvas id="canvas"></canvas>
Font image used
Additive
Second way is to modify the source image to give you masked Black, Red, Green, Blue versions of the text. The to render a color, draw the black text, then overlay using ctx.globalCompositeOperation = "lighter"to add the r,g,b amounts as needed.
Example of additive method
canvas.width = 430;
canvas.height = 16;
const ctx = canvas.getContext("2d");
const font = new Image;
font.src = "https://i.stack.imgur.com/FfGjd.png"
font.addEventListener("load", () => {
drawColorString("000Black text#F00 Red text#FF0 Yellow#0F0 Green#0FF Cyan#00F Blue#F0F Magenta", 10, 4);
});
const grad = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
grad.addColorStop(0,"#FFF");
grad.addColorStop(1,"#000");
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
function drawColorString(str, x, y) {
const parts = str.split("#");
for (const part of parts) {
const color = part.slice(0,3);
const chars = part.slice(3);
for (const char of chars) {
drawChar(char, color, x, y);
x += 8;
}
}
}
function drawChar(char, color, xpos, ypos) {
const addLayer = (channel, amount) => {
if (amount) {
ctx.globalAlpha = amount;
ctx.drawImage(font, x, y + 64 * (channel + 1), 8, 8, xpos, ypos, 8, 8);
}
}
const red = parseInt(color[0] + color[0], 16) / 255;
const green = parseInt(color[1] + color[1], 16) / 255;
const blue = parseInt(color[2] + color[2], 16) / 255;
const c = char.charCodeAt(0);
const x = (c % 32) * 8;
const y = (c / 32 | 0) * 8;
ctx.globalAlpha = 1;
ctx.drawImage(font, x, y, 8, 8, xpos, ypos, 8, 8);
ctx.globalCompositeOperation = "lighter";
addLayer(0, red);
addLayer(1, green);
addLayer(2, blue);
ctx.globalCompositeOperation = "source-over"; // default
ctx.globalAlpha = 1;
}
canvas {
border:1px solid black;
}
<canvas id="canvas"></canvas>
Example of the image used in above snippet.
Or you can combine the two methods and use the additive method to render coloured text to an offscreen canvas and then draw that canvas to the display canvas. That means you need only draw the text when it changes, not every frame if animated.
The easiest way to do this is to replace the white in your image with a transparent "hole" where you want the colour, then use .fillstyle to change the background of the canvas.
Save the image as a png, open some photo editing software, and take rub out a hole in the image where you want the colouring to be so you see the checkered transparent background.
Move this file to wherever you store images in your project.
add the image to your canvas element, make sure it fills the whole canvas. Use the following code to change the background of your canvas:
let canvas = document.getElementByID("colourthis")
let ctx = canvas.getContext("2D")
ctx.fillstyle = 'red'
The transparent parts of the image will now be red.
You could use some kind of randomizer to achieve the other colors.
use ctx.fillStyle = "transparent" to get the transparent background.
Thats the way I would do this in vanilla JS, no JQuery or anything. There are other ways but judging by the tags you used I doubt you are using any plugins for this sort of thing.

How can I grayscale a canvas image in JavaScript?

I have an image inside of an HTML5 canvas with the size of 28x28 pixels. I get the imageData of the canvas as an array of RGBA (red, green, blue, alpha) values using this code:
canvas = document.getElementById('canvas');
ctx = canvas.getContext("2d");
imgData = ctx.getImageData(0, 0, 28, 28);
Now I want to grayscale the image, so that I get an array of 784 values (28x28 pixels) where each pixel has one value (instead of four).
I've found a lot of different formulas for grayscaling, some are multiplying the rgb values, some are just calculating the average - I really don't know which of them to use...
I'm also stuck at getting 784 values - it's always 3136 (because of the 4 channels)...
Thanks in advance!
The main idea is to have the same value for the red green and blue component of the color. For this you need to calculate the lightness of every pixel. There are several ways to calculate the lightness. This is one of them.
window.onload = function() {
let canvas = document.getElementById("c");
let ctx = canvas.getContext("2d");
canvas.width = 50;
canvas.height = 50;
let srcImg = document.getElementById("sof");
ctx.drawImage(srcImg, 0, 0, ctx.canvas.width, ctx.canvas.height);
let imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
let pixels = imgData.data;
for (var i = 0; i < pixels.length; i += 4) {
let lightness = parseInt((pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3);
pixels[i] = lightness;
pixels[i + 1] = lightness;
pixels[i + 2] = lightness;
}
ctx.putImageData(imgData, 0, 0);
}
<canvas id="c"></canvas>
<img src="data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAZAAD/4QMtaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTJDRTJFNzlFMzkzMTFFODlEQ0FEQ0NCN0JCMjUxRTEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTJDRTJFN0FFMzkzMTFFODlEQ0FEQ0NCN0JCMjUxRTEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxMkNFMkU3N0UzOTMxMUU4OURDQURDQ0I3QkIyNTFFMSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxMkNFMkU3OEUzOTMxMUU4OURDQURDQ0I3QkIyNTFFMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/uAA5BZG9iZQBkwAAAAAH/2wCEABENDQ0ODRIODhIaEQ8RGh8XEhIXHyIXFxcXFyIjGx4dHR4bIyMpKi0qKSM2Njs7NjZBQUFBQUFBQUFBQUFBQUEBEhERFBYUGBUVGBcTFxMXHRcZGRcdLB0dIB0dLDgoIyMjIyg4MjUtLS01Mj09ODg9PUFBQUFBQUFBQUFBQUFBQf/AABEIADQAMgMBIgACEQEDEQH/xACCAAEAAgMBAAAAAAAAAAAAAAAAAwQBAgUGAQEAAwEAAAAAAAAAAAAAAAAAAgMEARAAAQMCAgYHBwUAAAAAAAAAAQACAxEEIRIxQYHREwXwUWGRoSIVcbHBMkJSY2JyIxQ1EQACAgEEAgMBAAAAAAAAAAAAARECITFRYQMSQkGBE3H/2gAMAwEAAhEDEQA/APcIiIAijnnit4zLK7Kwd9TqAVC/fMwxcwtpOJCweZgPkynXt8FC91VN6xlpaxuTp1uzS0nCb0nY6aKOGaOeJssZq1w2g6wfYpFJNNSvki004eGgiIunAiLUvBa4xkPc0HAGvm6kBRn5haiZ9pdRkR6MzhVrvjtVUsksKzWxFzYSfOyuagPXv71vJfyFmS/sSW66YjZXeqrDbcSthcPglcQOFICWuJ1VFfFYr3lzKb9fS64h6m6lIUQ0vZT50fMrNS3y0PZOf638ljKMxJwMbh9J/V7wuso4WRRNEMeUFuJa2gxOk0Ui1ddPCsffC/hl7b+dp+uXy+QiIplZkaVzrQS8G64FBLmOSujN2roa1S9OFSRM8VNcMNKPKOpw5IcvPfuj8NyR23MzPG+dsLmtcC4hozAdhopvT/zv6bU9P/O/ptVX4rE27HG9i39nmKdanapmL/Rm/buVxVoLQQSGTiF5IpirKtKQiIgCIiAIiIAiIgCIiA//2Q=="
id="sof" />
UPDATE:
Alternative lightness Calculations:
Wikipedia (Luma):
let lightness = parseInt(pixels[i]*.299 + pixels[i + 1]*.587 + pixels[i + 2]*.114);
elsewhere (source unknown):
let lightness = parseInt(3*pixels[i] + 4*pixels[i + 1] + pixels[i + 2] >>> 3);
Wikipedia (Linear Luminance):
let lightness = 0.2126 * pixels[i] + 0.715 * pixels[i+1] + 0.0722 * pixels[i+2];
You can directly set a filter to the rendering context. It will be simpler than calculate every pixel color.
For this you should use ctx.filter = 'grayscale(1)';
const img = document.querySelector('img');
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
img.onload = function() {
canvas.width = img.width;
canvas.height = img.height;
ctx.filter = 'grayscale(1)';
ctx.drawImage(img, 0, 0, img.width, img.height);
}
<canvas></canvas>
<img src="data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAZAAD/4QMtaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTJDRTJFNzlFMzkzMTFFODlEQ0FEQ0NCN0JCMjUxRTEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTJDRTJFN0FFMzkzMTFFODlEQ0FEQ0NCN0JCMjUxRTEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxMkNFMkU3N0UzOTMxMUU4OURDQURDQ0I3QkIyNTFFMSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxMkNFMkU3OEUzOTMxMUU4OURDQURDQ0I3QkIyNTFFMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/uAA5BZG9iZQBkwAAAAAH/2wCEABENDQ0ODRIODhIaEQ8RGh8XEhIXHyIXFxcXFyIjGx4dHR4bIyMpKi0qKSM2Njs7NjZBQUFBQUFBQUFBQUFBQUEBEhERFBYUGBUVGBcTFxMXHRcZGRcdLB0dIB0dLDgoIyMjIyg4MjUtLS01Mj09ODg9PUFBQUFBQUFBQUFBQUFBQf/AABEIADQAMgMBIgACEQEDEQH/xACCAAEAAgMBAAAAAAAAAAAAAAAAAwQBAgUGAQEAAwEAAAAAAAAAAAAAAAAAAgMEARAAAQMCAgYHBwUAAAAAAAAAAQACAxEEIRIxQYHREwXwUWGRoSIVcbHBMkJSY2JyIxQ1EQACAgEEAgMBAAAAAAAAAAAAARECITFRYQMSQkGBE3H/2gAMAwEAAhEDEQA/APcIiIAijnnit4zLK7Kwd9TqAVC/fMwxcwtpOJCweZgPkynXt8FC91VN6xlpaxuTp1uzS0nCb0nY6aKOGaOeJssZq1w2g6wfYpFJNNSvki004eGgiIunAiLUvBa4xkPc0HAGvm6kBRn5haiZ9pdRkR6MzhVrvjtVUsksKzWxFzYSfOyuagPXv71vJfyFmS/sSW66YjZXeqrDbcSthcPglcQOFICWuJ1VFfFYr3lzKb9fS64h6m6lIUQ0vZT50fMrNS3y0PZOf638ljKMxJwMbh9J/V7wuso4WRRNEMeUFuJa2gxOk0Ui1ddPCsffC/hl7b+dp+uXy+QiIplZkaVzrQS8G64FBLmOSujN2roa1S9OFSRM8VNcMNKPKOpw5IcvPfuj8NyR23MzPG+dsLmtcC4hozAdhopvT/zv6bU9P/O/ptVX4rE27HG9i39nmKdanapmL/Rm/buVxVoLQQSGTiF5IpirKtKQiIgCIiAIiIAiIgCIiA//2Q=="
/>

Trying to get average (mean) color of canvas vs slices thereof. getting inaccurate results

I'm trying to get the mean color of an image, to compare to the mean color of various slices of the image, to find the area that is most different from the mean. it's part of a project to crop an image automatically, based on areas of greatest interest.
I was getting strange results, so I created a demo page that compares a totally flat red image with slices thereof. each slice should have the same color as the image (192,0,0,255). but, as you can see, they don't:
http://gschoppe.com/projects/jQuery.smartCrop/demo.html (all data goes to the console)
Here's the average color function from the demo:
var getAverageColor = function(canvas) {
var context = canvas.getContext('2d');
var imgdata = context.getImageData(0, 0, canvas.width, canvas.height);
var pixels = imgdata.data;
var color = {red:0,green:0,blue:0,alpha:0};
// Loop over each pixel.
for (var i = 0, n = pixels.length; i < n; i += 4) {
color.red += pixels[i ]/255; //red
color.green += pixels[i+1]/255; //green
color.blue += pixels[i+2]/255; //blue
color.alpha += pixels[i+3]/255; //alpha
}
color.red = color.red/(pixels.length/4);
color.green = color.green/(pixels.length/4);
color.blue = color.blue/(pixels.length/4);
color.alpha = color.alpha/(pixels.length/4);
return color;
}
and here's the (simplified) version of the loop where it's called:
var findPointOfInterest = function(canvas) {
var sliceSize = Math.round(canvas.width/20);
var avgColor = getAverageColor(canvas);
console.log("avg color: ");
console.log(avgColor);
var sliceColor = null;
for(var i=0;i<canvas.width;i+=sliceSize) {
if(i+sliceSize > canvas.width)
sliceSize = canvas.width % sliceSize;
var temp = document.createElement('canvas');
temp.width = sliceSize;
temp.height = canvas.height;
var context = temp.getContext('2d');
context.clearRect ( 0, 0, sliceSize, canvas.height );
context.drawImage(canvas, i, 0, sliceSize, canvas.height, 0, 0, sliceSize, canvas.height);
sliceColor = getAverageColor(temp);
console.log(sliceColor);
}
}
I made a simpler version of the issue for JSFiddle, but it doesn't seem to have the bug! GAH! Here's the simple version: http://jsfiddle.net/tLMPk/ (everything logs to the console)
can anyone help me track this thing down?

Categories