Capturing a screenshot using window.scroll with high DPI settings - javascript

I'm using CefSharp 55.0.0 WinForms.
In order to take screenshots in CefSharp, I scroll the webpage using window.scroll within JavaScript and take an image of the current viewport. Once that has completed, it is then stitched back together again. This works fine for monitors that have their DPI setting set to 100%. However, when the monitor has a DPI greater than 100% screenshots do not work as expected and miss content.
Image 1 - 100%
Image 2 - 150%
Compare Image 1 to Image 2. While they both (practically) have the same width and height, Image 2 is missing a large portion of the content, compared to a perfect Image 1.
When DPI settings are above 100%, how can I correctly scroll and capture a screenshot that ensures I obtain everything it would if settings were at 100%?
Other details
The application has the correct DPI aware settings in theapp.manifest file and Cef.EnableHighDPISupport(); has been called in the Main method within Program.cs.
Screenshot Code (abridged)
int scrollHeight = GetDocHeight(); //some javascript that calcs the height of the document
int viewportHeight = ClientRectangle.Size.Height;
int viewportWidth = ClientRectangle.Size.Width;
int count = 0;
int pageLeft = scrollHeight;
bool atBottom = false;
while (!atBottom)
{
if (pageLeft > viewportHeight)
{
await GetBrowser().MainFrame.EvaluateScriptAsync("(function() { window.scroll(0," + (count * viewportHeight) + "); })();"); //I think the issue lies here
count++;
await PutTaskDelay();
using (Bitmap image = GetCurrentViewScreenshot())
{
//just a class that saves the partial images to a disk cache
cache.AddImage(count, image);
}
}
else
{
await GetBrowser().MainFrame.EvaluateScriptAsync("(function() { window.scrollBy(0," + pageLeft + "); })();");
atBottom = true;
count++;
await PutTaskDelay();
Rectangle cropRect = new Rectangle(new Point(0, viewportHeight - pageLeft), new Size(viewportWidth, pageLeft));
using (Bitmap src = GetCurrentViewScreenshot())
using (Bitmap target = new Bitmap(cropRect.Width, cropRect.Height))
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(src, new Rectangle(0, 0, target.Width, target.Height), cropRect, GraphicsUnit.Pixel);
cache.AddImage(count, target);
}
}
pageLeft = pageLeft - viewportHeight;
}
Current View Screenshot Method
private Bitmap GetCurrentViewScreenshot()
{
int width, height;
width = ClientRectangle.Width;
height = ClientRectangle.Height;
using (Bitmap image = new Bitmap(width, height))
{
using (Graphics graphics = Graphics.FromImage(image))
{
Point p, upperLeftDestination;
Point upperLeftSource = new Point(0, 0);
p = new Point(0, 0);
upperLeftSource = PointToScreen(p);
upperLeftDestination = new Point(0, 0);
Size blockRegionSize = ClientRectangle.Size;
graphics.CopyFromScreen(upperLeftSource, upperLeftDestination, blockRegionSize);
}
return new Bitmap(image);
}
}

While not a perfect solution, I found a workaround that involves setting the force-device-scale-factor flag to 1 in CefSettings. It's not perfect because the browser isn't scaled on high DPI displays, potentially making text hard to read for users. However, it does fix my more pressing issue of missing data in screenshots.
CefSettings settings = new CefSettings();
settings.CefCommandLineArgs.Add("force-device-scale-factor", "1");
Cef.Initialize(settings);
This question is still open for better suggestions, if there are any. :-)

Related

flashing base64 image in canvas

