Uploading 'canvas' image data to the server - javascript

I need to upload the canvas image data to the server (database) on the fly, i.e., I need to create a form and with an input=file, and post the image data without any user interaction.

FWIW, this is how I got it working.
My server is in google app engine. I send canvas.toDataURL()'s output as part of post request using jQuery.post. The data URL is base64 encoded image data. So on server I decode it and convert it to image
import re
import base64
class TnUploadHandler(webapp.RequestHandler):
dataUrlPattern = re.compile('data:image/(png|jpeg);base64,(.*)$')
def post(self):
uid = self.request.get('uid')
img = self.request.get('img')
imgb64 = self.dataUrlPattern.match(img).group(2)
if imgb64 is not None and len(imgb64) > 0:
thumbnail = Thumbnail(
uid = uid, img = db.Blob(base64.b64decode(imgb64)))
thumbnail.put()
From client I send the data like this:
$.post('/upload',
{
uid : uid,
img : canvas.toDataURL('image/jpeg')
},
function(data) {});
This may not be the best way to do it, but it works.

You don't need a file input, just get the data with ctx.getImageData() and post it to the server with Ajax.
See the MDN Documentation for CanvasRenderingContext2D.getImageData().
But you won't be able to get the image data in IE, even with ExCanvas.

Here is how I solved this. Posting the image as base64 array using JavaScript and then decoding and saving it as a image using PHP.
Client side (JavaScript):
$.post('/ajax/uploadthumbnail',
{
id : id,
img : canvas.toDataURL("image/png")
}, function(data) {
console.log(data);
});
Server side (PHP):
$img = $_POST['img'];
$img = str_replace('data:image/png;base64,', '', $img);
$img = str_replace(' ', '+', $img);
$data = base64_decode($img);
$file = $_SERVER['DOCUMENT_ROOT'] . '/images/some_name.png';
file_put_contents($file, $data);

