Pixel manipulation and canvas - javascript

Is there a way in javascript to change the alpha channels of each pixel into being fully transparent (a=0) while coding for pixel manipulation (meaning that you can still change the transparency in some of the alpha channels as desired)?
Basically, what I'm doing is: given some data for a specific image, I manipulate the pixel array using an algorithm so that some pixels become fully transparent unless they satisfy some certain condition. In the case of them satisfying the condition I want them to be fully opaque, aka alpha=1. However, because of a complication with the way the algorithm works, I need to have my data "reset"; meaning I want the pixel array to start off as having every alpha = 0. I can provide code if that helps in better understanding the scope of my question.
Thanks so much.
EDIT: I'm looking more for a method/one-line code. Would context.globalAlpha = 0 serve the purposes? Is there any pitfall I should be careful about?
EDIT2: This is my code. Does globalAlpha where I've put it do what I'm expecting it to do? I'm not sure how to use it...
function getBoundary(imagedata){
var imageData = new Array(imagedata.data.length);
imageData = imagedata.data;
var w = imagedata.width;
var h = imagedata.height;
var color1 = [];
var colorRight = [];
var colorDown = [];
context.globalAlpha = 0;
for (var i = 0; i < 4*w*h; i +=4) {
color1 = [imageData[i],imageData[i+1],imageData[i+2]];
colorRight = [imageData[i+4],imageData[i+5],imageData[i+6]];
colorDown = [imageData[4*w+i],imageData[4*w+i+1],imageData[4*w+i+2]];
if(colorRight = [255,255,255]){ //if right is white
if(color1 = [0,0,0]){
imageData[i+3] = 255;
}
else{
if(colorDown = [0,0,0]){
imageData[4*w+i+3] = 255;
}
}
}
else{ //colorRight = black
if(color1 = [0,0,0]){
if(colorDown = [255,255,255]){
imageData[i+3] = 255;
}
}
else if(color1 = [255,255,255]){
imageData[i+7] = 255;
if(colorDown = [0,0,0]){
imageData[4*w+i+3] = 255;
}
else{
}
}
}
}
console.log("done");
imagedata.data = imageData;
return imagedata;
}

You can use getImageData and flip all the alpha elements to zero:
You can create a function that zeros the alpha of all pixels on the canvas like this:
function zeroAllAlpha(){
var imageData=context.getImageData(0,0,canvas.width,canvas.height);
var data=imageData.data;
// set all alpha elements to zero (fully transparent);
for(var i=3;i<data.length;i+=4){
data[i]=0;
}
context.putImageData(imagedata,0,0);
}
And you can call the function with one line like this:
zeroAllAlpha();

Related

Why is canvas messing with my image's colors?

