Save images after using CSS filter - javascript

I'm building a new website that will let users apply filters to images (just like Instagram). I will use -webkit-filter for that.
The user must be able to save the filtered image. There is any way I can do that using JavaScript?

You can't save images directly, but you can render them in Canvas, then save from there.
See: Save HTML5 canvas with images as an image

There is no direct/straight forward method to export an image with CSS Filter.
Follow the below steps for Saving/Exporting an Image with -webkit-filter applied on it:
1. Render the image to a canvas:
var canvas = document.createElement('canvas');
canvas.id="canvasPhoto";
canvas.width = imageContaainer.width;
canvas.height = imageContaainer.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(imageContaainer, 0, 0, canvas.width, canvas.height);
Get the ImageData from canvas and apply the filter. Eg: I will apply grayscale filter to the ImageData below:
function grayscale(ctx) {
var pixels = ctx.getImageData(0,0,canvas.width, canvas.height);
var d = pixels.data;
for (var i=0; i<d.length; i+=4) {
var r = d[i];
var g = d[i+1];
var b = d[i+2];
var v = 0.2126*r + 0.7152*g + 0.0722*b;
d[i] = d[i+1] = d[i+2] = v
}
context.putImageData(pixels, 0, 0);
}
Add an event and use the below code to trigger download
function download(canvas) {
var data = canvas.toDataURL("image/png");
if (!window.open(data))
{
document.location.href = data;
}
}

Related

How to convert Canvas element to string

In general we can convert the HTML elements to string and then we can insert it into DOM later when needed. Similarly, I want to convert the "CANVAS" element to string along with its context properties.
In the following example, I am getting the string value of span tag with outerHTML property. Likewise I want to get the "CANVAS"element along with context properties.
Is there any method or property for this support?
Example code snippets:
var sp=document.createElement("span");
sp.innerHTML = "E2"
var e2 = sp.outerHTML;
$("#test1").append(e2);
var c=document.createElement("CANVAS");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.moveTo(20,20);
ctx.lineTo(100,20);
ctx.arcTo(150,20,150,70,50);
ctx.lineTo(150,120);
ctx.stroke();
var cn = c.outerHTML;
$("#test2").append(cn);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="test1">
<span>E1</span>
</div>
<div id="test2">
</div>
Seems like you already know how to get dom properties of the canvas object.
Now you only need "context" infos (image data as I understand it)
You can get the image data as a base64 string like this:
function CreateDrawing(canvasId) {
let canvas = document.getElementById(canvasId);
let ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(20,20);
ctx.lineTo(100,20);
ctx.arcTo(150,20,150,70,50);
ctx.lineTo(150,120);
ctx.stroke();
}
function GetDrawingAsString(canvasId) {
let canvas = document.getElementById(canvasId);
let pngUrl = canvas.toDataURL(); // PNG is the default
// or as jpeg for eg
// var jpegUrl = canvas.toDataURL("image/jpeg");
return pngUrl;
}
function ReuseCanvasString(canvasId, url) {
let img = new Image();
img.onload = () => {
// Note: here img.naturalHeight & img.naturalWidth will be your original canvas size
let canvas = document.getElementById(canvasId);
let ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
}
img.src = url;
}
// Create something
CreateDrawing("mycanvas");
// save the image data somewhere
var url = GetDrawingAsString("mycanvas");
// re use it later
ReuseCanvasString("replicate", url);
<canvas id="mycanvas"></canvas>
<canvas id="replicate"></canvas>
In short no!
You should realize the difference between a standard DOM-element and a canvas-element:
A created DOM-element is part of the mark-up language that can be viewed and changed.
In the canvas a vector image is drawn based upon the rules created in script. These rules are not stored in the element as text but as the image and can't be subtracted from the canvas element.
However there are other possibilities. We can get the variables from the ctx-object. But no info about coordinates:
var c=document.createElement("CANVAS");
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.moveTo(20,20);
ctx.lineTo(100,20);
ctx.arcTo(150,20,150,70,50);
ctx.lineTo(150,120);
ctx.stroke();
var ctxInfo = [];
for (ctxKey in ctx)
{
if (Object.prototype.toString.call(ctx[ctxKey]) !== "[object Function]" )
{
ctxInfo.push(ctxKey + " : " + ctx[ctxKey]);
}
}
console.log(ctxInfo);
To transfer from one canvas to the other I would keep a list (array or object) of instructions and write a generic function that applies them.
canvasObject = [["beginPath"], ["moveTo", 20, 20], ["lineTo", 100, 20], ["arcTo", 150, 20, 150, 70, 50], ["lineTo", 150, 120], ["stroke"]];
function createCanvas(cnvsObj)
{
var c = document.createElement("canvas");
var ctx = c.getContext("2d");
cnvsObj.forEach(function(element){
//loop through instructions
ctx[element[0]].apply(ctx, element.slice(1));
});
return c;
}
var a = createCanvas(canvasObject);
document.body.appendChild(a);

How to copy canvas image data to some other variable?

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>

Write from one fixed canvas to another floating

