• Hello [name]! Thanks for joining the GMC. Before making any posts in the Tech Support forum, can we suggest you read the forum rules? These are simple guidelines that we ask you to follow so that you can get the best help possible for your issue.

HTML5 Crispy canvas in WebKit-based browsers

Filipp_BSG

Member
*posting it into GMS2 subforum, but it occurs in GMS1 too*
When exporting to HTML5 and scaling the canvas (for example, as shown in YYG tutorial), it looks pixelated on mobiles even with interpolation turned on. Although, you can remove the following line from index.html:
Code:
<meta name ="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
This will remove the issue when using non-WebKit-based browsers (eg. Google Chrome 60 for Android):
Screenshot_20170911-222800.png

However, the crispiness still occurs when using WebKit-based browsers (eg. Firefox Focus for Android):
Screenshot_20170911-222740.png

Here is a better comparison:
scr.PNG

It seems like the initial canvas is scaled up and then down to fit the screen (I've left the live demo user-scalable, so, you can check it by achieving the pixelation with pinch-up scaling, which even increases the effect).

You can check the live demo here: https://black-snowflake.org/scale
Or download a .gmz: https://www.dropbox.com/s/pdmmwpeyq1t1vxh/scaletest.gmz?dl=0

I've tried applying different canvas CSS properties like image-rendering: auto;, but it had no effect. Also tried inserting this JS snippet right before executing the game script:
Code:
    <script type="text/javascript">
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    ctx.mozImageSmoothingEnabled = true;
    ctx.webkitImageSmoothingEnabled = true;
    ctx.msImageSmoothingEnabled = true;
    ctx.imageSmoothingEnabled = true;
    </script>
I hope that @Mike or someone else from GMS devs or community can point out a solution. If it's a bug, I'm going to fill it, but will be very grateful if there is a JS-based solution, which can help right now, because it's urgent for me.

Best regards,
Filipp
 
Last edited:

Mike

nobody important
GMC Elder
Try resizing the application surface to be the correct size, and not the view size - which will obviously be bigger due to having to run on the larger screen sizes. If you don't, it'll draw the initial shape to the larger surface just like on Desktop Chrome, and then shrink it to fit the mobile device screen - as you'd expect.
 

Filipp_BSG

Member
Try resizing the application surface to be the correct size, and not the view size - which will obviously be bigger due to having to run on the larger screen sizes. If you don't, it'll draw the initial shape to the larger surface just like on Desktop Chrome, and then shrink it to fit the mobile device screen - as you'd expect.
The scaling method in the example doesn't include view resizing. I'm using the following script:
Code:
/// scale_canvas(base width, base height, current width, current height, center);
//argument0 = base width;
//argument1 = base height;
//argument2 = current width
//argument3 = current height
//argument4 = center window (true, false);

var aspect = (argument0 / argument1);

if ((argument2 / aspect) > argument3)
    {
    window_set_size((argument3 * aspect), argument3);
    }
else
    {
    window_set_size(argument2, (argument2 / aspect));
    }

if (argument4) window_center();

surface_resize(application_surface, min(window_get_width(), window_get_width()), min(window_get_height(), argument1));
 

True Valhalla

Full-Time Developer
GMC Elder
Try resizing the application surface to be the correct size, and not the view size - which will obviously be bigger due to having to run on the larger screen sizes. If you don't, it'll draw the initial shape to the larger surface just like on Desktop Chrome, and then shrink it to fit the mobile device screen - as you'd expect.
Not OP, but this is an issue I noticed on iPhones when we started making HD games (640x960)...the assets we made looked awful on mobile devices. The canvas was being downsampled on high-DPI devices.

My fix for this is now included in the Mobility Engine for HTML5 games, but it would be nice to see this addressed on a GMS-level.
 
Last edited:

Speederman

Member
I'd come to this thread because I had this same problem. After some research, I finally found the solution by myself so I'm gonna share it now for everybody that is facing this same issue.

It seems that the resolution values received on mobile devices are DPI dependant when the "viewport" META tag is used. That DPI value must be taken into account when we define the scale of the viewport. Otherwise, browser_width and browser _height won't return the real device resolution but a lower value. That is because the viewport has been defined with a default scale of 1, which is fine for desktop browsers, but wrong for mobiles.

I have been able to fix it by changing the viewport scale with a small javascript function that can be easily implemented. You only have to edit your 'index.html' template file this way:

1: Replace the whole '<meta name="viewport" ...' line with this one:
HTML:
  <meta name="viewport" id="viewport" content="">
2: And add these lines right after the '<body>' line:
HTML:
        <script type="text/javascript">
            var scale = (1/window.devicePixelRatio).toString();
            document.getElementById("viewport").setAttribute("content", "initial-scale="+scale+", maximum-scale="+scale+", minimum-scale="+scale+", width=device-width, user-scalable=no, minimal-ui");
        </script>
And that's it. This way you create the "viewport" and modify it's scale on the page load with the correct pixel ratio value.
 
Last edited:
D

daniFM

Guest
I'd come to this thread because I had this same problem. After some research, I finally found the solution by myself so I'm gonna share it now for everybody that is facing this same issue.

It seems that the resolution values received on mobile devices are DPI dependant when the "viewport" META tag is used. That DPI value must be taken into account when we define the scale of the viewport. Otherwise, browser_width and browser _height won't return the real device resolution but a lower value. That is because the viewport has been defined with a default scale of 1, which is fine for desktop browsers, but wrong for mobiles.

I have been able to fix it by changing the viewport scale with a small javascript function that can be easily implemented. You only have to edit your 'index.html' template file this way:

1: Replace the whole '<meta name="viewport" ...' line with this one:
HTML:
  <meta name="viewport" id="viewport" content="">
2: And add these lines right after the '<body>' line:
HTML:
        <script type="text/javascript">
            var scale = (1/window.devicePixelRatio).toString();
            document.getElementById("viewport").setAttribute("content", "initial-scale="+scale+", maximum-scale="+scale+", minimum-scale="+scale+", width=device-width, user-scalable=no, minimal-ui");
        </script>
And that's it. This way you create the "viewport" and modify it's scale on the page load with the correct pixel ratio value.
Thank you for sharing your solution. Unfortunately, this doesn't work for fullscreen. It looks like if the browser ignored the viewport tag while in fullscreen. ¿Is there a chance you know some walkaround to this problem?
 

Speederman

Member
Thank you for sharing your solution. Unfortunately, this doesn't work for fullscreen. It looks like if the browser ignored the viewport tag while in fullscreen. ¿Is there a chance you know some walkaround to this problem?
No, sorry. I've also noticed that this solution doesn't work for Facebook Instant Games either. I've contacted Yoyo's staff and they have confirmed the issue and opened a bug thread here with a high priority. Let's hope they solve it soon and it works also for your case.
 
D

daniFM

Guest
No, sorry. I've also noticed that this solution doesn't work for Facebook Instant Games either. I've contacted Yoyo's staff and they have confirmed the issue and opened a bug thread here with a high priority. Let's hope they solve it soon and it works also for your case.
I'm making a guess here, but could it be because Facebook puts your game inside an iframe? If that's the case, maybe you can solve it by adding this line to your script:
HTML:
<script type="text/javascript">
  var scale = (1/window.devicePixelRatio).toString();
  window.parent.document.querySelector('meta[name="viewport"]').setAttribute("content", "initial-scale="+scale+", maximum-scale="+scale+", minimum-scale="+scale+", width=device-width, user-scalable=no, minimal-ui");
  document.getElementById("viewport").setAttribute("content", "initial-scale="+scale+", maximum-scale="+scale+", minimum-scale="+scale+", width=device-width, user-scalable=no, minimal-ui");
</script>
This won't solve my fullscreen problem, sadly. I'll try to conctact the staff about this, because I think my only chance is to resize the canvas manually (by javascript), which is going to be such a pain.
 
I'd come to this thread because I had this same problem. After some research, I finally found the solution by myself so I'm gonna share it now for everybody that is facing this same issue.

It seems that the resolution values received on mobile devices are DPI dependant when the "viewport" META tag is used. That DPI value must be taken into account when we define the scale of the viewport. Otherwise, browser_width and browser _height won't return the real device resolution but a lower value. That is because the viewport has been defined with a default scale of 1, which is fine for desktop browsers, but wrong for mobiles.

I have been able to fix it by changing the viewport scale with a small javascript function that can be easily implemented. You only have to edit your 'index.html' template file this way:

1: Replace the whole '<meta name="viewport" ...' line with this one:
HTML:
  <meta name="viewport" id="viewport" content="">
2: And add these lines right after the '<body>' line:
HTML:
        <script type="text/javascript">
            var scale = (1/window.devicePixelRatio).toString();
            document.getElementById("viewport").setAttribute("content", "initial-scale="+scale+", maximum-scale="+scale+", minimum-scale="+scale+", width=device-width, user-scalable=no, minimal-ui");
        </script>
And that's it. This way you create the "viewport" and modify it's scale on the page load with the correct pixel ratio value.
This script seemed to have worked up until the second to last upgrade on Chrome (Desktop). It does work on mobile, but as of right the canvas is blurry on Chrome Desktop unless you fullscreen the browser using F11. I'm not really sure if there is a fix for this.

I checked, it has nothing to do with the last game maker studio upgrades, but it is the last Chrome updates (Chrome versions: 76.0.3809 broke it, still broken on 77.0.3865). As I mentioned, you can get it to be completely sharp if you go full screen, but if you rescale the browser size it gets blurry.
 
Last edited:

Speederman

Member
Hi everybody!

I just wanted to update this thread, as I found a better solution and never came back to post it. Forget my first post, as my 'meta name="viewport"...' line now looks like this:
Code:
        <meta name ="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
And the other script is no needed anymore.

What I'm using now is an extension I created with a .js file that contains this code:
Code:
function scale_canvas(baseWidth, baseHeight, targetWidth, targetHeight) {
    var aspect = (baseWidth / baseHeight);
 
    // Calculate pixel ratio and new canvas size
    var pixelRatio = window.devicePixelRatio || 1;
    var backStoreRatio = (g_CurrentGraphics.webkitBackingStorePixelRatio || g_CurrentGraphics.mozBackingStorePixelRatio || g_CurrentGraphics.msBackingStorePixelRatio ||
                          g_CurrentGraphics.oBackingStorePixelRatio || g_CurrentGraphics.backingStorePixelRatio || 1);
    var pixelScale = pixelRatio / backStoreRatio;
 
    var scaledWidth = targetWidth * pixelScale;
    var scaledHeight = targetHeight * pixelScale;
 
    var posx = 0;
    var posy = 0;
    if ((scaledWidth / aspect) > scaledHeight) {
        var sW = scaledWidth;
        scaledWidth = scaledHeight * aspect;
        posx = Math.round(((sW - scaledWidth) / pixelScale) / 2);
        scaledWidth = Math.round(scaledWidth);
    } else {
        var sH = scaledHeight;
        scaledHeight = scaledWidth / aspect;
        posy = Math.round(((sH - scaledHeight) / pixelScale) / 2);
        scaledHeight = Math.round(scaledHeight);
    }
 
    // Update canvas size
    var ret = '{"w":'+scaledWidth+',"h":'+scaledHeight+',"x":'+posx+',"y":'+posy+'}';
    eval("gml_Script_gmcallback_window_set_size(null,null,'"+ret+"')");
 
    // Scale back canvas with CSS
    if(pixelScale != 1) {
        canvas.style.width = (scaledWidth / pixelScale) + "px";
        canvas.style.height = (scaledHeight / pixelScale) + "px";
    } else {
        canvas.style.width = "";
        canvas.style.height = "";
    }
 
    // Update canvas scale
    if(typeof g_CurrentGraphics.scale === "function")
        g_CurrentGraphics.scale(pixelScale, pixelScale);
}

function resize_canvas(width,height) {
    var displayWidth = window.innerWidth;
    var displayHeight = window.innerHeight;
  
    scale_canvas(width, height, displayWidth, displayHeight);
}
The function you must create in the extension and call from GM is resize_canvas and pass the width and height as arguments. You can get browser_width and browser_height values in GM and calculate your desired dimensions.

The js function will then adjust the canvas and run a callback that you must have in your project. Final width, height and x and y positions will be returned. The function places the game right in the middle of the browser canvas, but you can modify this in the .js file. Create a script in GM called gmcallback_window_set_size and enter this code:
Code:
/// @func gmcallback_window_set_size(args_json)
/// @arg args_json

var args_map = json_decode(argument0);

if(ds_exists(args_map, ds_type_map)) {
    var w = args_map[? "w"],
        h = args_map[? "h"],
        xx = args_map[? "x"],
        yy = args_map[? "y"];
    if(!is_undefined(w) and !is_undefined(h) and !is_undefined(xx) and !is_undefined(yy)) {
        view_wport[0] = w;
        view_hport[0] = h;
        window_set_size(w,h);
        window_set_position(xx,yy);
        surface_resize(application_surface,w,h);
    }
    ds_map_destroy(args_map);
}
As you can see, I'm using a view in GM and I'm adjusting its port width and height here.

And that's it. It works fine for me now, so I hope you all get it working too.

Cheers!
 
Last edited:
Hi everybody!

I just wanted to update this thread, as I found a better solution and never came back to post it. Forget my first post, as my 'meta name="viewport"...' line now looks like this:
Code:
        <meta name ="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
And the other script is no needed anymore.

What I'm using now is an extension I created with a .js file that contains this code:
Code:
function scale_canvas(baseWidth, baseHeight, targetWidth, targetHeight) {
    var aspect = (baseWidth / baseHeight);
 
    // Calculate pixel ratio and new canvas size
    var pixelRatio = window.devicePixelRatio || 1;
    var backStoreRatio = (g_CurrentGraphics.webkitBackingStorePixelRatio || g_CurrentGraphics.mozBackingStorePixelRatio || g_CurrentGraphics.msBackingStorePixelRatio ||
                          g_CurrentGraphics.oBackingStorePixelRatio || g_CurrentGraphics.backingStorePixelRatio || 1);
    var pixelScale = pixelRatio / backStoreRatio;
 
    var scaledWidth = targetWidth * pixelScale;
    var scaledHeight = targetHeight * pixelScale;
 
    var posx = 0;
    var posy = 0;
    if ((scaledWidth / aspect) > scaledHeight) {
        var sW = scaledWidth;
        scaledWidth = scaledHeight * aspect;
        posx = Math.round(((sW - scaledWidth) / pixelScale) / 2);
        scaledWidth = Math.round(scaledWidth);
    } else {
        var sH = scaledHeight;
        scaledHeight = scaledWidth / aspect;
        posy = Math.round(((sH - scaledHeight) / pixelScale) / 2);
        scaledHeight = Math.round(scaledHeight);
    }
 
    // Update canvas size
    var ret = '{"w":'+scaledWidth+',"h":'+scaledHeight+',"x":'+posx+',"y":'+posy+'}';
    eval("gml_Script_gmcallback_window_set_size(null,null,'"+ret+"')");
 
    // Scale back canvas with CSS
    if(pixelScale != 1) {
        canvas.style.width = (scaledWidth / pixelScale) + "px";
        canvas.style.height = (scaledHeight / pixelScale) + "px";
    } else {
        canvas.style.width = "";
        canvas.style.height = "";
    }
 
    // Update canvas scale
    if(typeof g_CurrentGraphics.scale === "function")
        g_CurrentGraphics.scale(pixelScale, pixelScale);
}

function resize_canvas(width,height) {
    var displayWidth = window.innerWidth;
    var displayHeight = window.innerHeight;
 
    scale_canvas(width, height, displayWidth, displayHeight);
}
The function you must create in the extension and call from GM is resize_canvas and pass the width and height as arguments. You can get browser_width and browser_height values in GM and calculate your desired dimensions.

The js function will then adjust the canvas and run a callback that you must have in your project. Final width, height and x and y positions will be returned. The function places the game right in the middle of the browser canvas, but you can modify this in the .js file. Create a script in GM called gmcallback_window_set_size and enter this code:
Code:
/// @func gmcallback_window_set_size(args_json)
/// @arg args_json

var args_map = json_decode(argument0);

if(ds_exists(args_map, ds_type_map)) {
    var w = args_map[? "w"],
        h = args_map[? "h"],
        xx = args_map[? "x"],
        yy = args_map[? "y"];
    if(!is_undefined(w) and !is_undefined(h) and !is_undefined(xx) and !is_undefined(yy)) {
        view_wport[0] = w;
        view_hport[0] = h;
        window_set_size(w,h);
        window_set_position(xx,yy);
        surface_resize(application_surface,w,h);
    }
    ds_map_destroy(args_map);
}
As you can see, I'm using a view in GM and I'm adjusting its port width and height here.

And that's it. It works fine for me now, so I hope you all get it working too.

Cheers!
I got it working! Will be doing some testing, but for now it seems to be really solid. Thanks!
 
S

Stanis

Guest
Hi everybody!

I just wanted to update this thread, as I found a better solution and never came back to post it. Forget my first post, as my 'meta name="viewport"...' line now looks like this:
Code:
        <meta name ="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
And the other script is no needed anymore.

What I'm using now is an extension I created with a .js file that contains this code:
Code:
function scale_canvas(baseWidth, baseHeight, targetWidth, targetHeight) {
    var aspect = (baseWidth / baseHeight);
 
    // Calculate pixel ratio and new canvas size
    var pixelRatio = window.devicePixelRatio || 1;
    var backStoreRatio = (g_CurrentGraphics.webkitBackingStorePixelRatio || g_CurrentGraphics.mozBackingStorePixelRatio || g_CurrentGraphics.msBackingStorePixelRatio ||
                          g_CurrentGraphics.oBackingStorePixelRatio || g_CurrentGraphics.backingStorePixelRatio || 1);
    var pixelScale = pixelRatio / backStoreRatio;
 
    var scaledWidth = targetWidth * pixelScale;
    var scaledHeight = targetHeight * pixelScale;
 
    var posx = 0;
    var posy = 0;
    if ((scaledWidth / aspect) > scaledHeight) {
        var sW = scaledWidth;
        scaledWidth = scaledHeight * aspect;
        posx = Math.round(((sW - scaledWidth) / pixelScale) / 2);
        scaledWidth = Math.round(scaledWidth);
    } else {
        var sH = scaledHeight;
        scaledHeight = scaledWidth / aspect;
        posy = Math.round(((sH - scaledHeight) / pixelScale) / 2);
        scaledHeight = Math.round(scaledHeight);
    }
 
    // Update canvas size
    var ret = '{"w":'+scaledWidth+',"h":'+scaledHeight+',"x":'+posx+',"y":'+posy+'}';
    eval("gml_Script_gmcallback_window_set_size(null,null,'"+ret+"')");
 
    // Scale back canvas with CSS
    if(pixelScale != 1) {
        canvas.style.width = (scaledWidth / pixelScale) + "px";
        canvas.style.height = (scaledHeight / pixelScale) + "px";
    } else {
        canvas.style.width = "";
        canvas.style.height = "";
    }
 
    // Update canvas scale
    if(typeof g_CurrentGraphics.scale === "function")
        g_CurrentGraphics.scale(pixelScale, pixelScale);
}

function resize_canvas(width,height) {
    var displayWidth = window.innerWidth;
    var displayHeight = window.innerHeight;
 
    scale_canvas(width, height, displayWidth, displayHeight);
}
The function you must create in the extension and call from GM is resize_canvas and pass the width and height as arguments. You can get browser_width and browser_height values in GM and calculate your desired dimensions.

The js function will then adjust the canvas and run a callback that you must have in your project. Final width, height and x and y positions will be returned. The function places the game right in the middle of the browser canvas, but you can modify this in the .js file. Create a script in GM called gmcallback_window_set_size and enter this code:
Code:
/// @func gmcallback_window_set_size(args_json)
/// @arg args_json

var args_map = json_decode(argument0);

if(ds_exists(args_map, ds_type_map)) {
    var w = args_map[? "w"],
        h = args_map[? "h"],
        xx = args_map[? "x"],
        yy = args_map[? "y"];
    if(!is_undefined(w) and !is_undefined(h) and !is_undefined(xx) and !is_undefined(yy)) {
        view_wport[0] = w;
        view_hport[0] = h;
        window_set_size(w,h);
        window_set_position(xx,yy);
        surface_resize(application_surface,w,h);
    }
    ds_map_destroy(args_map);
}
As you can see, I'm using a view in GM and I'm adjusting its port width and height here.

And that's it. It works fine for me now, so I hope you all get it working too.

Cheers!
@Speederman I'm pretty new to GM and would really like step by step help on applying this to the HTML5 game I'm working on now. Link is here: http://bankard.sdisend.com/game/

Would you be able to help me?
 
Top