I can't understand why when I'm creating an image from a base64 source (coming from another canvas cnvBase) it 'flashes' : the real image imgBW is OK and remains in place until a new base64 image is sended by cnvBase, but in cnvBW it appears for a fraction of a second and disappears immediately.
HTML
<canvas id="cnvBase"></canvas>
<img id="imgBW">
<canvas id="cnvBW"></canvas>
JS
function createBWImg(){
console.log('image '+imageNB.substr(0,50)) // data:image/png;base64,iVBORw0KG....
document.getElementById('imgBW').style.width = w+'px';
document.getElementById('imgBW').style.height = y+'px';
document.getElementById('imgBW').src = imageNB;
let newimage = new Image();
newimage.onload = function() {
ctxBW.drawImage(newimage, 0, 0);
};
newimage.src = imageNB;
}
Note: cnvBase sends a new image every second when User moves his smartphone (beta value):
window.addEventListener('deviceorientation', function(event) {
alpha= Math.round(event.alpha);
beta = Math.round(event.beta);
gamma= Math.round(event.gamma);
setTimeout(function() {
if (beta >= 0 && beta <= 90) {
getValues(alpha,beta,gamma);
}
}, 1000);
});
The getValues(a,b,g) function retrieves a part of the camera stream and places it in cnvBase according to the value of b
EDIT
OK, I think that image 'seems' to remain the same but in fact not. With this code, canvas keeps the right color for one second without flashing... I think something happens (but what?) during image production: something is sent to canvas (but what?).
let color;
if(b>=0 &&b<34) color='red';
if(b>=34 &&b<67) color='green';
if(b>=67 &&b<90) color='blue';
ctxBW.fillStyle = color;
ctxBW.fillRect(0, 0, w, y);
EDIT 2
More and more strange. When adding this part of code for PCs (because changing beta orientation is hard to obtain) it works perfectly: image in canvas doesn't flash and remains in place, but not on phones where it appears and disappears as soon as created (flash effect)
if(screen > 600){ // screen is screen width
setInterval(function(){
send_pseudo_beta(Math.floor((Math.random() * 10)+1)*8) // random beta
},1000);
}

How do I get the MIME type of an image/blob in javascript?

I'm working on a Chrome Extension in which I resize images (actually resize; not changing the browser display) that users right click on. When they right click on the image, I get access to the image's 'src'.
I can resize the images that aren't gifs fine; I'm using canvases to do this. You can see me do this here https://jsfiddle.net/cyqvacc6/6/.
img_url = 'https://i.imgur.com/SHo6Fub.jpg';
function get_image(image_url, emoji_name) {
var img_el = document.createElement('img');
img_el.onload = function () {
canvas = img_to_canvas(img_el);
emoji_sized_canvas = emoji_sized(canvas);
document.body.appendChild(emoji_sized_canvas);
};
img_el.src = image_url;
}
function img_to_canvas(img) {
canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
canvas_ctx = canvas.getContext('2d');
canvas_ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
return canvas;
}
function emoji_sized(canvas) {
var target_dim = emoji_dimensions(canvas.width, canvas.height);
var factor = 2;
var canvas_long_side = Math.max(canvas.width, canvas.height);
var target_long_side = Math.max(target_dim.width, target_dim.height);
new_canvas = document.createElement('canvas');
new_canvas_ctx = new_canvas.getContext('2d');
if ((target_long_side === canvas_long_side)) {
// Return the image.
return canvas;
} else if (target_long_side > canvas_long_side * factor) {
// Increase the size of the image and then resize the result.
new_canvas.width = canvas.width * factor;
new_canvas.height = canvas.height * factor;
new_canvas_ctx.drawImage(canvas, 0, 0, new_canvas.width, new_canvas.height);
return emoji_sized(new_canvas);
} else if (canvas_long_side > target_long_side * factor) {
// Half the size of the image and then resize the result.
var width = new_canvas.width = canvas.width / factor;
var height = new_canvas.height = canvas.height / factor;
new_canvas_ctx.drawImage(canvas, 0, 0, new_canvas.width, new_canvas.height);
return emoji_sized(new_canvas);
} else {
// Resize the image in one shot
new_canvas.width = target_dim.width;
new_canvas.height = target_dim.height;
new_canvas_ctx.drawImage(canvas, 0, 0, new_canvas.width, new_canvas.height);
return new_canvas;
}
}
function emoji_dimensions(width, height) {
const MAX_SIDE_LENGTH = 128;
// Get the larger side
long_side = Math.max(height, width);
// Determine the scale ratio
// If the image is between 95% to 100% of the target
// emoji size, don't adjust it's size.
var scale;
if ((long_side >= 0.95 * MAX_SIDE_LENGTH) && (long_side <= MAX_SIDE_LENGTH))
{
scale = 1;
} else {
scale = MAX_SIDE_LENGTH / long_side;
}
return {
'height': height * scale,
'width': width * scale
};
}
Unfortunately, I'm not seeing an easy way to resize gifs using canvases. When I try the same approach on gifs, the 'resized' image is no longer a gif; it's just the first frame of the gif resized.
I think I'm going to end up sending gifs to a server to resize them, but still, in order to do this, I need to know whether the image I'm working on is animated or not, which I don't know how to do.
So, how do I determine if an image is a gif? Also, is it possible to resize these gifs from the client, i.e. javascript?
For reference, I need to reduce the gifs in terms of byte size and pixel, i.e. the gif needs to be both below 128px in both height and width and less than 64k in total byte size.
Since your question actually contains multiple questions, it's quite hard to answer it, so I'll currently don't include code in here.
First, Canvas API can only draw the first frame of any animated image passed through an <img> element. According to specs.
Specifically, when a CanvasImageSource object represents an animated image in an HTMLOrSVGImageElement, the user agent must use the default image of the animation (the one that the format defines is to be used when animation is not supported or is disabled), or, if there is no such image, the first frame of the animation, when rendering the image for CanvasRenderingContext2D APIs.
So you won't natively be able to render all your gif's frames on the canvas.
For this, you'll have to parse the file and extract every frames of your file.
Here are is an untested library that do propose this functionality :
libgif-js.
If you don't like libraries, you could also write a script yourself.
edit: I tried this lib and it's awfull... don't use it, maybe you could fork it, but it's really not meant to do image processing
Once you've got the frames, you can resize these with canvas, and then reencode them all in a final gif file. Untested either gif.js seems to be able to do that.
Tested too, little bit less awfull but it doesn't like transparency and it needs to have the js files hosted, so no online demo... Would also probably need a fork...
And finally, to answer the title question, "How to check the MIME type of a file", check this Q/A.
Basically, the steps are to extract the 4 first bits of your file and checking it against magic-numbers. 'image/gif' magic-numbers are 47 49 46 38.

