SimonN / LixD

Lix: Lemmings-like game with puzzles, editor, multiplayer
https://www.lixgame.com
134 stars 17 forks source link

Magic pink won't become transparent on macOS (Workaround: Mass-convert to 32-bit PNG, see first post) #431

Open SimonN opened 2 years ago

SimonN commented 2 years ago

Workaround: Mass-convert the PNG files to the 32-bit PNG format (rather than palette-indexed PNG or 24-bit PNG). Lix will then detect pink correctly. To do this:

  1. Install ImageMagick 7.x.
  2. Open a shell, navigate to Lix's directory, then run the following command:
find images/ data/images/ -name \*.png -exec magick {} PNG32:{} \;

Lix builds and runs on Mac, but some graphics have problems:

Zrzut ekranu 2022-03-4 o 09 22 48

This bug is still in Lix 0.10.26 in 2024.

The first time I heard about this was in naymapl's report in #430 for Lix 0.9.42 in 2022 on a Mac mini m1 with macOS ARM64.

cameo69 reported that this same bug also manifests on an Intel Mac.

In 2024, Rhys-T investigated. Now it looks like it's not a Lix-specific bug, but rather a bug in Allegro 5's Mac-specific image loader.

SimonN commented 2 years ago

@naymapl wrote:

ok. I check source and all png files have this pink layer. Maybye is better to create source png files with real transparent layers ?

That would fix the pink, and give you correct physics. If you want it really fast, you could, in theory, go through the tiles in images/ yourself and replace the pink with full transparency (alpha 0) in all images.

Even replacing pink with transparency in the image files would still leave the wrongly-colored symbols on the buttons, but those aren't as important.

Lix 0.9.42 should, by itself, replace the pink with transparency and recolor the button symbols, but it doesn't. I've already investigated the source roughly: Allegro 5 and the addons are initialized, and I compute the colors with al_map_rgb_f afterwards. This should rule out any endianness issues.

But this is the first time that somebody is running Lix on macOS ARM64. Anything might be the problem. I'll investigate.

naymapl commented 2 years ago

Answer is not so simple :D

mouse l

I prepare file with transparent layer and now have no cursor :D

Zrzut ekranu 2022-03-5 o 17 10 43

naymapl commented 2 years ago

Ok - now is working - I maybye use wrong file name ;)

naymapl commented 2 years ago

Is it any way to make it bulk ? It so many images to make one by one :D

https://graphicdesign.stackexchange.com/questions/16120/batch-replacing-color-with-transparency

I found this but no idea how run this with LixD images.

SimonN commented 2 years ago

Lix images are normal PNGs with alpha. Assuming you have ImageMagick 7.x installed, here's a loop:

find images/ data/images/ -name \*.png -exec magick {} -transparent \#FF00FF {} \;

find -exec \; runs the command (that is in-between exec and \;) per file, and {} becomes the found filename.

Still looking for the problem in Lix or Allegro on macOS ARM64. What I do in Lix should work on all systems. Really makes me wonder...

naymapl commented 2 years ago

Thank you so much - working perfect ;) Now game looks perfect on Apple Silicon macOS. Zrzut ekranu 2022-03-5 o 18 03 23

If you want to test something just let me know.

btw will be nice to create lixd.app with icon for your app, for example with this app:

https://github.com/machinebox/appify

SimonN commented 2 years ago

(Sorry for late reply.)

Great to hear that you can play singleplayer well! I'll keep this workaround in mind then -- the shell loop to recolor all graphics.

The in-game recoloring still fails for the button icons. But the grey button icons (instead of pale blue) aren't a problem for singleplayer playability. You can still tell what's what.

But in multiplayer, you'd have 8 identical-looking player colors instead of 8 different lix colors. This would be more serious, you wouldn't be able to tell apart lix of different players. Thus, I won't close this bug yet.

cameo69 commented 2 years ago

Hi, for what it's worth, I have the same problem on an Intel Mac. And I think I cannot win Level 11 "Beneath the Lab" like this because Lemmings are repelled by the pink. Hence no way to get them out of the game :-(

Pasted Graphic 4

Guess I will try changing all graphics as suggested.

