fisholith's Forum Posts

  • The tick2() suggestion sounds like it could be pretty cool, but I actually just started looking through the "preview.js" file, and I'm not sure if tick2() will work. Though I could be misinterpreting what I found.

    In preview.js I regex searched for

    tick\w*\(

    to find all the different tick types.

    I found the various tick calls inside the logic() function declaration:

    // Process application logic
    Runtime.prototype.logic = function (cur_time) [/code:34pbx0dk]
    pretick() "// Tick objects-to-pre-tick"
    tick() "// Tick behaviors"
    posttick() "// Call posttick on behaviors"
    tick() "// Tick objects-to-tick"
    run() "// If the running layout has an event sheet, run it"
    tick2() "// Post-event ticking (tick2)" [Looks to tick behaviors]
    tick2() "// Tick objects-to-tick2"
    [i](The comments are taken from preview.js and precede the respective tick calls.)[/i]
    
    The logic() function is called from within a [i]different[/i] runtime tick() function, not to be confused with the object tick() function above.
    [code:34pbx0dk]Runtime.prototype.tick = function (background_wake, timestamp, debug_step)[/code:34pbx0dk][i](Note this is a function also named "tick" but it belongs to the runtime, where all the tick() functions called inside the logic() function belong to objects or behaviors.)[/i]
    
    Within this runtime tick() function, logic() is called, and afterwards draw() and drawGL() are called, and after that the snapshot canvas code runs.
    
    So I think the canvas may not get redrawn until after all of the various object tick calls are completed, and the logic() function exits. Would this matter? I'm not sure if there's a reason I wouldn't just get the canvas result from the previous drawGL call, unless it's been erased by the time the tick code runs.
    
    [b][update][/b]
    I had a weird and possibly terrible idea. Could I force the canvas to redraw from within the plugin by calling draw() or drawGL()? That seems like it could work, but also be really bad if put inside a loop in the event sheet.
  • Fortunately I've run into the disembodied "this" issue before, so I think I set that up correctly, but it's good to know I wasn't being paranoid. That said, I did set it up wrong the very first time. <img src="{SMILIES_PATH}/icon_e_smile.gif" alt=":)" title="Smile">

    img.src always blank

    Within the onload() function, I set up an alert() to print out the image URL that was being assigned to img.src. The src was always a base64 image string of a blank canvas with a size matching the game's window dimensions. For some reason, I'm just never getting anything but a blank image from:

    this.runtime.canvas.toDataURL("image/png")

    I was able to use this base64 image decoder to see what exactly was stored in the base64 image text.

    http://codebeautify.org/base64-to-image-converter

    Workaround

    So with some experimenting I may have found a workaround that allows me to get legitimate pixel color values.

    Rather than trying to capture the canvas inside my Action function, I'm instead passing one extra parameter to the action, an Image URL, so I can simply pass C2's System CanvasSnapshot URL variable to my Action.

    This guarantees that the URL contains an actual image. And it seems to work, so far. I'm actually getting correct color data. Ultimately I would like to be able to capture the game canvas entirely from within my plugin, but I don't know if there's a way to ensure that the capture is timed and sequenced correctly so that I actually get an image.

    Capturing from within my plugin?

    Do you have any ideas on what might be going wrong with capturing the canvas from within my plugin? You mentioned waiting for the image load trigger. Are you talking about waiting for a trigger related to the game canvas itself? It looks like when this.runtime.canvas.toDataURL("image/png") is used from within my plugin, the URL returned is a text representation of a large blank image matching the game window size. If there is a way to wait for it to contain image data that would be cool.

    My current code

    Instance vars.

    instanceProto.onCreate = function()
    {
    	// Captured pixel.
    	this.pixelCap = []; // Will be filled by a getImageData() call.
    	this.pixelCapX = 0; // Coord of captured pixel.
    	this.pixelCapY = 0; // Coord of captured pixel.
    };[/code:2lbr1k2g]
    
    Action.
    [code:2lbr1k2g]Acts.prototype.pixelCap = function ( posX , posY , dataURL )
    {
    	var img = new Image();
    	var self = this;
    
    	img.onload = function ()
    	{
    		// Make temp canvas, and draw image data into it.
    		var canvas = document.createElement('canvas');
    		canvas.width = img.width;
    		canvas.height = img.height;
    		var ctx = canvas.getContext('2d');
    		ctx.drawImage(img,0,0); // Paste image starting at coord (0,0).
    
    		// Save pixel data.
    		self.pixelCap = ctx.getImageData( posX , posY , 1 , 1 ); // Save to instance var.
    		self.pixelCapX = posX;
    		self.pixelCapY = posY;
    
    		// Trigger notice of completion.
    		self.runtime.trigger(cr.plugins_.fi_color.prototype.cnds.onCapturedPixel, self);
    	}
    
    	img["crossOrigin"] = "anonymous";
    	img.src = dataURL;
    };[/code:2lbr1k2g]
    
    Condition triggered from the onload in the Action code above.
    [code:2lbr1k2g]Cnds.prototype.onCapturedPixel = function () // REMEMBER: These params are C2 event sheet params.
    {
    	return   true;
    };[/code:2lbr1k2g]
    
    The Expression that retrieves the Red value of the pixel.
    [code:2lbr1k2g]Exps.prototype.cpR = function ( ret )
    {
    	ret.set_float(   this.pixelCap.data[ 0 ]   );
    };[/code:2lbr1k2g]
  • At the moment I have an action that captures a pixel to a plugin instance variable.

    It sets the onload() callback, and sets the img.src.

    When the onload() callback runs it grabs a pixel out of the image data at the coords passed to the action function.

    Speaking of which, now that I think of it, I should try printing the coords from within the onload() function to see if they are the correct values or undefined, as the action function scope is gone when onload() runs. Maybe that's the issue, and so I need to store the coords in a plugin instance variable.

    That's the point at which everything I print out is (0,0,0,255).

    So within the onload() I save the pixel data to a plugin instance variable, and then trigger a C2 plugin condition to indicate that the load is complete, and is ready for use.

    From there I can use an expression to get the R, G, or B values. Or at least I could if they weren't all 0.

    I'll post my code for the action condition and expression in a little bit, in an update.

    Thanks again for all the extra info and clarification.

  • Wow, thanks R0J0hound, that's super helpful.

    Sprite loadURL code

    I just looked up the Sprite's loadURL code.

    I see what look like 4 parts to the code.

    1. Declare some variables, including the image "img". [3 lines]

    2. Assign an onload() function. [a bunch of lines]

    3. An if-statement that checks stuff about the "url_". [2 lines]

    4. Finally "img.src = url_;". [1 line]

    So does that final assignment in part 4 "img.src = url_;" trigger the onload() function assigned in part 2? Or rather, does the completion of loading that newly assigned URL trigger it?

    Because otherwise I'm not sure where the loading of the image is happening.

    If that's the case, I may understand what's going on in your code. I think the assignment of that onload() function just threw me a bit, because it looked like nothing explicitly called it. So I'm assuming that again the assignment to img.src sets some behind-the-scenes stuff in motion that ultimately triggers the onload() function.

    Possibly realized my in-event color picking troubles

    As an added bonus, when looking through the Sprite's loadURL code, I may have figured out why my previous in-event attempts to pick colors acted so finicky. I think I was waiting till the screenshot was taken, via the triggered event, but I wasn't waiting until after I was done loading it into a sprite, via the sprite's triggered event.

    I've been trying to pick the post-webgl colors out of the 3rd party Canvas plugin by saving a screenshot with the System action, loading the screenshot into a Sprite, and then pasting that screenshot Sprite into a Canvas plugin, so I can pick it's colors. I keep forgetting that every image loading step is going to be asynchronous, not just the screenshot.

    Some questions about your code example

    Will the "canvas.getContext('2d');" in your first code example work if webgl is enabled?

    Or does that not matter because, once inside the onload() function, we're dealing with our own custom canvas for the screencap, rather than C2's game canvas?

    Thoughts on readpixels

    The "working only on frame one" thing might be because of how buffer clearing works when using webgl with preserveDrawingBuffer set to false, which I assume it is for C2 for performance reasons. Not sure though.

    At the second link you posted one of the comments seems to indicate that it might be possible to get data from the buffer after it's drawn but before it's handed to the compositor and cleared. Granted that would require inserting code into C2's runtime I gather, which is probably not the direction I should be going. Of course I could be totally wrong about all of the above, I'm just trying to follow along.

    [update]

    So I set up the plugin and implemented the example code, but I'm always getting [ 0 , 0 , 0 , 255 ] as the pixel values regardless of the coordinates I use. I'm using an alert() to print the pixel directly out of the onload() function, right after the call to ctx.getImageData().

    Still working on figuring out what's going on, but I'm open to any suggestions.

    Do I maybe need to wait for some aspect of the C2 canvas to load before passing it to "img.src"?

  • Hey ConstructorRichi, <img src="{SMILIES_PATH}/icon_e_smile.gif" alt=":)" title="Smile">

    I made my own modified version of the SDK template files, with some extra information comments, and a more graphical style of divider comments to break up and mark the different sections of code.

    I'm planning on making a tutorial to go along with my modified version of the SDK at some point, but I can upload a copy for you to take a look at now in case it might help. <img src="{SMILIES_PATH}/icon_e_smile.gif" alt=":)" title="Smile">

    https://dl.dropboxusercontent.com/u/382 ... mplate.zip

    You may alraedy know this, but the one really important step in creating a plugin is to make sure the "id" value you choose for the edittime.js file, matches two spots in the runtime.js file.

    In my SDK mod the places that must match are marked in both files with the following placeholder text:

    XXXXXXXXXXXXXXXX_MyPluginID_XXXXXXXXXXXXXXXX

    (Note the "ID" in the placeholder text.)

    edittime.js

    (Note the first placeholder is for the NAME, not the ID. Only the ID has to match between files.)

    //╔════════════════════════════════════════════════════════════════════════════╗
    //â•‘///////////////////////////////   Settings   ///////////////////////////////â•‘
    //╚════════════════════════════════════════════════════════════════════════════╝
    function GetPluginSettings()
    {
    	return {
    		"name":			"XXXXXXXXXXXXXXXX_MyPluginName_XXXXXXXXXXXXXXXX",
    		"id":			"XXXXXXXXXXXXXXXX_MyPluginID_XXXXXXXXXXXXXXXX",[/code:28nntpg6]
    		
    [b]runtime.js[/b]
    [i](You may need to scroll down a little to see both placeholder spots.)[/i]
    [code:28nntpg6]//╔════════════════════════════════════════════════════════════════════════════╗
    //â•‘/////////////////////////////   Plugin class   /////////////////////////////â•‘
    //╚════════════════════════════════════════════════════════════════════════════╝
    
    //----------------------------------------------------------------
    // *** CHANGE THE PLUGIN ID HERE *** - must match the "id" property in edittime.js
    //          vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    cr.plugins_.XXXXXXXXXXXXXXXX_MyPluginID_XXXXXXXXXXXXXXXX = function(runtime)
    {
    	this.runtime = runtime;
    };
    
    //----------------------------------------------------------------
    (function ()
    {
    	/////////////////////////////////////
    	// *** CHANGE THE PLUGIN ID HERE *** - must match the "id" property in edittime.js
    	//                            vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    	var pluginProto = cr.plugins_.XXXXXXXXXXXXXXXX_MyPluginID_XXXXXXXXXXXXXXXX.prototype;[/code:28nntpg6]
    
    [b]C2 plugin video tutorial - by John Velojet[/b]
    I found this nice walk-through for setting up a super simple plugin from start to finish. 
    [youtube video="ZlgJ4ts00C4"]
    [i](This is actually the first tutorial I use to make a working plugin, back when I started looking into making plugins.)[/i]
  • Okay, I've done some digging through the C2 js files, but I'm having trouble finding the point at which any image data is actually encountered.

    C2 snapshot code

    The Action in system.js calls "doCanvasSnapshot()"

    [system.js , line 1913]

    SysActs.prototype.SnapshotCanvas = function (format_, quality_)
    {
    	this.runtime.doCanvasSnapshot(format_ === 0 ? "image/png" : "image/jpeg", quality_ / 100);
    }; [/code:355ixjpx]
    
    "doCanvasSnapshot()" is called in system.js, and seems to be declared in preview.js ... maybe?
    I'm not especially familiar with the setup of the HTML5 canvas in C2, or in general. So I suspect there are probably some conventions I'm not recognizing.
    [preview.js , line 5043]
    [code:355ixjpx]Runtime.prototype.doCanvasSnapshot = function (format_, quality_)
    {
    	this.snapshotCanvas = [format_, quality_];
    	this.redraw = true;		// force redraw so snapshot is always taken
    }; [/code:355ixjpx]
    
    I found a spot in the tick code that seems to be involved with the snapshot process, and uses "this.snapshotCanvas" , but it seems like it's arranging for the snapshot data to be available to some other system somewhere, rather than handling image data itself.
    [preview.js , line 2346]
    [code:355ixjpx]// Snapshot the canvas if enabled
    if (this.snapshotCanvas)
    {
    	if (this.canvas && this.canvas.toDataURL)
    	{
    		this.snapshotData = this.canvas.toDataURL(this.snapshotCanvas[0], this.snapshotCanvas[1]);
    		
    		if (window["cr_onSnapshot"])
    			window["cr_onSnapshot"](this.snapshotData);
    		
    		this.trigger(cr.system_object.prototype.cnds.OnCanvasSnapshot, null);
    	}
    		
    	this.snapshotCanvas = null;
    } [/code:355ixjpx]
    
    The only other place I'm even seeing the word snapshot come up is in an assignment to an array named "window".
    I have no idea what's going on here. Is this some kind of variable function callback thing?
    [preview.js , line 6061]
    [code:355ixjpx]window["cr_getSnapshot"] = function (format_, quality_)
    {
    	var runtime = window["cr_getC2Runtime"]();
    	
    	if (runtime)
    		runtime.doCanvasSnapshot(format_, quality_);
    } [/code:355ixjpx]
    
    [b]My thoughts so far[/b]
    So I'm starting to suspect that the image data saved by the snapshot action is setup inside code much closer to the actual drawing of the main canvas, and the snapshot action just flags it to be saved to a variable for later use. I could be completely wrong though, as I'm not quite sure what I'm looking at.
    
    All that said, I'm not sure if I really need to get at the snapshot code in the first place. I was going to take a look at it just to get an idea of how to pick a color from the final rendered scene, though I don't know if that's the direction I should be going, or if there's a better way to get at the color data.
    
    Any suggestions are still welcome. I'm still not quite clear on how to access the canvas from within the SDK.
  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • Thanks for the reply rexrainbow,

    I'm familiar with that aspect of the third party Canvas plugin. I meant the HTML5 canvas that C2 renders the game into. Apologies, I should have been more specific in my first post. I think (though I may be wrong) R0J0hound's code I linked to in the first post samples C2's main HTML5 canvas directly.

    I was also thinking that since C2 can store a screencap of the canvas that includes applied shaders, would it be better to try to use an approach related to that screencap mechanism to get the post shader colors?

    I've been trying to get a simple method for picking screen colors for a while and all the event-and-object approaches I've tried end up being kind of fiddly, especially when it comes to capturing post shader colors.

    [edit] Oh, hey R0J0hound, I think you just posted right before I did.

    I have to head out briefly, but I'll be able to reply shortly. Thanks for the info.

    [edit 2] Thanks for the info, and the suggestion R0J0hound, I'll take a look at the screencap action code.

  • I'm looking to create a color utility plugin, to provide a variety of color related expressions and conditions.

    The first expressions I want to add would form a color picker that will get a color at a specified pixel coord on C2's game canvas.

    [edit] (By "canvas", I mean C2's game window area, not the 3rd party "Canvas" plugin.)

    It would look something like this:

    pxR( x , y )

    pxG( x , y )

    pxB( x , y )

    Ideally the picker expressions would get the color as it appears on the screen. (i.e. After all webGL effects have been applied.)

    Does anyone know how best to do this from within the SDK?

    Thanks in advance for any advice or suggestions.

    Possibly related R0J0hound example code

    I saw in another post that R0J0hound posted some javascript that could be executed from within a C2 event sheet to retrieve color at a given pixel, and this looks like what I want to do.

    Granted, I'm not sure if this is the best way to do it from within the SDK, since I might be able to get access to the canvas directly, rather than looking it up in the DOM by element id.

    // This is a paraphrase of R0J0hound's original code found at the link above.
    var canvas = document.getElementById( 'c2canvas' );
    var ctx = canvas.getContext( '2d' );
    var pixel = ctx.getImageData( x , y , 1 , 1 );
    pixel.data[ 0 ];") [/code:2hd9dwhd](That said, I wasn't able to get this code to work from within C2. It seemed to work up until assigning the canvas context into "ctx", which as near as I can tell always assigned null/undefined, as I could never get any data out of it, or .toString() the object. So I'm probably doing something wrong.)
  • Hey alextro, <img src="{SMILIES_PATH}/icon_e_smile.gif" alt=":)" title="Smile">

    Just in case you're interested, a while back I found a really cool interactive demo of various path finding algorithms.

    https://qiao.github.io/PathFinding.js/visual/

    At the site listed above, I believe the "breadth-first-search" algorithm is equivalent to the "flood-fill-search" algorithm. (3rd in the list.)

    Construct 2's built in path-finding algorithm is A*, (which is pronounced "A Star"). (1st in the list.)

    One other special case path finding solution that can be handy is "Goal-Based" path-finding. This one is usually only good when you want to have a massive number of objects path their way to a single goal location.

    This is a video that does a nice job of explaining it.

    Subscribe to Construct videos now
  • No worries. And thanks for sharing your findings, SirPumpAction. Very helpful to know.

    ... also I love your avatar.

  • Interesting.

    So, from what I gather, "Use high-DPI display" forces the browser to ignore the "device-pixel-ratio" specified by the mobile device.

    For instance, the Galaxy s6 normally scales everything up by 4x (device-pixel-ratio of the s6) to make webpages readable, but with "Use high-DPI display" enabled, it will instead render everything at 1x scale, or more importantly, 1-to-1 pixel density with the display's native resolution.

    That kind of makes sense, as you were seeing the full screen view of your game rendered at exactly 1/4th the display resolution, and then scaled up by 4x (device-pixel-ratio of the s6) to fit the screen. By coincidence your game at 1x scale happened to also be exactly 1/4th the display's resolution, which perfectly hid what was going on. <img src="{SMILIES_PATH}/icon_e_biggrin.gif" alt=":D" title="Very Happy">

    The device-pixel-ratio is a fixed scaling factor determined by the device manufacturer, but is different for different devices. This scaling factor came about as a way to scale websites to a reliably readable size across different devices and display resolutions.

    The description for the "Use high-DPI display" property is as follows,

    "Use high resolution display where available. If disabled, renders in low-res and upscales."

    So when enabled it renders directly at native resolution.

    Back towards the end of 2013 Construct 2 "r148" was released, and the high-DPI option became a project default. In the release notes Ashley explained the following:

    [quote:1jf7k7iy]"This build broadens support for any device with a high-DPI display, including modern Android phones and tablets, Tizen and Blackberry 10 devices, and Amazon tablets. Graphics can render at considerably better quality with high-DPI support, and especially text which can appear much crisper and more readable.

    Since high-DPI displays have many more pixels, using this mode can reduce performance since there is more rendering work for the GPU. So we've kept it as an option, but renamed from "Use iOS retina display" to "Use high-DPI display". It's on by default and we highly recommend using it whenever possible, but in some cases you may need to turn it off for performance to be acceptable."

    https://www.scirra.com/construct2/releases/r148)

    As he points out, it's theoretically possible to see a performance gain from turning it off, but it's essentially required for correct rendering on devices that use a scaling factor (device-pixel-ratio), and it's also worth noting that the performance effect was negligible enough back in 2013 to make it enabled by default. So in 2016 you should be pretty safe leaving it enabled, especially since it fixes the blur problem.

    [ edit ]

    Here's an interesting article on "device-pixel-ratio":

    http://juiceboxinteractive.com/ideas/a- ... e-devices/

  • Awesome! Happy to help. <img src="{SMILIES_PATH}/icon_e_smile.gif" alt=":)" title="Smile">

    If you're interested, you can also get more 3rd-party shader Effects in the "Extending Construct 2" > "Effects" section of this forum.

    The "WebGL Effects List" thread keeps a list of the published 3rd-party Effects.

    And depending on how far into effects you want to get, you can also code your own shaders for C2.

    Shaders for C2 can be programmed in the GLSL language, in any text editor.

    There's a template effect file included in the Construct 2 SDK files, which you can get at the link below.

    SDK: https://www.scirra.com/manual/15/sdk

  • That's a good point. On a mobile device it would be ideal to render once at the pixel art resolution and then just scale up to full screen.

    When running the game on the Galaxy, are you running the game as an installed android app, or are you running it from within a browser like Chrome or Firefox?

    You might want to try running from a few different browsers on the Galaxy to see if there's a difference in the scaling behavior. It's possible that the bilinear blurring is a side effect of the environment the game is running in and not you're project settings, especially given that the games scales correctly on your desktop computer.

    It may be that, the method C2 uses to tell the target environment to use "point" interpolation scaling, doesn't work for all environments.

    Finally you said in your first post that "Fullscreen scaling" was set to Low quality, which would seem to make sense, given that you don't want any filtering, but you could try high quality, to see if that makes a difference on the Desktop and Galaxy versions.

    This one is a long shot, but mobile graphics hardware does some weird things in response to settings you would think would be consistent across platforms.

    I've vaguely recall seeing a very (very) few situations where a graphics property on low settings will defer to the discretion of the target hardware on how to handle things, while a high setting will force the hardware to do things a certain way. Mind you, I've never seen or heard of that kind of thing in C2, but mobile graphics hardware is weird that way. It seems to me like it was a floating point precision thing, so likely not related.

    For an interesting example of said weirdness, here's an article on floating point calculations that does a good job of showing the inconsistent internal computational spookiness of mobile GPUs. Note: I don't think the effect described in the article below is specifically related to your problem. I was just trying to remember some of the articles I'd come across that showcases some of the same kind of esoteric mobile GPU characteristics that can result in unexpected behavior.

    http://www.youi.tv/mobile-gpu-floating- ... variances/

    (Scroll down to see various devices render the same exact shader script in uniquely distorted and variably buggy ways.)

  • Hey again,

    I suspected that Crop wouldn't fit the game correctly by default, which is why I mentioned the "start of layout" event that might be needed to see the pixels close up, but that said, did Crop mode fix the blurry pixel problem?

    The reason I'm curious is because Crop mode is (as far as I know) C2's most open-ended full screen display mode, and with some effort (and events) you can pretty much make it behave like any of the other modes, but with more control.

    The main thing that Crop mode will give you is that the game will not be rendered to an intermediate texture and then stretched to fit the target display. I'm not sure, but I suspect that might be the cause of the blur you're encountering.

    The Galaxy s6 has a res of

    2560 x 1440

    The image of the title screen you posted is

    1280 x 720

    And that title screen is a 2x scale-up of the pixel art, meaning its un-scaled res is

    640 x 360

    Out of curiosity, could your list you PC's monitor resolution?

    And most importantly, what is the game's "Window Size" property set to? If it's 640 x 360 that might help shed some light on the issue.

    Anyway, given the above, that means the pixel art is being scaled up exactly 4x to fit the Galaxy's screen, (from 640 x 360 to 2560 x 1440). Also is the image you posted from the Galaxy's screen scaled back down 2x, because it's not the full 2560 x 1440 res. No problem if it is, just curious.

    It looks to me like the 4x scaling being done on the Galaxy is using bilinear filtering. If I take your sharp title screen into Photoshop, scale it down (via Nearest Neighbor) to its 1x scale and then scale that up by 4x (via bilinear) to simulate the Galaxy's scaling, my re-scaled version is virtually identical to your blurry comparison image. (Note: I also scale my version back down by 2x to match the 1280 x 720 res of you're 2nd comparison image.)

  • Hey SirPumpAction,

    I have yet to build any games for mobile devices, so I'm not sure all of the potential causes, but one possibility comes to mind.

    "Crop" mode

    It may be that the "Full screen in browser" mode is rendering to an intermediate texture and then scaling it to fit the mobile's screen.

    To work around this you could try setting "Full screen in browser" to "Crop".

    The "Crop" mode means that your game will not be auto-rescaled regardless of the monitor dimensions.

    You'll then have exact control over how the game is scaled up to fit a monitor, but as a side effect you'll have to be a little careful with how you place hud elements, as object positions that work on a 16:9 aspect ratio display, might get cut off on a 4:3 aspect display.

    You may also need to zoom the layout with events, by detecting the monitor dimensions and scaling up by the best integer fit.

    Even if you don't plan to ultimately use Crop mode, you might want to at least try it to see if that suppresses the blurry bilinear filter effect you're seeing in the second image.

    Importantly, if your game displays at a 1-to-1 pixel scale (looks super tiny and zoomed out) when running in Crop mode, then you may want to temporarily add in an "On start of layout" >> "Set layout scale" to 3 or 4, so you can see if the pixels are coming out sharp when scaled up.

    Pixel Rounding

    One other option you might want to enable is "Pixel Rounding", though that isn't likely to fix the blur unless your entire title screen has pixel coordinates that are exactly half way in between integer pixel coords, (e.g. 0.5 by 0.5).

    That said, "Pixel Rounding" will ensure that all game objects will be rendered as if their origin points are snapped to integer pixel coords, though the internal game logic will still treat them as floating point coords.