how to crop a polynomial shape from an image - javascript

I searched for this question on the internet and the methods I found were about cropping images with CSS or cropping a rectangular shape with JavaScript. What I want is to crop a polynomial with JavaScript.
I have an Array of cartesian points Arr=[{x:x1,y:y1},{x:x2,y:y2},{x:x3,y:y3}] that makes a polynomial. The first element in the array equals the last element in the array.
Using the function crop, I want to use this array to crop a polynomial from a picture imageObj and save it in imageObj2.
var imageObj = new Image();
var imageObj2 = new Image();
var arr=[];
arr.push({x:x1,y:y1});
arr.push({x:x2,y:y2});
arr.push({x:x3,y:y3});
imageObj.src = 'link of imageObj';
crop(arr[],imageObj);
How can I build this function?

You have to use a canvas for this. It's the only way to modify an image permanently. This will also require CORS to be fulfilled.
As image loading is asynchronous that will be an aspect that needs to be dealt with either using Promises (not supported in IE) or callbacks. For the example below I'll use callbacks.
First part is the loading process as well as handling the callbacks properly.
The second part (cropImage()) show the complete process of cropping the image and ending up with a new image of the size of the crop.
It assumes the points to be scaled relative to original image size (tip: you don't need a closing point, ie. end point = first point, as fill() will automatically close the path for you).
var srcImage = new Image(); // create image object
srcImage.onload = crop; // set callback
srcImage.crossOrigin = ""; // for demo: =anonymous crossOrigin usage
srcImage.src = "http://i.imgur.com/U9X0n84.jpg";
document.body.appendChild(srcImage); // add original to DOM for comparsion
function crop() {
// - image is loaded and is represented as "this" inside this callback
// some "random" points for demo
var arr = [{x: 10, y: 90}, {x: 70, y: 10}, {x: 400, y: 200}, {x: 200, y: 220}];
// do the cropping, provide callback
cropImage(this, arr, function(img) {
// img is the cropped image - add to DOM for demo
document.body.appendChild(img);
})
}
function cropImage(image, arr, callback) {
// create a canvas element, and get 2D context for it:
var canvas = document.createElement("canvas"),
ctx = canvas.getContext("2d"),
i, minx = 10000, miny = 10000, maxx = -1, maxy = -1;
// find min max of array points here:
for(i = 0; i < arr.length; i++) {
if (arr[i].x < minx) minx = arr[i].x;
if (arr[i].x > maxx) maxx = arr[i].x;
if (arr[i].y < miny) miny = arr[i].y;
if (arr[i].y > maxy) maxy = arr[i].y;
}
// set proper size:
canvas.width = maxx - minx;
canvas.height = maxy - miny;
// translate context so corner of clip is (0,0)
ctx.translate(-minx, -miny);
// draw in image;
ctx.drawImage(image, 0, 0);
// create a clip path:
ctx.moveTo(arr[0].x, arr[0].y);
for(i = 1; i < arr.length; i++) ctx.lineTo(arr[i].x, arr[i].y);
// set comp. mode so image within path is kept:
ctx.globalCompositeOperation = "destination-atop";
ctx.fill();
// done, create an image object:
var dstImage = new Image();
dstImage.onload = function() {callback(this)};
dstImage.src = canvas.toDataURL(); // saves PNG in this case as data-uri
}

Related

Downscale canvas element then get imageData

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);

Draw multiple image's on canvas with each image rotated

I'm new into HTML5 programming and I wanted to know how to rotate each image when it is added into canvas. Should each of them be placed into a canvas and then rotated? If so how can i add multiple canvas into a single canvas context.
Fiddle : http://jsfiddle.net/G7ehG/
Code
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var sources = {
image1: 'http://farm3.static.flickr.com/2666/3686946460_0acfa289fa_m.jpg',
image2: 'http://farm4.static.flickr.com/3611/3686140905_cbf9824a49_m.jpg'
};
loadImages(sources, function(images) {
context.drawImage(images.image1, 100, 30, 200, 137);
context.drawImage(images.image2, 350, 55, 93, 104);
});
In your comment you mentioned that you know about context.rotate, but you don't want the context to stay rotated. That's not a problem at all. First, calling context.rotate only affects things which are drawn afterwards. Anything drawn before will stay were it was. Second, it can be easily reversed after drawing.
use context.save() to create a snapshot of all current context settings, including current rotation.
use context.rotate(angle) and draw your image. The angle is in Radian. That means a full 360° circle is Math.PI * 2. The point the image will be is rotated around is the current origin of the canvas (0:0). When you want to rotate the image around its center, use context.translate(x, y) to set the origin to where you want the center of the image to be, then rotate, and then draw the image at the coordinates -img.width/ 2, -img.height / 2
use context.restore() to return to your snapshot. Rotation and translation will now be like they were before.
Here is an example function which draws an image rotated by 45° at the coordinates 100,100:
function drawRotated(image, context) {
context.save();
context.translate(100, 100);
context.rotate(Math.PI / 4);
context.drawImage(image, -image.width / 2, -image.height / 2);
context.restore();
}

