Downscale canvas element then get imageData - javascript

I need users to be able to draw a canvas element that then needs to be converted into an array of length 784 so it be can be fed into an algorithm. The array should be of pixel shade intensity, which I can find by use the ctx.getImageData() method. However, since the user needs to draw it, the canvas element is 280x280 instead of the required 28x28 meaning I need to downscale the image drawn on the 280x280 canvas, then use getImageData.
I've tried just getting every 100th value from the 280x280 image data array, but I'm not sure if that would actually work, and I've struggled to draw the resulting array so I can't verify.
var guess = document.getElementById('guess');
var canvasBig = document.getElementById('canvasBig');
var ctxBig = canvasBig.getContext('2d');
var canvasSmall= document.getElementById('canvasSmall');
var ctxSmall = canvasBig.getContext('2d');
var canvas = new fabric.Canvas('canvasBig', {
isDrawingMode: true,
});
canvas.freeDrawingBrush.width = 50;
guess.addEventListener('click', function() {
var imageData = ctx.getImageData(0,0, canvasBig.width, canvasBig.height)
var resized = []
for(var i = 0; i < imageData.length; i++){
if(i % 100 == 0){
resized.push(imageData.length)
}
}
console.log(imageData)
ctxSmall.putImageData(imageData, 0, 0)
});

You can simply draw the larger canvas itself (without having to use getImageData) onto the smaller canvas, scaling to down when you draw:
ctxSmall.drawImage(canvasBig, 0, 0, 280, 280, 0, 0, 28, 28);

Related

WebGL glfx.js matrix transform (perspective) crops the image if it rotates

