liballeg / allegro5

The official Allegro 5 git repository. Pull requests welcome!
https://liballeg.org
Other
1.88k stars 284 forks source link

al_save_bitmap with screen is wrong #1337

Open katastic opened 2 years ago

katastic commented 2 years ago

I just posted on A.CC but the server is currently half-dead so here we go.

al_save_bitmap("screen.png", al_get_backbuffer(al_display));    

Saving a screenshot of the screen is wrong (at least on Linux). I just realized it's dumping a alpha channel! Which, AFAIK, is meaningless for a screen.

gnome-screenshot yields this:

Screenshot from 2022-05-23 03-40-28

al_save_bitmap loading with GIMP does this: (note alpha channel visible) image

Relevant Allegro code line: https://github.com/liballeg/allegro5/blob/20ea4d7bdd9e9999a9d89278dc06dc453fa3f0a6/src/bitmap_io.c#L223

Since this supports multiple "file handlers", it may not affect non-PNG save formats. I'll test that now. Though PNG is of course, 'the best' format to use.

katastic commented 2 years ago

Saving to a TARGA is also affected since TARGA supports alpha channels. PCX and BMP output fine.

Also, DDS doesn't appear to output any file at all even though the documentation says PCX, BMP, TGA and DDS are supported always. Separate issue raised for that #1339

SiegeLord commented 2 years ago

What does al_get_pixel return on one of the black regions of your screen?

katastic commented 2 years ago
RGBA
0.000000 0.000000 0.000000 0.623529
0.000000 0.000000 0.000000 0.623529
0.000000 0.000000 0.000000 0.623529
0.000000 0.000000 0.000000 0.623529
0.000000 0.000000 0.000000 0.623529
0.000000 0.000000 0.000000 0.623529
0.000000 0.000000 0.000000 0.623529
0.000000 0.000000 0.000000 0.623529
0.000000 0.000000 0.000000 0.623529
0.000000 0.000000 0.000000 0.623529
0.000000 0.000000 0.000000 0.623529
0.000000 0.000000 0.000000 0.623529
0.000000 0.000000 0.000000 0.623529
0.000000 0.000000 0.000000 0.623529
katastic commented 2 years ago

Not sure if anyone else needs this, but I wrote this D function to work around the problem.

void al_save_screen(string path)
    {
    auto disp = al_get_backbuffer(al_display);
    auto w = al_get_bitmap_width(disp);
    auto h = al_get_bitmap_height(disp);
    ALLEGRO_BITMAP* temp = al_create_bitmap(w, h);
    al_lock_bitmap(temp, al_get_bitmap_format(temp), ALLEGRO_LOCK_WRITEONLY);
    al_lock_bitmap(disp, al_get_bitmap_format(temp), ALLEGRO_LOCK_READONLY); // makes HUGE difference (6.4 seconds vs 270 milliseconds)
    al_set_target_bitmap(temp);
    for(int j = 0; j < h; j++)
        for(int i = 0; i < w; i++)
            {
            auto pixel = al_get_pixel(disp, i, j);
            pixel.a = 1.0; // remove alpha
            al_put_pixel(i, j, pixel);
            }
    al_unlock_bitmap(disp);
    al_unlock_bitmap(temp);
    al_save_bitmap(path.toStringz, temp);
    al_reset_target();
    al_destroy_bitmap(temp);
    }

Which runs in ~250 ms on my netbook vs ~170 ms for al_save_bitmap(). Resolution is ~1366x768. It would be very few changes to port it to C.

[edit] It appears doing clear_to_color on the destination, then adding the display, still produces the problem

    al_set_target_bitmap(temp);
    al_clear_to_color(ALLEGRO_COLOR(0,0,0,1));
    al_draw_bitmap(disp, 0, 0, 0);

Though it's marginally faster at ~210 ms.

pedro-w commented 2 years ago

I don't understand why compositing it onto an opaque black bitmap doesn't work. I (independently) wrote this

void save_opaque(const char* fi, ALLEGRO_BITMAP* b) {
  // Create clone bitmap
  int w = al_get_bitmap_width(b);
  int h = al_get_bitmap_height(b);
  ALLEGRO_BITMAP* tmp = al_create_bitmap(w, h);
  // Remember current target
  ALLEGRO_BITMAP* old = al_get_target_bitmap();
  al_set_target_bitmap(tmp);
  // Clear to opaque black
  al_clear_to_color(al_map_rgba_f(0.0f, 0.0f, 0.0f, 1.0f));
  al_draw_bitmap(b, 0.0f, 0.0f, 0);
  // Save and clean up
  al_save_bitmap(fi, tmp);
  al_destroy_bitmap(tmp);
  // Restore target
  al_set_target_bitmap(old);
}

to be called like save_opaque("shot.png", al_get_backbuffer(al_get_current_display())); and it 'worked' - but on my system the backbuffer never had an alpha channel anyway.

Also is it possible to create a colour with ALLEGRO_COLOR(0,0,0,1) rather than al_map_rgba_f, is that a D thing?

