I've borrowed this code to try and suit my needs http://www.html5canvastutorials.com/advanced/html5-canvas-get-image-data-tutorial/ since it does the minimum task of it renders a image pixel by pixel (i believe).
What I'm trying to do is create an array of each pixel's RGB information, and display the information in plain text.
To test I am trying this with small images, 5x5 pixels, also I have this in an .html file i've opened with chrome.
The lightly adapted JS
<script>
function drawImage(imageObj) {
var codepanel = document.getElementById('code');
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var imageX = 69;
var imageY = 50;
var imageWidth = imageObj.width;
var imageHeight = imageObj.height;
var pixels = new Array(); //my addition
var pixel = 0; //my addition
context.drawImage(imageObj, imageX, imageY);
var imageData = context.getImageData(imageX, imageY, imageWidth, imageHeight);
var data = imageData.data;
// iterate over all pixels
for(var i = 0, n = data.length; i < n; i += 4) {
var red = data[i];
var green = data[i + 1];
var blue = data[i + 2];
var alpha = data[i + 3];
pixels[pixel] = red + " " + green + " " + blue + " "; //my addition
pixel++; //my addition
}
codepanel.innerHTML = pixels.join(); //my addition
var x = 20;
var y = 20;
var red = data[((imageWidth * y) + x) * 4];
var green = data[((imageWidth * y) + x) * 4 + 1];
var blue = data[((imageWidth * y) + x) * 4 + 2];
var alpha = data[((imageWidth * y) + x) * 4 + 3];
for(var y = 0; y < imageHeight; y++) {
for(var x = 0; x < imageWidth; x++) {
var red = data[((imageWidth * y) + x) * 4];
var green = data[((imageWidth * y) + x) * 4 + 1];
var blue = data[((imageWidth * y) + x) * 4 + 2];
var alpha = data[((imageWidth * y) + x) * 4 + 3];
}
}
}
var imageObj = new Image();
imageObj.onload = function() {
drawImage(this);
};
imageObj.src = 'pallet.gif';
</script>
HTML
<!DOCTYPE HTML>
<html>
<head> </head>
<body>
<canvas id="myCanvas" width="100%" height="100%"></canvas>
<div id="code"> </div>
</body>
</html>
It means the image you drew to canvas came from a different origin than your page (file:// is considered a different origin too if you are testing with local pages).
The easiest way to solve this is:
If local, install a light-weight server to load the pages off (localhost) such as Mongoose.
If online, move the images to your own server or try to request cross-origin use. For this latter the external server need to be configured to allow this.
To request cross-origin do the following before setting src:
imageObj.crossOrigin = '';
imageObj.src = 'pallet.gif';
It the external server do not accept this will fail.
Related
Alright. I'm new to this, I'm not coder, just trying something for fun. And I'm confused.
I've found a tutorial about making a duotone image with canvas, and I'm so new to this that I can't figure out what I'm doing wrong. Maybe someone can help.
Here is my code. It displays the original image (demo_small.png), but doesn't show any of the effects on it. I guess that maybe I've to overwrite it after the last "return pixels", but I've not idea of what I'm doing so..
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
<script src="https://www.mattkandler.com/assets/application-9cbca3f8879431193adab436bd8e0cf7629ecf3752685f49f997ed4469f42826.js" type="text/javascript"></script>
</head>
<body>
<canvas id="idOfCanvasToDrawImageOn" width="img.width" height="img.height"></canvas>
<script>
//Getting the image pixels
var canvasId = 'idOfCanvasToDrawImageOn';
var imageUrl = 'demo_small.png';
var canvas = document.getElementById(canvasId);
var context = canvas.getContext('2d');
var img = new Image();
// img.crossOrigin = 'Anonymous';
img.onload = function() {
// Perform image scaling if desired size is given
var scale = 1;
context.canvas.width = img.width;
context.canvas.height = img.height;
context.scale(scale, scale);
// Draw image on canvas
context.drawImage(img, 0, 0);
// Perform filtering here
};
img.src = imageUrl;
//Then we'll need to grab the pixels from this newly created canvas image using the following function
Filters.getPixels = function(img) {
var c = this.getCanvas(img.width, img.height);
var ctx = c.getContext('2d');
ctx.drawImage(img, 0, 0);
return ctx.getImageData(0, 0, c.width, c.height);
};
//Converting to grayscale
Filters.grayscale = function(pixels) {
var d = pixels.data;
var max = 0;
var min = 255;
for (var i = 0; i < d.length; i += 4) {
// Fetch maximum and minimum pixel values
if (d[i] > max) {
max = d[i];
}
if (d[i] < min) {
min = d[i];
}
// Grayscale by averaging RGB values
var r = d[i];
var g = d[i + 1];
var b = d[i + 2];
var v = 0.3333 * r + 0.3333 * g + 0.3333 * b;
d[i] = d[i + 1] = d[i + 2] = v;
}
for (var i = 0; i < d.length; i += 4) {
// Normalize each pixel to scale 0-255
var v = (d[i] - min) * 255 / (max - min);
d[i] = d[i + 1] = d[i + 2] = v;
}
return pixels;
};
//Building a color gradient
Filters.gradientMap = function(tone1, tone2) {
var rgb1 = hexToRgb(tone1);
var rgb2 = hexToRgb(tone2);
var gradient = [];
for (var i = 0; i < (256 * 4); i += 4) {
gradient[i] = ((256 - (i / 4)) * rgb1.r + (i / 4) * rgb2.r) / 256;
gradient[i + 1] = ((256 - (i / 4)) * rgb1.g + (i / 4) * rgb2.g) / 256;
gradient[i + 2] = ((256 - (i / 4)) * rgb1.b + (i / 4) * rgb2.b) / 256;
gradient[i + 3] = 255;
}
return gradient;
};
//Applying the gradient
Filters.duotone = function(img, tone1, tone2) {
var pixels = this.getPixels(img);
pixels = Filters.grayscale(pixels);
var gradient = this.gradientMap(tone1, tone2);
var d = pixels.data;
for (var i = 0; i < d.length; i += 4) {
d[i] = gradient[d[i] * 4];
d[i + 1] = gradient[d[i + 1] * 4 + 1];
d[i + 2] = gradient[d[i + 2] * 4 + 2];
}
return pixels;
};
</script>
</body>
</html>
I was wondering how to load base64 on sites that have CSPs that don't allow it. Meaning we can't do stuff like:
image.src = "data:image/png;base64..."
So I figured I needed to find a different approach. I tried converting the base64 into a binary string then working from that to get a UintArray containing the pixel image data.
I don't know how to get and image out of the loadImage function, nor do I know what I'm doing wrong when getting the pixel(s).
I know that the conversion from the binary string to the pixel array is wrong, base64 must have some type of way of converting it, that is unknown to most of us.
Here is my code that tries to solve this:
var canvas = document.getElementById("canvas");
var imageBase64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAB4CAYAAAC0CXGGAAAD2ElEQVR4Xu1cMWhTURRNXAJ1l8QhlWwBoRahOIlOpS4tNF1cig6CQ4SOGnAQomPADIKD0sWlKbRLSyfFSQJSC0K2YDPY4G6hi7p6z4d7uPxXCfFk+7nvv5/zzrnv3Xvf+ykWgp9er/f771sajUYx2IVpnre/8MPzPhDB5u1PACZeQkjxu/aRUcHd1py5ZoBS90cllPqBqfsTgImTEKMYp8HUAKL9ZyQkAGRlTj1A08/A4eGhG+rMz8/nCYUK0f5xnaEMRB8QRRPtXwBwhCdOQvgDW62Wif/L5bJp0u/3zXW1WnVVNBqNjH1hYcFcj8djc91ut91ogYYSAgB8iAEYkIyEmGSaO29djbduLLn29qd9195duRfyCQHIzFpiIKeEMFrEET45OXE1jPM4NsZ1BO2VSsXtPxxKCACMgBiAhImGEugTGP6y2AglyGIfDA5ZnUkAxACEz8kl1O12TT7w/s0PI+uLV7+Z659fr7jzOGt/+/4lc3+z2cyXDwgA8CEGWD6AAkYJoR19on7nl+sDg70Lxo6ax5uT+4AAiAGrgXAosbu762t8MHDt9XrdtS8vLxt78lhIAMQA8QGWE7Ny+MHBgavxxcVF186KxblzYgEQA/Z4D91ieri/ZjT7oPzEXKPm2Tw/gHUCfeL1+Lnp/9XSlrsuCIAYAKc+dwnhJD57dN18FfWB47nP7rqQ3AcEAGYhMRDNgZ99eWRuQR+YeTzravr0xbGxIwNPr71078ccObMOsCReAMSALTXSUILtiW1ubuZaB9bX113N455ZOB8QABhfjEbFAAoQc+JoGSUaC7H2rE5EnVgAiA+IAVYHQgnhmTjMyKKxEObEeKILfQB9tigAUIkTA3AsMywhDB3Y+Z/UVQnUOJ4EwNgo4wMCQOr/rDL3/zFwc3vVnIVY+37LDAKejcYRwhFlK2u0PZ4v2rr8wfyEogBAnUcMgEbDEsqUDndmzFfYIebE0ZUYMzL0ueOVUzdnzviAAAQrc2KAhdOTVlYJ5wMCkLg";
canvas.width = 540;
canvas.height = 360;
var ctx = canvas.getContext('2d');
function loadImage(base64)
{
var binary_string = window.atob(base64.split(",")[1]);
var len = binary_string.length;
var pixels = new Uint8Array(len);
for (var i = 0; i < len; i++)
{
pixels[i] = binary_string.charCodeAt(i);
}
putImageData(ctx, {
data: pixels,
width: 100,
height: 100
}, 0, 0);
var img = new Image();
img.crossOrigin = "Anonymous";
console.log(pixels);
return img;
}
loadImage(imageBase64);
//ctx.drawImage(loadImage(imageBase64), 0, 0);
function putImageData(ctx, imageData, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
{
var data = imageData.data;
var height = imageData.height;
var width = imageData.width;
dirtyX = dirtyX || 0;
dirtyY = dirtyY || 0;
dirtyWidth = dirtyWidth !== undefined ? dirtyWidth : width;
dirtyHeight = dirtyHeight !== undefined ? dirtyHeight : height;
var limitBottom = dirtyY + dirtyHeight;
var limitRight = dirtyX + dirtyWidth;
for (var y = dirtyY; y < limitBottom; y++)
{
for (var x = dirtyX; x < limitRight; x++)
{
var pos = y * width + x;
ctx.fillStyle = 'rgba(' + data[pos * 4 + 0]
+ ',' + data[pos * 4 + 1]
+ ',' + data[pos * 4 + 2]
+ ',' + (data[pos * 4 + 3] / 255) + ')';
ctx.fillRect(x + dx, y + dy, 1, 1);
}
}
}
Maybe if we can find out how the base64 works then we can do it.
And with that:
Any help would be appreciated :)
Thanks!
Code for browserify here generated file (480kB):
var PNG = require('pngjs').PNG;
function getPNG(bin) {
return PNG.sync.read(bin);
}
function loadImage(base64)
{
var binary_string = Buffer.from(base64, 'base64');
var png = getPNG(binary_string);
return png;
}
window.getPNG = getPNG;
window.loadImage = loadImage;
And snippet demo:
<canvas id=canvas></canvas>
<script src="https://pastebin.com/raw/SN2vwZeg"></script>
<script>
var canvas = document.getElementById("canvas");
var imageBase64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAB4CAYAAAC0CXGGAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMxSURBVHhe7doxaFNRGAXg6KRxlyRD4hpqqUEoTqJTaZcWTBeXoIPgEHcNCArR3TcIDpUsLq3QLi1Z1FECEitiVtshDe52jDbeM/Sd3ub3NtH7cvt/i6cgD6/5T9+9L+9MytHa2tovxD/K5bLzNQ4b9Xpn8efE0gX4Js4bz+ib+jaScbs2g2RIMzzu6+kI+RZeB6QZZePuAJOuryPkW/gdaLfbSHalUgnpZFyvrx1ImvA6wGq1WqwTmUwGyWi1WkhGPp9Hstvd3UUyZmdnkYxer4dk1Ov1of9GHSHfwuuANPPV9ddIdrVr80h29Y9bSHbR0h0kQ+qEjpBv4XWA90Jsb28PyY5nlnGnWDabRbLTvVDShNcBJp0PpL0Rk/Y+0v6f6Qj5Fn4HoiiKdeD9yg8k48Ll70jGz6+XkOykv3/z7kUko1qtagcS7fR1gHEnigt9JLvOZvz/jGeeaQeSLvwO8F5oY2MDya7T6SDZFYtFJLvFxUUkQ/dCSRdeB6QzsfQ8v9lsItnNzc0h2UnfN+iZOGnC78D9rWUk417mEZLBMy/9nuf7BHfiVe8ZkvFyfhXJ0A4kzenrACtsX0UyXDuwM/MJyU47kHThdUA6Az/9/ADJ4A6kHxaQ7Paf7yAZ3IHHV14g2fEZWUfIt/A6wPcB6TuxRqOBZLjeByqVCpIdf2em94GkCb8Drs+BXDsw6nMiHSHfJr8D0nMg7gC/E8dnYte9EJ+J+Z067gDTEfItvA7w3kd6/2fcz4UYv4vBeyMdId8mvwPX396KdWC5ewPJkN7/+dd7IX6/aDX3AcnQEfItvA6wwnoayeCZ5DOx616Iz8TcuZ2lfSQ7HSHfJr8D0nkgac+FmH4Crp/AqNfr9/vF6Wn8MJDuDF9AAsVWpL+FfPvvC+h2u8NL4Eg/Ad90Ab6JCxh36cZt5E+g0XBbYC6XO9jc0O2Wbk6DN6oRoFpFGIhvpv5OFCEc490KAhQXEI7xbRMB+B/MYgsYonH+JKs74LqAw6aGb55SRz5h+pl3T1PCBcVFSiPF4zPqAsbnyPdtMZEwhk/oJYz8l/hLGOdqww/bSimllFJKKaWUi1TqN5LWUoxlg3+MAAAAAElFTkSuQmCC"
var ctx = canvas.getContext('2d');
var png = loadImage(imageBase64.split(",")[1]);
putImageData(ctx, {
data: png.data,
width: png.width,
height: png.height
}, 0, 0);
function putImageData(ctx, imageData, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight)
{
var data = imageData.data;
var height = imageData.height;
var width = imageData.width;
dirtyX = dirtyX || 0;
dirtyY = dirtyY || 0;
dirtyWidth = dirtyWidth !== undefined ? dirtyWidth : width;
dirtyHeight = dirtyHeight !== undefined ? dirtyHeight : height;
var limitBottom = dirtyY + dirtyHeight;
var limitRight = dirtyX + dirtyWidth;
for (var y = dirtyY; y < limitBottom; y++)
{
for (var x = dirtyX; x < limitRight; x++)
{
var pos = y * width + x;
ctx.fillStyle = 'rgba(' + data[pos * 4 + 0]
+ ',' + data[pos * 4 + 1]
+ ',' + data[pos * 4 + 2]
+ ',' + (data[pos * 4 + 3] / 255) + ')';
ctx.fillRect(x + dx, y + dy, 1, 1);
}
}
}
</script>
Unlike how you maybe expecting, the binary data representing a PNG image are not an array of pixels. The binary data has headers and various meta data describing the file, then a compressed image.
If you need to get pixels out of a png binary without using the native browser decoding functionality, you either have to study the PNG format yourself or use a specialized library which will give you access to the pixels of the image.
Thee are many image libraries which support the PNG format. Following is a specialized library which can do the trick for you.
https://www.npmjs.com/package/pngjs
You can use Browserify to use this library in the front end.
for my learning purposes, I'm trying to copy this example.
Everything seems to be fine, but for some reasons, canvas is not updating. Whenever the code executes, is terminated or done canvas is always 0px width 0px height. Maybe I'm doing some mistakes (for sure I should say), but I have no idea what can be wrong.
// create the network
const { Layer, Network } = window.synaptic;
var inputLayer = new Layer(2);
var hiddenLayer = new Layer(15);
var outputLayer = new Layer(3);
inputLayer.project(hiddenLayer);
hiddenLayer.project(outputLayer);
var myNetwork = new Network({
input: inputLayer,
hidden: [hiddenLayer],
output: outputLayer
});
var imgRef = new Image();
imgRef.src = './reference.png';
var width = imgRef.width;
var height = imgRef.height;
imgRef.setAttribute('crossOrigin', '');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var canvasd = ctx.getImageData(0 , 0, width, height)
var refCanvas = document.getElementById('canvas1');
var rctx = canvas.getContext('2d');
var refHTML = document.getElementById('reference');
rctx.drawImage(refHTML, 0, 0);
var ref = rctx.getImageData(0, 0, width, height);
imageData = ctx.getImageData(0,0,width,height);
// train the network - learn XOR
var learningRate = .1;
for (var i = 0; i < 20000; i++)
{
for (var x=0;x<width;x++){
for(var y=0;y<height;y++){
myNetwork.activate([x/width, y/height]);
myNetwork.propagate(learningRate, pixel(ref.data, x,y));
var rgb = myNetwork.activate([x/width,y/height]);
imageData.data[((height * y) + x) * 4] = (rgb[0] )* 255;
imageData.data[((height * y) + x) * 4 + 1] = (rgb[1] ) * 255;
imageData.data[((height * y) + x) * 4 + 2] = (rgb[2] ) * 255;
imageData.data[((height * y) + x) * 4 + 3] = 1;
}
}
ctx.putImageData(imageData,0,0);
}
function pixel(data,a,b){
var red = data[((height*b)+a)*4];
var green = data[((height*b)+a)*4 + 1];
var blue = data[((height*b)+a)*4 + 2]
return [red/225, green/225, blue/225]
}
Code is tested on Firefox.
On line var canvas = document.getElementById('canvas'); you are selecting a canvas element from the page. This can sometimes create a conflict between JS defined Width/Height and the CSS defined Width/Height.
try instead:
var canvas = document.createElement('canvas');
canvas.height = y;
canvas.width = x;
I'm attempting to post large base64s (around 3500000 characters long) via ajax to a server side script that converts the base64 into an image. The issue is that sometimes the post times out with the server never receiving the base64. The timeout limit is currently set at 20 seconds, which I would expect is more than enough.
I don't really want to scale the image down any further as it is already at a lower resolution than I would like it to be (the images that are posted will be physically printed, so need to be reasonably high-res).
The potential solutions I can think of are:
Reduce the resolution of the images within the canvas
Reduce the resolution of the image created by the canvas
Reduce the colour range of the canvas
The last one is the one that interests me the most, as I have already implemented the other two as much as I feel comfortable doing, but I'm not sure how to go about it.
Any advice or solutions on how to go about this would be appreciated. Thanks.
• Reduce the colour range of the canvas
You can use canvas.getImageData and canvas.putImageData to do this.
Here is sample that first paints a canvas with a set of random colors
<canvas id="before" width="300" height="200"></canvas>
var bcanvas = document.getElementById('before')
var bctx = bcanvas.getContext("2d");
for (var i = 0; i < 300; i = i + 3) {
var r = parseInt(Math.random() * 256)
var g = parseInt(Math.random() * 256)
var b = parseInt(Math.random() * 256)
bctx.fillStyle = "rgba(" + r + ", " + g + ", " + b + ", 1)";
bctx.fillRect(i, 0, 3, 200);
}
alert(bcanvas.toDataURL().length);
And then we loop through the pixels, reducing the number of colors (here we just divide each pixel's r g and b values by 16, round it down and then scale it upto 255, ending up with ~16 distinct values for each of r g and b in place of 255 each)
var imgData = bctx.getImageData(0, 0, 300, 200);
var pixels = imgData.data;
// if a pixel is slightly red make it full red, same for blue and green
for (var nPixel = 0; nPixel < pixels.length; nPixel += 4) {
pixels[nPixel] = parseInt(pixels[nPixel] / 16) * 16;
pixels[nPixel + 1] = parseInt(pixels[nPixel + 1] / 16) * 16;
pixels[nPixel + 2] = parseInt(pixels[nPixel + 2] / 16) * 16;
}
var acanvas = document.getElementById('after')
var actx = acanvas.getContext("2d");
actx.putImageData(imgData, 0, 0);
alert(acanvas.toDataURL().length);
Resulting in an approximately 17% reduction in the DataURL length.
Note that this is just a "how to". You'll probably need to read up on the png image format and what kind of color optimization will have the benefit to figure out "how to" do it so that I reduce the image size.
Warning : alert boxes ahead. Do not panic.
var bcanvas = document.getElementById('before')
var bctx = bcanvas.getContext("2d");
for (var i = 0; i < 300; i = i + 3) {
var r = parseInt(Math.random() * 256)
var g = parseInt(Math.random() * 256)
var b = parseInt(Math.random() * 256)
bctx.fillStyle = "rgba(" + r + ", " + g + ", " + b + ", 1)";
bctx.fillRect(i, 0, 3, 200);
}
alert(bcanvas.toDataURL().length);
var imgData = bctx.getImageData(0, 0, 300, 200);
var pixels = imgData.data;
// if a pixel is slightly red make it full red, same for blue and green
for (var nPixel = 0; nPixel < pixels.length; nPixel += 4) {
pixels[nPixel] = parseInt(pixels[nPixel] / 16) * 16;
pixels[nPixel + 1] = parseInt(pixels[nPixel + 1] / 16) * 16;
pixels[nPixel + 2] = parseInt(pixels[nPixel + 2] / 16) * 16;
}
var acanvas = document.getElementById('after')
var actx = acanvas.getContext("2d");
actx.putImageData(imgData, 0, 0);
alert(acanvas.toDataURL().length);
<canvas id="before" width="300" height="200"></canvas>
<br />
<canvas id="after" width="300" height="200"></canvas>
I am creating a plugin that has drawing features and I want to add a filter button. Right now, when the "grayscale" option is selected from a pull down list, the function called filterImage is called. The make_base function shown in the code simply loads an image into the canvas. Currently the image appears in the canvas, but no grayscale is applied. How would I go about doing this? Can the make_base function (putting in an image) and grayscale filter function be called in the same function?
var canvas = document.getElementById("art-board");
var context = canvas.getContext("2d");
var selectObj = document.getElementById("filter");
var index = selectObj.selectedINdex;
var filter = selectObj[index].value;
make_base(canvas, context);
if(filter=="grayscale"){
filterImage(canvas, context);
}
function filterImage(canvas, context){
imageData = context.getImageData(0, 0, canvas.width, canvas.height),
data = imageData.data,
iLen = data.length;
for (i = 0; i < iLen; i+=4) {
avg = 0.3 * data[i] + 0.59 * data[i + 1] + 0.11 * data[i + 2];
data[i] = avg + 100;
data[i + 1] = avg + 50;
data[i + 2] = avg + 255;
}
context.putImageData(imageData, 0, 0);
}
Not 100% sure, but I think there's a problem with your loop.
I found working RGB values/code on HTML5Rocks
var r = d[i];
var g = d[i+1];
var b = d[i+2];
// CIE luminance for the RGB
// The human eye is bad at seeing red and blue, so we de-emphasize them.
var v = 0.2126*r + 0.7152*g + 0.0722*b;
d[i] = d[i+1] = d[i+2] = v