I am using the glfx.js library in order to use matrix transformation to create the perspective effect for my images. In my app, the system works just like photoshop's smart objects (where you render a flat image and get perspective results after render)
glfx.js uses this function canvas.perspective(before, after) to apply matrix transforms to images, by assigning before and after coordination of the 4 points in an image, and it runs the Matrix command in the background to transform my image.
My issue is that if the resulting image that I want after the transformation applied to it is bigger than the original image (happens if you rotate the image) then the WebGL canvas is going to crop my image.
Look at the following fiddle:
https://jsfiddle.net/human_a/o4yrheeq/
window.onload = function() {
try {
var canvas = fx.canvas();
} catch (e) {
alert(e);
return;
}
// convert the image to a texture
var image = document.getElementById('image');
var texture = canvas.texture(image);
// apply the perspective filter
canvas.draw(texture).perspective( [0,0,774,0,0,1094,774,1094], [0,389,537,0,732,1034,1269,557] ).update();
image.src = canvas.toDataURL('image/png');
// or even if you replace the image with the canvas
// image.parentNode.insertBefore(canvas, image);
// image.parentNode.removeChild(image);
};
<script src="https://evanw.github.io/glfx.js/glfx.js"></script>
<img id="image" crossOrigin="anonymous" src="https://images.unsplash.com/photo-1485207801406-48c5ac7286b2?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=600&fit=max&s=9bb1a18da78ab0980d5e7870a236af88">
Any ideas on how we can make the WebGL canvas fit the rotated image (not make the image smaller) or somehow extract the whole image instead of the cropped one?
More pixels
There is no cover all solution. This is because when you convert from 2D to 3D the size of the projected image can possibly approch infinity (near clipping prevents infinity) so no matter how large you make the image output there is always the possibility of some clipping being applied.
With that caveat out of the way there is a solution for most situations that can avoid clipping. It is very simple, just expand the canvas to hold the additional content.
Find the bounds
To simplify the calculations I have changed the after array to a set of normalised points (they represent the after coords as a scale factor of the image size). I then use the image size to convert to real pixel coordinates. Then from that I workout the min size a texture needs to be to hold both the original image and the projection.
With that info I just create the texture (as a canvas) draw the image. Adjust the befor array if needed (in case some projection points are in negative space) and apply the filter.
So we have an image object that has a width and a height. And you have the projection of those points.
// assuming image has been loaded and is ready
var imgW = image.naturalWidth;
var imgH = image.naturalHeight;
The set the corner array (before)
var before = [0, 0, imgW, 0, 0, imgH, imgW, imgH];
The projection points. To make it easier to deal with I have normalised the projection points to the image size
var projectNorm = [[0, 0.3556], [0.6938, 0], [0.9457, 0.9452], [1.6395, 0.5091]];
If you want to use the absolute coordinates as in the fiddle's after array use the following code. The normalisation is reversed in the snippet after then next, so you can skip the normalisation. I have just updated the answer quickly as I am short of time.
var afterArray = [0,389,537,0,732,1034,1269,557];
projectNorm = [];
for(var i = 0; i < afterArray.length; i+= 2){
afterArray.push([afterArray[i] / before[i], afterArray[i + 1] / before[i + 1]]);
}
Now calculate the size of the projection. This is the important part as it works out the size of the canvas.
var top, left, right, bottom;
top = 0;
left = 0;
bottom = imgH;
right = imgW;
var project = projectNorm.map(p => [p[0] * imgW, p[1] * imgH]);
project.forEach(p => {
top = Math.min(p[1], top);
left = Math.min(p[0], left);
bottom = Math.max(p[1], bottom);
right = Math.max(p[0], right);
});
Now that all the data we need has been gathered we can create a new image that will accommodate the projection. (assuming that the projection points are true to the projection)
var texture = document.createElement("canvas");
var ctx = texture.getContext("2d");
texture.width = Math.ceil(right - left);
texture.height = Math.ceil(bottom - top);
Draw the image at 0,0
ctx.setTransform(1, 0, 0, 1, left, top); // put origin so image is at 0,0
ctx.drawImage(image,0,0);
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
Then flatten the projection point array
var after = [];
project.forEach(p => after.push(...p));
Move all points into positive projection space
after.forEach((p,i) => {
if (i % 2) {
before[i] += -top;
after[i] += -top;
} else {
before[i] += -left;
after[i] += -left;
}
});
The final step is to create the glfx.js objects and apply the filter
// create a fx canvas
var canvas = fx.canvas();
// create the texture
var glfxTexture = canvas.texture(texture);
// apply the filter
canvas.draw(glfxTexture).perspective( before, after ).update();
// show the result on the page
document.body.appendChild(canvas);
Demo
Demo of your snippet using the above method (slight modification for image load)
// To save time typing I have just kludged a simple load image wait poll
waitForLoaded();
function waitForLoaded(){
if(image.complete){
projectImage(image);
}else{
setTimeout(waitForLoaded,500);
}
}
function projectImage(image){
var imgW = image.naturalWidth;
var imgH = image.naturalHeight;
var projectNorm = [[0, 0.3556], [0.6938, 0], [0.9457, 0.9452], [1.6395, 0.5091]];
var before = [0, 0, imgW, 0, 0, imgH, imgW, imgH];
var top, left, right, bottom;
top = 0;
left = 0;
bottom = imgH;
right = imgW;
var project = projectNorm.map(p => [p[0] * imgW, p[1] * imgH]);
project.forEach(p => {
top = Math.min(p[1], top);
left = Math.min(p[0], left);
bottom = Math.max(p[1], bottom);
right = Math.max(p[0], right);
});
var texture = document.createElement("canvas");
var ctx = texture.getContext("2d");
texture.width = Math.ceil(right - left);
texture.height = Math.ceil(bottom - top);
ctx.setTransform(1, 0, 0, 1, left, top); // put origin so image is at 0,0
ctx.drawImage(image,0,0);
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
var after = [];
project.forEach(p => after.push(...p));
after.forEach((p,i) => {
if (i % 2) {
before[i] += -top;
after[i] += -top;
} else {
before[i] += -left;
after[i] += -left;
}
});
// create a fx canvas
var canvas = fx.canvas();
// create the texture
var glfxTexture = canvas.texture(texture);
// apply the filter
canvas.draw(glfxTexture).perspective( before, after ).update();
// show the result on the page
document.body.appendChild(canvas);
}
#image {
display : none;
}
<script src="https://evanw.github.io/glfx.js/glfx.js"></script>
<img id="image" crossOrigin="anonymous" src="https://images.unsplash.com/photo-1485207801406-48c5ac7286b2?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=9bb1a18da78ab0980d5e7870a236af88">
Notes and a warning
Note that the projection points (after array) do not always match the final corner points of the projected image. If this happens the final image may be clipped.
Note This method only works if the before points represent the exterme corners of the original image. If the points (before) are inside the image then this method may fail.
Warning There is no vetting of the resulting image size. Large Images can cause the browser to become sluggish, and sometimes crash. For production code you should do your best to keep the image size within the limits of the device that is using your code. Clients seldom return to pages that are slow and/or crash.