SiegeLord commented 2 years ago

That's a D-syntax, but in C/C++ you can use {0, 0, 0, 1} or w/e other struct initializer syntax. ALLEGRO_COLOR has all of its fields public.

As for this issue, I'd like to know why the screen has alpha < 1. Maybe I need to try to compile your game and see what's up.

pedro-w commented 2 years ago

Something seems odd. The background should be opaque; katastic is drawing a layer with alpha=1 then one with alpha = 0.5 then one with alpha = 0.25: https://github.com/katastic/dgravity/blob/daf46923b22ece54458d206ff2a195869b4475a4/src/g.d#L250-L270 It's just the backbuffer seems to have some other idea about alpha. Also in this minimal (C) code

#include <allegro5/allegro.h>
#include <allegro5/allegro_image.h>
/* You can make blob.png with imagemagick: */
/* magick logo: -resize 64x64 -transparent white blob.png */
int main() {
  al_init();
  al_init_image_addon();
  ALLEGRO_DISPLAY* disp = al_create_display(320,240);
  al_clear_to_color(al_map_rgba_f(0.0f, 0.0f, 0.0f, 0.0f));
  ALLEGRO_BITMAP* blob = al_load_bitmap("blob.png");
  al_draw_bitmap(blob, 0.0f, 0.0f, 0);
  al_save_bitmap("direct.png", al_get_backbuffer(disp));
  ALLEGRO_BITMAP* tmp = al_create_bitmap(320,240);
  al_set_target_bitmap(tmp);
  al_clear_to_color(al_map_rgba_f(0.0f, 1.0f, 0.0f, 1.0f));
  al_draw_bitmap(al_get_backbuffer(disp), 0.0f, 0.0f, 0);
  al_save_bitmap("indirect.png", tmp);
  return 0;
}

I'd expect indirect.png to be blob.png on a green background but it is just transparent same as direct.png. (apologies if I've misunderstood alpha blending!)

katastic commented 2 years ago

If there's a snippet of code you want me to try on my machine, I can do that. I'm going to re-try the provided clear_to_color() code tonight to double check I'm not mixing up screenshots with code.

And maybe do some al_get_pixel logging between each draw layer of the "space" textures.

The images I'm drawing (of which you can grab from my dgravity repo above), all have transparency layers themselves, in addition to drawing them with al_draw_tinted_bitmap() alpha values.

https://github.com/katastic/dgravity/blob/5c469603180fc4304c5d28b251c2553c584a85ef/data/seamless_space.png?raw=true

Also the blending mode used (perhaps this is key):

al_set_blender(ALLEGRO_BLEND_OPERATIONS.ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA);

[edit]

    al_clear_to_color(ALLEGRO_COLOR(0,0,0,1));
    al_draw_bitmap(disp, 0, 0, 0);

definitely keeps the transparency compared to

    for(int j = 0; j < h; j++)
        for(int i = 0; i < w; i++)
            {
            auto pixel = al_get_pixel(disp, i, j);
            pixel.a = 1.0; // remove alpha
            al_put_pixel(i, j, pixel);
            }

in https://github.com/katastic/dgravity/blob/daf46923b22ece54458d206ff2a195869b4475a4/src/main.d#L281-L310

image

pedro-w commented 2 years ago

Right. I was able to compile and run your game on MacOS and get the same issue with the translucent screenshot, so it's not a platform dependent thing. (D compiles amazingly quick, it's really impressive!) In a nutshell I'd expect drawing a translucent image over an opaque background to give me an opaque bitmap with the image blended in. In my mini-program above, drawing the backbuffer onto a background just replaces the background with the screen image, alpha and all.

Espyo commented 2 years ago

I've experienced this problem independently too, so I've got some nuggets of wisdom to share.

  1. It looks like it's only a problem with al_set_blender(ALLEGRO_ADD, ALLEGRO_ALPHA, ALLEGRO_INVERSE_ALPHA);
  2. This problem does not happen on my older machine, but happens on my newest one. Both are running Lubuntu (though 16 vs 22).
  3. The display backbuffer's bitmap format is ALLEGRO_PIXEL_FORMAT_ABGR_8888 on my old machine, and ALLEGRO_PIXEL_FORMAT_ARGB_8888 in the current one, so that's probably not the issue.
  4. Display flags are the same on both machines.
  5. As for display option differences, old machine vs new...
    • ALLEGRO_RED_SHIFT 0 vs 16
    • ALLEGRO_BLUE_SHIFT 16 vs 0
    • ALLEGRO_ACC_RED_SIZE to ALLEGRO_ACC_ALPHA_SIZE 16 vs 0
    • ALLEGRO_AUX_BUFFERS 4 vs 0
    • ALLEGRO_DEPTH_SIZE 24 vs 0
    • ALLEGRO_STENCIL_SIZE 8 vs 0
    • ALLEGRO_SWAP_METHOD 32867 (?) vs 0
    • ALLEGRO_OPENGL_MINOR_VERSION 5 vs 6

Hopefully these will help figure out the problem.