In-game screenshot via BitmapData.draw(FlxG.stage) differs from reality

  • I have a game where there's a FlxSprite used as an overlay. It uses BlendMode.ADD.

    When I take a normal Windows screenshot of it while running, it looks as the window looks:

    looks right

    But if I use code in-game to draw the stage to a BitmapData object, then encode it and save it as a PNG, the overlay has a different blendmode, and is also offset to the right 20-40 px:

    Overlay much different and offset

    The code I use to take the screenshot is:

    if (FlxG.keys.justPressed.F12) {
    	var screenshot = new BitmapData(1920, 1080, true, 0x0);
    	var png = screenshot.encode(screenshot.rect, new PNGEncoderOptions()); // I guess openfl doesn't implement this in flash
    	var s = png.readUTFBytes(png.length);
    	var filename = SessionData.generateID("MathildaGame-" + getStateName() + "-", ".png");
    	var path = crashDumper.path + "screenshots/";
    	if (!FileSystem.exists(Util.pathFix(path)))
    	var f = File.write(path + filename);

    Is this expected behaviour from OpenFL and/or HaxeFlixel? If so that's a bit unfortunate, as it limits my in-game screenshotting capabilities (which were a handy way to get a screenshot even when I'm in fullscreen native mode, which I'm not here.)

  • My guess is, OpenFL normally renders the stage in hardware (hence problems with fullscreen screenshots in the first place) but this differs from what you get accessing the stage object with draw() because BlendMode output differs between hardware and software currently (although technically it shouldn't, but this is one area not yet implemented consistently in OpenFL.) Does that sound about right?

    BTW, I believe my method doesn't differ much in any practical way from the HaxeFlixel screen grabber add-on (except to hardcode a bunch of presets.)

  • I found this code commented out in BitmapData.draw()...I wonder if it would be able to grab it how it looks in hardware?

    		//var renderSession = @:privateAccess Lib.current.stage.__renderer.renderSession;
    		//__drawGL (renderSession, width, height, source, matrix, colorTransform, blendMode, clipRect, smoothing, !__usingPingPongTexture, false, true);

    Oh, except, that block of code isn't even run. I always forget the "#if !openfl_legacy" at the top of these files.

  • Have you seen this: FlxScreenGrab? It's an add-on so it may be outdated but I guess you can learn how it works.

  • @DleanJeans That's what I was referring to by "the HaxeFlixel screen grabber add-on" :) Thanks for the reply though. Maybe I should try with that just to see if it does differ from my pared-down version any.

  • It's interesting, it's not limited to interesting things like overlays. I just took one and one of two sprites I had set .color on had the wrong colour, whereas the other was fine. And the stage had a black right-margin (plus an even bigger right and bottom white margin?) vs native:


  • @DleanJeans said in In-game screenshot via BitmapData.draw(FlxG.stage) differs from reality:

    Have you seen this: FlxScreenGrab? It's an add-on so it may be outdated but I guess you can learn how it works.

    OK, attempt 1. Commented out my code, and instead put this just before switching to the minigame state that I want to take a shot of:

    		FlxScreenGrab.defineHotKeys(["F12"], true, false);

    Once in the game, pressing F12 did nothing.

    Attempt 2, ignore the note in their docs about using a string:

    		FlxScreenGrab.defineHotKeys([FlxKey.F12], true, false);

    Still, pressing F12 does nothing.

    Attempt 3, put this into my minigame state after create(). Still nothing.
    Attempt 4, same spot, back to "F12" as a string. Still nothing.


  • OK, this makes sense so far. Even though there are static methods for setting the hotkeys, they're only used by a non-static method, so I need to actually make a FlxScreenGrab object:

    		add(new FlxScreenGrab());

    With that, at least I know that grab() is being called, from a trace I added there. But no dialog comes up or anything like that, as one would expect from the docs.

  • Seems that the save() function requires flash for its dialog, or (!lime_legacy || lime < "2.9.0"), which I think returns false in my case because HaxeFlixel still uses lime legacy by default IIRC, and now I'm on lime 2.9.1. So the function just finishes without saving anything at all, because there's no other #elseafter those two options.

  • administrators

    The screengrab addon dialogs were changed from using systools to lime.ui.FileDialog a while ago to avoid additional dependencies. However, a bit later, a change in Lime made the dialog class only available for Next, see

  • @Gama11 right...

    Even without the save dialog, and just putting the save code in an #else block, I'm getting a null object error on


    Running trace() on png.length just before that line, interestingly, doesn't output anything before the crash. Maybe I need to build clean or something...

  • Oh, of course, because now it's crashing on my trace() line. That means png is null. Why is that? Because of this:

    	#if flash
    		png = PNGEncoder.encode(screenshot.bitmapData);
    	#elseif openfl_legacy
    		png = screenshot.bitmapData.encode(screenshot.bitmapData.rect, "png");
    		png = screenshot.bitmapData.encode(screenshot.bitmapData.rect, new PNGEncoderOptions());

    In my stripped-down code, I picked the third block there, which works (on openfl_legacy) at least in that it outputs something--but the second block is what's being executed in FlxScreenGrab.

  • Indeed, I think you can trace FlxScreenGrab's encode() call to this line in legacy:

    ...because of "png" being passed instead of a PNGEncoderOptions. So it just returns null on legacy.

    Legacy's the default, and openfl next currently breaks a bunch of random things in my project. Fixing either of these is beyond my scope at the moment. I guess someone in my position won't be getting normal-looking screenshots, then. :) Next project I guess I'll start with Next :)

    Unless something like this would work:

    			if ( (compressorOrQuality, PNGEncoderOptions)) {
    				return byteArray = lime_bitmap_data_encode (__handle, "png", 0);
    			} else if ( (compressorOrQuality, JPEGEncoderOptions)) {
    				return byteArray = lime_bitmap_data_encode (__handle, "jpg", cast (compressorOrQuality, JPEGEncoderOptions).quality / 100);
    			} else if ( (compressorOrQuality, String)) {
    				return byteArray = lime_bitmap_data_encode (__handle, compressorOrQuality, 0);

    Nope, the blendmode thing is still borked. Maybe the other context is an improvement though?

  • Yes, it still has blending issues (and colours incorrect when you simply set FlxSprite.color, as seen above), but at least the dimensions are OK and don't have strange margins (unlike as seen above.)

  • OK, if anyone's interested in this, you'll need these two pieces:

    And then you'll also need to supply a path, since, as Gama11 said, there's no file picker dialog right now on legacy.

    If you're using the excellent crashdumper anyway, you can use my code, which depends on it (Util and a crashdumper instance):

    		var path = Main.crashDumper.path + "screenshots/";
    		if (!FileSystem.exists(Util.pathFix(path)))
    		FlxScreenGrab.presetPath = path;
    		FlxScreenGrab.defineHotKeys(["F12"], true, false);

    Then just put this in any state's create() that you want to screenshot from:

    add(new FlxScreenGrab());

Log in to reply