How to display data in a Uint16Array as an image in Javascript/HTML5?

I am trying to display image data from a DICOM file. I have parsed the file and extracted the 16 bit greyscale image into a Uint16Array. I think it is a convenient format if I want to pass this to WebGL as a texture, but what to do if I want to paste this onto a canvas ?
Thanks.
Thanks #GameAlchemist.
I found this to work for me.
// get the image data (16 bit unsigned array of intensities) from the ArrayBuffer
pixelData = new Uint8Array(arraybuffer, frameOffset, numPixels);
// set up canvas
var imageCanvas = document.getElementById('canvas_1');
imageCanvas.setAttribute("width", rows);
imageCanvas.setAttribute("height", columns);
var ctx = imageCanvas.getContext('2d');
// imageData is Uint8ClampedArray
var imageData = ctx.getImageData(0, 0, imageCanvas.width, imageCanvas.height);
// this part seems slow :(
for(var i = 0; i < numPixels; i++) {
imageData.data[4*i] = (pixelData[i]*255)/4095;
imageData.data[4*i+1] = (pixelData[i]*255)/4095;
imageData.data[4*i+2] = (pixelData[i]*255)/4095;
imageData.data[4*i+3] = 255;
}
ctx.putImageData(imageData, 0, 0);
The above works flawlessly.
Is there something that I am missing which can make it faster ?
Here is my code with a Uint8Array displayBuffer existing variable.
Instead of the loop, you can use the data.set() magic:
var tlCanvas = document.getElementById("tlCanvas");
tlCanvas.width = width; // width defined in displayBuffer
tlCanvas.height = height; // height defined in displayBuffer
var tlCtx = tlCanvas.getContext('2d');
var imgData= tlCtx.createImageData(width, height)
imgData.data.set(displayBuffer);
tlCtx.putImageData(imgData, 0, 0);
Some complement info here: How to create a new ImageData object independently?

JavaScript canvas, manually cloning a canvas onto another generates a weird pattern