I'm developing an app that has a painting feature. The user can paint on an image that is initially made of only pure black and pure white pixels. Later, after the user has finished painting, I need to do some processing on that image based on the colors of each pixel.
However, I realized that by the time I processed the image, the pixels weren't purely black/white anymore, but there were lots of greys in between, even if the user didn't paint anything. I wrote some code to check it and found out there were over 250 different colors on the image, while I was expecting only two (black and white). I suspect canvas is messing with my colors somehow, but I can't figure out why.
I hosted a demo on GitHub, showcasing the problem.
The image
This is the image. It is visibly made of only black and white pixels, but if you want to check by yourself you can use this website. It's source code is available on GitHub and I used it as a reference for my own color counting implementation.
My code
Here is the code where I load the image and count the unique colors. You can get the full source here.
class AppComponent {
/* ... */
// Rendering the image
ngAfterViewInit() {
this.context = this.canvas.nativeElement.getContext('2d');
const image = new Image();
image.src = 'assets/image.png';
image.onload = () => {
if (!this.context) return;
this.context.globalCompositeOperation = 'source-over';
this.context.drawImage(image, 0, 0, this.width, this.height);
};
}
// Counting unique colors
calculate() {
const imageData = this.context?.getImageData(0, 0, this.width, this.height);
const data = imageData?.data || [];
const uniqueColors = new Set();
for (let i = 0; i < data?.length; i += 4) {
const [red, green, blue, alpha] = data.slice(i, i + 4);
const color = `rgba(${red}, ${green}, ${blue}, ${alpha})`;
uniqueColors.add(color);
}
this.uniqueColors = String(uniqueColors.size);
}
This is the implementation from the other site:
function countPixels(data) {
const colorCounts = {};
for(let index = 0; index < data.length; index += 4) {
const rgba = `rgba(${data[index]}, ${data[index + 1]}, ${data[index + 2]}, ${(data[index + 3] / 255)})`;
if (rgba in colorCounts) {
colorCounts[rgba] += 1;
} else {
colorCounts[rgba] = 1;
}
}
return colorCounts;
}
As you can see, besides the implementations being similar, they output very different results - my site says I have 256 unique colors, while the other says there's only two. I also tried to just copy and paste the implementation but I got the same 256. That's why I imagine the problem is in my canvas, but I can't figure out what's going on.
You are scaling your image, and since you didn't tell which interpolation algorithm to use, a default smoothing one is being used.
This will make all the pixels that were on fixed boundaries and should now span on multiple pixels to be "mixed" with their white neighbors and produce shades of gray.
There is an imageSmoothingEnabled property that tells the browser to use a closest-neighbor algorithm, which will improve the situation, but even then you may not have a perfect result:
const canvas = document.querySelector("canvas");
const width = canvas.width = innerWidth;
const height = canvas.height = innerHeight;
const ctx = canvas.getContext("2d");
const img = new Image();
img.crossOrigin = "anonymous";
img.src = "https://raw.githubusercontent.com/ajsaraujo/unique-color-count-mre/master/src/assets/image.png";
img.decode().then(() => {
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, 0, 0, width, height);
const data = ctx.getImageData(0, 0, width, height).data;
const pixels = new Set(new Uint32Array(data.buffer));
console.log(pixels.size);
});
<canvas></canvas>
So the best would be to not scale your image, or to do so in a computer friendly fashion (using a factor that is a multiple of 2).

How to specify color properties (fill and stroke) of a path in JS

So I'm trying to optimize some designs in my process, for this task I'm experimenting with javascripts for Illustrator and Photoshop, the issue i have right now is that for drawing, filling and changing the stroke color of my paths. So how I can an array of colors in JS for Illustrator to fill a path of one color and the stroke of another color?
Right know it just take my last declared CMYK color and ignores previous ones.
Code:
var RectangleColor = new CMYKColor();
RectangleColor.black = 0;
RectangleColor.cyan = 0;
RectangleColor.magenta = 0;
RectangleColor.yellow = 0;
var RectangleColorStroke = new CMYKColor();
RectangleColor.black = 0;
RectangleColor.cyan = 80;
RectangleColor.magenta = 80;
RectangleColor.yellow = 0;
and in the drawing method
rect.filled = true;
rect.fillColor = RectangleColor;
rect.strokeColor = RectangleColorStroke;
Note: I don't really know if exist a property named strokeColor, but since it didn't show any error then was decided to use it.
Well i know is not the most clean way but already found how.
declared this variable
var col = new GrayColor();
col.gray = 100;
have this at the code
var rect = artLayer.pathItems.rectangle (monthsCubeY,monthsCubeX,180,100); //draw rectangle at the selected coordenates
monthsCubeX = monthsCubeX + 190; //move the coordenates for the next rectangle
rect.filled = true;
rect.stroked = true;
strokeWidth = 1;
rect.fillColor = RectangleColor;
strokeColor = col;
I dont know much of coding, but this seems to work for me.

JavaScript running pretty slow in specific computer