cameo69 commented 2 years ago

find images/ data/images/ -name *.png -exec magick {} -transparent #FF00FF {} \;

did the trick. Thank you so much.

image
SimonN commented 2 years ago

Thanks for the report that this bug manifests on the Intel Mac!

I haven't investigated since March 2022, but it's getting more important again. Lix is largely unplayable on all Macs until we mass-replace the pink with the shell loop, and even then, the UI elements remain unrecolored.

SimonN commented 1 year ago

My new year's resolution for 2023 is to solve this recoloring bug.

I still don't have a Mac, but I have a first theory for you to test:

Clone my unstable Lix repository. Check out branch issue431. I deleted this and merged it into Lix 0.10.7.

  1. Check out a more recent Lix version, 0.10.7 or newer, e.g., the current 0.10.14.
  2. Build and run Lix as usual.

Do the levels from your earlier screenshots (Beneath the Lab, or the basher tutorial) still show the pink rectangles?

SimonN commented 1 year ago

@cameo69, please post the output of dub build -f. I'm interested in the line Performing "release" build [...].

E.g., naymapl on ARM had Performing "release" build using ldc2 for aarch64, arm_hardfloat. Do you get "hardfloat" too? Background: I have some theories about the ALLEGRO_COLOR struct: Possible longer float than 32-bit, possible padding even though the struct is { float, float, float, float }.

Rhys-T commented 2 months ago

Still happening in Lix 0.10.26 on macOS 10.15.7 Catalina (Intel, 2019 iMac) with Allegro . I may have found some more info that might help with this.

Full disclosure: I'm not exactly building Lix the normal way. I'm actually building it under a package manager called (wait for it) Nix. It gives me sandboxed, reproducible builds, and it lets me change out dependencies like Allegro just for Lix without affecting anything else I have installed. However, I did have to do a couple of weird hacks to get it to build and run correctly in that environment, and I can't promise that's not affecting anything… but so far I don't think that's causing what I'm seeing here.

'Performing' line looks like this:

Performing "releaseXDG" build using /nix/store/3frj21nwm6czz2c91ngflzyh44pc5f27-ldc-1.39.0/bin/ldc2 for x86_64.

I tried building Lix against various versions of Allegro, going back to 5.2.2.0[^quicktime], but it always got the pink blocks. Looking at them in Digital Color Meter, it's actually being rendered as an even more intense pink/magenta that is out of gamut for sRGB. The native values on my iMac are [0xFB, 0x00, 0xFF], while sRGB shows [0xFF (clipped), 0x00 (clipped), 0xFF (clipped)]. I can also see the difference if I open e.g. mouse.png in Preview or GIMP and compare it against the in-game cursor.