Unity3d: WebGL video texture

I want to play a video in WebGL but MovieTextures re not supported. i've tried using Jonas' Simple MoTextures Sample for WebGL and it works great, but only with certain videos. I've deduced that it only works with videos that have a 2:1 aspect ratio. Such as the example one (640x320) or one I did that was 2000x1000.
If I try to use any other video I always have the same warning (hundreds of times):
WARNING: texture bound to texture unit 0 is not renderable. It maybe
non-power-of-2 and have incompatible texture filtering.
I am completely stuck at this point. The code is 100% the one in the sample.
BTW, in my 2000x1000 video I also get that warning, but only 5 times, and the video plays perfectly.
The code in JSlib for create and update functions:
WebGLMovieTextureCreate: function(url)
{
var str = Pointer_stringify(url);
var video = document.createElement('video');
video.style.display = 'none';
video.src = str;
return videoInstances.push(video) - 1;
},
WebGLMovieTextureUpdate: function(video, tex)
{
if (videoInstances[video].paused)
return;
GLctx.bindTexture(GLctx.TEXTURE_2D, GL.textures[tex]);
GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, true);
GLctx.texImage2D(GLctx.TEXTURE_2D, 0, GLctx.RGBA, GLctx.RGBA, GLctx.UNSIGNED_BYTE, videoInstances[video]);
GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, false);
},
And the C# code:
public WebGLMovieTexture (string url)
{
m_Instance = WebGLMovieTextureCreate(url);
m_Texture = new Texture2D(0, 0, TextureFormat.ARGB32, false);
m_Texture.wrapMode = TextureWrapMode.Clamp;
}
public void Update()
{
var width = WebGLMovieTextureWidth(m_Instance);
var height = WebGLMovieTextureHeight(m_Instance);
if (width != m_Texture.width || height != m_Texture.height)
{
m_Texture.Resize(width, height, TextureFormat.ARGB32, false);
m_Texture.Apply();
}
WebGLMovieTextureUpdate(m_Instance, m_Texture.GetNativeTextureID());
}

jQuery changing the size of Video players