JavaScript canvas image data manipulation

I want to resize image using very simple algorithm. I have something like this:
var offtx = document.createElement('canvas').getContext('2d');
offtx.drawImage(imageSource, offsetX, offsetY, width, height, 0, 0, width, height);
this.imageData = offtx.getImageData(0, 0, width, height).data;
offtx.clearRect(0, 0, width, height);
for(var x = 0; x < this.width; ++x)
{
for(var y = 0; y < this.height; ++y)
{
var i = (y * this.width + x) * 4;
var r = this.imageData[i ];
var g = this.imageData[i+1];
var b = this.imageData[i+2];
var a = this.imageData[i+3];
offtx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")";
offtx.fillRect(0.5 + (x * this.zoomLevel) | 0, 0.5 + (y*this.zoomLevel) | 0, this.zoomLevel, this.zoomLevel);
}
}
this.imageData = offtx.getImageData(0, 0, this.width * this.zoomLevel, this.height * this.zoomLevel);
However, the problem I have with this solution, is that the image looses any transparency information that way. I don't know if this happens somewere in this algorithm, or maybe putImageData that I am using later to display that image is doing this, but I can't seem to be able to preserve transparency.
Each time I do this I create a canvas, I put the image on that canvas and use getImageData to get image from that canvas as you can see in the first lines of the code. Maybe there is no other way, so I might not mind that...
But the problem is I use two for loops to draw resized image and then use getImageData to store that image information. This is a wierd way to do it. I would prefer to create empty image data and fill it with all the original image information only resized. I can't grasp that with my mind, I can't image the loop structure for this. To show what I mean:
for(var x = 0; x < this.width; ++x)
{
for(var y = 0; y < this.height; ++y)
{
var i = (y * this.width + x) * 4;
var r = this.imageData[i ];
var g = this.imageData[i+1];
var b = this.imageData[i+2];
var a = this.imageData[i+3];
//I WOULD LIKE MAGIC TO HAPPEN HERE THAT WILL
//RESIZE THAT CURRENT PIXEL AND MOVE IT TO THE NEW IMAGE DATA RESIZED
//SO EVERYTHING IS DONE NICE AND CLEAN IN THIS LOOP WITHOUT THE
//GETIMAGEDATA LATER AND MAYBE SET TRANSPARENT PIXELS WHILE I'M AT IT
}
}
I can't figure out the MAGIC part.
Thank you for reading!
Why not just use the built-in drawImage combined with image smoothing disabled? Doing this operation in a loop is not only relative slow but also prone to errors (as you already discovered).
Doing it the following way will give you the "pixel art" look and will also preserve the alpha channel:
var factor = 4; /// will resize 4x
offtx.imageSmoothingEnabled = false; /// prefixed in some browsers
offtx.drawImage(imageSource, offsetX, offsetY, width, height,
0, 0, width * factor, height * factor);
Here is an online demo.
Try using this library I recently made which can load an image, resize it fixed width & height or precentage.
It does exactly what you need, and much more like converting canvas to base64, blob, etc...
var CanvaWork = new CanvaWork();
CanvaWork.canvasResizeAll(obj.canvas, function(canvases){
// "canvases" will be an array containing 3 canvases with different sizes depending on initial options
});
https://github.com/vnbenny/canvawork.js
Hope this helps you!

html5 canvas array of images- draw image to canvas