I have an imageCanvas with it's Transform matrix
Image = new image();
image.src = //stuff;
imageContext.drawImage(Image, 0, 0);
var t = imageContext.currentTransform;
that is drawn on another canvas
context.drawImage(imageCanvas, 0, 0);
on which I draw lines
context.moveTo(mousePos.X, mousePos.Y);
context.lineTo(currentPos.X, currentPos.Y);
context.stroke();
I save my lines into a vector of objects 'lines'.
I would like to join the two layers into the 'Image', but this function works only for traslation but not for rotation and scaling.
var saveCanvas = document.createElement('canvas');
var saveContext = saveCanvas.getContext('2d');
saveCanvas.width = Image.width;
saveCanvas.height = Image.height;
saveContext.drawImage(Image,0,0);
saveContext.save();
var t = imageContext.currentTransform;
saveContext.setTransform(1,0,0,1,-t.e,-t.f);
for(var i = 0; i < lines.length; i++){
saveContext.beginPath();
saveContext.moveTo(lines[i].from.X, lines[i].from.Y);
saveContext.lineTo(lines[i].to.X, lines[i].to.Y);
saveContext.stroke();
}
saveContext.restore();
Image.src = saveCanvas.toDataURL();
how can I modify the arguments of 'setTransform' to solve the problem ?
Just use the rest of the transforms attributes.
var t = imageContext.currentTransform;
saveContext.setTransform(
t.a,
t.b,
t.c,
t.d,
-t.e,
-t.f);

How to draw image to canvas if you just have the link to the image?

In Javascript, what's the best way to draw an image to a canvas only if you have the link to the image, like this: http://www.mydomain.com/folder/car.png?
Note: the link will be on the same domain incase you were wondering.
Instantiate an Image(), set the url, and call context.drawImage(params).
http://www.html5canvastutorials.com/tutorials/html5-canvas-images/
http://jsfiddle.net/dYupU/
You just need to define a js Image object, and use drawImage()
var d_canvas = document.getElementById('canvas');
var context = d_canvas.getContext('2d');
var ballon = new Image();
ballon.src = "http://i.imgur.com/6l6v2.png";
context.drawImage(ballon, 100, 1)
Here's an example with multiple links, and an alert inside the loop to show you each link's value
var can = document.getElementById('pictures');
var ctx = can.getContext('2d');
var img = ['http://placekitten.com/200/300','http://placekitten.com/100/100','http://placekitten.com/125/125','http://placekitten.com/50/50','http://placekitten.com/150/300'];
for(var i = 0; i <= img.length - 1; i ++){
var cat = new Image();
cat.src = img[i];
ctx.drawImage(cat, 50, 50);
alert('drew ' + cat.src);
}
DEMO

HTML5 Canvas - Repeat Canvas Element as a Pattern

I have a 64x64 canvas square element which I want to repeat in x- and y-directions to fill the page. I've found many explanations on how to do this with an image, but none explaining how to do it with a canvas element. Here is my code so far:
$(document).ready(function(){
var canvas = document.getElementById('dkBg');
var ctx = canvas.getContext('2d');
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
ctx.fillStyle = 'rgb(0,0,0)';
//I want the following rectangle to be repeated:
ctx.fillRect(0,0,64,64);
for(var w=0; w<=64; w++){
for(var h=0; h<=64; h++){
rand = Math.floor(Math.random()*50);
while(rand<20){
rand = Math.floor(Math.random()*50);
}
opacity = Math.random();
while(opacity<0.5){
opacity = Math.random();
}
ctx.fillStyle= 'rgba('+rand+','+rand+','+rand+','+opacity+')';
ctx.fillRect(w,h,1,1);
}
}
});
The thing is, I don't want all of the random numbers/etc to be regenerated. I just want to tile the exact same square to fit the page. Is this possible?
Here is a fiddle: http://jsfiddle.net/ecMDq/
Doing what you want is actually super easy. You do not need to use images at all. The createPattern function accepts an image or another canvas! (Or a video tag, even)
All you have to do is make a canvas that is only 64x64 large and make the pattern on it. Let's call this canvas pattern. You only have to make your design once.
Then with the context of the main canvas we can do:
// "pattern" is our 64x64 canvas, see code in fiddle
var pattern = ctx.createPattern(pattern, "repeat");
ctx.fillStyle = pattern;
Working example using your code to make the pattern, then repeating it onto a 500x500 canvas:
http://jsfiddle.net/tGa8M/
You can get the base64 version of your image using the toDataURL() method of the canvas element.
From there it's as simple as setting the background-image of your page to the string "url(" + base64 + ")"
Here is a working example: http://jsfiddle.net/ecMDq/1/
$(document).ready(function(){
var canvas = document.getElementById('dkBg');
var ctx = canvas.getContext('2d');
ctx.canvas.width = 64; //window.innerWidth;
ctx.canvas.height = 64; //window.innerHeight;
ctx.fillStyle = 'rgb(0,0,0)';
//I want the following rectangle to be repeated:
ctx.fillRect(0,0,64,64);
for(var w=0; w<=64; w++){
for(var h=0; h<=64; h++){
rand = Math.floor(Math.random()*50);
while(rand<20){
rand = Math.floor(Math.random()*50);
}
opacity = Math.random();
while(opacity<0.5){
opacity = Math.random();
}
ctx.fillStyle= 'rgba('+rand+','+rand+','+rand+','+opacity+')';
ctx.fillRect(w,h,1,1);
}
}
document.documentElement.style.backgroundImage =
'url(' +canvas.toDataURL() + ')';
});
Note that you need to make the canvas 64x64 because that's the size of your source image.
You can also now make the canvas display:none, or even remove it from the dom completely because it's only acting as a source for the background-image.
Also, what on earth is up with those while loops?
while(rand<20){
rand = Math.floor(Math.random()*50);
}
It looks like you're trying to enforce a minimum value. Just use this:
rand = Math.floor(Math.random() * (50-20) + 20);

Categories