I am making a "simple" bookmarklet for 4chan (potentially extensible to other sites) that will download all images in the current thread as a zip archive. The images displayed in a 4chan thread are just thumbnails. The images themselves are provided as links that can be accessed by clicking the thumbnails.
My code should work almost perfectly. I found the class that contains the links to the full-sized images. I select all of these 's with jquery. I am using JSZip to compile the images. But JSZip requires the image data encoded in base64. After scourging SO for methods to do this, it seems almost unanimous that drawing an image onto a canvas and converting the image to base64 that way is the best way to do it. However, since 4chan provides links to its images instead of them being right there in the site, the canvas becomes "tainted" when I draw a linked image onto it, and I cannot get the base64 encoding from it.
How can I do this differently so that it works? Is there a way around the cross origin thing? I tried adding crossorigin="anonymous" to the images I'm creating, but it doesn't work.
var getDataUri = function (targetUrl, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var reader = new FileReader();
reader.onloadend = function () {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
var proxyUrl = 'https://cors-anywhere.herokuapp.com/';
xhr.open('GET', proxyUrl + targetUrl);
xhr.responseType = 'blob';
xhr.send();
};
getDataUri(path, function (base64) {
// base64 availlable here
})
While converting into base64 you can use a proxy URL (https://cors-anywhere.herokuapp.com/) before your image path to avoid cross-origin issue
Check the window.btoa() (doc) which you can use to base-64 encode binary data.
There shouldn't be need for canvas in this case, just download the data with "ajax" (SO) instead.
If you use canvas and the image is for example JPEG you will get a re-compressed image as well (reduced quality) and it will be slower so I wouldn't recommend canvas in this case.
Here is a solution as well (SO).
Related
I want to show tiff/tif images inside an HTML5 canvas. Following this doc I´ve accomplished to do it with uploaded images but when I need to dynamically reference external images by URL the browser always force the download.
var xhr = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.open('GET', "url/of/a/tiff/image/file.tiff");
xhr.onload = function (e) {
var tiff = new Tiff({buffer: xhr.response});
var canvas = tiff.toCanvas();
document.body.append(canvas);
};
xhr.send();
At the begining I had CORS issues but I solved that problem, now the uploaded images are display correctly but URL are not.
How can I do this for both cases uploaded images and URL
Thanks in advance!
UPDATE!
Working on later I noticed that when load .tiff files there is no problem. The download is forced when is a .tif file. Are these mime types differents?
My custom server embeds meta data about an image in the PNG meta data fields. The image is loaded via a regular img tag. I'd like to access the meta data from JavaScript - any way to achieve this?
If not, what are the alternatives for serving additional information for an image? The images are generated on the fly and are relatively expensive to produce, so I'd like to serve the meta data and the image data in a single round trip to the server.
i had a similar task. I had to write physical dimensions and additional metadata to PNG files. I have found some solutions and combined it into one small library.
png-metadata
it could read PNG metadata from NodeJS Buffers, and create a new Buffers with new metadata.
Here how you can read PNG metadata in the Browser:
function loadFileAsBlob(url){
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status === 200) {
resolve(this.response);
// myBlob is now the blob that the object URL pointed to.
}else{
reject(this.response);
}
};
xhr.send();
})
};
const blob = await loadFileAsBlob('1000ppcm.png');
metadata = readMetadataB(blob);
A couple of solutions I can think of:
Pass the metadata as headers, use XMLHttpRequest to load the image and display it by converting the raw bytes to a data uri, as talked about here. Looks roughly like this:
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function() {
var metadata = xhr.getResponseHeader("my-custom-header");
image.src = window.URL.createObjectURL(xhr.response);
}
xhr.open('GET', 'http://whatever.com/wherever');
xhr.send();
Alternatively, write a little png parser in js (or compile libpng to javascript using something like emscripten), and do basically the same thing as above.
It probably wouldn't be too hard to write actually; since you don't care about the image data, you'd just have to write the chunk-traversing code. Read up on how chunks are laid out here, and figure out what chunk type you're storing the metadata in. Still, don't really recommend this if you can just use headers...
I'm trying to produce the same base64 data for an image file in both JavaScript and in Ruby. Unfortunately both are outputting two very different values.
In Ruby I do this:
Base64.encode64(File.binread('test.png'));
And then in JavaScript:
var image = new Image();
image.src = 'http://localhost:8000/test.png';
$(image).load(function() {
var canvas, context, base64ImageData;
canvas = document.createElement('canvas');
context = canvas.getContext('2d');
canvas.width = this.width;
canvas.height = this.height;
context.drawImage(this, 0, 0);
imageData = canvas.toDataURL('image/png').replace(/data:image\/[a-z]+;base64,/, '');
console.log(imageData);
});
Any idea why these outputs are different?
When you load the image in Ruby the binary file without any modifications will be encoded directly to base-64.
When you load an image in the browser it will apply some processing to the image before you will be able to use it with canvas:
ICC profile will be applied (if the image file contains that)
Gamma correction (where supported)
By the time you draw the image to canvas, the bitmap values has already been changed and won't necessarily be identical to the bitmap that was encoded before loading it as image (if you have an alpha channel in the file this may affect the color values when drawn to canvas - canvas is a little peculiar at this..).
As the color values are changed the resulting string from canvas will naturally also be different, before you even get to the stage of re-encoding the bitmap (as PNG is loss-less the encoding/compressing should be fairly identical, but factors may exist depending on the browser implementation that will influence that as well. to test, save out a black unprocessed canvas as PNG and compare with a similar image from your application - all values should be 0 incl. alpha and at the same size of course).
The only way to avoid this is to deal with the binary data directly. This is of course a bit overkill (in general at least) and a relative slow process in a browser.
A possible solution that works in some cases, is to remove any ICC profile from the image file. To save an image from Photoshop without ICC choose "Save for web.." in the file menu.
The browser is re-encoding the image as you save the canvas.
It does not generate an identical encoding to the file you rendered.
So I actually ended up solving this...
Fortunately I am using imgcache.js to cache images in the local filesystem using the FileSystem API. My solution is to use this API (and imgcache.js makes it easy) to get the base64 data from the actual cached copy of the file. The code looks like this:
var imageUrl = 'http://localhost:8000/test.png';
ImgCache.init(function() {
ImgCache.cacheFile(imageUrl, function() {
ImgCache.getCachedFile(imageUrl, function(url, fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(e) {
console.log($.md5(this.result.replace(/data:image\/[a-z]+;base64,/, '')));
};
reader.readAsDataURL(file);
});
});
});
});
Also, and very importantly, I had to remove line breaks from the base64 in Ruby:
Base64.encode64(File.binread('test.png')).gsub("\n", '');
I am working on a FineUploader implementation. Special request is to create thumbnails on the fly client-side and then upload those with the original image-upload.
I have an implementation that works on FF, but does not seem to work on iOs. It looks like so:
var uploader = new qq.FineUploaderBasic({
button: document.getElementById(buttonID),
request: {
endpoint: '/up/load/a/' + $('section#ajax-viewport').data('albumid')
},
callbacks: {
onSubmit: function(id, fileName) {
// getFile obtains the file being uploaded
file = this.getFile(id);
// create a thumbnail & upload it:
ThumbDown(file, id, 200, fileName);
},
}
})
This code calls a function:
function ThumbDown(file, id, dimension, fileName) {
var reader = new FileReader();
reader.onload = function(e) {
var img = document.createElement("img");
img.onload = function (ev) {
var thumbnailDimensions; // object holding width & height of thumbnail
var c=document.getElementById("canvas-for-thumbnails"); // must be a <canvas> element
var ctx=c.getContext("2d");
// set thumbnail dimensions of canvas:
thumbnailDimensions = calcThumbnailDimension (img.width, img.height, dimension )
c.width = thumbnailDimensions.width;
c.height = thumbnailDimensions.height;
var ctx = c.getContext("2d");
ctx.drawImage(img, 0, 0, c.width, c.height);
uploadThumbnail(c.toDataURL('image/jpeg'), //a base64 encoded representation of the image
id,
fileName); // we need filename to combine with mother-image on the server
};
img.src = e.target.result;
}
reader.readAsDataURL(file);
} // end function
Finally the Thumbnail is uploaded with a dumb ajax-call:
function uploadThumbnail (base64encodedString, id, fileName) {
$.post('/up/thumb',
{
img : base64encodedString,
id: id,
fileName: fileName
},
function(data) {});
}
My questions:
1) Currently I have two uploads: one for mother-image and another for thumbnail. I would like to combine this in one FineUploader call. However, I do not see a way to do this, due to the asynchronous nature of my thumbnail creation.
Am I missing something? Is this possible to reduce this to one FineUploader call?
2) This code uploads the thumbnails as a base64 encoded string. I would like to upload the thumbnail as an image (or as a blob ?). Perhaps by following this recipe of Jeremy Banks. Would that work with FineUploader?
3) Are there other options/methods of FineUploader that I have missed but I should be using?
Any help is, as always, greatly appreciated.
So, it is already trivial to upload the original image. Fine Uploader takes care of that for you. If I understand correctly, you want to also upload a scaled version of the image (which you have already generated). I suggest you take the image you have drawn onto the canvas and convert it to a Blob. Then, you can submit this Blob directly to Fine Uploader, where it will upload it for you.
For example, change the value of uploadThumbnail to this:
function uploadThumbnail(thumbnailDataUri, id, filename) {
var imageBlob = getImageBlob(thumbnailDataUri, "image/jpeg"),
blobData = {
name: filename,
blob: imageBlob
};
// This will instruct Fine Uploader to upload the scaled image
uploader.addBlobs(blobData);
}
function getImageBlob(dataUri, type) {
var binary = atob(dataUri.split(',')[1]),
array = [];
for(var i = 0; i < binary.length; i++) {
array.push(binary.charCodeAt(i));
}
return new Blob([new Uint8Array(array)], {type: type});
}
Note: the getImageBlob function was adapted from this Stack Overflow answer. If this works for you, be sure to upvote the answer I've linked to.
Server-side note
A Blob is pretty much a File without a name property. Your server-side code will handle the upload of a Blob pretty much the same way as it does a File or form submit containing a <input type="file"> form field. The only noticeable difference to your server will be the filename parameter value in the Content-Disposition header of the multipart boundary containing the file. To put it another way, your server may think the image is named "blob" or perhaps some other generic name, due to the way most browsers generate multipart encoded requests that contain Blob objects. Fine Uploader should be able to get around that by explicitly specifying a file name for the browser to include in blob's Content-Disposition header, but this ability does not have wide browser support. Fine Uploader gets around this limitation, to some degree, by including a "qqfilename" parameter with the request containing the actual name of the Blob.
Future native support for thumbnail generation & scaling
The plan is to add native support for thumbnail previews to Fine Uploader. This is covered in feature requests #868 and #896. There are other related feature requests open, such as image rotation and validation related to images. These features and other image-related features will likely be added to Fine Uploader in the very near future. Be sure to comment on the existing feature requests or add additional requests if you'd like.
As of version 4.4 of FineUploader, as Ray Nicholus pointed out would eventually happen, this functionality has been baked into their framework.
Here is an example of setting the upload sizes when creating a FineUploader instance:
var uploader = new qq.FineUploader({
...
scaling: {
sizes: [
{name: "small", maxSize: 100},
{name: "medium", maxSize: 300}
]
}
});
See their page on uploading scaled images.
It relates to another question Cross Origin Resource Sharing Headers not working only for safari .
I am trying to load an image into canvas from s3.
Seems safari < 6.0 has a bug related to loading images via CORS. So the canvas get tainted though the image has cors enabled. So I was thinking if there is some way to make an ajax request and then load the response into a canvas ?
Note : Ajax request works properly with CORS. Just that while loading image safari doesn't respect the crossOrigin attribute and hence the request is made without cross-origin.
I have my images at s3 so there is no way to encode it to base64 and get it from amazon directly
I am preferring not to set up a proxy at my domain for the image
some javascript
var img_location = "//temp_upload_test.s3.amazonaws.com/IMG_0222.JPG"
var img = new Image();
img.onload = function(){
console.log("image loaded")
EXIF.getData(img,function(){
console.log("image data read");
var orientation = EXIF.getTag(img,'Orientation');
console.log("orientation"+orientation);
load_image_into_canvas_with_orientation(img,orientation);
})
console.log("image loaded function complete");
}
img.crossOrigin = "anonymous";
$(img).attr("crossOrigin","anonymous");
img.src = img_location;
One way I am trying to approach the problem is make an xhr request to s3. get the image as BinaryFile and then decode it to base64 and use it as img's src . But while decoding I get a DOM exception not sure if the idea itself is wrong
img.crossOrigin = "anonymous";
It does not works in all cases because of security issue.
You can load any image in canvas by converting that image to base64 string and then bind to canvas.
Example:
<img src="www.example.com/name.png" alt="test" />
to
<img src="data:image/png;base64,vnaXYZetc..." alt="test" />
then load it to in canvas.
Thank you.