I'm trying to make a text effect similar to the effect found at the bottom of this article
My proposed approach is:
Make two canvasses, one is visible, the other is invisible I use this as a buffer.
Draw some text on the buffer canvas
Loop over getImageData pixels
if pixel alpha is not equal to zero (when there is a pixel drawn on the canvas buffer) with a small chance, ie 2%, draw a randomly generated circle with cool effecs at that pixel on the visible canvas.
I'm having trouble at step 4. With the code below, I'm trying to replicate the text on the second canvas, in full red. Instead I get this weird picture.
code
// create the canvas to replicate the buffer text on.
var draw = new Drawing(true);
var bufferText = function (size, textFont) {
// set the font to Georgia if it isn't defined
textFont = textFont || "Georgia";
// create a new canvas buffer, true means that it's visible on the screen
// Note, Drawing is a small library I wrote, it's just a wrapper over the canvas API
// it creates a new canvas and adds some functions to the context
// it doesn't change any of the original functions
var buffer = new Drawing(true);
// context is just a small wrapper library I wrote to make the canvas API a little more bearable.
with (buffer.context) {
font = util.format("{size}px {font}", {size: size, font: textFont});
fillText("Hi there", 0, size);
}
// get the imagedata and store the actual pixels array in data
var imageData = buffer.context.getImageData(0, 0, buffer.canvas.width, buffer.canvas.height);
var data = imageData.data;
var index, alpha, x, y;
// loop over the pixels
for (x = 0; x < imageData.width; x++) {
for (y = 0; y < imageData.height; y++) {
index = x * y * 4;
alpha = data[index + 3];
// if the alpha is not equal to 0, draw a red pixel at (x, y)
if (alpha !== 0) {
with (draw.context) {
dot(x/4, y/4, {fillColor: "red"})
}
}
}
}
};
bufferText(20);
Note that here, my buffer is actually visible to show where the red pixels are supposed to go compared to where they actually go.
I'm really confused by this problem.
If anybody knows an alternative approach, that's very welcome too.
replace this...
index = x * y * 4;
with...
index = (imageData.width * y) + x;
the rest is good :)

Any javascript method for canvas image cover detection?

I'm developing following case.
Html canvas created by JS.
There are a star image(loaded from png with alpha=0 background) and a diamond image(also loaded from png with alpha=0 background) on canvas.
diamond image is moving toward the star image.
when diamond image is completely behind the star image, like showing star image only and diamond image is completely behind the star image, alert("Hidden");
if more than one pixel of the diamond is shown, alert should not appear.
Since the alpha value of the background of the star is 0, which means star is not a rectangle, it is difficult to detect whether the star image is fully covering the diamond image.
Is there any library or way to detect whether an image is fully covered by other?
Or, does any one know the name of this algorithm so that I can implement in JS?
Thanks for any help!
For objects with unknown shape we can check if object is behind by using pixel check.
Here is a full example on how to do this:
ONLINE DEMO HERE
(GameAlchemist provided a modified version here)
/// basic allocations
var ctx = demo.getContext('2d'),
os = document.createElement('canvas'),
octx = os.getContext('2d'),
w = os.width = demo.width,
h = os.height = demo.height,
/// the images
urlD = 'http://i.imgur.com/U72xIMZ.png',
urlS = 'http://i.imgur.com/n5rgo11.png',
imgD = new Image(),
imgS = new Image(),
cnt = 2,
/// check region (optimized)
rect = [140, 140, 180, 60];
/// load images and when ready, start show
imgD.crossOrigin = imgS.crossOrigin = 'anonymous';
imgD.onload = imgS.onload = function() {
cnt--;
if (cnt === 0) start();
}
imgD.src = urlD;
imgS.src = urlS;
The main function checks the pixels within the region defined above. To optimize we can narrow down the search area. If you need to check if image is visible on the other size the region is simply extended to check that area as well.
The function compares an off-screen canvas with just the foremost image drawn against the "live" canvas where both background and foreground are drawn.
If live canvas = off-screen canvas that means the background image is not visible.
function start() {
octx.drawImage(imgS, (w - imgS.width) * 0.5, 20);
var x = -50,
buffer1 = octx.getImageData(rect[0], rect[1], rect[2], rect[3]).data,
len = buffer1.length;
loop();
function loop() {
ctx.clearRect(0, 0, w, h);
ctx.drawImage(imgD, x, 130);
ctx.drawImage(imgS, (w - imgS.width) * 0.5, 20);
if (compare() === true) {
info.innerHTML = 'Object is behind!';
return;
}
x += 2;
if (x < w) requestAnimationFrame(loop);
}
function compare() {
var buffer2 = ctx.getImageData(rect[0], rect[1], rect[2], rect[3]).data,
i = len - 1;
while(i--) {
if (buffer1[i] !== buffer2[i]) return false
}
return true;
}
}

