spelunky-fyi / Playlunky

Extended Mod Management for Spelunky 2 via dll injection
MIT License
14 stars 11 forks source link

Don't premultiply cached/vanilla images #37

Open Dregu opened 2 years ago

Dregu commented 2 years ago

Thoughts from Discord:

  1. DDS files in the game are premultiplied alpha, but extraction does not un-premultiply when making PNGs (premultiplied PNG is not even a thing but whatever)
  2. Multiplying/Dividing the alpha is lossy and we can't really get an original PNG back that could be both loaded in PL and look right in an editor
  3. Playlunky is premultiplying all images from mods, cause they are likely PNG and not premultiplied
  4. Playlunky is also premultiplying the cached vanilla files it extracted when stitching entity sheets or sprite maps, which is bad and any unmodified parts get premultiplied twice in the end
  5. Using any mods that custom sprite map to items.png will break all the other items because of 4
  6. There might even be a feedback loop so files in the cache get premultiplied again and again

Solutions:

I tried to undo the premultiplication and then load the textures again in PL https://imgur.com/a/Z4ToeAS I extracted the divided textures with this modification but nothing looks quite right either way...

def dds_to_png(data):
    """Takes a .DDS `Image` and returns .png data."""
    img = Image.open(io.BytesIO(data))
    px = img.load()
    width, height = img.size
    for i in range(width):
        for j in range(height):
            if px[i, j][3] != 0:
                R = int(255.0 * px[i, j][0] / px[i, j][3] + 0.5)
                G = int(255.0 * px[i, j][1] / px[i, j][3] + 0.5)
                B = int(255.0 * px[i, j][2] / px[i, j][3] + 0.5)
                a = px[i, j][3]
                px[i, j] = (R, G, B, a)
    new_data = io.BytesIO()
    img.save(new_data, format="PNG")
    return new_data.getvalue()

Originally posted by @Dregu in https://github.com/spelunky-fyi/modlunky2/issues/304#issuecomment-1288104234