I don't know why but my script is returning the wrong value for alpha channel.
This is what i have:
function getPixel(x,y,px,py,i){
//user click: x y
//picture location: px py
//array key: i
//location of click has to be changed to be relevant to this temp canvas
//as image will now be at position 0,0
var x = Math.round(0 - (px - x) );
var y = Math.round(0 - (py - y) );
//temp canvas
var c = document.createElement('canvas');
var context = c.getContext('2d');
c.width = pArray[i].img.width;
c.height = pArray[i].img.height;
context.drawImage(pArray[i].img,0,0);
var d = context.getImageData(x,y,1,1);
if(d[3] != 0){
console.log('Not Alpha'); //always happens
} else {
console.log('Alpha'); // never happens
}
console.log(x + ', ' + y + ', ' + c.width + ', ' + c.height + ', ' + pArray[i].img.src);
}
My console output shows:
8, 42, 128, 128, [Full URL Hidden]/images/1.png
Here is also the image I am testing it with :
Can anyone see any glaring mistake that might explain why the alpha never equals 0 ?
JSFiddle testing location x1 and y1:
http://jsfiddle.net/darkyen/UCSU2/15/
Well as per specs the .getImageData(); returns an imagedata object. In that object there is an array data which has all your data.
You were missing the data , so basically since d is the imagedata element there for it has not element defined for index 3 => d[3] === undefined,
hence it was failing ,
try d[3].data
http://jsfiddle.net/UCSU2/16/ <- here is a working fiddle
context.getImageData() returns ImageData (see this page), not Pixel Array.
So change the line
var d = context.getImageData(x,y,1,1);
to
var d = context.getImageData(x,y,1,1).data;
will do the work.
Canvas ImageData reference: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#imagedata
Pixel manipulation reference: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#pixel-manipulation
MDN ImageData reference (not complete yet): https://developer.mozilla.org/en-US/docs/DOM/ImageData
Related
I am trying to find all the hex colors in an image and if possible, circle or highlight the X, Y position of where the hex color(s) are. My current code is attempting to find all colors and almost crashes my browser and my attempt to find the X,Y coordinates of each image isn't going good either.
I have two functions doing different things, it's what I have tried to work with to give an example of what has been attempted... Any help would be great!
Any assistance would be amazing!
<canvas id="myCanvas" width="240" height="297" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<img id="origImage" width="220" height="277" src="loggraph.PNG">
<script>
function getPixel(imgData, index) {
var i = index*4, d = imgData.data;
return [d[i],d[i+1],d[i+2],d[i+3]] // [R,G,B,A]
}
function getPixelXY(imgData, x, y) {
return getPixel(imgData, y*imgData.width+x);
}
function goCheck() {
var cvs = document.createElement('canvas'),
img = document.getElementsByTagName("img")[0];
cvs.width = img.width; cvs.height = img.height;
var ctx = cvs.getContext("2d");
ctx.drawImage(img,0,0,cvs.width,cvs.height);
var idt = ctx.getImageData(0,0,cvs.width,cvs.height);
console.log(getPixel(idt, 852)); // returns array [red, green, blue, alpha]
console.log(getPixelXY(idt,1,1)); // same pixel using x,y
}
function getColors(){
var canvas = document.getElementById("myCanvas");
var devices = canvas.getContext("2d");
var imageData = devices.getImageData(0, 0, canvas.width, canvas.height);
var data = imageData.data;
// iterate over all pixels
for(var i = 0, n = data.length; i < n; i += 4) {
var r = data[i];
var g = data[i + 1];
var b = data[i + 2];
var rgb = "("+r+","+g+","+b+")";
var incoming = i*4, d = imageData.data;
var bah = [d[incoming],d[incoming+1],d[incoming+2],d[incoming+3]];
$('#list').append("<li>"+rgb+"</li>");
colorList.push(rgb);
}
$('#list').append("<li>"+[d[incoming],d[incoming+1],d[incoming+2],d[incoming+3]]+"</li>");
}
}
Must check all pixels
To find a pixel that matches a color will require, in the worst case (pixel of that color not in image), that you step over every pixel in the image.
How not to do it
Converting every pixel to a DOM string is about the worst way to do it, as DOM string use a lot of memory and CPU overhead, especially if instantiated using jQuery (which has its own additional baggage)
Hex color to array
To find the pixel you need only check each pixels color data against the HEX value. You convert the hex value to an array of 3 Bytes.
The following function will convert from CSS Hex formats "#HHH" "#HHHH", "#HHHHHH" and "#HHHHHHHH" ignoring the alpha part if included, to an array of integers 0-255
const hex2RGB = h => {
if(h.length === 4 || h.length === 5) {
return [parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16), parseInt(h[3] + h[3], 16)];
}
return [parseInt(h[1] + h[2], 16), parseInt(h[3] + h[4], 16), parseInt(h[5] + h[6], 16)];
}
Finding the pixel
I do not know how you plan to use such a feature so the example below is a general purpose method that will help and can be modified as needed
It will always find a pixel if you let it even if there is no perfect match. It does this by finding the closest color to the color you are looking for.
The reason that of finds the closest match is that when you draw an image onto a 2D canvas the pixel values are modified slightly if the image has transparent pixels (pre-multiplied alpha)
The function finds the pixel by measuring the spacial distance between the pixel and the hex color (simple geometry Pythagoras). The closest color is the one that is the smallest distance.
It will return the object
{
x, // the x coordinate of the match
y, // the y coordinate of the match
distance, // how closely the color matches the requested color.
// 0 means a perfect match
// to 441 completely different eg black and white
// value is floored to an integer value
}
If the image is tainted (cross origin, local device storage), or you pass something that can not be converted to pixels the function will return undefined
The function keeps a canvas that it uses to get pixel data as it assumes that it will be use many times. If the image is tainted it will catch the error (add a warning to the console), cleanup the tainted canvas and be ready for another image.
Usage
To use the function add it to your code base, it will setup automatically.
Get an image and a hex value and call the function with the image, CSS hex color, and optionally the threshold distance for the color match.
Eg find exact match for #FF0000
const result = findPixel(origImage, "#FF0000", 0); // find exact match for red
if (result) { // only if found
console.log("Found color #FF0000 at pixel " + result.x + ", " + result.y);
} else {
console.log("The color #FF0000 is not in the image");
}
or find color close to
const result = findPixel(origImage, "#FF0000", 20); // find a match for red
// within 20 units.
// A unit is 1 of 256
if (result) { // only if found
console.log("Found closest color within " + result.distance + "units of #FF0000 at pixel " + result.x + ", " + result.y);
}
or find closest
// find the closest, no threshold ensures a result
const result = findPixel(origImage, "#FF0000");
console.log("Found closest color within " + result.distance + "units of #FF0000 at pixel " + result.x + ", " + result.y);
Code
The function is as follows.
const findPixel = (() => {
var can, ctx;
function createCanvas(w, h) {
if (can === undefined){
can = document.createElement("canvas");
ctx = can.getContext("2d");
}
can.width = w;
can.height = h;
}
function getPixels(img) {
const w = img.naturalWidth || img.width, h = img.naturalHeight || img.height;
createCanvas(w, h);
ctx.drawImage(img, 0, 0);
try {
const imgData = ctx.getImageData(0, 0, w, h);
can.width = can.height = 1; // make canvas as small as possible so it wont
// hold memory. Leave in place to avoid instantiation overheads
return imgData;
} catch(e) {
console.warn("Image is un-trusted and pixel access is blocked");
ctx = can = undefined; // canvas and context can no longer be used so dump them
}
return {width: 0, height: 0, data: []}; // return empty pixel data
}
const hex2RGB = h => { // Hex color to array of 3 values
if(h.length === 4 || h.length === 5) {
return [parseInt(h[1] + h[1], 16), parseInt(h[2] + h[2], 16), parseInt(h[3] + h[3], 16)];
}
return [parseInt(h[1] + h[2], 16), parseInt(h[3] + h[4], 16), parseInt(h[5] + h[6], 16)];
}
const idx2Coord = (idx, w) => ({x: idx % w, y: idx / w | 0});
return function (img, hex, minDist = Infinity) {
const [r, g, b] = hex2RGB(hex);
const {width, height, data} = getPixels(img);
var idx = 0, found;
while (idx < data.length) {
const R = data[idx] - r;
const G = data[idx + 1] - g;
const B = data[idx + 2] - b;
const d = R * R + G * G + B * B;
if (d === 0) { // found exact match
return {...idx2Coord(idx / 4, width), distance: 0};
}
if (d < minDist) {
minDist = d;
found = idx;
}
idx += 4;
}
return found ? {...idx2Coord(found / 4, width), distance: minDist ** 0.5 | 0 } : undefined;
}
})();
This function has been tested and works as described above.
Note Going by the code in the your question the alpha value of the image and CSS hex color is ignored.
Note that if you intend to find many colors from the same image this function is not the best suited for you needs. If this is the case let me know in the comment and I can make changes or instruct you how to optimism the code for such uses.
Note It is not well suited for single use only. However if this is the case change the line const findPixel = (() => { to var findPixel = (() => { and after you have used it remove the reference findpixel = undefined; and JS will clean up any resources it holds.
Note If you also want to get the actual color of the closest found color that is trivial to add as well. Ask in the comments.
Note It is reasonably quick (you will be hard pressed to get a quicker result) but be warned that for very large images 4K and above it may take a bit, and on very low end devices it may cause a out of memory error. If this is a problem then another solution is possible but is far slower.
I am guessing that there may be an answer about this on the internet somewhere but I can not find it. I am making a graphing calculator and I tried to make "plots" follow a certain function y = 2x. Though i can not seem to find out how to make the plots have their own x and y (their unique x and y).
function CreateDot() {
this.ix = 1; //this is the x value of the function and is also used in y = 2x
this.fy = this.ix * 2; //this is the y value of the funciton y = 2x
this.newDot = function() {
//this is defining the x value of the plot and scaling the value to have it follow the grid
this.x1 = spacing * this.ix;
// this is defining the y value of the plot and scaling the value to have it follow the grid
this.y1 = 500 - spacing * this.fy; //
//this is the code for creating the "plot" which is a dot on the screen and red in colour
stroke(255,0,0);
strokeWeight(25);
//this is defining the position of the point with the values above x1 and y1
point(this.x1,this.y1);
//this is supposed to allow the next value of x=2 in the function y = 2x and so allowing the next coordinates of the plot to be ( 2, 4 ) and the next dot (3, 6) and so on.
this.ix = this.ix + 1;
}
}
I what I noticed is that after I made this a constructor function and put new dots into an array and limited it to 5, I ran it and one dot flew all the way to the right. I printed each of the objects x and y and they had the same values..
So my question is how do I make sure Each object has their own unique x and y values?
Thanks in advance!
You have to put the function either on the prototype and call it that way, or you have to pass the dots as parameters to the update function:
// Prototype
function CreateDot( spacing ) {
this.ix = 1;
this.fy = this.ix * 2;
this.x1 = spacing * this.ix;
this.y1 = 500 - spacing * this.fy;
}
CreateDot.prototype.addOne = function() {
// this refers to: 'the dot you call addOne upon'.
this.ix = this.ix + 1;
};
var myDot = new CreateDot( 250 );
console.log( 'before myDot.addOne() : ' + myDot.ix );
myDot.addOne();
console.log( 'after myDot.addOne() : ' + myDot.ix );
// Seperate function
var addOne_f = function( dot ) {
// this refers to 'the window instance', so you need to use the 'dot' parameter to refer to the dot.
dot.ix = dot.ix + 1;
};
console.log( 'before addOne_f( myDot ) : ' + myDot.ix );
addOne_f( myDot );
console.log( 'after addOne_f( myDot ) : ' + myDot.ix );
// Inside an array:
var dots = [
new CreateDot( 200 ),
new CreateDot( 225 ),
new CreateDot( 250 )
];
// Update all dots
dots.forEach( dot => dot.addOne());
// Update the second dot again to show they're not linked
dots[ 1 ].addOne();
console.log( dots.map( dot => dot.ix ));
I have a DICOM image and i want to apply a W/L on the image. In normal terms, i am trying to change the color of the image as i drag the mouse over it. however it becomes completely white when i try to change it.
here's what I am doing.
this is just a part of the code.
var imageData = ctx.getImageData(0, 0, img.width, img.height);
var data = imageData.data;
var pixels = imageData.data,
len = pixels.length;
var a = 256 * slope / window_width,
b = 256 * ((intercept - window_level) / window_width);
for (i = 0; i < len; i += 4) {
//pixval = a * (pixels[i] * 256 + pixels[i + 1]) + b;
//pixels[i] = pixels[i + 1] = pixels[i + 2] = pixval
if (pixels[i + 3] == 0)
continue;
var pixelIndex = i;
var red = pixels[pixelIndex]; // red color
var green = pixels[pixelIndex + 1]; // green color
var blue = pixels[pixelIndex + 2]; // blue color
var alpha = pixels[pixelIndex + 3];
var pixelValue = a * (red * 256 + green + b);
pixels[i] = pixels[i + 1] = pixels[i + 2] = pixelValue;
pixels[i + 3] = 0xff;
//console.log(pixelValue == pixval);
}
here's the JSFIDDLE with the complete code.
The main problem is that you're changing your image data when drawing it. You need to store a raw version of your image data, then "apply" your window level dynamically when drawing. Otherwise, you're just compounding the changes on top of each other.
Try making two canvas objects: one (offscreen) to load and store your image data and the other to draw it. Making this relatively small change to your code, it appeared to work right.
There may be other issues with your starting values -- I think you may be missing rescale slope and intercept -- but fixing the first issue should make everything easier to figure out.
Here's the jsfiddle: https://jsfiddle.net/8ne1pnaj/5/
var imageData = ctxData.getImageData(0, 0, img.width, img.height);
// ...
ctx.putImageData(imageData, 0, 0)
ctxData is the original data. ctx is the drawing context.
I am trying to manipulate imageData but my imageData array returns all 0's after I load up the image and get its pixel data.
There are a few html elements like sliders and text boxes. Please ignore those.
There is an ImageObject data structure where I am storing all image properties like image,pixelData and so on..
i first load the image, get its pixel data and then return a callback to store ImageObject.imageData. However in the log ImageObject.data returns all 0's.
ImageObject = {};
var MainCtx;
var MainCanvas;
//get the image pixel properties
function start()
{
//load up the main canvas and ctx
MainCanvas = document.getElementById("canvas");
MainCtx = MainCanvas.getContext('2d');
//first load up the image and then get its pixel data
ImageObject.loadImage(function(imageData){
ImageObject.imageData = imageData;
ImageObject.data = ImageObject.imageData.data;
console.log(ImageObject.data); // -> data return all 0's in the array
for(var i = 0; i < ImageObject.data.length; i += 4) {
var brightness = 0.34 * ImageObject.data[i] + 0.5 * ImageObject.data[i + 1] + 0.16 * ImageObject.data[i + 2];
// red
ImageObject.data[i] = brightness;
// green
ImageObject.data[i + 1] = brightness;
// blue
ImageObject.data[i + 2] = brightness;
}
ImageObject.ctx.putImageData(ImageObject.imageData,ImageObject.image.width,ImageObject.image.height);
});
}
ImageObject.loadImage = function(callback)
{
ImageObject.image = new Image();
ImageObject.image.src = 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg';
ImageObject.image.addEventListener('load',function()
{
MainCtx.drawImage(ImageObject.image,0,0);
callback(ImageObject.getImageData(ImageObject.image));
});
}
ImageObject.getImageData = function(img)
{
this.canvas = getCanvas(img.width,img.height);
this.ctx = this.canvas.getContext('2d');
return this.ctx.getImageData(0,0,this.canvas.width,this.canvas.height);
}
function getCanvas(w,h)
{
var c = document.createElement('canvas');
c.width = w;
c.height = h;
return c;
}
start();
I have shared the jsFiddle link below
jsFiddle
Can someone please take a look as to what am I doing wrong?
Your issue is that you're getting your image data from a new canvas that you have created inside the getCanvas function. You need to run getImageData on the existing canvas.
Here is an update to the console.log
console.log(MainCtx.getImageData(0, 0, MainCanvas.width, MainCanvas.height));
This still will not work straight away as you are loading the image from a different origin, so make sure you host the image locally, first.
Try to put the information inside a 32 bit unsigned integer array through its buffer.
Something like
var img = canvas.getImageData(),
data32 = new Uint32Array(img.data.buffer);
//loop through the pixels
for(var i=0; i<data32.length, i++){
var pixel = data32[i];
//get the colors
var r = (pixel) & 0xff,
g = (pixel >> 8) & 0xff,
b = (pixel >> 16) & 0xff;
a = (pixel >> 24) & 0xff;
//put the pixels back in (no change)
data32[i] = (a << 24)
| (b << 16)
| (g << 8)
| r;
}
Author: Matt Lockyer
Retrieved from: http://mattlockyer.github.io/iat455/workshop-1.html
The quick answer is:
You try to get image data from a new(empty) canvas.
Explanation:
In your function getImageData, you set your canvas using a function called getCanvas.
In this function, instead of using your actual canvas with your image drawn on it, you create a new canvas (document.createElement('canvas');)
Since, you have set your canvas as MainCanvas, you should use it in your function getImageData like this:
this.canvas = MainCanvas;
Unfortunately, I can't make it work in your jsfiddle because of Cross-Origin Resource Sharing policy.
I'm trying to add new artboard with the help of java script. I wasn't able to find solution nowhere. The scripting guidelines from adobe are just poor (to not use more strong words).
What ever I'm trying it returns the error:
Error 1242: Illegal argument - argument 1 - Rectangle value expected
when I use value of artboard.artboardRect from other artboard then it creates artboard in the same place but I can't modify it (resize) which makes this option useless.
artboards.add(artboards[0].artboardRect);//works
artboards.add([0,0,200,50]);//Error 1200: an Illustrator error coccurred: 1346458189('PARAM')
var rect = artboards[0].artboardRect;
rect[0] = 0;
rect[1] = 0;
rect[2] = 200;
rect[3] = 50;
artboards.add(rect);//Error 1242: Illegal argument - argument 1 - Rectangle value expected
After searching extensively I've found this workaround:
var newRect = function(x, y, width, height) {
var l = 0;
var t = 1;
var r = 2;
var b = 3;
var rect = [];
rect[l] = x;
rect[t] = -y;
rect[r] = width + x;
rect[b] = -(height - rect[t]);
return rect;
};
artboard = artboards.add(artboards[0].artboardRect);
artboard.name = "new name";
artboard.artboardRect = newRect(0, 0, 200, 50);
In several places in Illustrator Scripting PDFs rect or someOtherPropRect is defined as "array of 4 numbers". For example,
var document = app.documents.add();
$.writeln(document.artboards[0].artboardRect); // 0,792,612,0
returned values corresponds to topLeftX, topLeftY, bottomRightX, bottomRightY.
To make sense of these values, we need to take a look at the coordiate system of Illustrator. Illustrator's UI uses a modified coordinate system, not the actual cartesian one. In other words, top left of the screen is the origin, not bottom left. But when scripting, actual cartesian coordinate system is used.
Because of this difference in coordinate system, entered values that are on the Y axis should be nagative. So, if I want to reposition and resize the artboard, say, move it to (200,50) and resize it to (400,300), what I need to do is:
var document = app.activeDocument;
var artboard = document.artboards[0];
var x = 200;
var y = 50;
var w = 400;
var h = 300;
artboard.artboardRect = [x, -y, (x + w), -(y + h)];
$.writeln(document.artboards[0].artboardRect); // 200, -50, 600, -350
This solution can be wrapped in a function:
function rect(x, y, w, h) {
return [x, -y, (x + w), -(y + h)];
}
artboard.artboardRect = rect(200, 50, 400, 300);
Both Y and H should be negative, otherwise you'll get an error saying
Error 1200: an Illustrator error occurred: 1346458189 ('PARM')
or incorrect reposition/resize.
Just in case someone else encounters Error: an Illustrator error occurred: 1346458189 ('PARM').
In CS6 at least this happens if height is not negative or if width is negative. This applies to all values of type rect.
x and y can be positive or negative, it does't matter.
so this will work:
app.activeDocument.artboards.add([0 , 0, 200, -50]);
app.activeDocument.artboards.add([-10 , -10, 200, -50]);
app.activeDocument.artboards.add([10 , 10, 200, -50]);
but this will not work:
app.activeDocument.artboards.add([0 , 0, 200, 50])
app.activeDocument.artboards.add([0 , 0, -200, -50])
app.activeDocument.artboards.add([0 , 0, -200, 50])