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
Related
A canvas has CSS background-color: #D3D3D3; and one property ctx.fillStyle = "#000";.
This JS code needs to leave the stroke intact and make every thing else transparent background. How can this be achieved?
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
let clearBack = imageData;
for (var i = 3; i < clearBack.length; i += 4) {
if (pixle is not black) { //if pixel belongs to the stroke leave it
clearBack[i] = 0;
}
}
You are close. You can go through every 4 pixels (red green blue alpha) and check each color with the imageData. Then if its not black, do what you want with it.
You can use putImageData to rewrite any modified pixels back to the screen. Make sure you keep the original ImageData object in your imageData var though.
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); //take away the .data
var r, g, b, a;
for(var i = 0; i+3 < imageData.data.length; i+=4) {
r = imageData.data[i];
g = imageData.data[i+1];
b = imageData.data[i+2];
a = imageData.data[i+3];
if((r > 0 || g > 0 || b > 0) && a > 0) { // if pixel is not black, and not transparent
imageData.data[i+3] = 0; //set alpha to 0
}
}
ctx.putImageData(imageData, 0, 0); //put the imageData back to the screen
Heres a fiddle. I was doing something slightly different there but you get the idea.
https://jsfiddle.net/q0n5sxku/
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);
I used the following code to remove white color in image. It works for the image of the original fiddle, but it doesn't work with mine. I believe the code is totally fine, and it's a problem with the image I use, but why? And how can I make it work for my image?
Working with original image in tutorial: https://jsfiddle.net/gaby/UuCys/3/
NOT working with my image (I simply replace the img src): http://jsfiddle.net/UuCys/119/
Here is the code
When I try to return each pixel before changes, every pixel has 0 value for red,green or blue...why?
function white2transparent(img)
{
var c = document.createElement('canvas');
var w = img.width, h = img.height;
c.width = w;
c.height = h;
var ctx = c.getContext('2d');
ctx.drawImage(img, 0, 0, w, h);
var imageData = ctx.getImageData(0,0, w, h);
var pixel = imageData.data;
var r=0, g=1, b=2,a=3;
for (var p = 0; p<pixel.length; p+=4)
{
if (
pixel[p+r] == 255 &&
pixel[p+g] == 255 &&
pixel[p+b] == 255) // if white then change alpha to 0
{pixel[p+a] = 0;}
}
ctx.putImageData(imageData,0,0);
return c.toDataURL('image/png');
}
This affect here (image below) was achieved with a couple simple Photoshop steps takes, the colors parts were turn white, the background (various shades of white gray), was made transparent. Is it possible to achieve this with canvas?
The images inside the circles below is the final result.
The images were originally colored, like the 2nd from top image was this one:
See that circle in the middle, basically all the white was cut out in an aliased way.
Same with this zoho logo:
The 2nd from bottom was originally something like this:
Except the red R was just a Y in the middle and instead of all the text and green strip seen in image here, it just had some grainy texture in shades of gray around it. And via photoshop the Y was made trasnparent, and the texture and stamp was just made solid, removing the 3d shadow etc.
Putting this above yandex stamp through the photoshop algorithm gives this (i replaced the white with black for demo/visibility puproses)
This was jagged after the photoshop algorithm but in final application the image is reduced to around 80x80px and that makes it look real smooth and anti-aliased. So real final result is this which looks very decent.
The problem is multifaceted as there are regions which require different approaches, for example, the last image where the main text needs to be converted to white but keep transparency, while the bottom bar in the same image is solid but need the white text to be retained while the solid background to be removed.
It's doable by implementing tools to select regions and apply various operators manually - automatically will be a much larger challenge than it may appear to be.
You could make requirements to the user to only upload images with an alpha channel. For that you can simply replace each non-transparent pixel with white. It becomes more a policy issue than a technical one in my opinion.
For example
Taking the logo:
var img = new Image();
img.crossOrigin = "";
img.onload = process;
img.src = "http://i.imgur.com/HIhnb4A.png"; // load the logo
function process() {
var canvas = document.querySelector("canvas"), // canvas
ctx = canvas.getContext("2d"), // context
w = this.width, // image width/height
h = this.height,
idata, data32, len, i, px; // iterator, pixel etc.
canvas.width = w; // set canvas size
canvas.height = h;
ctx.drawImage(this, 0, 0); // draw in image
idata = ctx.getImageData(0, 0, w, h); // get imagedata
data32 = new Uint32Array(idata.data.buffer); // use uint32 view for speed
len = data32.length;
for(i = 0; i < len; i++) {
// extract alpha channel from a pixel
px = data32[i] & 0xff000000; // little-endian: ABGR
// any non-transparency? ie. alpha > 0
if (px) {
data32[i] = px | 0xffffff; // set this pixel to white, keep alpha level
}
}
// done
ctx.putImageData(idata, 0, 0);
}
body {background:gold}
<canvas></canvas>
Now the problem is easy to spot: the "#" character is just solid because there is no transparency behind it. To automate this would require first to knock out all whites, then apply the process demoed above. However, this may work in this single case but probably not be a good thing for most.
There will also be anti-aliasing issues as it's not possible to know how much of the white you want to knock out as we don't analyze the edges around the white pixels. Another possible challenge is ICC corrected image where white may not be white depending on ICC profile used, browser support and so forth.
But, it's doable to some degree - taking the code above with a prestep to knock out entirely white pixels for this logo:
var img = new Image();
img.crossOrigin = "";
img.onload = process;
img.src = "http://i.imgur.com/HIhnb4A.png"; // load the logo
function process() {
var canvas = document.querySelector("canvas"), // canvas
ctx = canvas.getContext("2d"), // context
w = this.width, h = this.height,
idata, data32, len, i, px; // iterator, pixel etc.
canvas.width = w; // set canvas size
canvas.height = h;
ctx.drawImage(this, 0, 0); // draw in image
idata = ctx.getImageData(0, 0, w, h); // get imagedata
data32 = new Uint32Array(idata.data.buffer); // use uint32 view for speed
len = data32.length;
for(i = 0; i < len; i++) {
px = data32[i]; // pixel
// is white? then knock it out
if (px === 0xffffffff) data32[i] = px = 0;
// extract alpha channel from a pixel
px = px & 0xff000000; // little-endian: ABGR
// any non-transparency? ie. alpha > 0
if (px) {
data32[i] = px | 0xffffff; // set this pixel to white, keep alpha level
}
}
ctx.putImageData(idata, 0, 0);
}
body {background:gold}
<canvas></canvas>
Use this
private draw(base64: string) {
// example size
const width = 200;
const height = 70;
const image = new Image();
image.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
const imageData = ctx.getImageData(0, 0, width, height);
for (let x = 0; x < imageData.width; x++) {
for (let y = 0; y < imageData.height; y++) {
const offset = (y * imageData.width + x) * 4;
const r = imageData.data[offset];
const g = imageData.data[offset + 1];
const b = imageData.data[offset + 2];
// if it is pure white, change its alpha to 0
if (r == 255 && g == 255 && b == 255) {
imageData.data[offset + 3] = 0;
}
}
}
ctx.putImageData(imageData, 0, 0);
// output base64
const result = canvas.toDataURL();
};
image.src = base64;
}
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?