I know that the question has been asked before, but all the answers are from a few years ago, so perhaps something changed.
I found this https://github.com/apollolm/phantasm , which seems like exactly what I need. But I'm on OSX and it appears not to be supported.
So, how can I use javascript to save an image of a partulcar portion of a webpage?
You can use phantomjs for rendering page and getting its image.
Example for rendering your question:
var page = require('webpage').create();
page.viewportSize = {width: 1600, height: 900};
console.log('opening page');
page.open('http://stackoverflow.com/questions/37570827/saving-element-of-webpage-as-an-image-using-js', function(status) {
console.log('check status ', status);
if (status == 'success') {
console.log('getting rect for part of page which we want to render to file');
var rect = page.evaluate(function() {
var questionElement = document.getElementById("question");
return questionElement.getBoundingClientRect();
});
console.log('setting rect ', rect);
page.clipRect = rect;
console.log('rendering, check file question.png near your script');
page.render("question.png");
}
console.log('exiting');
phantom.exit();
})
run it with phantomjs SCRIPT_FILENAME.js and wait for result:
Related
I wrote the following code:
var image_pinch_help = document.getElementById("image_pinch_help");
var pinch_img_el = document.getElementById("pinch_img_el");
function openImage(img_url) {
pinch_img_el.src = img_url;
if (pinch_img_el.complete) {
IMGloaded();
}
else {
pinch_img_el.addEventListener('load', IMGloaded())
pinch_img_el.addEventListener('error', function() {
alert('error')
});
}
image_pinch_help.style.display = "block";
document.getElementById("pinch_values").setAttribute("content", "");
}
function IMGloaded() {
var screen_height = window.screen.height;
console.log("HERE IS THE HEIGHT: " + screen_height);
var img_height = pinch_img_el.offsetHeight;
console.log("HERE IS THE IMAGE: " + img_height);
}
The code starts at the function openImage(). This function is called to open an Image at my webpage. So the image gets the full width of the screen. The screen where the image is shown on is image_pinch_help. You see that I make this screen visible. To allow the user to zoom into the image I am manipulation the HTML DOM, that the user can scroll at the whole page. I do this here: document.getElementById("pinch_values").setAttribute("content", "");.
When the Image loaded I need to get the height of the screen, which works perfectly. But I also need to get the height of the loaded image. The problem is that its always returning an empty string. I read a long time ago that this could be caused by HTML DOM Manipulations.
But how to fix this disaster?
~Marcus
I am trying to create an ajax request to my views.py that returns a cropped segment of a larger image. The problem that I am having is that the image doesn't seem to be downloaded, or at least isn't being drawn. There isn't an error message associated with it, just nothing happens (including "test" not being logged to the console). I have left a few of my attempts with the javascript in but none of them seemed to work.
The view function also doesn't appear to be particularly rapid with the 'called' response being printed 5+ seconds after the request. I am wondering if this is because it takes a long time to load the image? If this is the case is there a way to speed this up i.e. to only load the image once and keep it in memory or just a better way of doing it. It is quite a large image (12000x6000px, 28.5 MB JPEG).
I know that the views.py function is being called by confirming it with the logging module.
So in short:
Why is my image not being displayed?
Is there a quicker way of delivering the image?
Views.py:
def test(request):
img = Image.open(settings.STATIC_PATH + 'geogame/big_map.jpg')
img.load()
xpos = int(request.GET.get('xpos', ''))
ypos = int(request.GET.get('ypos', ''))
upper = ypos*10 - 300
left = xpos*10 - 600
right = xpos*10 + 600
lower = ypos*10 + 300
cropped_img = img.crop((left, upper, right, lower))
logging.warn('called')
response = HttpResponse(content_type = 'image/jpg')
img.save(response, "JPEG")
return response
Ajax request:
Game.zoom = function() {
zoomx = Game.clickX
zoomy = Game.clickY
$.ajax({
url: 'zoom/',
data: {xpos: zoomx, ypos: zoomy},
type: 'get',
success: function(data) {
//Game.zoomedImg.src = "data:image/jpg;base64,"+data
//Game.zoomedImg.src = data;
//console.log(Game.zoomedImg.src);
Game.zoomedImg.onload = function() {
console.log("load");
Game.zoomedImg.src = "data:image/jpg;base64,"+data
}
//$("#" + this.zoomedImg).one("load", function() {
// console.log("test");
// Game.ctx.drawImage(Game.zoomedImg, 0, 0);
// }).attr("src", "data:image/jpg;base64,"+data);
//Game.ctx.drawImage(Game.zoomedImg, 0, 0);
console.log("hello");
//};
},
failure: function(data) {
console.log('failed to load zoomed image')
}
});
}
Thanks!
EDIT
buffer = cStringIO.StringIO() logging.warn('egege')
cropped_img.save(buffer, format = "JPEG")
logging.warn('ffgge')
img_str = base64.urlsafe_b64encode(buffer.getvalue())
logging.warn('leflefe')
return HttpResponse(img_str)
Well I believe I have solved both parts
The image needs to be loaded into a string buffer and base64 encoded to be downloaded after being loaded like so:
buffer = cStringIO.StringIO()
cropped_img.save(buffer, format = "JPEG")
img_str = base64.b64encode(buffer.getvalue())
return HttpResponse(img_str)
This is seemingly because the crop action is lazy and needs to be saved before it takes effect.
Also to speed up the process the image load operation and make it a single occurence the following two lines can be moved to the beginning of the views.py file OUTSIDE of the function.
img = Image.open(settings.STATIC_PATH + 'geogame/big_map.jpg')
img.load()
I have been running PhantomJS 1.9.6 happily on a turnkey Linux server for about 4 months now. Its purpose is to take an SVG file and create different sizes using the page.render function.
This has been doing this but since a few days ago has started to generate a black mono output.
Please see below:
The code:
var page = require('webpage').create(), system = require('system'), address, output, ext, width, height;
if ( system.args.length !== 4 ) {
console.log("{ \"result\": false, \"message\": \"phantomjs.rasterize: error need address, output, extension arguments\" }");
//console.log('phantomjs.rasterize: error need address, output, extension arguments');
phantom.exit(1);
}
else if( system.args[3] !== "jpg" && system.args[3] !== "png"){
console.log("{ \"result\": false, \"message\": \"phantomjs.rasterize: error \"jpg\" or \"png\" only please\" }");
//console.log('phantomjs.rasterize: error "jpg" or "png" only please');
phantom.exit(1);
}
else {
address = system.args[1];
output = system.args[2];
ext = system.args[3];
width = 1044;
height = 738;
page.viewportSize = { width: width, height: height }; //postcard size
page.open(address, function (status) {
if (status !== 'success') {
console.log("{ \"result\": false, \"message\": \"phantomjs.rasterize: error loading address ["+address+"]\" }");
//console.log('phantomjs.rasterize: error loading address ['+address+'] ');
phantom.exit();
} else {
window.setTimeout(function () {
//--> redner full size postcard
page.render( output + "." + ext );
//--> redner smaller postcard
page.zoomFactor = 0.5;
page.clipRect = {top:0, left:0, width:width*0.5, height:height*0.5};
page.render( output + ".50." + ext);
//--> redner postcard thumb
page.zoomFactor = 0.25;
page.clipRect = {top:0, left:0, width:width*0.25, height:height*0.25};
page.render( output + ".25." + ext);
//--> exit
console.log("{ \"result\": true, \"message\": \"phantomjs.rasterize: success ["+address+"]>>["+output+"."+ext+"]\" }");
//console.log('phantomjs.rasterize: success ['+address+']>>['+output+'.'+ext+']');
phantom.exit();
}, 100);
}
});
}
Does anyone know what can be causing this? There have been no server configuration changes that I know of.
Many thanks for your help.
UPDATE:
It appears the problem is not with PhantomJS but with an update made to the Google Chrome browser on the 20th May (http://googlechromereleases.blogspot.co.uk/2014/05/stable-channel-update_20.html). This effectively changed the way SVG files are built and obviously a change has been made that stops PhantomJS reading the SVG correctly.
The application we have runs correctly on Safari so as expected my first thought about the code not being the issue was correct.
I have logged a forum post in Google Groups (https://groups.google.com/forum/#!topic/google-chrome-techhelp/Y99OXOtikXI) to try and get some help on this, whether it be to roll back the Chrome browser or to provide a flag that enables me to turn off this breaking feature.
FURTHER UPDATE:
This is an issue with an update made to the Google Chrome browser. I have logged a bug report at the address below so hopefully it can be rectified as soon as possible. Thank you for all your help.
http://code.google.com/p/chromium/issues/detail?id=382536
When using PhantomJS for screen capture where most of a page's content is getting added on or during load via JavaScript, I run into a problem. Calling render() produces the correct image, showing the full content of the page, but evaluating document.body.clientHeight returns a tiny value that is presumably the page's height before any content gets added.
How can I get the height/width of the image as PhantomJS is rendering it? I don't think it's a timing issue, I've tried swapping the order of things or setting long delays to ensure everything is totally loaded.
var wp = require('webpage');
var page = wp.create();
page.viewportSize = { width: 1024, height: 768};
page.open(url, function (status) {
if (status === 'success') {
var f = "rendered.png";
//Produces an image with height 4073px
page.render(f);
//height is only 150
var height = page.evaluate(function() { return document.body.offsetHeight }),
width = page.evaluate(function() { return document.body.offsetWidth });
console.log(height,width);
}
});
Try using the page.onLoadFinished callback:
var wp = require('webpage');
var page = wp.create();
page.viewportSize = { width: 1024, height: 768};
page.open(url);
page.onLoadFinished = function() {
var f = "rendered.png";
//Produces an image with height 4073px
page.render(f);
//height is only 150
var height = page.evaluate(function() { return document.body.offsetHeight }),
width = page.evaluate(function() { return document.body.offsetWidth });
console.log(height,width);
};
This should return the correct height and width after the page content has finished loading. I think the difference is that page.onLoadFinished waits for all content to finish loading and not just for a 200 OK response which is equivalent to the success status.
I've read about various kinds of ways getting image dimensions once an image has fully loaded, but would it be possible to get the dimensions of any image once it just started to load?
I haven't found much about this by searching (which makes me believe it's not possible), but the fact that a browser (in my case Firefox) shows the dimensions of any image I open up in a new tab right in the title after it just started loading the image gives me hope that there actually is a way and I just missed the right keywords to find it.
You are right that one can get image dimensions before it's fully loaded.
Here's a solution (demo):
var img = document.createElement('img');
img.src = 'some-image.jpg';
var poll = setInterval(function () {
if (img.naturalWidth) {
clearInterval(poll);
console.log(img.naturalWidth, img.naturalHeight);
}
}, 10);
img.onload = function () { console.log('Fully loaded'); }
The following code returns width/height as soon as it's available. For testing change abc123 in image source to any random string to prevent caching.
There is a JSFiddle Demo as well.
<div id="info"></div>
<img id="image" src="https://upload.wikimedia.org/wikipedia/commons/d/da/Island_Archway,_Great_Ocean_Rd,_Victoria,_Australia_-_Nov_08.jpg?abc123">
<script>
getImageSize($('#image'), function(width, height) {
$('#info').text(width + ',' + height);
});
function getImageSize(img, callback) {
var $img = $(img);
var wait = setInterval(function() {
var w = $img[0].naturalWidth,
h = $img[0].naturalHeight;
if (w && h) {
clearInterval(wait);
callback.apply(this, [w, h]);
}
}, 30);
}
</script>
One way is to use the HEAD request, which asks for HTTP Header of the response only. I know in HEAD responses, the size of the body is included. But I don't know if there anything available for size of images.