I'm using the embedded Rhino Interpreter in Blue (a music composition environment for Csound) to generate a "score" (music notation). In blue you can do this by writing a function an then doing
score = myFunction()
My function gets an image using onLoad and extracts the pixel information, which will be used to generate the score. The problem is my function doesn't get enough time to load the image and return the data before it assigns it to a variable. I've tried using setTimeout() but that didn't help.
I tried this in a browser and it returns "undefined" indeed.
Basically I need a way of delaying the assignment to the score variable. Is this possible?
Thank you
function score(){
var img = new Image();
img.src = "http://static.webshopapp.com/shops/023001/files/024718445/256x256x2/major-dog-barbell-mini.jpg";
img.crossOrigin = "Anonymous";
var score = "abc";
img.onload = function(){
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
score = "i1 0 2 440 0.5\n"
for (var i=0;i<imgData.data.length;i+=4){
score += "i1 + 0.1 " + (imgData.data[i] + 500).toString() + " 0.5\n"
}
return score;
}
}
score = score();
// TRY THIS IN BROWSER - RETURNS UNDEFINED
//console.log(score())
(Author of Blue here)
For Blue, it is actually using Nashorn now which is built into Java 8. (I have renamed the object to JavaScriptObject in the new Blue release.)
Nashorn provides a JS engine but does not, as far as I understand, provide all of the APIs one expects in a browser. I ran and debugged your code and found some exceptions being thrown regarding "document" and "Image" not being defined. I rewrote the code using Java objects, such as:
function genScore(){
var url = new java.net.URL("http://static.webshopapp.com/shops/023001/files/024718445/256x256x2/major-dog-barbell-mini.jpg");
var img = javax.imageio.ImageIO.read(url);
score = "i1 0 2 440 0.5\n"
for (var i = 0; i < img.getHeight(); i++) {
for (var j = 0; j < img.getWidth(); j++) {
var rgb = img.getRGB(i, j);
score += "i1 + 0.1 " + (rgb + 500).toString() + " 0.5\n"
};
}
return score;
}
score = genScore();
and that roughly worked. (I think your code is using just the red values if I understood correctly; this code would have to be modified with a bit mask and shift to get just the R value from the RGB; more information about Java's BufferedImage class available at https://docs.oracle.com/javase/7/docs/api/java/awt/image/BufferedImage.html).
What you need, is a callback function passed into the score function that will be fired when the image has been loaded:
// Adding a callback function as parameter
function score(callback){
var img = new Image();
img.src = "http://static.webshopapp.com/shops/023001/files/024718445/256x256x2/major-dog-barbell-mini.jpg";
img.crossOrigin = "Anonymous";
var score = "abc";
img.onload = function(){
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
score = "i1 0 2 440 0.5\n"
for (var i=0;i<imgData.data.length;i+=4)
{
score += "i1 + 0.1 " + (imgData.data[i] + 500).toString() + " 0.5\n"
}
// Now we can run the callback with our score data
return callback(score);
}
}
score(function(score){
console.log(score);
// Do your stuff with score data...
});
Related
I am working on a small app, that loads user image onto a server, lets him choose one of the filters and gives image back.
I need to somehow save the initial image data with no filters applied.
But as i found out, in JS there is no natural way to copy vars.
I tried using LoDash _.clone() and one of the jQuery functions to do this, but they didn't work.
When I applied a cloned data to image, function putImageData couldn't get the cloned data because of the wrong type.
It seems, that clone functions somehow ignore object types.
Code:
var img = document.getElementById("image");
var canvas = document.getElementById("imageCanvas");
var downloadLink = document.getElementById("download");
canvas.width = img.width;
canvas.height = img.height;
var context = canvas.getContext('2d');
context.drawImage(img, 0, 0, img.width, img.height);
document.getElementById("image").remove();
initialImageData = context.getImageData(0, 0, canvas.width, canvas.height); //initialImageData stores a reference to data, but I need a copy
///////////////////////
normalBtn.onclick = function(){
if(!(currentState == converterStates.normal)){
currentState = converterStates.normal;
//here I need to apply cloned normal data
}
};
So, what can I do here???
Thanks!!!
The correct way to copy a typed array is via the static function from
eg
var imageData = ctx.getImageData(0,0,100,100);
var copyOfData = Uint8ClampedArray.from(imageData.data); // create a Uint8ClampedArray copy of imageData.data
It will also allow you to convert the type
var copyAs16Bit = Uint16Array.from(imageData.data); // Adds high byte. 0xff becomes 0x00ff
Note that when converting to a smaller type the extra bits are truncated for integers. When converting from floats the value not the bits are copied. When copying between signed and unsigned ints the bits are copied eg Uint8Array to Int8Array will convert 255 to -1. When converting from small int to larger uint eg Int8Array to Uint32Array will add on bits -1 becomes 0xffff
You can also add optional map function
// make a copy with aplha set to half.
var copyTrans = Uint8ClampedArray.from(imageData.data, (d, i) => i % 4 === 3 ? d >> 1 : d);
typedArray.from will create a copy of any array like or iterable objects.
Use :
var image = …;
var data = JSON.parse(JSON.stringify(image).data);
var arr = new Uint8ClampedArray(data);
var copy = new ImageData(arr, image.width, image.height);
An ImageData object holds an Uint8ClampedArray which itself holds an ArrayBuffer.
To clone this ArrayBuffer, you can use its slice method, or the one from the TypedArray View you get :
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'orange';
ctx.fillRect(0,0,300,150);
var original = ctx.getImageData(0,0,300,150);
var copiedData = original.data.slice();
var copied = new ImageData(copiedData, original.width, original.height);
// now both hold the same values
console.log(original.data[25], copied.data[25]);
// but can be modified independently
copied.data[25] = 0;
console.log(original.data[25], copied.data[25]);
<canvas id="canvas"></canvas>
But in your case, an easier solution, is to call twice ctx.getImageData.
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'orange';
ctx.fillRect(0,0,300,150);
var original = ctx.getImageData(0,0,300,150);
var copied = ctx.getImageData(0,0,300,150);
// both hold the same values
console.log(original.data[25], copied.data[25]);
// and can be modified independently
copied.data[25] = 0;
console.log(original.data[25], copied.data[25]);
<canvas id="canvas"></canvas>
And an complete example :
var ctx = canvas.getContext('2d');
var img = new Image();
// keep these variables globally accessible to our script
var initialImageData, filterImageData;
var current = 0; // just to be able to switch easily
img.onload = function(){
// prepare our initial state
canvas.width = img.width/2;
canvas.height = img.height/2;
ctx.drawImage(img, 0,0, canvas.width, canvas.height);
// this is the state we want to save
initialImageData = ctx.getImageData(0,0,canvas.width,canvas.height);
// get an other, independent, copy of the current state
filterImageData = ctx.getImageData(0,0,canvas.width,canvas.height);
// now we can modify one of these copies
applyFilter(filterImageData);
button.onclick = switchImageData;
switchImageData();
}
// remove red channel
function applyFilter(image){
var d = image.data;
for(var i = 0; i < d.byteLength; i+=4){
d[i] = 0;
}
}
function switchImageData(){
// use either the original one or the filtered one
var currentImageData = (current = +!current) ?
filterImageData : initialImageData;
ctx.putImageData(currentImageData, 0, 0);
log.textContent = current ? 'filtered' : 'original';
}
img.crossOrigin = 'anonymous';
img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg';
<button id="button">switch imageData</button>
<code id="log"></code><br>
<canvas id="canvas"></canvas>
The same with slice:
var ctx = canvas.getContext('2d');
var img = new Image();
// keep these variables globally accessible to our script
var initialImageData, filterImageData;
var current = 0; // just to be able to switch easily
img.onload = function(){
// prepare our initial state
canvas.width = img.width/2;
canvas.height = img.height/2;
ctx.drawImage(img, 0,0, canvas.width, canvas.height);
// this is the state we want to save
initialImageData = ctx.getImageData(0,0,canvas.width,canvas.height);
// get an other, independent, copy of the current state
filterImageData = new ImageData(initialImageData.data.slice(), initialImageData.width, initialImageData.height);
// now we can modify one of these copies
applyFilter(filterImageData);
button.onclick = switchImageData;
switchImageData();
}
// remove red channel
function applyFilter(image){
var d = image.data;
for(var i = 0; i < d.byteLength; i+=4){
d[i] = 0;
}
}
function switchImageData(){
// use either the original one or the filtered one
var currentImageData = (current = +!current) ?
filterImageData : initialImageData;
ctx.putImageData(currentImageData, 0, 0);
log.textContent = current ? 'filtered' : 'original';
}
img.crossOrigin = 'anonymous';
img.src = 'https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg';
<button id="button">switch imageData</button>
<code id="log"></code><br>
<canvas id="canvas"></canvas>
A Note For Readers: This is a long question, but it needs a background to understand the question asked.
The color quantization technique is commonly used to get the dominant colors of an image.
One of the well-known libraries that do color quantization is Leptonica through the Modified Median Cut Quantization (MMCQ) and octree quantization (OQ)
Github's Color-thief by #lokesh is a very simple implementation in JavaScript of the MMCQ algorithm:
var colorThief = new ColorThief();
colorThief.getColor(sourceImage);
Technically, the image on a <img/> HTML element is backed on a <canvas/> element:
var CanvasImage = function (image) {
this.canvas = document.createElement('canvas');
this.context = this.canvas.getContext('2d');
document.body.appendChild(this.canvas);
this.width = this.canvas.width = image.width;
this.height = this.canvas.height = image.height;
this.context.drawImage(image, 0, 0, this.width, this.height);
};
And that is the problem with TVML, as we will see later on.
Another implementation I recently came to know was linked on this article Using imagemagick, awk and kmeans to find dominant colors in images that links to Using python to generate awesome linux desktop themes.
The author posted an article about Using python and k-means to find the dominant colors in images that was used there (sorry for all those links, but I'm following back my History...).
The author was super productive, and added a JavaScript version too that I'm posting here: Using JavaScript and k-means to find the dominant colors in images
In this case, we are generating the dominant colors of an image, not using the MMCQ (or OQ) algorithm, but K-Means.
The problem is that the image must be a as well:
<canvas id="canvas" style="display: none;" width="200" height="200"></canvas>
and then
function analyze(img_elem) {
var ctx = document.getElementById('canvas').getContext('2d')
, img = new Image();
img.onload = function() {
var results = document.getElementById('results');
results.innerHTML = 'Waiting...';
var colors = process_image(img, ctx)
, p1 = document.getElementById('c1')
, p2 = document.getElementById('c2')
, p3 = document.getElementById('c3');
p1.style.backgroundColor = colors[0];
p2.style.backgroundColor = colors[1];
p3.style.backgroundColor = colors[2];
results.innerHTML = 'Done';
}
img.src = img_elem.src;
}
This is because the Canvas has a getContext() method, that expose 2D image drawing APIs - see An introduction to the Canvas 2D API
This context ctx is passed to the image processing function
function process_image(img, ctx) {
var points = [];
ctx.drawImage(img, 0, 0, 200, 200);
data = ctx.getImageData(0, 0, 200, 200).data;
for (var i = 0, l = data.length; i < l; i += 4) {
var r = data[i]
, g = data[i+1]
, b = data[i+2];
points.push([r, g, b]);
}
var results = kmeans(points, 3, 1)
, hex = [];
for (var i = 0; i < results.length; i++) {
hex.push(rgbToHex(results[i][0]));
}
return hex;
}
So you can draw an image on the Canvas through the Context and get image data:
ctx.drawImage(img, 0, 0, 200, 200);
data = ctx.getImageData(0, 0, 200, 200).data;
Another nice solution is in CoffeeScript, ColorTunes, but this is using a as well:
ColorTunes.getColorMap = function(canvas, sx, sy, w, h, nc) {
var index, indexBase, pdata, pixels, x, y, _i, _j, _ref, _ref1;
if (nc == null) {
nc = 8;
}
pdata = canvas.getContext("2d").getImageData(sx, sy, w, h).data;
pixels = [];
for (y = _i = sy, _ref = sy + h; _i < _ref; y = _i += 1) {
indexBase = y * w * 4;
for (x = _j = sx, _ref1 = sx + w; _j < _ref1; x = _j += 1) {
index = indexBase + (x * 4);
pixels.push([pdata[index], pdata[index + 1], pdata[index + 2]]);
}
}
return (new MMCQ).quantize(pixels, nc);
};
But, wait, we have no <canvas/> element in TVML!
Of course, there are native solutions like Objective-C ColorCube, DominantColor - this is using K-means
and the very nice and reusable ColorArt by #AaronBrethorst from CocoaControls.
Despite the fact that this could be used in a TVML application through a native to JavaScriptCore bridge - see How to bridge TVML/JavaScriptCore to UIKit/Objective-C (Swift)?
my aim is to make this work completely in TVJS and TVML.
The simplest MMCQ JavaScript implementation does not need a Canvas: see Basic Javascript port of the MMCQ (modified median cut quantization) by Nick Rabinowitz, but needs the RGB array of the image:
var cmap = MMCQ.quantize(pixelArray, colorCount);
that is taken from the HTML <canvas/> and that is the reason for it!
function createPalette(sourceImage, colorCount) {
// Create custom CanvasImage object
var image = new CanvasImage(sourceImage),
imageData = image.getImageData(),
pixels = imageData.data,
pixelCount = image.getPixelCount();
// Store the RGB values in an array format suitable for quantize function
var pixelArray = [];
for (var i = 0, offset, r, g, b, a; i < pixelCount; i++) {
offset = i * 4;
r = pixels[offset + 0];
g = pixels[offset + 1];
b = pixels[offset + 2];
a = pixels[offset + 3];
// If pixel is mostly opaque and not white
if (a >= 125) {
if (!(r > 250 && g > 250 && b > 250)) {
pixelArray.push([r, g, b]);
}
}
}
// Send array to quantize function which clusters values
// using median cut algorithm
var cmap = MMCQ.quantize(pixelArray, colorCount);
var palette = cmap.palette();
// Clean up
image.removeCanvas();
return palette;
}
[QUESTION]
How to generate the dominant colors of a RGB image without using the HTML5 <canvas/>, but in pure JavaScript from an image's ByteArray fetched with XMLHttpRequest?
[UPDATE]
I have posted this question to Color-Thief github repo, adapting the RGB array calculations to the latest codebase.
The solution I have tried was this
ColorThief.prototype.getPaletteNoCanvas = function(sourceImageURL, colorCount, quality, done) {
var xhr = new XMLHttpRequest();
xhr.open('GET', sourceImageURL, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function(e) {
if (this.status == 200) {
var uInt8Array = new Uint8Array(this.response);
var i = uInt8Array.length;
var biStr = new Array(i);
while (i--)
{ biStr[i] = String.fromCharCode(uInt8Array[i]);
}
if (typeof colorCount === 'undefined') {
colorCount = 10;
}
if (typeof quality === 'undefined' || quality < 1) {
quality = 10;
}
var pixels = uInt8Array;
var pixelCount = 152 * 152 * 4 // this should be width*height*4
// Store the RGB values in an array format suitable for quantize function
var pixelArray = [];
for (var i = 0, offset, r, g, b, a; i < pixelCount; i = i + quality) {
offset = i * 4;
r = pixels[offset + 0];
g = pixels[offset + 1];
b = pixels[offset + 2];
a = pixels[offset + 3];
// If pixel is mostly opaque and not white
if (a >= 125) {
if (!(r > 250 && g > 250 && b > 250)) {
pixelArray.push([r, g, b]);
}
}
}
// Send array to quantize function which clusters values
// using median cut algorithm
var cmap = MMCQ.quantize(pixelArray, colorCount);
var palette = cmap? cmap.palette() : null;
done.apply(this,[ palette ])
} // 200
};
xhr.send();
}
but it does not gives back the right RGB colors array.
[UPDATE]
Thanks to all the suggestions I got it working. Now a full example is available on Github,
The canvas element is being used as a convenient way to decode the image into an RGBA array. You can also use pure JavaScript libraries to do the image decoding.
jpgjs is a JPEG decoder and pngjs is a PNG decoder. It looks like the JPEG decoder will work with TVJS as is. The PNG decoder, however, looks like it's made to work in a Node or web browser environment, so you might have to tweak that one a bit.
I want to compute the distance between two figures drawn in two canvases, actually i'm doing the following, iterating through the data of the canvases (canvases have the same size):
var computeDifference = function() {
var imgd1 = bufferCtx.getImageData(0, 0, w, h).data;
var imgd2 = targetCtx.getImageData(0, 0, w, h).data;
var diff = 0;
for(var i=0; i<imgd1.length; i+=4) {
var d = (imgd1[i]-imgd2[i]);
var tot = d > 0 ? d : -d;
diff += tot
}
return diff;
}
this is not very efficient.
Is there a better method? I read about composite operations, but I'm not sure if that could help in this case.
I've purposely considered only the R channel because for now I'm operating with black and white images, but I'm probably going to consider the other channels later.
You can use the new difference blending method on a single canvas, draw both images in with mode set before the last draw, then extract the bitmap data to get the total sum.
You would use the same property, globalCompositeOperation, to set blending mode with.
This way you are letting the browser do the initial work calculating the difference on each component leaving you only to sum them up. You are also saving one canvas, one call to getImageData() which is relative expensive on an hardware accelerated system:
ctx.drawImage(image1, x, y);
ctx.globalCompositeOperation = "difference"; // use composite to set blending...
ctx.drawImage(image2, x, y);
// extract data, and sum -
Note: IE11 does not support the new blending modes. For IE you would need to do the difference calculations manually as initially.
You can feature detect this by providing the fast method when supported, manual when not:
ctx.globalCompositeOperation = "difference";
if (ctx.globalCompositeOperation === "difference") {
// fast
}
else {
// manual
}
Live performance test
Test1 will do manual difference calclation, test2 will use browser difference blending mode. On my setup FireFox wins with more than a 4x factor (slightly less difference in Chrome).
var canvas1 = document.createElement("canvas"),
canvas2 = document.createElement("canvas"),
ctx1 = canvas1.getContext("2d"),
ctx2 = canvas2.getContext("2d"),
img1 = new Image, img2 = new Image,
count = 2,
startTime1, startTime2, endTime1, endTime2, sum1, sum2;
performance = performance || Date; // "polyfill" the performance object
img1.crossOrigin = img2.crossOrigin = ""; // we need to extract pixels
img1.onload = img2.onload = loader;
img1.src = "http://i.imgur.com/TJiD5GM.jpg";
img2.src = "http://i.imgur.com/s9ksOb1.jpg";
function loader() {if(!--count) test1()} // handle async load
function test1(){
startTime1 = performance.now();
ctx1.drawImage(img1, 0, 0);
ctx2.drawImage(img2, 0, 0);
var data1 = ctx1.getImageData(0, 0, 500, 500).data,
data2 = ctx2.getImageData(0, 0, 500, 500).data,
i = 0, len = data1.length, sum = 0;
// we do all channels except alpha channel (not used in difference calcs.)
while(i < len) {
sum += Math.abs(data2[i] - data1[i++]) +
Math.abs(data2[i] - data1[i++]) +
Math.abs(data2[i] - data1[i++]);
i++
}
sum1 = sum;
endTime1 = performance.now();
test2();
}
function test2(){
startTime2 = performance.now();
ctx1.drawImage(img1, 0, 0);
ctx1.globalCompositeOperation = "difference";
if (ctx1.globalCompositeOperation !== "difference")
alert("Sorry, use Firefox or Chrome");
ctx1.drawImage(img2, 0, 0);
var data = ctx1.getImageData(0, 0, 500, 500).data,
i = 0, len = data.length, sum = 0;
// we do all channels except alpha channel
while(i < len) {
sum += data[i++];
sum += data[i++];
sum += data[i++];
i++;
}
sum2 = sum;
endTime2 = performance.now();
result();
}
function result() {
var time1 = endTime1 - startTime1,
time2 = endTime2 - startTime2,
factor = time1 / time2,
res = "Manual method: " + time1.toFixed(3) + "ms<br>";
res += "Blending mode: " + time2.toFixed(3) + "ms<br>";
res += "Factor: " + factor.toFixed(2) + "x<br>";
res += "Sum 1 = " + sum1;
res += "<br>Sum 2 = " + sum2;
document.querySelector("output").innerHTML = res;
}
<output>Loading images and calculating...</output>
I'm trying to blend two ImageData objects into a single object in order to obtain result similar to the pictures shown in this link
The following is the Javascript code that has the two ImageData
var redImage = copy.getImageData((SCREEN_WIDTH - VIDEO_WIDTH)/2,(SCREEN_HEIGHT - VIDEO_HEIGHT)/2,VIDEO_WIDTH,VIDEO_HEIGHT);
var bluImage = copy.getImageData((SCREEN_WIDTH - VIDEO_WIDTH)/2,(SCREEN_HEIGHT - VIDEO_HEIGHT)/2,VIDEO_WIDTH,VIDEO_HEIGHT);
var redData = redImage.data;
var blueData = blueImage.data;
// Colorize red
for(var i = 0; i < redData.length; i+=4) {
redData[i] -= (redData[i] - 255);
}
redImage.data = redData;
// Draw the pixels onto the visible canvas
disp.putImageData(redImage,(SCREEN_WIDTH - VIDEO_WIDTH)/2 - 25,(SCREEN_HEIGHT - VIDEO_HEIGHT)/2);
// Colorize cyan
for(var i = 1; i < blueData.length; i+=4) {
blueData[i] -= (blueData[i] - 255);
blueData[i+1] -= (blueData[i+1] - 255);
}
blueImage.data = blueData;
// Draw the pixels onto the visible canvas
disp.putImageData(blueImage,(SCREEN_WIDTH - VIDEO_WIDTH)/2 + 25,(SCREEN_HEIGHT - VIDEO_HEIGHT)/2);
How do i merge/blend the redData and blueData before putting it on the canvas ?
The formula you can use to mix two images is fairly simple:
newPixel = imageMainPixel * mixFactor + imageSecPixel * (1 - mixFactor)
Example assuming both buffers are of equal length:
var mixFactor = 0.5; //main image is dominant
//we're using the red buffer as main buffer for this example
for(var i = 0; i < redData.length; i+=4) {
redData[i] = redData[i] * mixFactor + blueData[i] * (1 - mixFactor);
redData[i+1] = redData[i+1] * mixFactor + blueData[i+1] * (1 - mixFactor);
redData[i+2] = redData[i+2] * mixFactor + blueData[i+2] * (1 - mixFactor);
}
Now your red buffer contains the mixed image.
To add an offset you can simply redraw the images with an offset value, for example:
var offset = 20; //pixels
copy.drawImage(originalImage, -offset, 0); // <--
var redImage = copy.getImageData( /*...*/ );
copy.drawImage(originalImage, offset, 0); // -->
var bluImage = copy.getImageData( /*...*/ );
If you have not onlyImageDataobjects, but also sourcecanvaselements, you can use this method.
You can obtain base64-encoded image data by callingtoDataURLcanvas method. Then you can createImageelement from that data and then paste that image to destination canvas viadrawImage.
Example code:
function mergeImageData(callback, sources) {
var canvas = document.createElement('canvas'),
context,
images = Array.prototype.slice.call(arguments, 1).map(function(canvas) {
var img = new Image();
img.onload = onLoad;
img.src = canvas.toDataURL();
return img;
}
),
imgCounter = 0,
widths = [],
heights = [];
function onLoad() {
widths.push(this.width);
heights.push(this.height);
if (++imgCounter == images.length) {
merge();
};
};
function merge() {
canvas.width = Math.max.apply(null, widths);
canvas.height = Math.max.apply(null, heights);
context = canvas.getContext('2d');
images.forEach(function(img) {
context.drawImage(img, 0, 0, img.width, img.height);
}
);
callback(context.getImageData(0, 0, canvas.width, canvas.height));
};
};
what about functions of setting the transmission format 3d - from format full side by side to anaglyph, alternating rows, alternating columns, chessboard, original side by side and 2d from 3d ?
I know questions like this have been asked several time, but I have yet to find just what I'm looking for. I am reading an image into a canvas object (in javascript) and trying to manipulate some specific pixels. For example, I am looking for the color RGB: 224 64 102, and trying to change this to a different color.
I can apply greyscale to the image, so I know the manipulation works, but the code is not finding any pixels with this color (that Adobe Illustrator said was the RGB color). I'm hoping I'm just missing a small detail. The code is below, hopefully someone will see it.
Thanks!
var canvas = document.getElementById("testcanvas");
var canvasContext = canvas.getContext('2d');
imgObj = new Image();
imgObj.src = "ss.jpg";
//imgObj.width = 200;
//imgObj.height = 200;
var imgW = imgObj.width;
var imgH = imgObj.height;
canvas.width = imgW;
canvas.height = imgH;
canvasContext.drawImage(imgObj, 0, 0);
var imgPixels = canvasContext.getImageData(0, 0, imgW, imgH);
//hash_table = {};
for (var x = 0; x < imgPixels.width; x++) {
for (var y = 0; y < imgPixels.height; y++)
{
var i = (y * imgPixels.width + x) * 4;
//Want to go from:
//E04066
//224 64 102 -> to
//134 135 185
if(imgPixels.data[i] == 224 && imgPixels.data[i+1] == 64 && imgPixels.data[i+2] == 102) {
imgPixels.data[i] = 134;
imgPixels.data[i+1] = 135;
imgPixels.data[i+2] = 185;
}
//To greyscale:
/*
var avg = (imgPixels.data[i] + imgPixels.data[i + 1] + imgPixels.data[i + 2]) / 3;
imgPixels.data[i] = avg;
imgPixels.data[i + 1] = avg;
imgPixels.data[i + 2] = avg;
imgPixels.data[i + 3] = 255;
*/
}
}
canvasContext.putImageData(imgPixels, 0, 0, 0, 0, imgPixels.width, imgPixels.height);
//color_count = 0;
//for(key in hash_table) {
// color_count++;
//}
//console.log(color_count);
//console.log(hash_table);
return canvas.toDataURL();
});
});
</script>
</head>
<body>
<canvas id="testcanvas"></canvas>
<img src="ss.jpg" id="testimage"/>
You are probably unable to get image data from canvas because the canvas has been tainted by cross-origin data.
If that file, ss.jpg is local then it won't work. I imagine that's the case.
Search for canvas cross-origin on SO or Google for more information on that. There's a lot out there. Here's a bit of an explanation:
http://simonsarris.com/blog/480-understanding-the-html5-canvas-image-security-rules
Here's a site about enabling it on your server:
http://enable-cors.org/
Otherwise, your code works. Here is the same code converting a tiny red dot into a tiny green dot:
http://jsfiddle.net/RBaxt/
Canvas really don't work with .JPG format. You have to convert your image into .PNG using any picture editing tool like Photoshop. Your code works well.
I think you are loading an Image that is not ready to be painted. Below I have updated your code above, though I have not test it but I feel it could lead you somewhere
var canvas = document.getElementById("testcanvas");
var canvasContext = canvas.getContext('2d');
imgObj = new Image();
imgObj.src = "ss.jpg";
//imgObj.width = 200;
//imgObj.height = 200;
var imgW = imgObj.width;
var imgH = imgObj.height;
imgObj.onload = function(){
//Put the pixel manipulation code here;
// This ensures the image has been loaded before it is accessed.
}