I'm using the HTML5 canvas and JavaScript to make a basic game, and I have an array of images for the numbers 1-10, and then have another array for the Welsh words for the numbers 1-10.
What I want to do is select a random element from the images array and a random element from the words array and display them both on the canvas. The user will then click on a tick to indicate if the word represents the correct number, or a cross if it doesn't.
The problem is that I'm not sure how to draw an array element to the canvas. I have the following code, which I was going to use just to test that it works, before I think about how to make the elements drawn be chosen at random:
function drawLevelOneElements(){
/*First, clear the canvas */
context.clearRect(0, 0, myGameCanvas.width, myGameCanvas.height);
/*This line clears all of the elements that were previously drawn on the canvas. */
/*Then redraw the game elements */
drawGameElements();
/*Now draw the elements needed for level 1 (08/05/2012) */
/*First, load the images 1-10 into an array */
var imageArray = new Array();
imageArray[0] = "1.png";
imageArray[1] = "2.png";
imageArray[2] = "3.png";
imageArray[3] = "4.png";
imageArray[4] = "5.png";
imageArray[5] = "6.png";
imageArray[6] = "7.png";
imageArray[7] = "8.png";
imageArray[8] = "9.png";
imageArray[9] = "10.png";
/*Then create an array of words for numbers 1-10 */
var wordsArray = new Array();
wordsArray[0] = "Un";
wordsArray[1] = "Dau";
wordsArray[2] = "Tri";
wordsArray[3] = "Pedwar";
wordsArray[4] = "Pump";
wordsArray[5] = "Chwech";
wordsArray[6] = "Saith";
wordsArray[7] = "Wyth";
wordsArray[8] = "Naw";
wordsArray[9] = "Deg";
/*Draw an image and a word to the canvas just to test that they're being drawn */
context.drawImage(imageArray[0], 100, 30);
context.strokeText(wordsArray[3], 500, 60);
}
but for some reason, when I view the page in the browser, in the firebug console, I get the error:
Could not convert JavaScript argument arg 0 [nsIDOMCanvasRenderingContext2D.drawImage]
context.drawImage(imageArray[0], 100, 30);
I'm not sure if this is how I'm meant to access the image in array element 0... could someone please point out what I'm doing wrong?
* EDIT *
I've changed the code below the to arrays to:
var image1 = new Image();
image1.src = imageArray[0];
/*Draw an image and a word to the canvas just to test that they're being drawn */
context.drawImage(image1, 100, 30);
context.strokeText(wordsArray[3], 500, 60);
but for some reason, the only the element from the wordsArray is drawn to the canvas- the image element from imageArray isn't displayed at all.
Any ideas?
You need to create a javascript image with it's src set to your array value
var img = new Image();
img.src = imageArray[0];
ctx.drawImage(img, 100, 30);
Without doing that you're trying to ask the canvas to draw a string of "1.png" for example which is not what you're after here!
This is the code for drawGameElements()
/* This function draws the game elements */
function drawGameElements(){
/* Draw a line for the 'score bar'. */
context.moveTo(0, 25);
context.lineTo(700, 25);
context.stroke();
/* Draw current level/ total levels on the left, and current score on the right. */
context.font = "11pt Calibri"; /* Text font & size */
context.strokeStyle = "black"; /* Font colour */
context.strokeText(currentLevel + "/" + totalLevels, 10, 15);
context.strokeText(currentScore, 650, 15);
}
Literally, all it's doing is drawing a 'score bar' on the canvas, which is just a line across the top, the current level/ total levels, and the user's current score. I don't think this is the issues, as the elements that this function is meant to display are being displayed correctly.
This is an old one but the reason why the image is not showing is probably because you have to call onLoad then set the src like so:
var img = new Image();
img.onLoad = function() {ctx.drawImage(img, 100, 30);};
img.src = imageArray[0];
I solved this using recursive calls on the method img.onload to draw images.
E.g.:
var cx = 10;//x initial position to draw
var cy = 10;//y initial position to draw
var space = 300; //distance between images to draw
var imageArray = new Array();
imageArray[0] = "1.png";
imageArray[1] = "2.png";
imageArray[2] = "3.png";
//etc....
//build a Image Object array
var imgs = new Array();
for(i = 0; i < imageArray.length; i++){
imgs[i] = new Image();
imgs[i].src = imageArray[i];//attention if the images are in a folder
}
var ri = 1;//index of images on the array
imgs[0].onload = function(){
context.drawImage(imgs[0], cx, cy);
cy += imgs[0].height + space;
callDraw(context, imgs, cx, cy, ri, space);
}
The recursive function is defined as following:
function callDraw(context, imgs, cx, cy, ri, space){
if(ri == imgs.length)
return;
context.drawImage(imgs[ri], cx, cy);
cy += imgs[ri].height + space;
ri++;
callDraw(context, imgs, cx, cy, ri, space);
}

How to check if a specific pixel of an image is transparent?