Here is a demo of an online signature app that I wrote last year Canvas Signature Demo. This has the advantage of posting only the vector data to the server. With all of the path info, you could also apply smoothing algorithms or scale it as needed before persisting.
<canvas id="signature" width="300" height="100"></canvas>
<form method="post" id="signature_form" action="signing.aspx">
<input type="hidden" name="paths" id="paths"/>
<p><label>Cover #</label> <input type="text" id="covernumber" name="covernumber"/>
<input type="submit" id="save" value="Save"/>
</form>
I store the path data into a hidden field and post that to the server.
signature.js Core logic below:
mouseDown: function(event) {
var point = this.getRelativePoint(event);
this.paths.push( [ point ] );
this.ctx.fillRect(point.x,point.y,1,1);
this.penDown = true;
this.updateField();
},
mouseUp: function(event) {
this.penDown = false;
this.ctx.closePath();
if ( Prototype.Browser.IE && event.srcElement.tagName != "INPUT" ) {
var ver = getInternetExplorerVersion();
if ( ver >= 8 && ver < 9 && document.selection ) {
document.selection.empty();
}
}
},
mouseMove: function(event) {
if ( this.penDown ) {
var lastPath = this.paths[ this.paths.length - 1 ];
var lastPoint = lastPath[ lastPath.length - 1 ];
var point = this.getRelativePoint(event);
lastPath.push( point );
this.ctx.strokeStyle = "#000000";
this.ctx.beginPath();
this.ctx.moveTo(lastPoint.x,lastPoint.y);
this.ctx.lineTo(point.x, point.y);
this.ctx.stroke();
this.ctx.closePath();
this.updateField();
}
},
updateField: function() {
if ( this.field ) {
this.field.value = this.paths.toJSON();
}
}
Here is my relevant server side .Net code (C#).
if ( Request("paths") ) {
var objBitmap : Bitmap = new Bitmap(300, 100);
var objGraphics : Graphics = Graphics.FromImage(objBitmap);
objGraphics.Clear(Color.Transparent);
objGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var paths:Array = eval(Request("paths")) || [];
var p : int;
var q : int;
var path : Array;
for ( p = 0; p< paths.length; p++ ) {
var path = paths[p];
if ( path.length == 1 ) {
objGraphics.DrawRectangle(new Pen(Color.Black), path[0].x, path[0].y, 1, 1);
} else {
for ( q = 1; q<path.length; q++ ) {
var prev = path[q-1];
var curr = path[q];
objGraphics.DrawLine(new Pen(Color.Black), parseInt(prev.x),parseInt(prev.y),parseInt(curr.x),parseInt(curr.y));
}
}
}
objBitmap.Save("C:\\temp\\" + Request("covernumber") + ".png", ImageFormat.Png);
objBitmap.Dispose();
objGraphics.Dispose();
}

You can get the image data in the form of a data: url, this only works in Firefox and Opera so far though.
http://cow.neondragon.net/index.php/681-Canvas-Todataurl

Related

Resize and upload script taking too long

I'm using a script which resizes images client side before uploading them via Ajax to a PHP file. I am resizing images client side to reduce the power demand on the server. My script works but is taking too long to upload the image and I need help understanding why this is the case. The first part of the file asynchronously resizes the image. The second part encodes the resized image source to dynamically created hidden input and finally, Ajax uploads the dynamically created input value to a PHP file by using a timeout 3-second delay function.
$(document).ready(function() {
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
/* Upload Logo Image */
$("#loader-wrapper").hide();
$("#logo_image").change(function(){
$("#ImgText").fadeOut(10, "linear");
$("#loader-wrapper").css('background-image', 'none').fadeIn(200, "linear");
const form = document.querySelector('#user_update_settings');
form.addEventListener('change', async (e) => {
e.preventDefault();
// Get data URI of the selected image
const formData = new FormData(e.currentTarget);
const photoField = formData.get('logo_image');
const dataUri = await dataUriFromFormField(photoField);
// Defines Resized image URL
const imgEl = document.createElement('img');
imgEl.addEventListener('load', () => {
const resizedDataUri = resizeImage(imgEl, 600);
// Deletes Previous Input
var element = document.getElementById('new_logo_image');
if (typeof(element) != 'undefined' && element != null)
{
var elementExists = document.getElementById("new_logo_image");
elementExists.remove();
}
// Creates New Input
var objTo = document.getElementById('LogoInputContainer')
var image_input = document.createElement("input");
image_input.setAttribute("name", "new_logo_image");
image_input.setAttribute("id", "new_logo_image");
image_input.setAttribute("type", "hidden");
image_input.setAttribute("value", resizedDataUri);
objTo.appendChild(image_input);
});
imgEl.src = dataUri;
});
// Resize Script
function dataUriFromFormField (field) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.addEventListener('load', () => {
resolve(reader.result);
});
reader.readAsDataURL(field);
});
}
function resizeImage(imgEl, wantedWidth) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const aspect = imgEl.width / imgEl.height;
canvas.width = wantedWidth;
canvas.height = wantedWidth / aspect;
ctx.drawImage(imgEl, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL();
}
});
// Image Upload
$("#logo_image").change(function(){
setTimeout(() => {
if(window.File && window.FileReader && window.FileList && window.Blob){
if($(this).val()){
// Checks File Extension
var fileExtension = ['jpeg', 'jpg', 'png', 'gif', 'bmp'];
if ($.inArray($(this).val().split('.').pop().toLowerCase(), fileExtension) == -1) {
alert("Only formats are allowed : "+fileExtension.join(', '));
exit();
}
$.ajax({
url: "/logo_uploader",
type: "POST",
data: new FormData($("#user_update_settings")[0]),
contentType: false,
cache: false,
processData: false,
dataType: 'json',
success:function(response){
if(response.type == 'success'){
$("#loader-wrapper").fadeOut();
$(".logo_image_container").css("background-image", "url("+ response.logo_image +")");
}else{
$("#loader-wrapper").fadeOut();
$("#ImgText").fadein(10, "linear").text("+ response.msg +");
}
}
});
}
} else {
alert("Can't upload! Your browser does not support File API!</div>");
return false;
}
}, 3000);
});
});
Client-side, my logo uploader validates the file type, deletes the previous image [if it exists] decodes the $request->new_logo_image and saves the file in the folder 'logos' and in a SQL database.
// User ID
$user_id = auth()->user()->id;
// Reserved Goods
$user = Users::where('id', $user_id)->first();
// Path
$FilePathDB = FilePathDB::first();
// Path
$file_path = $FilePathDB->public_img_path;
$path = $file_path.$user->logo_image;
// Deletes Previous Logo [If Applicable]
if(file_exists($path)) {
File::delete($path);
}
// Validates Extension
$validatedData = $request->validate([
'logo_image' => 'required|image|mimes:jpg,png,jpeg',
]);
// Generates a Unique New Name
$new_name = 'logos/'.$user_id.trim($user->username).uniqid().'.jpg';
// Uploads request Image if file does not exist
if($request->hasFile('logo_image')) {
$image = $request->new_logo_image; // your base64 encoded
$image = str_replace('data:image/png;base64,', '', $image);
$image = str_replace(' ', '+', $image);
File::put($file_path.$new_name, base64_decode($image));
// Logo Image Variable
$formFields = array(
'logo_image' => $new_name
);
// User ID Update
$users = Users::where('id', $user_id)
->update($formFields);
// If Successful
$response = print json_encode(array(
'type'=> 'success',
'msg' => 'Logo Uploaded Successfully',
'logo_image' => 'storage/'.$new_name,
));
} else {
// If unsuccessful
$response = print json_encode(array(
'type'=> 'failed',
'msg' => 'Ooops, image upload unsuccessful. Please try again!',
));
}
Condensed HTML
<form method="POST" action="/user_update_settings" id="user_update_settings" enctype="multipart/form-data">
<label for="logo_image" class="pointer UploadImgBtn transition">
<input class="DisplayNone" type="file" name="logo_image" id="logo_image" accept="image/*">
<a>Upload Image</a>
</label>
</form>
On average, for a 3MB image, it takes 3 seconds for JS canvas to resize the image and 15-20 seconds to upload the already resized file - now between 40-50KB in size. Knowing the image has been resized, why does it take so long for my code to upload the resized image? Are there any loops I haven't considered running in the background which are consuming the client's resources?
After much thinking, I have just found the solution. When I ran my Ajax script I was submitting the whole form to the server which included both the old and the new image. To reduce the data load, one can either put the hidden input into a new form or else specify the data which is to be sent to the PHP file.