I writing a code that take a Black & White image and check the pixels in an specific area (with an square shape) and finally retur the sum of how many of them are balck, each pixel of the area is read in a For loop like the next example:
function is_box_black_corner(x,y,width,heigth){
var counter=0;
for (var i=x; i<(x+width); i++){
for (var j=y; j<(y+heigth); j++){
if(my_isblack(i,j)==1){
counter++;
}
}
}
And as you can see inside the for loop a I call a function that verifies if the specific pixel is fairly black:
function my_isblack(x,y){
var p = ctx.getImageData(x, y, 1, 1).data;
if(p[0]<50 && p[1]<50 && p[2]<50){
return 1;
}
else{
return 0;
}
}
As you can imagine, this is a little bit computational expensive. but the problem is that with my computer, suddenly it got much slower than others (even with worst processors). I already check the RAM memory and the processor and none of them were used more than 30%, and the processor before running the code is close to 0%.
And don’t know where else to look. I appreciate some help, also if somebody knows how to do this much faster it will be highly apreciated
I will try wiht one call to getImageData as suggested by #ASDFGerte:
var x=10; var y=10; var width=50; var height=50;
var counter=0;
var image;
var p; //global data
function init(){
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(10, 10, 50, 50); //this is just a black square
image = ctx.getImageData(x, y, width, height); //Load the image
p = image.data; //get the data of the image
is_box_black_corner();
};
function is_box_black_corner(){
for (var i=x; i<(x+width); i++){
for (var j=y; j<(y+height); j++){
if(my_isblack(i,j)==1){
counter++;
}
}
}
console.log(counter);
};
function my_isblack(x,y){
if(p[0]<50 && p[1]<50 && p[2]<50){ //check the global data
return 1;
}
else{
return 0;
}
};

HTML5 Canvas transparency weirdness with pngs

I'm trying to apply transparency to some ImageData retrieved from a canvas. Simple, right? Just set an appropriate value to every fourth datapoint... only, it only seems to work with pixels that don't have pre-existing alpa values.
//create 2 canvases
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
canvas.id = "tempCanvas";
var canvas2 = document.createElement("canvas");
document.body.appendChild(canvas2);
var ctx2 = canvas2.getContext("2d");
canvas2.id = "tempCanvas2";
//draw the png in the first canvas
var testimg = document.getElementById("testimg");
ctx.drawImage(testimg, 0, 0, 200, 200);
//helper function to opacity to the pixels
opacity = function(pixels, value){
var d = pixels.data;
for(var i=0;i<d.length;i+=4){
d[i+3] = value*255; //scale by 255
if(d[i+3] > 255) d[i+3] = 255; //clamp
}
return pixels;
}
//make the first canvas' image data opaque
var data = ctx.getImageData(0,0,200,200);
data = opacity(data, 0.5);
//draw to second canvas
ctx2.fillStyle="#900";
ctx2.fillRect(0,0,200,200);
//should get a jsfiddle logo overlayed on red background
//instead, get yucky grey background
ctx2.putImageData(data, 0, 0);
(It's a little hard to reproduce because of same-origin policy, but here's a fiddle that uses the png jsfiddle logo: http://jsfiddle.net/97c0eetr/ )
Why does this opacity algorithm not work for the jsfiddle logo (and my other transparent pngs that I'm testing on http://localhost)?
Edit: FF42 Ubuntu, if it makes a difference.
You've hit the big endian / little endian issue : On most of today's computers/devices, the endianness is little endian, meaning that for a 32 bit value, byte order is 3-2-1-0.
So the opacity, which is the 4th byte (index 3) will be found at pixelIndex * 4 + 0 ... == pixelIndex * 4.
Just change, in your code, your opacity function to :
opacity = function(pixels, value){
var d = pixels.data;
for(var i=0;i<d.length;i+=4){
d[i] = value*255; //scale by 255
if(d[i] > 255) d[i] = 255; //clamp
}
return pixels;
}
updated fiddle here :
http://jsfiddle.net/97c0eetr/5/
The best 100% solution of course is to have two opacity function that you choose depending on endinannes of the browser. But by betting on little endian, you cover like 99% of the devices.
If you want to know endianness, i once made a small gist to get it, find it here :
https://gist.github.com/TooTallNate/4750953
Here's the code :
function endianness () {
var b = new ArrayBuffer(4);
var a = new Uint32Array(b);
var c = new Uint8Array(b);
a[0] = 0xdeadbeef;
if (c[0] == 0xef) return 'LE';
if (c[0] == 0xde) return 'BE';
throw new Error('unknown endianness');
}
And by the way, your function could simplify to
opacity = function(pixels, value){
value = Math.floor(value * 255);
if (value>255) value = 255;
var d = pixels.data;
for(var i=0;i<d.length;i+=4){
d[i] = value;
}
return pixels;
}
Edit : So why the background isn't red ? -->> When using putImageData, you are using a raw 1 pixel for 1 pixel copy method. So everything gets replaced, whatever, for instance, the globalCompositeOperation or any setting. So the red background is just overwritten by the new data.
Solution is to putImageData on a temporary canvas -it will be written with the right opacity-, then to drawImage it on your target canvas, so that the opacity is taken into account for the draw.
Since you already had two canvases, change was easy, see updated fiddle here :
http://jsfiddle.net/97c0eetr/4/

Are there any javascript libs to pixel compare images using HTML5 canvas (or any other means)?

Would like to test if 2 images pixel match and/or whether a smaller sized image pixel matches some same sized section of a larger image.
The first part, test if two images match perfectly, is pretty straight forward.
//assuming data1,data2 are canvas data of images of the same size
function isMatch(data1,data2){
for(var i = 0; i<data1.length; i++){
if(data1[i] != data2[i]) return false;
}
return true;
}
As long as you aren't using extremely hi-res images I wouldn't worry about speed, one iteration trough canvas data like this is typically very fast, ~10ms per Mega Pixel on a decent computer with a modern browser. Remember width and height of the images must be the same. For most practical purposes this yes or no match is not very useful, encoding errors lead to imperceptible changes in images, it is best to use a metric to measure how far apart the two images are. A naive but fairly effective metric is the RMS of the two images:
//assuming data1,data2 are canvas data of images of the same size
function rmsDiff(data1,data2){
var squares = 0;
for(var i = 0; i<data1.length; i++){
squares += (data1[i]-data2[i])*(data1[i]-data2[i]);
}
var rms = Math.sqrt(squares/data1.length);
return rms;
}
Here the rms will range from [0,255], 0 if the images match exactly, 255 if one image is all rgba(0,0,0,0) and the other is all rgba(255,255,255,255). How to threshold what is an acceptable rms difference is up to you to tune (around 1 to 2 perhaps).
For the second part of your question, whether a smaller sized image pixel matches some same sized section of a larger image, I'm assuming that they are not translated and that we know the scale difference between the two, otherwise this would open it up into a very complicated and interesting problem see Pattern Recognition. Then with this egregious assumption I would use the canvas to scale the large image down to the exact same size as the smaller one and get the rms between them. Don't test for an exact match as scaling will always create slight errors and never be an exact match.
I don't know if there is anything like that out there, and I'm not sure the web-browser is the place to do it in... however I have been playing around with the canvas element a little bit and wanted to share what I come up with. Maybe you find something useful in it.
The code below is tested in Chrome 20 only.
You must have the images and the script loaded from the same domain, or it will not work (I didn't find a way to upload images on jsfiddle so I didn't put this there)
To test if two images are identical I load each image and draw it on a canvas element, and compare the width, height and pixeldata.
index.html
<!doctype html>
<html>
<head>
<title></title>
<script type="application/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script type="application/javascript" src="compare.js"></script>
</head>
<body>
</body>
</html>
Identical pixels
compare.js
$(function(){
function compare(url_a, url_b, callback) {
var canvas = $("<canvas>").appendTo(document.body)[0];
var ctx = canvas.getContext('2d');
var
image_a = new Image(),
image_b = new Image();
function getData(image) {
//Set canvas to the same size as the image
canvas.width = image.width;
canvas.height = image.height;
//Draw the image on the canvas
ctx.drawImage(image, 0, 0);
//Get the image data from the canvas.
return ctx.getImageData(0,0,canvas.width,canvas.height);
}
function compareIdentical(A,B) {
// Check sizes
if (
A.width != B.width ||
A.height != B.height ||
A.data.length != B.data.length
) {
return callback(false,'Not same size');
}
var a=A.data; b=B.data;
//Check image data
for (var idx=0, len=A.data.length; idx < len; ++idx) {
if (a[idx] !== b[idx]) {
return callback(false,'Not same');
}
}
return callback(true,'Identical');
}
$(image_a).load(function(){
console.log('Image A loaded');
image_a = getData(image_a);
//Load image b
image_b.src = url_b;
});
$(image_b).load(function(){
console.log('Image B loaded');
image_b = getData(image_b);
canvas.parentNode.removeChild(canvas);
compareIndentical(image_a, image_b);
//comparePartOf(image_a, image_b);
});
//Load image a
image_a.src = url_a;
}
//IMPORTANT: Images must be loaded from the same domain as the
//script, or getImageData will not give any data.
compare(
'IMG_2195.JPG',
'IMG_2195.JPG',
function(result,message){
console.log(result,message);
}
);
});
To test if an image is part of another image, is slow.
Compare sizes: if SMALL > BIG then it can't be part of BIG.
For every x and y of BIG, compare if SMALL is a fit.
(add this to the code above, and change the call after image B is loaded)
function comparePartOf(bigimg, smallimg) {
if (
bigimg.width < smallimg.width ||
bigimg.height < smallimg.height ||
bigimg.data.length < smallimg.data.length
) {
return callback(false,'bigimg smaller than smallimg');
}
var
big=bigimg.data,
small=smallimg.data,
idx,
len=small.length/4,
big_x,
big_y,
big_width = bigimg.width,
big_height = bigimg.height,
small_x,
small_y,
small_width = smallimg.width,
small_height = smallimg.height,
lenw = big_width - small_width,
lenh = big_height - small_height,
big_offset,
small_offset,
result=false;
for(big_x=0; big_x < lenw; ++big_x) {
for(big_y=0; big_y < lenh; ++big_y) {
result = true;
for (idx=0; idx < len; ++idx) {
small_x = idx % small_width;
small_y = Math.floor(idx / small_width);
big_offset = (
(big_x + small_x) + ((big_y + small_y) * big_width)
)<<2;
small_offset = idx << 2;
if (
big[big_offset++] != small[small_offset++] ||
big[big_offset++] != small[small_offset++] ||
big[big_offset++] != small[small_offset++] ||
big[big_offset] != small[small_offset]
) {
result = false;
break;
}
}
if (result) return callback(true,'Found at '+big_x+' '+big_y);
}
}
return callback(false,'not found');
}
you could try paper.js
it allows you to use your image as a raster
http://paperjs.org/tutorials/images/using-pixel-colors/
http://paperjs.org/tutorials/images/working-with-rasters/
the paper.js lib can definitely do more than comparing images..
here's a simple script that works..
// rasterize both images
var im_a = new Raster('image_a');
var im_b = new Raster('image_b');
var ratio = Math.round(im_a.width / 100);
// downsize the images so we don't have to loop through all the pixels.
im_a.size = new Size(im_a.width/ratio, im_a.height/ratio);
im_b.size = new Size(im_b.width/ratio, im_b.height/ratio);
//hide the images, so they don't display on the canvas
im_a.visible = false;
im_b.visible = false;
var different = false;
// check the image dimensions
if((im_a.width == im_b.width) && (im_a.height == im_b.height)){
// loop through the pixels
for(x = 0 ; x < im_a.width ; x++){
for(y = 0; y < im_a.height; y++){
if(im_a.getPixel(x,y) != im_b.getPixel(x,y) ){
different = true;
break;
}
}
}
}else{
alert('not the same size');
}
Well, i don't know if there's something out there for that so, i'll give it a shot:
first, you will need a function to compare two arrays, you will find one anywhere on the web; like:
function arrayCompare(){
if (x === y)
return true;
if (x.length != y.length)
return false;
for (key in x) {
if (x[key] !== y[key]) {
return false;
}
}
return true;
}
then you create a function that will return the imageData, of the image:
function getImgData(imgUrl){
var img = new Image(); // Create new img element
img.src = '../img/logo.png'; // params are urls for the images
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(img1,0,0);
var imageData = ctx.getImageData(0,0,canvas.width, canvas.height);
//returns an array, see documentation for * info
return imageData.data;
}
then you can do something like:
var imgData1 = getImgData('../img/image1');
var imgData2 = getImgData('../img/image2');
if(arrayCompare(imgData1, imgData2)){
//images are the same;
} else {
// images are different
}
Now, this covers the first part of the problem. I'm sure that with a little bit of effort, you can find a function on the web to compare 2 arrays finding out if one is contained by the other.
As a final note, i am aware that these arrays are very very large and that this implemention may not be cost effective. I know that there are probably better solutions for this problem, but this was what i could think of. I'm sure you can find more effective array comparers around and, maybe my approach is completely useless near others that i don't know.
Resemble.js is a JavaScript library for image comparison. From GitHub description:
Resemble.js can be used for any image analysis and comparison
requirement you might have in the browser. However, it has been
designed and built for use by the PhantomJS powered visual regression
library PhantomCSS. PhantomCSS needs to be able to ignore antialiasing
as this would cause differences between screenshots derived from
different machines.
Resemble.js uses the HTML5 File API to parse image data, and canvas
for rendering image diffs.
assuming data1 & data2 are arrays. you can use canvasPixelArray(s)
var same = [];
same = data1 && data2;
if (same.length < data1.length) {
//your code here
}

Categories