I found one reference to a similar problem from way back in 2010: al_convert_mask_to_alpha not converting magic pink to transparent on Mac OS X (and iOS) but working elsewhere. I tried stripping out color profiles in case it was that (although as far as I can tell there aren't any to remove), as well as adding in an sRGB profile that came with my system. Neither had any effect.

I noticed that some of the images were indexed-color, and wondered if Allegro was somehow interpreting color values in the palette differently than color values in a true-color PNG somehow. I tried converting them all to true-color using this command:

find images/ data/images/ -name \*.png -exec magick {} PNG32:{} \;

and it seems to work! The sprites, UI, and level terrain all get recolored correctly, and the lix follow the level terrain properly.

Screenshots Screen Shot 2024-09-12 at 12 00 11 AM Screen Shot 2024-09-12 at 12 11 13 AM Screen Shot 2024-09-11 at 10 50 04 PM Screen Shot 2024-09-11 at 10 49 58 PM

(I initially messed up and used PNG24 instead of PNG32, and it almost worked, but the lix and other player-colored sprites had black boxes around them. It turns out that that messed up lixrecol.png, and all its black pixels got changed to transparent - so the game didn't know to look for black in the other images.)

I'm still not entirely sure what's causing this, or why changing to true-color worked, but from my (admittedly limited) testing, this seems to be a better workaround than the -transparent \#FF00FF version shown above.

Hope this helps!

[^quicktime]: Once I went far enough back, I was having trouble getting Allegro itself to build, because it was using frameworks like QuickTime that I didn't have headers for anymore.

SimonN commented 2 months ago

Thanks for your excellent investigation! This helps in several areas:

You've seen the bug appear in the current Lix 0.10.26. This confirms that my hand-rolled reimplementation of al_convert_mask_to_alpha hasn't fixed the bug.

Originally, I conjectured that Allegro 5's al_convert_mask_to_alpha produce the bug because it calls memcmp to compare the 4 floats in a color, which treats +0 and -0 differently. My hand-written function compares each of the 4 color components with ==, which sees +0 and -0 as equal. These suspicions about the floating point behavior were also the reason why I asked for the "Performing" line, to see whether the bug depended on seeing "hardfloat" in that line. Now I believe that "hardfloat" is unrelated to this bug.

I'll roll back to Allegro 5's implementation and remove my hand-written reimplementation.


You've seen pink pixels considerably off-target and you've even proved it in Digital Color Meter. That again suggests that neither Lix's hand-rolled pink conversion nor Allegro 5's al_convert_mask_to_alpha are the main culprits. The off pink must come from elsewhere. Allegro 5 as a whole isn't off the hook yet.

Next ideas for my investigation:

  1. How does Allegro 5 load PNGs from file? Does it have special code that differs by OS? I'll dig around at the Allegro 5 source code.
  2. What is the reason behind your out-of-bounds pink ("an even more intense pink/magenta that is out of gamut for sRGB")? Which step from PNG file to rendering on screen introduces a pink that's not 0xFF, 0x00, 0xFF a.k.a., in Allegro 5 bitmaps, the three floats 1.0, 0.0, 1.0?
  3. What libraries does macOS offer by default to load PNGs? It would surprise me though to see buggy PNG libraries on the Mac, a popular ecosystem for professional graphic and video editing.
  4. I'll ask the Allegro 5 community for what has come from the 2010 sighting of this bug.

You found a better workaround that fixes the GUI recoloring and the multiplayer recoloring. Now the networking mode is playable on Mac Lix. That's great!

I'll edit the first post to provide your workaround (with PNG32:{}) instead of the old workaround (with transparent \#FF00FF).

I'll consider adding a shell script for Mac users that runs your line, and explain in the Mac build instructions to run that script. This isn't ideal either: This will produce many changed files in the versioned tree and git status won't be happy.

The obvious next move would be to change all PNGs in the versioned tree to PNG32, commit them, and consider this a bugfix. I'm wary of this for several reasons:

  1. It allows the bug to resurface. Users can paint their own terrain, put it in a different PNG format into the tile tree, then run into the bug.
  2. I'd commit already-correct files again, but bigger filesize-wise, which makes the repository fatter. The existing palette-indexed PNGs work correctly on Windows and Linux, and repositories should contain the minimal truth.
  3. I (or a future maintainer) cannot run Optipng over PNGs habitually anymore before committing new images. Optipng changes to palette-indexed PNG to compress better. If I forget this, I'll silently break Lix on Mac.
  4. I (or a future maintainer) have to remember converting everything to PNG32 before committing, regardless of what other tooling we have. Again, I can forget this and silently break Lix on Mac.

Ideally, I find the source of the bug before I commit everything as PNG32.

Rhys-T commented 2 months ago

Glad I could help.

Yeah, I can definitely see how just changing the upstream images to PNG32 would be a more fragile approach.

Normally Allegro uses libpng to read PNG files, but on macOS it usually replaces that with a native loader that goes through Cocoa's NSImage class. If I disable the native loader - by passing -DWANT_NATIVE_IMAGE_LOADER=off to cmake and rebuilding Allegro - then the indexed-color PNGs work just fine. Obviously requiring a special build of Allegro isn't particularly practical either, but it at least narrows down where the problem is.

I still don't understand where the native loader is breaking, though. Both loaders seem to lock the new bitmap as ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE when filling it, which shouldn't even be able to represent out-of-range values - each channel is just a one-byte unsigned integer (0-255). And the images seem to have the right colors in e.g. Preview.app or Quick Look, which would also be using NSImage. (Well, almost the right colors. Digital Color Meter says it's [0xFF, 0x01, 0xFF] in sRGB, but that could just be a rounding error when it's converting back from native colors or something.)

(Also:

Still happening in Lix 0.10.26 on macOS 10.15.7 Catalina (Intel, 2019 iMac) with Allegro .

Whoops! Meant to say "[…] with Allegro 5.2.9.1". I stopped to look up the version number, got sidetracked, and never pasted it back in. Of course it's "with Allegro"!)

Rhys-T commented 2 months ago

Okay, I've been adding various debug prints to Allegro's native macOS bitmap loader trying to figure this out, and have found a little more info. Bear in mind that I have no real experience with Allegro, and while I kinda understand the concepts of color spaces/profiles in theory, I haven't actually worked with them to any significant extent, so I'm mostly guessing my way through this. With that out of the way:

I can't find this documented anywhere, but it currently seems like Allegro (at least on macOS?) is working with colors exclusively in native RGB(A) format - not sRGB. That would explain how it's possible for the loader to set an out-of-sRGB-gamut color in the new bitmap - neither the ABGR_8888 bytes nor the floats in an ALLEGRO_COLOR are being treated as sRGB.

From what I can tell, for most PNGs - at least the ones in Lix's data, which don't specify color profiles - the NSBitmapImageRep comes back in sRGB IEC61966-2.1 colorspace. In that form, magic pink is represented as #FF00FF and gets transparent-ized.[^misint] [^misint]: The other colors presumably get incorrectly reinterpreted as native RGB, but it's not wrong enough to notice without a side-by-side comparison, at least to me.

But when the PNG file is in indexed format, the NSBitmapImageRep is instead in Generic RGB colorspace, and the pixel values inside seem to have already been converted to native RGB - turning magic pink into #FB00FF. And Allegro doesn't bother to check what color space the NSBitmapImageRep is in before extracting the data. This only seems to happen for normal indexed images - indexed-with-alpha gets treated the same as true-color. In either case, the values Cocoa returns for the colors are 'correct' - it's just that Allegro is discarding the color space info.

Meanwhile, the libpng-based loader that Allegro uses on other systems doesn't seem to do any conversion, besides asking libpng to gamma-adjust based on Allegro's best guess as to the correct gamma value for your screen (or a config setting or the SCREEN_GAMMA environment variable, if available). It still manages to match the magic pink no matter what I set SCREEN_GAMMA to - presumably because 0.0 and 1.0 (or 0x00 and 0xFF) both map to themselves on a standard gamma curve.

I actually don't see much of anything in Allegro's code that does anything color-space-related[^nonrgb]. I could be wrong - again, I am very much not an expert on this - but it feels like Allegro is basically assuming "RGB is RGB", shoving whatever values the loader gets from the PNG onto the screen, and hoping for the best. On Linux and Windows, that means the raw pixel/palette values from the file. But on macOS, Cocoa sometimes does its own conversion first, and the combination of those behaviors ends up breaking things. I'm not really sure where to go with this next… [^nonrgb]: Besides having a few functions for converting to and from non-RGB color spaces like HSV or CMYK.

SimonN commented 2 months ago

disable the native loader - by passing -DWANT_NATIVE_IMAGE_LOADER=off to cmake indexed-color PNGs work just fine.

Thanks! That narrows it down to Allegro 5's fundamental image loading. It could still be the Mac libraries, but it's reasonable to assume the Mac libraries themselves to work fine, and to dig deeper into Allegro 5's usage of those Mac libraries.

But on macOS, Cocoa sometimes does its own conversion first

Yes, this sounds like a good angle of attack: Can/should Allegro 5 ask Cocoa not to convert anything?

With all of these findings, it's looking reasonable to file this against Allegro 5 directly. That bug would be: Allegro 5 on Mac loads slightly-off colors from PNG, unless the source was PNG32 or you disable the native image loader (at configuration time). The failing pink-to-transparent conversion is merely a symptom of this bug; after all, my hand-rolled conversion failed in the same way.

These days, at the weekend at latest, I'll look through Allegro 5's issues. If I don't see anything similar filed already, I'll file it. Or would you like to spearhead this instead? Feel free to open the bug before I do. I'll watch it closely regardless of who opens it.

Rhys-T commented 2 months ago

This isn't directly related to this issue, but:

btw will be nice to create lixd.app with icon for your app, for example with this app:

machinebox/appify

If you do end up trying to make a proper macOS .app bundle for Lix, you're likely to run into https://github.com/SiegeLord/DAllegro5/issues/56. Basically, the game executable works outside the bundle, but as soon as Allegro detects that the game is running from a bundle, it does something that ends up confusing the D Allegro bindings and causing a crash. The proposed fix/workaround in that issue seems to work, but hasn't actually been released or even committed yet.

I realize it's unlikely that anyone here besides me will be in exactly the right intersection of niches to find this useful, but if any Mac users are looking to get a working, double-clickable macOS version of Lix and are using the Nix package manager I mentioned earlier, I've added my package for it to the Nix User Repository as nur.repos.Rhys-T.lix-game (source code here). I wouldn't recommend it as the official way of installing Lix on macOS, though - Nix is a rather weird package manager if you're not used to it…

Edit 10/7/2024: I suppose I should clarify that the package I did should work on the Linux version of Nix too, if any NixOS users are looking to play the game - but now we're getting even further off topic for this issue…

Rhys-T commented 1 month ago

If you're on a multi-core Mac and have something like GNU Parallel installed, you can change the command to something like this to let it convert multiple PNGs at once:

find images/ data/images/ -name \*.png -print0 | parallel -0 magick {} PNG32:{} \;
SimonN commented 1 month ago

In the Allgero 5 Mac PNG bug, you wrote:

[multiplayer] colors do get adjusted - but the colors that the game is looking for are taken from the first row of lixrecol.png, so they've also been adjusted, and still match what's in the sprites.

That gives me an idea for a theoretical workaround: Instead of hardcoding the magic pink as al_map_rgb_f(1, 0, 1), which ends up mismatching the imported pink from palette-indexed PNG files, I should gather the definition of magic pink also from a palette-indexed PNG file. E.g., I could ship a 1x1 pink palette-indexed PNG and import that before converting any magic pink.

The problem with this is: 32-bit PNG will then fail to convert from pink to transparent. I'd have to run every PNG through two rounds of color-to-transparency conversion, once for the color from the 1x1 file (presumably #FB00FF) and once for #FF00FF. It will thus dent the image-loading performance on Mac, but if it works around our bug here without touching the tiles or Allegro 5, it's a serious candidate.

It also relies on the following: Given many different palette-indexed PNGs, Allegro 5 will load all pink pixels across all these PNGs identically, i.e., all these pink pixels load as the same single (wrong) value. Do we know for sure that this is so?

Rhys-T commented 1 month ago

You'd have to do the same thing to other special colors too. Black would probably be #000000 no matter what and not need that trick, but the lix eye color, the grays that map to shades of UI blue, etc. would all need to be handled that way.

Looking for both possible values regardless of the image also runs the risk of false positives - particularly for the 'gray → UI blue' mapping, where the adjusted value might just happen to line up with another non-adjusted shade of gray.

It also relies on the following: Given many different palette-indexed PNGs, Allegro 5 will load all pink pixels across all these PNGs identically, i.e., all these pink pixels load as the same single (wrong) value. Do we know for sure that this is so?

So far it seems to be that way for me, but I don't have any way to know for sure, and would be hesitant to rely on it. I don't know why those images in particular end up getting pre-converted to native by NSImage, nor how NSImage decides which monitor's native color space to use, if you have more than one.

I tried to see if there was any way to disable the native loader at runtime, but I couldn't find any official way to do that. If you needed to, I guess you could temporarily replace the implementation of +[NSImage imageFileTypes] to return an empty array before initializing Allegro and switch it back afterwards, but I don't know how safe that is either.


On a completely unrelated note, I've found a different way of running the PNG32 conversion that's much faster (as in ~3 seconds vs almost a minute):

find images/ data/images/ -name \*.png -exec magick mogrify -define png:color-type=6 -depth 8 {} +

This batches as many images as possible into a single call to magick.