I am creating a webapp where the an image is shown to the user and the user can draw on the image (basically the image is in a canvas).
Now once the user is done drawing, the user will press the save button and the image needs to be saved in static folder (not locally). Since I am using Django to accomplish this; my request.FILES is always empty whenever I call the route.
My question is how can I save the canvas on which the user drew to a folder in the project and not locally.
index.html
<div id="uploaderForm">
<form enctype="multipart/form-data" action="{% url 'addImg' %}" method="post">
<input type="hidden" for="image_data" id="image_data" name="image_data" />
</form>
<div class="uploaderItem" id="uploaderSubmit">
Add to the gallery
</div>
</div>
script.js
function addImage()
{
console.log("In add image");
var image_data = canvas.toDataURL();
console.log("Image data:", image_data);
$('#image_data').val(image_data);
$('#uploaderForm > form').submit()
}
views.py
def add(request):
print request.FILES
#print request.POST['image_data']
return HttpResponse("You're looking at question")
the FILES array is reserved for files uploaded in file input fields through a multipart form. What you're doing is passing a base-64 encoded string representing your image, to a hidden text field that is then sent to your server. By your code, the image should be found here:
image_data = request.POST.get('image_data')
It will be a base-64 string, so you'll need to decode it, the example below can be applied to almost any data URL, but the format will depend on your browser, so it's kinda tricky:
import re
pattern = r'^data:(?P<mime_type>[^;]+);base64,(?P<image>.+)$'
result = re.match(pattern, image_data)
if result:
mime_type = result.group('mime_type')
image = result.group('image').decode('base64')
Be careful: by not using a file as transport, you are basically dumping the whole image in the server's memory, and that's expensive if you're planning to serve a lot of clients, and it's also time consuming, so your request could timeout before you are done with the image.
To fix this, you should consider uploading the image through AJAX, which is a supported way to treat Blobs in javascript, that way you could use Django's file upload facilities more efficiently
Related
On the client side I have an svg element, under which there's an image and some text on that image (so it looks like a poster with text on it). I need to send this poster as a whole to the server. The HTML looks like this:
<svg id="poster_svg" width="1000" height="1000" style="max-width: 100%;">
<image href="/static/images/motiv1.jpg" width="100%" height="100%"/>
<p class="centered" id="poster_text"></p>
</svg>
The text inside the p is generated by some other user actions. I found a tutorial online which converts the svg element to base64 which then I can use to send to server through a POST request.
The JavaScript code for it is:
var SVGDomElement = document.getElementById("poster_svg");
var serializedSVG = new XMLSerializer().serializeToString(SVGDomElement);
var base64Data = window.btoa(serializedSVG);
const json = {
image: base64Data
};
const options = {
method: "POST",
body: JSON.stringify(json),
headers:{
'Content-Type':'application/json'
}
};
fetch('/makeposter',options).then(res=>{
return res.json()
}).then(cont=>{
console.log(cont)
});
On the server side (which uses Flask), the base64 is recieved successfully, but when converted to an image, it is blank. The server code for converting the base64 to image is this:
cont = request.get_json()
imgb64 = cont['image']
decoded = base64.b64decode(imgb64)
f=open("test.png","wb")
f.write(decoded)
f.close()
Why is the image blank, and how can I fix it?
base64-encoding does not magically convert SVG to PNG. After decoding on the server, the result will still be a string, and its content will be still the same serialized SVG you quoted for the client side.
This includes the fact that the image tag still only holds a reference to the JPG file, not the file content itself. You will need to make sure that the relative path to that file is still valid in the context of server-side execution.
Lastely, if you want to convert the result from SVG to PNG, you need some software to do the actual work. I have no further knowledge how you would go about that with Flask.
You could also do the conversion client-side, see for example this question for a how-to. Just exchange the download function described there with an upload function. But note that there is a serious caveat: the <image> href inside the SVG must point to the same domain as the page this is executed on, otherwise export will be blocked.
Aside Using a HTML <p> tag directly inside SVG content does not work. You have to use a <text> element.
Short version
I have a webapp using Magnolia, I need to upload a comment with posibility of multiple files, I want to use AJAX, before saving the files as assets I want to be able to check the user's permission, I figured I need a custom Java-based REST endpoint, I made it work, but I have issues saving "jcr:data" into an asset.
Long version
I have a webapp, I have registered users (using PUR), I have different roles for users (for simplicity let's say User and Editor) and I have a Post and a Comment content types. Every User can create a Post and add files, every Post holds the creator's UUID, array of Comment UUIDs and array of file UUIDs (from Assets app), every Post has a comment section and every Comment can have files attached (zero or multiple) to it. Every Editor can post a comment to every Post, but Users can only post comments to their own Posts.
My form for comments looks something like this:
<form action="" method="post" id="comment-form" enctype="multipart/form-data">
<input type="file" name="file" id="file" multiple />
<textarea name="text"></textarea>
<button type="button" onclick="sendComment();">Send</button>
</form>
I tried using Javascript model to process the data and I was able to save the asset correctly, however only one. I couldn't access the other files in the model.
I tried solving it (and improving user experience) by using AJAX and a REST endpoint. I opted not to use the Nodes endpoint API, because I didn't know how to solve the permission issue. I know I can restrict access to REST for each role, but not based on ownership of the Post. So I created my own Java-based endpoint (copied from documentation).
In the sendComment() function in Javascript I create an object with properties like name, extension, mimeType, ..., and data. I read in the documentation that you should send the data using the Base64 format, so I used FileReader() to accomplish that:
var fileObject = {
// properties like name, extension, mimeType, ...
}
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
// this part is easy
};
xhttp.open("PUT", "http://localhost:8080/myApp/.rest/assets/v1/saveAsset", true);
xhttp.setRequestHeader("Content-type", "application/json");
var reader = new FileReader();
reader.onload = function() {
fileObject.data = reader.result;
// I also tried without the 'data:image/png;base64,' part by reader.result.split(",")[1];
xhttp.send(JSON.stringify(fileObject));
};
reader.readAsDataURL(file); //where file is the value of the input element.files[i]
In Java I created a POJO class that has the same properties as the javascript object. Including a String data.
The code for the endpoint looks like this:
public Response saveAsset(Asset file) {
// Asset is my custom POJO class
Session damSession;
Node asset;
Node resource;
try {
damSession = MgnlContext.getJCRSession("dam");
asset = damSession.getRootNode().addNode(file.getName(), "mgnl:asset");
asset.setProperty("name", file.getName());
asset.setProperty("type", file.getExtension());
resource = asset.addNode("jcr:content", "mgnl:resource");
InputStream dataStream = new ByteArrayInputStream(file.getData().getBytes());
ValueFactory vf = damSession.getValueFactory();
Binary dataBinary = vf.createBinary(dataStream);
resource.setProperty("jcr:data", dataBinary);
resource.setProperty("fileName", file.getName());
resource.setProperty("extension", file.getExtension());
resource.setProperty("size", file.getSize());
resource.setProperty("jcr:mimeType", file.getMimeType());
damSession.save();
return Response.ok(LinkUtil.createLink(asset)).build();
} catch (RepositoryException e) {
return Response.ok(e.getMessage()).build(); //I know it's not ok, but that's not important at the moment
}
}
The asset gets created, the properties get saved apart from the jcr:data. If I upload an image and then download it either by the link I get as a response or directly from the Assets app, it cannot be opened, I get the format is not supported message. The size is 0, image doesn't show in the Assets app, seems like the data is simply not there, or it's in the wrong format.
How can I send the file or the file data to the Java endpoint? And how should I receive it? Does anybody know what am I missing? I honestly don't know what else to do with it.
Thank you
The input stream had to be decoded from Base64.
InputStream dataStream = new ByteArrayInputStream(Base64.decodeBase64(file.getData().getBytes()));
...and it only took me less than 3 months.
Noticed it after going through the source code for REST module that for unmarshalling Binary Value it had to be encoded to Base64, so I tried decoding it and it started to work.
I have a file upload that saves images as .png on the server and the link in a mysql database. To show thumbnails of the image before uploading I have a function that convertes the file list object to a preview pic. Now I want the user to edit the file selection later. For that I need to load the images from the server back as thumbnails. I think the best way to do this is to convert the file path stored in the database to a file object and apply this object to the function that creates the thumbnails that I don't need to rewrite this function.
So my question is how can I convert my stored image links to a file list object?
Edit:
upload:
user selection -> file object -> base 64 -> blob -> display blob -> (maybe) edit -> upload selection as base 64 to server -> base 64 to .png -> save pic -> save link
later edit selection by user(how to do?):
saved link -> file object -> base 64 -> blob -> display blob -> (maybe) edit -> ...
saved link -> file object How to do? Possible? Better way?
I hope it's now clearer to understand.
If someone has an idea how to do this or a better way please answer.
(I know that you should show Code when asking a question but I don't think that it is necesarry to upload the whole upload function here)
You process is all frowned, so I may not answer correctly your question, but I'm pretty sure this is an X-Y problem.
Javascript File objects inherit from the Blob object.
From MDN :
A File object is a specific kind of a Blob, and can be used in any context that a Blob can.
Never convert a File to a base64 dataURL, if it's to convert it back to a Blob ; this makes no sense and only pollute the browser's memory.
To display a Blob (or a File) in the browser, from media elements, or iframes, use the URL.createObjectURL(blob) method. This will return a blobURI, that will be available only for the life time of the initiating page, and only for the user's browsers. In case of user uploaded Files, the file is not even copied to memory, and the uri returned is just a direct pointer to the file on the user's system.
inp.onchange = function(){
var url = URL.createObjectURL(this.files[0]);
var img = new Image();
img.onload = function(){
document.body.appendChild(this);
}
img.onerror = function(){
console.log('probably not a supported image file');
}
img.src = url;
}
<input id="inp" type="file" accept="image/*">
If you need to modify the image, you can do so by drawing the resulting image on a canvas.
Then, instead of exporting your canvas to a dataURI, directly use the toBlob method, which can be polyfilled.
To send you File/Blob on your server, send the File/Blob directly instead of its 30% heavier base64 string representation.
This can be done really easily thanks to the FormData API.
var xhr = new XMLHttpRequest();
xhr.onload = done;
var formData = new FormData();
formData.append('file', theFile, fileName);
xhr.open('POST', yourServer);
xhr.send(formData);
Then you can retrieve it server side just like any File uploaded through the basic Multipart/Form method.
In my jsp page, I have a manipulated photo within a html5 canvas and I want to upload it to facebook upon clicking the upload button.
FacebookType publishPhotoResponse = facebookClient.publish("me/photos", FacebookType.class,
BinaryAttachment.with("test.jpeg", getClass().getResourceAsStream("xxx")),
Parameter.with("message", "Test"));
out.println("Published photo ID: " + publishPhotoResponse.getId());
I am using Restfb for the uploading part in my servlet. However, I have no clue what attribute is needed for me to pass over to servlet side for processing (for example: "xxx"). When I used toDataURL, the URL of the image is base64. Does facebook api allows me to upload photo using the base64 format?
var base64URL = document.getElementById('canvasImg').src;
var decodedURL = escape(window.atob(base64URL));
alert(decodedURL);
The above coding seems to contain error as it won't display the alert. Should I decode the base64 data first before handling it over to servlet or should I pass the whole base64 data to servlet to handle the decoding?
I have an image generated on the client side which I want to transfer to the server through a form. For example, let's say that I have a registration form in which the profile image is generated automatically by JavaScript, and I want to transfer that image to django.
What is the best way to transfer the image binary data to the server when user hit the submit button? what form field should I use?
thanks!
Found an answer myself, here's the solution in case someone needs it:
In the client side, this is how you get the image from canvas and set it to a form field (a hidden char field):
var dataURL = document.getElementById('canvas_id').toDataURL("image/png");
document.getElementById('id_hidden_image_field').value = dataURL;
And in django side:
add to the form a hidden field called 'hidden_image_field', which will be used to pass the data. this field is a simple CharField. you can add max_length limit to make sure image is in a reasonable size (note: not dimensions but actual size).
to parse the image data:
import re
import base64
dataUrlPattern = re.compile('data:image/(png|jpeg);base64,(.*)$')
ImageData = request.POST.get('hidden_image_field')
ImageData = dataUrlPattern.match(ImageData).group(2)
# If none or len 0, means illegal image data
if (ImageData == None or len(ImageData) == 0:
# PRINT ERROR MESSAGE HERE
pass
# Decode the 64 bit string into 32 bit
ImageData = base64.b64decode(ImageData)
and now ImageData contains the binary data, you can simply save to a file and it should work.
hope this helps.