James here. I'm working on a project (actually two projects that both require the same code) that deals with posts, except the content is always 100% of the users screen, and uses jquery to divide the width to make the same amount of columns no matter what screen resolution. I'm having a trouble with video posts however. If anyone could help me write (or write, that'd be way more helpful) a script that forces a default 500px video to the width of the posts? The script that I'm using to divide posts is as follows. Any answers at ALL would be helpful. Oh and I'm bumping this because it's almost a week old, and I have still not received a working script.
var container = function(){
var posts = $(document).width() - 40;
var entry = (posts - 200) / 5;
$("#posts").css("width", posts);
$(".entry").css("width", entry);
$("img.photo").css("width", entry - 22);
}
container();
The site I'm doing this on is http://jamestestblog5.tumblr.com
Thank you to anyone who can help with this, it's REALLY bothering me!
Hiya please see demo here : http://jsfiddle.net/ytcAk/
you can read the logic when you click grow button.
fork from old solution. :) (please let me know if this is what you are looking for if not I will remove this post)
Jquery code
function increaseVideoSize() {
var columnWidth = 450; // width of your content column - any
var defaultVideoWidth = 400; // theme tag width - 400,500
var increaseRatio = columnWidth/defaultVideoWidth;
$(".video-post").each(function() {
var iframe = $("iframe", this);
if(iframe.length>0) {
var currHeight = iframe.height();
var newHeight = currHeight * increaseRatio;
iframe.height(newHeight).width(columnWidth);
} else {
var object = $("object", this);
var embed = $("embed", object);
var currHeight = object.attr('height');
var newHeight = currHeight * increaseRatio;
object.width(columnWidth).attr('height', newHeight);
embed.width(columnWidth).attr('height', newHeight);
};
});
};​

How can I change an image with another keeping aspect ratio?

I have the following HTML code:
<img id="Card00" src="images/words/back.png" onclick="imageClicked(0,0);">
And the following Javascript code:
function ShowImage(id, row, column)
{
var imagePath = 'images/words/' + id + '.png';
var imgId = '#Card' + row + '' + column;
$(imgId).attr('src', imagePath);
var width = $(imgId).attr('width');
var height = $(imgId).attr('height');
if (width > height)
$(imgId).attr('width', 200);
else
$(imgId).attr('height', 200);
}
imageClicked only calls ShowImage.
Back.png is 200x200. I want to change back.png image with another one. That another one (id + .png), is bigger than 200x200, so I need to resize it on ShowImage function.
But I can't do that, because I can't get its new width and height in this piece of code:
var width = $(imgId).attr('width');
var height = $(imgId).attr('height');
My goal is to change back.png image with another one, keeping aspect radio.
And there is another problem: first I see the bigger image, and then, sometimes, it gets 200x200. I said sometimes because sometimes I get its width or its height.
Any advice?
The best thing to do is just load the image into memory first, then once it loads get its real height and width, use that info -- URL, height and width -- to determine the appropriate dimensions. Note that this isn't foolproof, though, as caching can mess it up.
Note that you can generalize the below to include maximum dimensions in the function itself, but I think it's pretty easily adaptable:
// Example: replaceImage($("img#someID"), "http://example.com/logo.png");
var MAX_HEIGHT = 80;
var MAX_WIDTH = 80;
function keepAspectRatio(temp, $target, url) {
// Get height and width once loaded
var realHeight = temp.height;
var realWidth = temp.width;
// Get how much to divide by (1 if image fits in dimensions)
// Get rid of ", 1" if you just want everything to be either
// MAX_HEIGHT tall or MAX_WIDTH wide
var factor = Math.max(realHeight/MAX_HEIGHT, realWidth/MAX_WIDTH, 1);
realHeight /= factor;
realWidth /= factor;
// Set the target image's source, height and width
$target.attr("src", url).css({height: realHeight, width: realWidth});
}
function replaceImage($target, url) {
$("<img>").load(function() {
keepAspectRatio(this, $target, url);
}).attr("src", url);
}
jsFiddle Example
EDIT
Swapped places of attr and load per the comment below. I'm not going to go too crazy with optimization beyond that, as what we'd really do if we wanted things perfect would be to preload everything, get its real dimensions, and use that information each time we change the original image.
Did you try:
var width = $(imgId).clientWidth;
var height = $(imgId).clientHeight;

Categories