Image Upload Javascript PHP - How to keep original filename?

I'm trying to build-in an Image Uplaoder to my CMS for the gallery.
I've done some research and found what I need to build it.
The uploader uses three files. The first one is where to select the images for upload and showing some progress. Connected to this is a js file for resizing the selected images first and upload them afterwards. And last but not least a file to process the images on server via php and for writing data into sql-database.
The good point is: Everything works as it should.
BUT I have a problem with sorting the images. Because they are getting a md5 generated filename, and the uploader handles multiple images at a time, some images that I took for example at the end of a day are showed first and the first pictures of the day are for example anywhere between them.
So here comes my question: Is there a way to keep the orignal filename and name the uploaded image for example anything like "1234md5randomdigits_ORIGINALFILENAME.jpg"?
I've tried a lot of $_FILES and other php parameters, but they were empty...
Here is my upload file for selecting images:
<!DOCTYPE html>
<html>
<head>
<title>multiple.php</title>
<link rel="stylesheet" href="./style.css" />
<head>
<body>
<h1>Upload Images...</h1>
<form>
<input type="file" multiple />
<div class="photos">
</div>
</form>
<script src="./upload.js"></script>
</body>
</html>
Here comes the upload.js file
// Once files have been selected
document.querySelector('form input[type=file]').addEventListener('change', function(event){
// Read files
var files = event.target.files;
// Iterate through files
for (var i = 0; i < files.length; i++) {
// Ensure it's an image
if (files[i].type.match(/image.*/)) {
// Load image
var reader = new FileReader();
reader.onload = function (readerEvent) {
var image = new Image();
image.onload = function (imageEvent) {
// Add elemnt to page
var imageElement = document.createElement('div');
imageElement.classList.add('uploading');
imageElement.innerHTML = '<span class="progress"><span></span></span>';
var progressElement = imageElement.querySelector('span.progress span');
progressElement.style.width = 0;
document.querySelector('form div.photos').appendChild(imageElement);
// Resize image
var canvas = document.createElement('canvas'),
max_size = 1200,
width = image.width,
height = image.height;
if (width > height) {
if (width > max_size) {
height *= max_size / width;
width = max_size;
}
} else {
if (height > max_size) {
width *= max_size / height;
height = max_size;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
// Upload image
var xhr = new XMLHttpRequest();
if (xhr.upload) {
// Update progress
xhr.upload.addEventListener('progress', function(event) {
var percent = parseInt(event.loaded / event.total * 100);
progressElement.style.width = percent+'%';
}, false);
// File uploaded / failed
xhr.onreadystatechange = function(event) {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
imageElement.classList.remove('uploading');
imageElement.classList.add('uploaded');
imageElement.style.backgroundImage = 'url('+xhr.responseText+')';
console.log('Image uploaded: '+xhr.responseText);
} else {
imageElement.parentNode.removeChild(imageElement);
}
}
}
// Start upload
xhr.open('post', 'process.php', true);
xhr.send(canvas.toDataURL('image/jpeg'));
}
}
image.src = readerEvent.target.result;
}
reader.readAsDataURL(files[i]);
}
}
// Clear files
event.target.value = '';
});
And this my "process.php" to process the uploaded data:
<?php
$save_path="/images";
// Generate filename
$filename = md5(mt_rand()).".jpg";
// Read RAW data
$data = file_get_contents("php://input");
// Read string as an image file
$image = file_get_contents("data://".substr($data, 5));
// Save to disk
if ( ! file_put_contents($save_path.$filename, $image)) {
exit();
}
// Clean up memory
unset($data);
unset($image);
//Includes and SQL go after that
// Return file URL
echo $save_path.$filename;
?>
I'd be very happy about some help! :)
In support of my comment above if you send a custom header in the ajax function you can process that server side. I think I got the syntax right for accessing the filename from the files collection
/* ajax: add custom header */
xhr.open('post', 'process.php', true);
xhr.setRequestHeader( 'filename', files[i].name );
xhr.send(canvas.toDataURL('image/jpeg'));
/* php: resort to original md5 name if header failed */
$filename=!empty( $_SERVER['HTTP_FILENAME'] ) ? $_SERVER['HTTP_FILENAME'] : md5(mt_rand()).".jpg";
As I originally forgot to add HTTP_ to the beginning of the custom header ( php ) it initially would not have worked - a simple oversight on my behalf. To correct that I put together a quick demo of using the custom headers idea from end to end, though the code that follows does not entirely emulate your original code with the image processing, canvas, FileReader etc it does show the important aspect of assigning the custom request header and how to process that server-side in php so I hope it will give you an idea how you can implement the original filename feature.
<?php
/*
emulate server side processing of uploaded file -
here it simply sends back the custom headers and
a single POST variable but this would be processing
the image data and saving the file
*/
if( $_SERVER['REQUEST_METHOD']=='POST' ){
ob_clean();
$filename = !empty( $_SERVER['HTTP_FILENAME'] ) ? $_SERVER['HTTP_FILENAME'] : md5( mt_rand() ).".jpg";
$filetype = !empty( $_SERVER['HTTP_FILETYPE'] ) ? $_SERVER['HTTP_FILETYPE'] : 'M.I.A';
$filesize = !empty( $_SERVER['HTTP_FILESIZE'] ) ? $_SERVER['HTTP_FILESIZE'] : 'M.I.A';
$action = !empty( $_POST['action'] ) ? $_POST['action'] : 'M.I.A';
/* send proper content type response header */
header( 'Content-Type: application/json' );
/* add some custom response headers to show how you can pass headers and process them */
header( sprintf( 'Uploaded-Filename: %s', $filename ) );
header( sprintf( 'Uploaded-Filesize: %s', $filesize ) );
header( sprintf( 'Uploaded-Filetype: %s', $filetype ) );
/* send payload back to ajax callback */
exit( json_encode( array(
'filename' => $filename,
'filesize' => $filesize,
'filetype' => $filetype,
'action' => $action
)));
}
?>
<!doctype html>
<html>
<head>
<title>ajax custom headers</title>
<style>
body,body *{
font-family:calibri,verdana,arial;
font-size:0.9rem;
}
</style>
<script>
function bindEvents(){
/* DOM elements */
var oDiv=document.getElementById('results');
var oPre=document.getElementById('headers');
var oBttn=document.getElementById('bttn');
var oFile=document.querySelector('form input[type="file"]');
/* basic callback function to show response */
var callback=function(r,h){
oDiv.innerHTML=r;
oPre.innerHTML=h;
}
oBttn.onclick=function(){
/* as there is only a single file we know the index is zero */
var oCol=oFile.files;
var file=oCol.item(0).name;
var size=oCol.item(0).size;
var type=oCol.item(0).type;
/* ultra basic ajax request with custom request headers */
var xhr=new XMLHttpRequest();
xhr.onreadystatechange=function(){
if( this.readyState==4 && this.status==200 ){
/*
The callback can take whatever arguments we want - here simply
the response and some headers - could easily process specific
response headers rather than all
*/
callback.call( this, this.response, this.getAllResponseHeaders() );
}
};
xhr.open( 'POST', location.href, true );
xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
/* add custom request headers - original file details */
xhr.setRequestHeader( 'filename', file );
xhr.setRequestHeader( 'filetype', type );
xhr.setRequestHeader( 'filesize', size );
xhr.send( 'action=headers-test' );
}
}
document.addEventListener( 'DOMContentLoaded', bindEvents, false );
</script>
</head>
<body>
<div id='results'></div>
<pre id='headers'></pre>
<form method='post'>
<input type='file' />
<input type='button' id='bttn' value='Send Ajax Request with custom headers' />
</form>
</body>
</html>
Following on from your comment regarding multiple files all sharing the same name hopefully the following might help.
<form method='post'>
<input type='file' multiple=true />
<input type='button' id='bttn' value='Send Ajax Request with custom headers' />
<div class='photos'></div>
</form>
<script>
document.querySelector( 'form input[type="file"]' ).addEventListener( 'change', function( event ){
// Read files
var files = event.target.files;
// Iterate through files
for( var i = 0; i < files.length; i++ ) {
// Ensure it's an image
if ( files[i].type.match( /image.*/ ) ) {
// Load image
var reader = new FileReader();
/*
assign custom properties to the reader
object which will allow you to access
them within callbacks
*/
reader.filename=files[i].name;
reader.filesize=files[i].size;
reader.filetype=files[i].type;
reader.onload = function( readerEvent ) {
/*
assign each new image with the properties from the reader
- these will be available within the ajax function so you
can set the custom headers
*/
var image = new Image();
image.filename=this.filename;
image.filesize=this.filesize;
image.filetype=this.filetype;
image.onload = function( imageEvent ) {
console.log('image onload - - - > > > > > %s -> %s',this.filename,this.filesize);
// Add element to page
var imageElement = document.createElement('div');
imageElement.classList.add('uploading');
imageElement.innerHTML = '<span class="progress"><span></span></span>';
var progressElement = imageElement.querySelector('span.progress span');
progressElement.style.width = 0;
document.querySelector('form div.photos').appendChild( imageElement );
// Resize image
var canvas = document.createElement('canvas'),
max_size = 1200,
width = image.width,
height = image.height;
if ( width > height ) {
if( width > max_size ) {
height *= max_size / width;
width = max_size;
}
} else {
if( height > max_size ) {
width *= max_size / height;
height = max_size;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage( image, 0, 0, width, height );
// Upload image
var xhr = new XMLHttpRequest();
if( xhr.upload ) {
xhr.upload.addEventListener('progress', function(event) {
var percent = parseInt( event.loaded / event.total * 100 );
progressElement.style.width = percent+'%';
}, false);
xhr.onreadystatechange = function(event) {
if( xhr.readyState == 4 ) {
if( xhr.status == 200 ) {
imageElement.classList.remove('uploading');
imageElement.classList.add('uploaded');
imageElement.style.backgroundImage = 'url('+xhr.responseText+')';
} else {
imageElement.parentNode.removeChild( imageElement );
}
}
}
xhr.open( 'post', location.href, true ); //'process.php'
xhr.setRequestHeader( 'filename', image.filename );
xhr.setRequestHeader( 'filetype', image.filetype );
xhr.setRequestHeader( 'filesize', image.filesize );
xhr.send( canvas.toDataURL('image/jpeg') );
}
};
image.src = readerEvent.target.result;
};
reader.readAsDataURL( files[i] );
}
}
// Clear files
event.target.value = '';
});
</script>
This is what I use to process potentially any number of upload files if you leave the file name as it is here it will leave it the same as the users file name. Make your form element name an array and it will loop through it uploading all the files. You'll also have to set your form type to multipart. This should be a lot easier to manage than what it looks like you're trying to do though.
$target_dir = "images/";
extract($_POST);
$error=array();
$extension=array("jpg", "gif");
$i=0;
foreach($_FILES["filetoupload"]["tmp_name"] as $key=>$tmp_name) {
$file_name = $_FILES["filetoupload"]["name"][$key];
$ext = strtolower(pathinfo($file_name,PATHINFO_EXTENSION));
$file_tmp=$_FILES["filetoupload"]["tmp_name"][$key];
if(in_array($ext,$extension)) {
if ($_FILES["filetoupload"]["size"][$key] < 5000000) {
if ($_FILES["filetoupload"]["size"][$key] != 0) {
if(!file_exists( $target_dir . $file_name )) {
move_uploaded_file($file_tmp , $target_dir . $file_name );
}
}
}
} else {
array_push($error,"$file_name, ");
}
}
in this example the name attribute of all of the file input fields is name="filetoupload[]"

How can i get image dimensions after upload with jQuery File Upload?

When i try to upload a file via jQuery File Upload i am taking below response.
Is it posible to add image dimensions to this result like width: 300px and height: 400px? I try to edit UploadHandler.php for that need but i can't success. There is a function called set_additional_file_properties in UploadHandler.php. I tried to add a custom variable to the function but it fails because i think this function called before the file upload process. It even could not find the file to get dimensions. I don't know why probably i am looking to wrong side of the document.
protected function set_additional_file_properties($file) {
$file->dimensions = file_exists($file->url);
$file->deleteUrl = $this->options['script_url']
.$this->get_query_separator($this->options['script_url'])
.$this->get_singular_param_name()
.'='.rawurlencode($file->name);
$file->deleteType = $this->options['delete_type'];
if ($file->deleteType !== 'DELETE') {
$file->deleteUrl .= '&_method=DELETE';
}
if ($this->options['access_control_allow_credentials']) {
$file->deleteWithCredentials = true;
}
}
Solution
protected function set_additional_file_properties($file) {
$a = getimagesize(realpath(dirname($file->url))."/".$file->name);
$width = $a[0];
$height = $a[1];
if ($a) {
$file->width = $width;
$file->height = $height;
}
...
}
If I get your problem (you want to add width and height information to the image on the server side) then this should help:
http://php.net/manual/function.getimagesize.php
Returns an array with up to 7 elements. Not all image types will include the channels and bits elements.
Index 0 and 1 contains respectively the width and the height of the image.
=> getimagesize($file)
Only need to change UploadHandler.php file in set_additional_file_properties($file) function for the following:
protected function set_additional_file_properties($file) {
$filesize = getimagesize("files/".$file->name); //'files' folder is default folder to upload
$width = $filesize[0];
$height = $filesize[1];
if ($filesize) {
$file->width = $width;
$file->height = $height;
}
$file->deleteUrl = $this->options['script_url']
.$this->get_query_separator($this->options['script_url'])
.$this->get_singular_param_name()
.'='.rawurlencode($file->name);
$file->deleteType = $this->options['delete_type'];
if ($file->deleteType !== 'DELETE') {
$file->deleteUrl .= '&_method=DELETE';
}
if ($this->options['access_control_allow_credentials']) {
$file->deleteWithCredentials = true;
}
}
just added these lines:
$filesize = getimagesize("files/".$file->name); //'files' folder is default folder to upload
$width = $filesize[0];
$height = $filesize[1];
if ($filesize) {
$file->width = $width;
$file->height = $height;
}

html2canvas.js not capturing image for dynamically generated content

I am using vivagraphs to generate dynamic svg element but when I click capture button, no nodes and edges are shown.
This is the script:
$(document).ready(function() {
//var testdiv = document.getElementById("testdiv");
$('#btn').click(function(){
html2canvas($("#graph1"), {
onrendered: function(canvas) {
var myImage = canvas.toDataURL("img/png");
window.open(myImage);
}
});
});
While I inspect for elements svg is shown after rendering graph but snapshot does not contain nodes and edges.
Is there an alternative for html2canvas or can I fix this issue?
if you want to save the image from canvas to some image format here is some help for you. hope this will help you out.
$(document).ready(function() {
$('#btn').click(function(){
html2canvas(document.getElementById('graph1'), {
onrendered: function(canvas) {
var cs = new CanvasSaver('save_img.php',canvas,'myimage')
}
});
});
});
here CanvasSaver() function define is here below which take three parameters one is a php file which process image from RAW date to some image format. i'll write the code of 'save_img.php' belwo this script part and save that file in your root directory.
function CanvasSaver(url, cnvs, fname) {
this.url = url;
if(!cnvs || !url) return;
fname = fname || 'picture';
var data = cnvs.toDataURL("image/png");
data = data.substr(data.indexOf(',') + 1).toString();
var dataInput = document.createElement("input") ;
dataInput.setAttribute("name", 'imgdata') ;
dataInput.setAttribute("value", data);
dataInput.setAttribute("type", "hidden");
var nameInput = document.createElement("input") ;
nameInput.setAttribute("name", 'name') ;
nameInput.setAttribute("value", fname + '.jpg');
var myForm = document.createElement("form");
myForm.method = 'post';
myForm.action = url;
myForm.appendChild(dataInput);
myForm.appendChild(nameInput);
document.body.appendChild(myForm) ;
myForm.submit() ;
document.body.removeChild(myForm) ;
}
in above script whatever the image formate you want to save from browser give that image extension in this function above script
nameInput.setAttribute("value", fname + '.jpg');
now here is the code for your 'save_img.php' and save it in your root directory.
<?php
# we are a PNG image
header('Content-type: image/png');
# we are an attachment (eg download), and we have a name
header('Content-Disposition: attachment; filename="' . $_POST['name'] .'"');
#capture, replace any spaces w/ plusses, and decode
$encoded = $_POST['imgdata'];
$encoded = str_replace(' ', '+', $encoded);
$decoded = base64_decode($encoded);
#write decoded data
echo $decoded;
?>
you maybe using beta version of lib , goto releases on github page of html2canvas and download stable alpha version

How to post image on twitter in Phonegap using javascript

I'm currently implementing a web smartphone application with Phonegap. On this application, users can post images they take with the phone camera on Facebook. This feature has been succesfully implemented only using javascript, by sending a base 64 encoded image. Now, I want to implement the same feature using Twitter.
I found some very interesting blog posts about this and I'm already be able to update the user status only using javascript... but I can't post images too using the update_with_media Twitter web service.
According too this post, someone says it's impossible to implement this operation without using a server side code (like a php script for example).
So my question is : is it possible to use the update_with_media Twitter web service only with javascript ?
I send you my code to have an overview of the current solution. I've taken this article as working base : http://oodlestechnologies.com/blogs/Twitter-integration-on-PhoneGap-using-ChildBrowser-and-OAuth-for-iOS-and-Android-Platforms
Here is my HTML code.
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<script type="text/javascript" src="../js/jquery/jquery.min.js"></script>
<script type="text/javascript" src="../cordova-2.5.0.js"></script>
<script type="text/javascript" src="../js/childBrowser/childbrowser.js"></script>
<script type="text/javascript" src="../js/helpers/jsOAuth-1.3.6.js"></script>
<script type="text/javascript" src="../js/helpers/twitter.js"></script>
</head>
<body>
<h4>Oodles Twitter App</h4>
<table border="1">
<tr>
<th>Login using Twitter</th>
<th>
<button id="loginBtn" onclick="Twitter.init();">Login</button>
<button id="logoutBtn" onclick="logOut();">Logout</button>
</th>
</tr>
<tr id="tweetText">
<td colspan="2"><textarea id="tweet"></textarea></td>
</tr>
<tr id="tweetBtn">
<td colspan="2" align="right">
<button id="tweeter" onclick="Twitter.tweet();">Tweet</button>
</td>
</tr>
<tr><td colspan="2"><div id="welcome">Please Login to use this app</div></td></tr>
</table>
<br/>
<br/>
<button onclick="javascript:location.reload();">Recharger la page</button>
</body>
</html>
Here is my twitter.js code : (The point is in the post method)
$(document).ready(function() {
document.addEventListener("deviceready", onDeviceReady, false);
});
function onDeviceReady() {
var root = this;
cb = window.plugins.childBrowser;
if (!localStorage.getItem(twitterKey)) {
$("#loginBtn").show();
$("#logoutBtn").hide();
$("tweetBtn").hide();
$("tweetText").hide();
}
else {
$("#loginBtn").hide();
$("#logoutBtn").show();
$("tweetBtn").show();
$("tweetText").show();
}
if (cb != null) {
cb.onLocationChange = function(loc) {
root.locChanged(loc);
};
cb.onClose = function() {
root.onCloseBrowser()
};
cb.onOpenExternal = function() {
root.onOpenExternal();
};
}
}
function onCloseBrowser() {
console.log("onCloseBrowser!");
}
function locChanged(loc) {
console.log("locChanged!");
}
function onOpenExternal() {
console.log("onOpenExternal!");
}
// Consumer key : ...
// Consumer secret : ...
// GLOBAL VARS
var oauth; // It Holds the oAuth data request
var requestParams; // Specific param related to request
var options = {consumerKey: '...', consumerSecret: '...', callbackUrl: "http://www.google.fr"};
var twitterKey = "twtrKey"; // This key is used for storing Information related
var Twitter = {
init: function() {
// Apps storedAccessData , Apps Data in Raw format
var storedAccessData, rawData = localStorage.getItem(twitterKey);
// here we are going to check whether the data about user is already with us.
if (localStorage.getItem(twitterKey) !== null) {
// when App already knows data
storedAccessData = JSON.parse(rawData); //JSON parsing
//options.accessTokenKey = storedAccessData.accessTokenKey; // data will be saved when user first time signin
options.accessTokenSecret = storedAccessData.accessTokenSecret; // data will be saved when user first first signin
// javascript OAuth take care of everything for app we need to provide just the options
oauth = OAuth(options);
oauth.get('https://api.twitter.com/1/account/verify_credentials.json?skip_status=true',
function(data) {
var entry = JSON.parse(data.text);
console.log("USERNAME: " + entry.screen_name);
}
);
}
else {
// we have no data for save user
oauth = OAuth(options);
oauth.get('https://api.twitter.com/oauth/request_token',
function(data) {
requestParams = data.text;
cb.showWebPage('https://api.twitter.com/oauth/authorize?' + data.text); // This opens the Twitter authorization / sign in page
cb.onLocationChange = function(loc) {
Twitter.success(loc);
}; // Here will will track the change in URL of ChildBrowser
},
function(data) {
console.log("ERROR: " + JSON.stringify(data));
}
);
}
},
/*
When ChildBrowser's URL changes we will track it here.
We will also be acknowledged was the request is a successful or unsuccessful
*/
success: function(loc) {
// Here the URL of supplied callback will Load
/*
Here Plugin will check whether the callback Url matches with the given Url
*/
if (loc.indexOf("http://www.google.fr") >= 0) {
// Parse the returned URL
var index, verifier = '';
var params = loc.substr(loc.indexOf('?') + 1);
params = params.split('&');
for (var i = 0; i < params.length; i++) {
var y = params[i].split('=');
if (y[0] === 'oauth_verifier') {
verifier = y[1];
}
}
// Here we are going to change token for request with token for access
/*
Once user has authorised us then we have to change the token for request with token of access
here we will give data to localStorage.
*/
oauth.get('https://api.twitter.com/oauth/access_token?oauth_verifier=' + verifier + '&' + requestParams,
function(data) {
var accessParams = {};
var qvars_tmp = data.text.split('&');
for (var i = 0; i < qvars_tmp.length; i++) {
var y = qvars_tmp[i].split('=');
accessParams[y[0]] = decodeURIComponent(y[1]);
}
$('#oauthStatus').html('<span style="color:green;">Success!</span>');
$('#stage-auth').hide();
$('#stage-data').show();
oauth.setAccessToken([accessParams.oauth_token, accessParams.oauth_token_secret]);
// Saving token of access in Local_Storage
var accessData = {};
accessData.accessTokenKey = accessParams.oauth_token;
accessData.accessTokenSecret = accessParams.oauth_token_secret;
// Configuring Apps LOCAL_STORAGE
console.log("TWITTER: Storing token key/secret in localStorage");
localStorage.setItem(twitterKey, JSON.stringify(accessData));
oauth.get('https://api.twitter.com/1/account/verify_credentials.json?skip_status=true',
function(data) {
var entry = JSON.parse(data.text);
console.log("TWITTER USER: " + entry.screen_name);
$("#welcome").show();
document.getElementById("welcome").innerHTML = "welcome " + entry.screen_name;
successfulLogin();
// Just for eg.
app.init();
},
function(data) {
console.log("ERROR: " + data);
}
);
// Now we have to close the child browser because everthing goes on track.
window.plugins.childBrowser.close();
},
function(data) {
console.log(data);
}
);
}
else {
// Just Empty
}
},
tweet: function() {
var storedAccessData, rawData = localStorage.getItem(twitterKey);
storedAccessData = JSON.parse(rawData); // Paring Json
options.accessTokenKey = storedAccessData.accessTokenKey; // it will be saved on first signin
options.accessTokenSecret = storedAccessData.accessTokenSecret; // it will be save on first login
// javascript OAuth will care of else for app we need to send only the options
oauth = OAuth(options);
oauth.get('https://api.twitter.com/1/account/verify_credentials.json?skip_status=true',
function(data) {
var entry = JSON.parse(data.text);
Twitter.post();
}
);
},
/*
We now have the data to tweet
*/
post: function() {
alert('Post !');
var theTweet = $("#tweet").val(); // You can change it with what else you likes.
oauth.post('https://upload.twitter.com/1/statuses/update_with_media.json',
{
'status': theTweet,
'media': //HERE IS THE PROBLEM, WHAT TO DO HERE ?
}, "multipart/form-data",
function(data)
{
alert('Data 1 !');
console.log('------Data1 : ' + data);
var entry = JSON.parse(data.text);
console.log(entry);
done();
},
function(data) {
//var json_result = JSON.parse(data);
//alert(json_result.text.error);
var entry = JSON.stringify(data);
console.log('------Data2 : ' + entry);
}
);
}
}
function done() {
alert("OKKK !");
$("#tweet").val('');
}
function successfulLogin() {
$("#loginBtn").hide();
$("#logoutBtn,#tweet,#tweeter,#tweetBtn,#tweetText").show();
}
function logOut() {
//localStorage.clear();
window.localStorage.removeItem(twitterKey);
document.getElementById("welcome").innerHTML = "Please Login to use this app";
$("#loginBtn").show();
$("#logoutBtn,#tweet,#tweeter,#tweetText,#tweetBtn").hide();
}
After many tests (sending a base64 image, sending a blob, sending a binary file, ...) here is the return message from Twitter I have :
{\"errors\":[{\"message\":\"Internal
error\",\"code\":131}]}","xml":"","requestHeaders":{"Content-Type":"multipart/form-data"},"responseHeaders":{"date":"Fri,
19 Apr 2013 15:45:28
GMT","content-encoding":"deflate","strict-transport-security":"max-age=631138519","status":"500
Internal Server
Error","server":"tfe","content-type":"application/json;
charset=utf-8","version":"HTTP/1.1"}}
A "solution" (by send a blob) have been posted on the Twitter dev forum but not working for me : dev.twitter.com/discussions/6969
Does anyone want to implement the same feature or have a solution ? Thank you !
------ EDITED :
I just want to use Javascript and I don't want to implement any server-side solution (no PHP, C#, Java...).
According to the docs, Twitter requires the multipart/form-data enctype, which means a base64 string isn't going to work.
Unlike POST statuses/update, this method expects raw multipart data. Your POST request's Content-Type should be set to multipart/form-data with the media[] parameter ~ https://dev.twitter.com/docs/api/1/post/statuses/update_with_media
However, you could host an endpoint that takes base64, converts it to a real file, and forwards the request to Twitter. For example (untested):
<?php
$base64 = $_POST['image'];
$data = base64_decode( $base64 );
// Make name unique to avoid conflicts.
$temp_file = uniqid() . $_POST['name'];
// Save the file to a temp location.
file_put_contents( $temp_file, $data );
$temp_info = pathinfo( $temp_file );
$temp_type = $temp_info['extension'];
$temp_name = basename( $temp_file, '.' . $temp_type );
// OAuth library recommended by Twitter: https://github.com/themattharris/tmhOAuth
// See original: https://github.com/themattharris/tmhOAuth-examples/blob/master/images.php
require 'tmhOAuth.php';
require 'tmhUtilities.php';
$tmhOAuth = new tmhOAuth( array(
'consumer_key' => $_POST['consumer_key'],
'consumer_secret' => $_POST['consumer_secret'],
'user_token' => $_POST['user_token'],
'user_secret' => $_POST['user_secret'],
));
// note the type and filename are set here as well
// Edit: Not sure if the `type` and `filename` params are necessary.
$params = array( 'media[]' => "#{$temp_file};type={$temp_type};filename={$temp_name}" );
$code = $tmhOAuth->request( 'POST', $tmhOAuth->url( '1/status/update_with_media' ),
$params,
true, // use auth
true // multipart
);
// Remove temp file.
unlink( $temp_file );
if ( $code == 200 ) {
tmhUtilities::pr( json_decode( $tmhOAuth->response['response'] ) );
}
tmhUtilities::pr( htmlentities( $tmhOAuth->response['response'] ) );
?>
And you might call it like:
$.ajax({
// You'll want to use https to protect the oauth info.
url: "https://mysite.com/proxy.php",
type: "POST",
data: {
image: "base64 data...",
name: "foo.png",
consumer_key: options.consumerKey,
consumer_secret: options.consumerSecret,
user_token: options.accessTokenKey,
user_secret: options.accessTokenSecret
},
success: function( data ) {
console.log( data );
}
});
For anyone trying to post images to Twitter using client JS, I was able to post to twitter using the solution by gary-buynary-co-za (https://github.com/bytespider/jsOAuth/pull/11) at the end of this forum. Pretty much ended up using Phonegap FileTransfer and FileTransferOptions objects for transferring image to twitter api, but used jsOAuth for preparing FileTransferOptions headers and signatures. The solution could definitely be cleaned up though.

Categories