I have a concept of what I’d like to do, but need help putting the pieces together.
I am playing around with html2canvas for a feedback form. Its works well for me but my Flickr (cross domain) photograph images don’t work.
I am wondering instead of creating an entire proxy server cant I just convert the images to base64 on the client side? Then once converted just do a replace of the “data-image” src with the base64 string?
Html
<div class='container1'>
<div class='box photo col3'> <a data-image='http://farm4.static.flickr.com/3737/1530282_3bc6b9.jpg' data-toggle='lightbox' href='http://www.flickr.com/photos/1773075#N04' target='_blank'></a></div>
…..
</div>
JS (source:SO)
function convertImgToBase64(url, callback, outputFormat){
var canvas = document.createElement('CANVAS'),
ctx = canvas.getContext('2d'),
img = new Image;
img.crossOrigin = 'Anonymous';
img.onload = function(){
var dataURL;
canvas.height = img.height;
canvas.width = img.width;
ctx.drawImage(img, 0, 0);
dataURL = canvas.toDataURL(outputFormat);
callback.call(this, dataURL);
canvas = null;
};
img.src = url;
}
Putting it together:
Here is where I have the issues. What I am trying to do is a for each data-image in div class container1, replace the src image with a base64 string so its included in my canvas.
$( "data-image" ).each( function( index, element ){
convertImgToBase64($( this ).text(), function(base64Img){
replace $(this).attr(‘data-image’) with base64 string
});
});
Selector [atribute_name] get all elements with specified attribute.
Also text() get text between open and close tag, use data('image') instead.
$("[data-image]").each( function( index, element ){
convertImgToBase64($( this ).data('image'), function(base64Img){
$(element).attr('src', base64Img);
});
});
Demo
Related
For some reason,I can't use any xhr request in my webview,so all the src of img can be loaded when I open the html, but if I want to save them to a pdf using something like jsPDF, all the img will be missing because of xhr request fail.So I want to ask is there any way to convert img src to base64 after page loaded without xhr request? And after page loaded,I think the images are already downloaded, so is it possible to find it locally and convert to base64 then set them back to src?
maybe use canvas to change srcs to base64?
const images = document.querySelectorAll("img");
images.forEach(img => {
img.onload = function() {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const dataURL = canvas.toDataURL("image/jpeg");
img.setAttribute("src", dataURL);
};
});
I'm using an ip camera.
For live stream:
<img id="ip-camera-frame" src="http://192.168.1.10/GetData.cgi?CH=1"></img>
I take a snapshot from camera with link "/GetImage.cgi?CH=0" and i can set "img" tag in modalbox.
This snapshot is OK, I want to download captured image but all download methots getting an new capture and download.
<div id="snapshot" class="modal-demo">
<div class="custom-modal-text">
<img id="snapshot-frame" width="100%"></img >
</div>
<div class="modal-footer">
<button type="button" id="save-snapshot">Download Snapshot</button>
</div>
You can try to copy image data from already loaded and displayed image.
as it describe here:
Get image data in JavaScript?
and download it like it described here:
Browser/HTML Force download of image from src="data:image/jpeg;base64..."
so you should have something like:
document.getElementById('save-snapshot').addEventListener("click", function(e) {
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.onload = function() {
var canvas = document.createElement("canvas");
canvas.width = this.width;
canvas.height = this.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(this, 0, 0);
// guess the original format, maybe "image/jpg"
var dataURL = canvas.toDataURL("image/png");
window.open(dataURL.replace(/^data:image\/(png|jpg);base64,/, ""));
};
img.src = document.getElementById("ip-camera-frame").src;
}, false);
I'm using a plugin (dom-to-image) to generate a SVG content from a div.
It returns me a dataURL like this:
data image/xml, charset utf-8, <svg...
If a put this on a <img src the image is shown to normally.
The intent is to grab this dataURL, convert it to base64 so I can save it as an image.png on a mobile app.
Is it possible?
I tryied this solution https://stackoverflow.com/a/28450879/1691609
But coudn't get to work.
The console fire an error about the dataUrl
TypeError: Failed to execute 'serializeToString' on 'XMLSerializer': parameter 1 is not of type 'Node'.
==== UPDATE :: PROBLEM EXPLANATION/HISTORY ====
I'm using Ionic Framework, so my project is an mobile app.
The dom-to-image is already working cause right now, its rendering a PNG through toPng function.
The problem is the raster PNG is a blurry.
So I thought: Maybe the SVG will have better quality.
And it IS!! Its 100% perfect, actually.
On Ionic, I'm using 2 step procedure to save the image.
After get the PNG generated by the dom-to-img(base64) dataURL, I convert it to a Blob and then save into device.
This is working, but the final result, as I said, is blurry.
Then with SVG maybe it will be more "high quality" per say.
So, in order to do "minimal" change on a process that s already working :D I just need to convert an SVG into base64 dataURL....
Or, as some of you explained to me, into something else, like canvas...
I don't know any much :/
===
Sorry for the long post, and I really, really thank your help guys!!
EDIT COUPLE OF YARS LATER
Use JS fiddle for a working example: https://jsfiddle.net/msb42ojx/
Note, if you don't own DOM content (images), and those images don't have CORS enabled for everyone (Access-Control-Allow-Origin header), canvas cant render those images
I'm not trying to find out why is your case not working, here is how I did when I had something similar to do:
get the image sourcce (dom-to-image result)
set up a canvas with that image inside (using the image source)
download the image from canvas in whatever image you like: png, jpeg whatever
by the way you can resize the image to a standard format
document.getElementById('mydownload').onclick= function(){
var wrapper = document.getElementById('wrapper');
//dom to image
domtoimage.toSvg(wrapper).then(function (svgDataUrl) {
//download function
downloadPNGFromAnyImageSrc(svgDataUrl);
});
}
function downloadPNGFromAnyImageSrc(src)
{
//recreate the image with src recieved
var img = new Image;
//when image loaded (to know width and height)
img.onload = function(){
//drow image inside a canvas
var canvas = convertImageToCanvas(img);
//get image/png from convas
var pngImage = convertCanvasToImage(canvas);
//download
var anchor = document.createElement('a');
anchor.setAttribute('href', pngImage.src);
anchor.setAttribute('download', 'image.png');
anchor.click();
};
img.src = src;
// Converts image to canvas; returns new canvas element
function convertImageToCanvas(image) {
var canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
canvas.getContext("2d").drawImage(image, 0, 0);
return canvas;
}
// Converts canvas to an image
function convertCanvasToImage(canvas) {
var image = new Image();
image.src = canvas.toDataURL("image/png");
return image;
}
}
#wrapper{
background: red;
color: blue;
}
<script src="https://rawgit.com/tsayen/dom-to-image/master/src/dom-to-image.js"></script>
<button id='mydownload'>Download DomToImage</button>
<div id="wrapper">
<img src="http://i.imgur.com/6GvKdxY.jpg"/>
<div> DUDE IS WORKING</div>
<img src="http://i.imgur.com/6GvKdxY.jpg"/>
</div>
I translated #SilentTremor's solution into React/JS-Class:
class SVGToPNG {
static convert = function (src) {
var img = new Image();
img.onload = function () {
var canvas = SVGToPNG.#convertImageToCanvas(img);
var pngImage = SVGToPNG.#convertCanvasToImage(canvas);
var anchor = document.createElement("a");
anchor.setAttribute("href", pngImage.src);
anchor.setAttribute("download", "image.png");
anchor.click();
};
img.src = src;
};
static #convertImageToCanvas = function (image) {
var canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
canvas.getContext("2d").drawImage(image, 0, 0);
return canvas;
};
static #convertCanvasToImage = function (canvas) {
var image = new Image();
image.src = canvas.toDataURL("image/png");
return image;
};
}
export default SVGToPNG;
Usage:
let dataUrl = someCanvas.toDataURL("image/svg+xml");
SVGToPNG.convert(dataUrl);
I am working on a component in which there is file-upload HTML control, upon selecting an image using the file-upload element, the image would be rendered on the HTML5 Canvas element.
Here is JSFiddle with sample code: https://jsfiddle.net/govi20/spmc7ymp/
id=target => selector for jcrop element
id=photograph => selector for fileupload element
id=preview => selector for canvas element
id=clear_selection => selector for a button which would clear the canvas
Third-party JS libraries used:
<script src="./js/jquery.min.js"></script>
<script src="./js/jquery.Jcrop.js"></script>
<script src="./js/jquery.color.js"></script>
Setting up the JCrop:
<script type="text/javascript">
jQuery(function($){
var api;
$('#target').Jcrop({
// start off with jcrop-light class
bgOpacity: 0.5,
keySupport: false,
bgColor: 'black',
minSize:[240,320],
maxSize:[480,640],
onChange : updatePreview,
onSelect : updatePreview,
height:160,
width:120,
addClass: 'jcrop-normal'
},function(){
api = this;
api.setSelect([0,0,240,320]);
api.setOptions({ bgFade: true });
api.ui.selection.addClass('jcrop-selection');
});
});
clear canvas event which will be triggered on clear button click event:
jQuery('#clear_selection').click(function(){
$('#target').Jcrop({
setSelect: [0,0,0,0],
});
});
code that renders image on HTML5 Canvas:
function readURL(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#target').attr('src', e.target.result);
setProperties();
}
reader.readAsDataURL(input.files[0]);
}
}
function setProperties(){
$('#target').Jcrop({
setSelect: [0,0,240,320]
});
}
$("#photograph").change(function(){
readURL(this);
});
code to crop and render an image on the canvas:
var canvas = document.getElementById('preview'),
context = canvas.getContext('2d');
make_base();
function updatePreview(c) {
console.log("called");
if(parseInt(c.w) > 0) {
// Show image preview
var imageObj = $("#target")[0];
var canvas = $("#preview")[0];
var context = canvas.getContext("2d");
context.drawImage(imageObj, c.x, c.y, c.w, c.h, 0, 0, canvas.width, canvas.height);
}
};
function make_base() {
console.log("make_base called");
var base_image = new Image();
base_image.src = '';
base_image.onload = function () {
context.drawImage(base_image, 0, 0);
}
}
Here are a bunch of issues I am facing with the above setup:
updatePreview function is not getting called on selection, hence the canvas is not getting rendered.
crop selection box is not draggable (I am using bootstrap CSS, I suspect it is due to missing/mismatching dependency).
Canvas is HTML5 element, which means the end-user must have an HTML5 compatible browser, I am working on an app that has millions of users. Forcing users to use the latest browser is not a feasible option. What should be the fallback mechanism here?
Here's basic html 5 code:
https://jsfiddle.net/zm7e0jev/
This code crops the image, shows a preview and sets the value of an input element to the base64 encoded cropped image.
You can fetch the image file in php the following way:
//File destination
$destination = "/folder/cropped_image.png";
//Get convertable base64 image string
$image_base64 = $_POST["png"];
$image_base64 = str_replace("data:image/png;base64,", "", $image_base64);
$image_base64 = str_replace(" ", "+", $image_base64);
//Convert base64 string to image data
$image = base64_decode($image_base64);
//Save image to final destination
file_put_contents($destination, $image);
Submitting base64 image string as a post variable has it's server post size limits and base64 encoding makes the cropped image file size even bigger (~33%) then the raw data of the cropped image would be which makes the upload take even longer.
To set the post size limit: What is the size limit of a post request?
Keep in mind that an increased post size limit can be abused for a DoS attack as example.
Instead I suggest converting the base64 cropped image to a data blob and then add it to the form on submit as a file:
https://jsfiddle.net/g3ysk6sf/
Then you can fetch the image file in php the following way:
//File destination
$destination = "/folder/cropped_image.png";
//Get uploaded image file it's temporary name
$image_tmp_name = $_FILES["cropped_image"]["tmp_name"][0];
//Move temporary file to final destination
move_uploaded_file($image_tmp_name, $destination);
Update:
FormData() is only partially support in IE10 and not supported in older versions of IE
So I suggest sending the base64 string as a fallback, though this will cause problems with bigger images so it needs to check the filesize and show an error popup when the image is above a specific size.
I'll post an update with the fallback code below when I've got it working.
Update 2:
I added a fallback for IE10 and below:
https://jsfiddle.net/oupxo3pu/
The only limitation is the image size that can be submitted when using IE10 and below, in case the image size is too big the js code will throw an error. The maximum size to work for post values is different between each server, the js code has a variable to set the maximum size.
The php code below is adapted to work with above fallback:
//File destination
$destination = "/folder/cropped_image.png";
if($_POST["png"]) {//IE10 and below
//Get convertable base64 image string
$image_base64 = $_POST["png"];
$image_base64 = str_replace("data:image/png;base64,", "", $image_base64);
$image_base64 = str_replace(" ", "+", $image_base64);
//Convert base64 string to image data
$image = base64_decode($image_base64);
//Save image to final destination
file_put_contents($destination, $image);
} else if($_FILES["cropped_image"]) {//IE11+ and modern browsers
//Get uploaded image file it's temporary name
$image_tmp_name = $_FILES["cropped_image"]["tmp_name"][0];
//Move temporary file to final destination
move_uploaded_file($image_tmp_name, $destination);
}
There is no fallback code for the canvas element yet, I'm looking into it.
The post size limitation in the fallback for older browsers is one of the reasons I dropped support for older browsers myself.
Update 3:
The fallback I recommend for the canvas element in IE8:
http://flashcanvas.net/
It supports all the canvas functions the cropping code needs.
Keep in mind it requires flash. There is a canvas fallback (explorercanvas) that does not require flash but it does not support the function toDataURL() which we need to save our cropped image.
Seahorsepip's answer is fantastic. I made a lot of improvements on the non-fallback answer.
http://jsfiddle.net/w1Lh4w2t/
I would recommend not doing that strange hidden png thing, when an Image object works just as well (so long as we're not supporting fallbacks).
var jcrop_api;
var canvas;
var context;
var image;
var prefsize;
Though even then we are, you're better off getting that data out of the canvas at the end and putting it in that field only at the end.
function loadImage(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function(e) {
image = new Image();
image.src = e.target.result;
validateImage();
}
reader.readAsDataURL(input.files[0]);
}
}
But, if you want more functions than just crop, if we attach the jcrop to an inserted canvas (which we destroy with the jcrop on refresh). We can easily do anything we can do with a canvas, then validateImage() again and have the updated image visible in place.
function validateImage() {
if (canvas != null) {
image = new Image();
image.src = canvas.toDataURL('image/png');
}
if (jcrop_api != null) {
jcrop_api.destroy();
}
$("#views").empty();
$("#views").append("<canvas id=\"canvas\">");
canvas = $("#canvas")[0];
context = canvas.getContext("2d");
canvas.width = image.width;
canvas.height = image.height;
context.drawImage(image, 0, 0);
$("#canvas").Jcrop({
onSelect: selectcanvas,
onRelease: clearcanvas,
boxWidth: crop_max_width,
boxHeight: crop_max_height
}, function() {
jcrop_api = this;
});
clearcanvas();
}
Then on submit we submit any pending operations, like applyCrop() or applyScale(), adding data into hidden fields for fallback stuff, if we have those things needed. We then have a system we can easily just modify the canvas, in any way, then when we submit the canvas data gets sent properly.
function applyCrop() {
canvas.width = prefsize.w;
canvas.height = prefsize.h;
context.drawImage(image, prefsize.x, prefsize.y, prefsize.w, prefsize.h, 0, 0, canvas.width, canvas.height);
validateImage();
}
The canvas is added to a div views.
<div id="views"></div>
To catch the attached file in PHP (drupal), I used something like:
function makeFileManaged() {
if (!isset($_FILES['croppedfile']))
return NULL;
$path = $_FILES['croppedfile']['tmp_name'];
if (!file_exists($path))
return NULL;
$result_filename = $_FILES['croppedfile']['name'];
$uri = file_unmanaged_move($path, 'private://' . $result_filename, FILE_EXISTS_RENAME);
if ($uri == FALSE)
return NULL;
$file = File::Create([
'uri' => $uri,
]);
$file->save();
return $file->id();
}
I need to write a new HTML file from a string using file system, I'm using Cordova 2.4.0. . That HTML would have some images loaded from a local folder, so because I need just one file (a HTML without png/jpg images alongside it) I'm trying to encode this images and apply them through CSS as background images (The CSS code is embeded in the same String, future HTML file). Well, the way I make these modifications to the string is by using functions returns. For example:
padding-top: 5%; background-image: url(' + agregaImagenLocal('../img/ESTELAR.png') + '); background-color: white;
The function "agregaImagenLocal(pathToLocalImage)" :
function agregaImagenLocal(pathToLocalFile) {
var canvas = document.getElementById('canvasOculto');
var imagen = new Image(150,100);
canvas.width = imagen.width;
canvas.height = imagen.height;
var contextoCanvas = canvas.getContext('2d');
imagen.onload = function () {
contextoCanvas.drawImage(imagen, 0, 0);
urlImagenLocal = canvas.toDataURL();
}
imagen.src = pathToLocalFile;
return urlImagenLocal //???????
}
I was doing this without the "onload" event, so it returned a blank image. But with this function inside the "onload" I don't know how to return the base64 encoded image to the first function.
This is the function to convert an Image (by file path) to Base64:
function convertImgToBase64(url, callback, outputFormat){
var canvas = document.createElement('CANVAS');
var ctx = canvas.getContext('2d');
var img = new Image;
img.crossOrigin = 'Anonymous';
img.onload = function(){
canvas.height = img.height;
canvas.width = img.width;
ctx.drawImage(img,0,0);
var dataURL = canvas.toDataURL(outputFormat || 'image/png');
callback.call(this, dataURL);
// Clean up
canvas = null;
};
img.src = url;
}
Once the conversion is made, you can use jQuery to set the background CSS using the Base64 code.
var imageUrl = ""; //Your Local Image Path HERE
convertImgToBase64(imageUrl, function(base64Img){
$('.output').css("background-image", base64Img);
}
This will set the source of an element with the class 'output' to the base64 image.