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[]"
Related
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.
I have issue to send base64 image from canvas to PHP by using AJAX POST request. After i do some research on mr google, i found out alternative that people suggested to increase PHP server on php.ini etc memory_limit.. Maybe because of base64 length is too long and post method didn't worked.
Then, i use 2nd alternative is by using BLOB. I follow instruction from other stackoverflow author is to add function dataURItoBlob(). Looks like is working. But, the problem is, how i want to extract from blob files to base64 back in PHP? I tried several way, but no any result shown.
phpinfo.php
post_max_size = 999M;
memory_limit = 128M;
upload_max_filesize = 999M;
HTML
<button class="btn btn-secondary btn-done" title="Save Image" type="button" id="saveimg" data="<?php echo $product_id; ?>"><i class="fa fa-shopping-cart"></i></button>
JAVASCRIPT
$(document).delegate('#saveimg','click', function(event) {
var outputType = '<?php echo $type ?>';
var product = $(this).attr('data');
myCanvas.deactivateAll().renderAll();
var dataURL = myCanvas.toDataURL({
format: 'jpg',
quality:1,
multiplier: canvas_multiply
});
var blob = dataURItoBlob(dataURL);
var fi = new FormData();
fi.append('imgBase64_0', blob, 'test.jpg');
$.ajax({
url: 'index.php?load=product/storeimg&p_id=' + product,
type: 'post',
dataType: 'json',
cache: false,
contentType: false,
processData: false,
data: fi,
success: function (json){
// SOME CODE HERE //
},
});
});
function dataURItoBlob(dataURI) {
var byteString = atob(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
var blob = new Blob([ab], {type: mimeString});
return blob;
}
BLOB Array from print_r ($_FILES)
[files] => Array(
[imgBase64_0] => Array
(
[name] => test.jpg
[type] => image/jpeg
[tmp_name] => /tmp/phpDpBZ0F
[error] => 0
[size] => 667026
)
)
PHP
if (isset($_FILES)) {
foreach ($_FILES as $name => $value) {
${$name} = array(
'name' => $value['name'],
'type' => $value['type'],
'tmp_name' => $value['tmp_name'],
'error' => $value['error'],
'size' => $value['size']
);
}
if (is_dir($_FILES[$name]['tmp_name']) && is_writable($_FILES[$name]['tmp_name'])) {
$log = 'Writable and exist.'."\n";
} else {
$log = 'Not writable, or does not exist.'."\n";
}
if (move_uploaded_file($_FILES[$name]['tmp_name'], DIR_IMAGE)) {
$txt .= "Successfully move.\n";
} else {
$txt .= "Fail to move.\n";
}
}
Thank you for the help.
Saving a canvas generated image via ajax is quite straightforward so hopefully I understood your question correctly. Without knowing how the canvas is generated in your code nor the size of the image it is hard to say whether simply increasing file upload limits in PHP would solve the problem but if you look though the code below ( save & run as example with minor edit to save location ) you should find your solution is quite easy - hopefully!
For the demo, the image used was as shown below and saved locally to avoid using the url itself which leads to a warning
Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases
may not be exported.
<?php
if( $_SERVER['REQUEST_METHOD']=='POST' && isset( $_POST['image'],$_POST['filename'] ) ){
$image=$_POST['image'];
$filename=$_POST['filename'];
/* edit to suit own environment */
$savepath='c:/temp/fileuploads/1/';
$target=$savepath . $filename;
$result=file_put_contents( $target, base64_decode( $image ) );
header('HTTP/1.1 200 OK',true,200);
header('Content-Type: text/plain');
exit( $result ? sprintf( 'File uploaded & saved as %s', $target ) : sprintf( 'Unable to save %s',$filename ) );
}
?>
<!doctype html>
<html>
<head>
<meta charset='utf-8' />
<title>HTML Canvas Image to PHP</title>
<script>
(function(options){
document.addEventListener('DOMContentLoaded',function(e){
/*
generate a canvas with some sort of image -
in this example a promo picture from the classic
B-Horror film "The Blob"
*/
var canvas=document.getElementById('canvas');
var ctx=canvas.getContext('2d');
var img=new Image();
img.src='/images/tmp/TheBlob.jpg';
img.onload=function(){
canvas.width=img.width;
canvas.height=img.height;
ctx.drawImage( img, 0, 0, canvas.width, canvas.height );
}
/*
Button click event handler
create FormData Object and read the canvas data
then send via ajax to a PHP script ( in this case the same page )
to process the uploaded image.
*/
function bindEvents(event){
var fd=new FormData();
fd.append('action','save');
fd.append('image', canvas.toDataURL('image/jpg').replace( /^data:image\/(png|jpg);base64,/, '' ) );
fd.append('filename',img.src.split('/').pop() )
var ajax=function(url,data,callback){
var xhr=new XMLHttpRequest();
xhr.onreadystatechange=function(){
if( this.readyState==4 && this.status==200 )callback.call( this, this.response );
};
xhr.open( 'POST', url, true );
xhr.send( data );
};
var callback=function(r){
alert(r)
}
ajax.call( this, location.href, fd, callback );
}
document.getElementById('bttn').addEventListener( 'click', bindEvents, options );
}, options );
})({ passive:true, capture:false, once:false });
</script>
</head>
<body>
<canvas id='canvas'></canvas>
<input type='button' id='bttn' value='Upload & Save Image' />
</body>
</html>
Here is my js, and action
public function postyAction()
{
$this->_helper->viewRenderer->setNoRender(TRUE);
// list of valid extensions, ex. array("jpeg", "xml", "bmp")
$allowedExtensions = array("jpeg","xlsx", "jpg", "gif", "doc", "docx", "xls", "odt", "pdf");
// max file size in bytes
$sizeLimit = 6 * 1024 * 1024;
$f = '../dir/'; //dir to save files
$uploader = new qqFileUploader($allowedExtensions, $sizeLimit);
$result = $uploader->handleUpload($f);
// to pass data through iframe you will need to encode all html tags
echo htmlspecialchars(json_encode($result), ENT_NOQUOTES);
}
function createUploader(){
var UploadedFileNames = [];
var uploader = new qq.FileUploader({
element: document.getElementById('file-uploader-demo1'),
action: '../orders/posty',
sizeLimit: 10000000, //10MB maximo
debug:false,
onComplete: function(id, fileName, responseJson){
var success = responseJson["success"];
if(success == true){
var files = $(".qq-upload-file").toArray();
var UploadedFileNames = [];
for ( var i = 0; i < files.length; i++ ) {
UploadedFileNames.push( files[ i ].innerHTML );
}
$( "#filenem" ).val( UploadedFileNames.join( "#" ) );
}
//$("#filenem").val(fileName);
}
});
}
// in your app create uploader as soon as the DOM is ready
// don't wait for the window to load
window.onload = createUploader;
I wish to get an array of files onComplete(), so that i can insert them into db. my code gets the inner html of the file list but longer file names appear to be dotted hence cant get the real file name. eg . e"myverylongdocume..now.doc" instead of "myverylongdocumentnow.doc" for instance
I've created a functionality on my website where user's can change the background image via upload. The procedure is following:
User goes to settings page and selects an image file to be uploaded. After selecting image, the browser will output it so that user can preview
it before actually saving it's file to in to the folder and filepath in to the database. After that, if user is happy with the result, he can save it to the
folder by pressing "Upload Background Image" button.
All of the above is handled with AJAX.
I am having trouble to just output the image to the browser without actually saving it twice, first into tests folder and after that into backgrounds folder.
I'm using CodeIgniter as my backend framework and jQuery for my AJAX requests.
Here are my methods for outputting (testing) and saving the image:
public function test_image()
{
if($this->input->is_ajax_request())
{
// This part of code needs to be replaced to only just output the image (return it as a JSON), not actually saving it to another a folder
$ext = pathinfo($_FILES['userfile']['name'], PATHINFO_EXTENSION);
$new_img_name = random_string('unique'). "." . $ext;
$config['upload_path'] = './public/images/uploads/tests';
$config['allowed_types'] = 'gif|jpg|jpeg|png';
$config['max_size'] = '1000000';
$config['max_width'] = '2000';
$config['max_height'] = '1600';
$config['file_name'] = $new_img_name;
$this->load->library('upload', $config);
if (!$this->upload->do_upload()) {
$this->output->set_content_type('application_json');
$this->output->set_output(json_encode(array('image_errors' => $this->upload->display_errors('<p class="text-center">','</p>'))));
return false;
} else {
$this->output->set_content_type('application_json');
$this->output->set_output(json_encode(array('userfile' => $new_img_name)));
}
} else {
echo "Not an ajax request";
}
}
// This method works properly
public function upload_background_image()
{
if (isset($_POST))
{
$ext = pathinfo($_FILES['userfile']['name'], PATHINFO_EXTENSION);
$new_img_name = random_string('unique'). "." . $ext;
$config['upload_path'] = './public/images/uploads/backgrounds';
$config['allowed_types'] = 'gif|jpg|jpeg|png';
$config['max_size'] = '1000000';
$config['max_width'] = '2000';
$config['max_height'] = '1600';
$config['file_name'] = $new_img_name;
$this->load->library('upload', $config);
if (!$this->upload->do_upload()) {
$this->output->set_content_type('application_json');
$this->output->set_output(json_encode(array('image_errors' => $this->upload->display_errors('<p class="text-center">','</p>'))));
return false;
} else {
$this->load->model('user_model');
$user_id = $this->session->userdata('user_id');
$upload_photo = $this->user_model->updateUserInfo($user_id, ['body_background_url' => $new_img_name]);
if ($upload_photo === true) {
$this->session->set_userdata(['body_background_url' => $new_img_name]);
redirect(base_url());
}
}
}
}
And here's my AJAX:
$("#bg-cover-file").change(function(e) {
e.preventDefault();
var form = $(this).closest('form');
form.ajaxSubmit({
dataType: 'json',
beforeSubmit: function() {
},
success: function(response) {
if(response.userfile) {
// Output the image
$('.test-image').attr('src', response.userfile);
$('span.file-input').hide();
// Change the form action attribute
var new_path = 'uploads/upload_background_image';
form.attr('action', new_path);
} else {
$('#error-modal').modal('show');
$("#error-body").html(response.image_errors);
return false;
}
}
});
return false;
});
--Working Demo--
I have put comments in this demo to explain what the steps are so please read them.
If you don't understand anything in this answer please leave a comment below and i will update the answer until you understand line for line. You don't learn from copy/paste so please be sure to understand the answer.
function MyFunction() {
var img=document.getElementById('BackgroundImage');
var Status=document.getElementById('Status');
var savebtn=document.getElementById('savebtn');
/* SetBG will target the body tag of the web page.
You can change this to any element -
var SetBG=document.getElementById('YourID').style;
*/
var SetBG=document.body.style;
//Split the image name
var fileExt=img.value.split('.');
//Use the last array from the split and put to lowercase
var fileformat=fileExt[fileExt.length -1].toLowerCase();
// Check the file extension (Image formats only!)
if((fileformat==='jpg')||(fileformat==='gif')||(fileformat==='png')||(fileformat==='jpeg')) {
if (img.files && img.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
//----Image is ready for preview.
SetBG.background='url('+e.target.result+') no-repeat center center fixed';
/*---- Optional, Set background as cover ---*/
SetBG.backgroundSize="cover";
SetBG.OBackgroundSize="cover";
SetBG.webkitBackgroundSize="cover";
//--Hide Loading Message
Status.style.display="none";
//----- Display (Save/Upload button?)
savebtn.style.display="block";
}
/*-------Reading File....
Display a message or loading gif for large images to be processed?
*/
Status.innerHTML="Loading...";
Status.style.display="block";
savebtn.style.display="none";
reader.readAsDataURL(img.files[0]);
}
}else{
/*----User file input not accepted (File isn't jpg/gif/png/jpeg)
Empty the input element and set the background to default.
*/
Status.innerHTML="Format not accepted";
Status.style.display="block";
savebtn.style.display="none";
SetBG.background='white';
document.getElementById('BackgroundImage').value='';
}
}
#Status{display:none;background:white;color:black;font-size:16pt;}
#savebtn{display:none;}
<div id="Status"></div>
<input type="file" id="BackgroundImage" onchange="MyFunction()"/>
<button id="savebtn" onclick="alert('Now upload the image');">Upload and save</button>
I hope this helps. Happy coding!
This may help you
let assume your browse button's id is bg-cover-file and the id of the image tag where you want to display the image preview_image
$(document).on("change", "#bg-cover-file", function(event)
{
if (this.files && this.files[0])
{
var reader = new FileReader();
reader.onload = function (e)
{
$('#preview_image').attr('src', e.target.result);
}
reader.readAsDataURL(this.files[0]);
}
});
function MyFunction() {
var img=document.getElementById('BackgroundImage');
var Status=document.getElementById('Status');
var savebtn=document.getElementById('savebtn');
/* SetBG will target the body tag of the web page.
You can change this to any element -
var SetBG=document.getElementById('YourID').style;
*/
var SetBG=document.body.style;
//Split the image name
var fileExt=img.value.split('.');
//Use the last array from the split and put to lowercase
var fileformat=fileExt[fileExt.length -1].toLowerCase();
// Check the file extension (Image formats only!)
if((fileformat==='jpg')||(fileformat==='gif')||(fileformat==='png')||(fileformat==='jpeg')) {
if (img.files && img.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
//----Image is ready for preview.
SetBG.background='url('+e.target.result+') no-repeat center center fixed';
/*---- Optional, Set background as cover ---*/
SetBG.backgroundSize="cover";
SetBG.OBackgroundSize="cover";
SetBG.webkitBackgroundSize="cover";
//--Hide Loading Message
Status.style.display="none";
//----- Display (Save/Upload button?)
savebtn.style.display="block";
}
/*-------Reading File....
Display a message or loading gif for large images to be processed?
*/
Status.innerHTML="Loading...";
Status.style.display="block";
savebtn.style.display="none";
reader.readAsDataURL(img.files[0]);
}
}else{
/*----User file input not accepted (File isn't jpg/gif/png/jpeg)
Empty the input element and set the background to default.
*/
Status.innerHTML="Format not accepted";
Status.style.display="block";
savebtn.style.display="none";
SetBG.background='white';
document.getElementById('BackgroundImage').value='';
}
}
#Status{display:none;background:white;color:black;font-size:16pt;}
#savebtn{display:none;}
<div id="Status"></div>
<input type="file" id="BackgroundImage" onchange="MyFunction()"/>
<button id="savebtn" onclick="alert('Now upload the image');">Upload and save</button>
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