Html5 Get Particular Layer image data

i am creating a image slider with html5 and jquery what i want to do is add 3 images on top of each other in one canvas and then get pixeldata of first image and remove some of it's pixels to show 2nd image through first i'm using jCanvas
Plug-in To Do This In Jquery What I've Got So Far Is
$(document).ready(function(){
function invert() {
$("canvas").setPixels({
x: 150, y: 150,
width: 100, height: 75,
// loop through each pixel
each: function(px) {
px.r = 255 - px.r;
px.g = 255 - px.g;
px.b = 255 - px.b;
px.a = 255 - px.a;
}
});
}
$("canvas")
.addLayer({
method: "drawImage",
source: "images/01.jpg",
x: 100, y: 100,
width: 480, height: 440
}).addLayer({
method: "drawImage",
source: "images/02.jpg",
x: 100, y: 100,
width: 380, height: 340
}).addLayer({
method: "drawImage",
source: "images/01.jpg",
x: 100, y: 100,
width: 280, height: 240,
load: invert
})
// Draw each layer on the canvas
.drawLayers();
});
Now What it Does Is making A hole In all the Images Means Erase all the Pixels Of That Portion Of all Images and Show the Background of canvas Is It Possible to Get Only Pixels Of Particular image or layer and Invert It is there any jquery plug-in available? any other way to do that? Any Help On this Will Be Very Useful To Me....Thanx In Advance....
Keep in mind that drawing on a canvas is like painting on paper, it doesn't remember what you drew before only what you have in the canvas right now so if you draw one image and then draw over it with another, the old picture is lost forever.
What you should do is keep all three images in three different buffers (simply load the three different images into three different image objects).
Then draw the top most image in the context.
When you wish to dissolve the first image into the second, instead of deleting pixels from the top image (which will only show the the background), simply use the same coordinates you would use to remove pixels from the first image to get the pixel data from the second image (the coordinates for deleting pixel from the top image can be used as indexes to the image data for the second image) and copy those values to the canvas, again using the same coordinates, for example:
If you algorithm leads you to first remove pixel x = 100, y = 175, use those coordinates to get the data from the buffer of the second image and copy that to the same coordinates in the canvas' image data.
Here's some code:
var width = 300;
var height = 300;
var img1 = new Image();
img1.src = "image1.png";
var img2 = new Image();
img2.src = "image2.png";
function go()
{
// Wait for the images to load
if ( !img1.complete || !img2.complete )
{
setTimeout( go, 100 );
return;
}
// Create a temporary canvas to draw the other images in the background
var tc = document.createElement( "canvas" );
tc.width = width;
tc.height = height;
var c2 = tc.getContext( "2d" );
// Draw the first image in the real canvas (change the ID to your canvas ID)
var c = document.getElementById( "myCanvas" ).getContext( "2d" );
c.drawImage( img1, 0, 0 );
var data1 = c.getImageData( 0, 0, width, height ); // Get the data for the first image
// Draw the second image in the temporary canvas (which is hidden) and get its data
c2.drawImage( img2, 0, 0 );
var data2 = c2.getImageData( 0, 0, width, height );
// Copy the data from the hidden image to the visible one
// This is where your magic comes into play, the following
// is just a very very simple example
var pix1 = data1.data;
var pix2 = data2.data;
for ( var x = 0; x < width; x++ )
{
for ( var y = 0; y < height; y++ )
{
var pos = ( ( y * width ) + x ) * 4;
pix1[ pos ] = pix2[ pos++ ];
pix1[ pos ] = pix2[ pos++ ];
pix1[ pos ] = pix2[ pos++ ];
pix1[ pos ] = pix2[ pos ];
}
}
// Redraw the visible canvas with the new data
c.putImageData( data1, 0, 0 );
}
window.onload = go;
The canvas element does not provide the ability to use layers like that. You may need to check add-ons like canvas collage or CanvasStack

Categories

Resources