Is there any way to check if a selected (x,y) point of a PNG image is transparent?
Building on Jeff's answer, your first step would be to create a canvas representation of your PNG. The following creates an off-screen canvas that is the same width and height as your image and has the image drawn on it.
var img = document.getElementById('my-image');
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
After that, when a user clicks, use event.offsetX and event.offsetY to get the position. This can then be used to acquire the pixel:
var pixelData = canvas.getContext('2d').getImageData(event.offsetX, event.offsetY, 1, 1).data;
Because you are only grabbing one pixel, pixelData is a four entry array containing the pixel's R, G, B, and A values. For alpha, anything less than 255 represents some level of transparency with 0 being fully transparent.
Here is a jsFiddle example: http://jsfiddle.net/thirtydot/9SEMf/869/ I used jQuery for convenience in all of this, but it is by no means required.
Note: getImageData falls under the browser's same-origin policy to prevent data leaks, meaning this technique will fail if you dirty the canvas with an image from another domain or (I believe, but some browsers may have solved this) SVG from any domain. This protects against cases where a site serves up a custom image asset for a logged in user and an attacker wants to read the image to get information. You can solve the problem by either serving the image from the same server or implementing Cross-origin resource sharing.
Canvas would be a great way to do this, as #pst said above. Check out this answer for a good example:
getPixel from HTML Canvas?
Some code that would serve you specifically as well:
var imgd = context.getImageData(x, y, width, height);
var pix = imgd.data;
for (var i = 0, n = pix.length; i < n; i += 4) {
console.log pix[i+3]
}
This will go row by row, so you'd need to convert that into an x,y and either convert the for loop to a direct check or run a conditional inside.
Reading your question again, it looks like you want to be able to get the point that the person clicks on. This can be done pretty easily with jquery's click event. Just run the above code inside a click handler as such:
$('el').click(function(e){
console.log(e.clientX, e.clientY)
}
Those should grab your x and y values.
The two previous answers demonstrate how to use Canvas and ImageData. I would like to propose an answer with runnable example and using an image processing framework, so you don't need to handle the pixel data manually.
MarvinJ provides the method image.getAlphaComponent(x,y) which simply returns the transparency value for the pixel in x,y coordinate. If this value is 0, pixel is totally transparent, values between 1 and 254 are transparency levels, finally 255 is opaque.
For demonstrating I've used the image below (300x300) with transparent background and two pixels at coordinates (0,0) and (150,150).
Console output:
(0,0): TRANSPARENT
(150,150): NOT_TRANSPARENT
image = new MarvinImage();
image.load("https://i.imgur.com/eLZVbQG.png", imageLoaded);
function imageLoaded(){
console.log("(0,0): "+(image.getAlphaComponent(0,0) > 0 ? "NOT_TRANSPARENT" : "TRANSPARENT"));
console.log("(150,150): "+(image.getAlphaComponent(150,150) > 0 ? "NOT_TRANSPARENT" : "TRANSPARENT"));
}
<script src="https://www.marvinj.org/releases/marvinj-0.7.js"></script>
Building on Brian Nickel's answer, only the wanted single pixel of the source image is drawn onto a 1*1 pixel canvas, which is more efficient than drawing the entire image just to get a single pixel:
function getPixel(img, x, y) {
let canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
canvas.getContext('2d').drawImage(img, x, y, 1, 1, 0, 0, 1, 1);;
let pixelData = canvas.getContext('2d').getImageData(0, 0, 1, 1).data;
return pixelData;
}
With : i << 2
const pixels = context.getImageData(x, y, width, height).data;
for (let i = 0, dx = 0; dx < data.length; i++, dx = i << 2)
{
if (pixels[dx+3] <= 8) { console.log("transparent x= " + i); }
}
Here's a consolidation of a few answers into a runnable snippet that lets you upload a file, hover to preview the RGB value of each pixel, then click to put the RGB in a div.
Pertinent to the original question, the last value (alpha) is the transparency. 0 is fully transparent and 255 is fully opaque.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const input = document
.querySelector('input[type="file"]');
input.addEventListener("change", e => {
const image = new Image();
image.addEventListener("load", e => {
const {width, height} = image;
canvas.width = width;
canvas.height = height;
ctx.drawImage(image, 0, 0);
const {data} = ctx.getImageData(
0, 0, width, height
);
const rgb = (x, y) => {
const i = (x + y * width) * 4;
return data.slice(i, i + 4).join(", ");
};
canvas.addEventListener("mousemove", event => {
const {offsetX: x, offsetY: y} = event;
console.log(rgb(x, y));
});
canvas.addEventListener("click", event => {
const {offsetX: x, offsetY: y} = event;
document.querySelector("div")
.textContent = rgb(x, y);
});
});
image.addEventListener("error", () =>
console.error("failed")
);
image.src = URL
.createObjectURL(event.target.files[0]);
});
.as-console-wrapper {
height: 21px !important;
}
<div>
Upload image and mouseover to preview RGB. Click to select a value.
</div>
<form>
<input type="file">
</form>
<canvas></canvas>
References:
HTML5 Canvas - How to get adjacent pixels position from the linearized imagedata Uint8ClampedArray?
How to upload image into HTML5 canvas

Categories

Resources