Jump to content

AndersM

Members
  • Posts

    12
  • Joined

  • Last visited

Posts posted by AndersM

  1. 7 minutes ago, Rick Brewster said:

    That should include any 32-bit BMP saved by PDN for the last 5+ years.

    That doesn't really answer my question 🙂

    I'm not using PDN so I haven't got any samples of the BMPs it produce.

     

    Against better judgment I've now looked a bit more into this. As far as I can tell, when dealing with DIBs, many parts of Windows expect or produce a color table for BI_BITFIELDS DIBs - However not all. Notably, parts of the gdi, icm, and the imaging part of MS Paint, among others, expect a color table in v3 BI_BITFIELDS DIBs but explicitly not in v4 or v5. FWIW, this corresponds to my original implementation.

    I have no idea how all these different parts manage to interoperate when they can't agree on the data format.

  2. 1 hour ago, Rick Brewster said:

    Why v4? I was under the impression that only v1 (BITMAPINFOHEADER) and v5 (BITMAPV5HEADER) are relevant

    That was just a conincidence. I just picked one of the file sets that demonstrate the problem. v3, v4 and v5 all have it.

     

    I can read v1, v2, v3, v4, and v5 in BI_RGB and BI_BITFIELDS format but I only write v1 and v2 in BI_RGB format (since BI_BITFIELDS has no purpose in that version (because no alpha)), and I only write v3, v4, and v5 in BI_BITFIELDS format (since I need the alpha). v2 btw is an undocumented, unreleased format. It's only included for completeness.

    For some reason the Windows shell only supports alpha for v4 and later.

    image.png.d075dde1d24e60301d2e561aecae5cc9.png

    Also, the Windows shell doesn't like the color table:

    image.png.f5b0a8f14d42bb44c2180f33e93d4bc5.png

     

    For the clipboard only v1 and v5 are relevant.

     

    1 hour ago, Rick Brewster said:

    FWIW, WIC adds the 3 masks when writing out a V5 bitmap/DIB. That's canonical enough for me.

    Okay, but have you tried opening a v5 BI_BITFIELDS bmp file produced by WIC in, for example, MS Paint?

     

    So far it appears that the clipboard expects a DIB with a color table but everything else expects one without. So far I haven't found a single application that can load the color table v4/v5 bmps correctly. Even online editors experience the problem (tested with both Firefox, Edge and Chrome).

    image.png

  3. On 2/6/2024 at 4:19 AM, Rick Brewster said:

    I'll reopen the issue over on GitHub and poke into this some more for the 5.0.13 servicing release. It sounds like what you're saying is that the fix is to add in the 3 masks. It may also make sense to just punt this over to WIC -- have it save a BMP to an in-memory stream, skip past the BITMAPFILEHEADER, and copy the remaining bytes to the clipboard.

     

    I think I'm going crazy.

    As you've seen I have implemented the change in Graphics32 and closed the issue there.

    Since many of the bitmaps I used for unit testing were made under the old assumption I decided to regenerate all the bitmaps. And now that I had the bitmaps I thought I would see how Windows Paint handled them - and this is where things got really depressing.

     

    I've attached a zip with two 16x16x32bit bitmaps in v4 BI_BITFIELDS format. One with the color table and one without. The bitmaps look like this (in my own bitmap editor):

    image.png.998fc37930dcbbaafedf8387051fb8d9.png

    And this is what they look like in Paint when I load them from file:

    bgra_v4_bottomup.bmp (with color table)

    image.png.6eaa3a94b1c4c0846fc9a6e3eb662c8a.png

    bgra_v4_bottomup_missing_color_table.bmp (without color table)

    image.png.ae72c40881130f9dd19bcff2b7b5dfe5.png

    Okay, I thought, I guess I was wrong about being wrong. But then I did a copy from my editor (which now produce CF_DIBV5 data with the color table) and pasted into Paint:

    image.png.2ef5efaed267b3befb14f986097d11bf.png

    (╯°□°)╯︵ ┻━┻

     

    Anyway, I'm too busy with work and stuff to get to the bottom of this right now but I just wanted to give you a heads up.

    bgra_v4_bottomup.zip

    • Upvote 1
  4. Alas, it seems I have to concede defeat on this issue. I was wrong.

     

    I noticed that the MSDN documentation on bitmap headers has once again been rephrased and now reads:

    Quote

    The red, green, and blue bitfield masks for BI_BITFIELD bitmaps immediately follow the BITMAPINFOHEADER, BITMAPV4HEADER, and BITMAPV5HEADER structures. The BITMAPV4HEADER and BITMAPV5HEADER structures contain additional members for red, green, and blue masks as follows. [...]

    The text that follows shows evidence of having been copied from somewhere else (it makes reference to the function which has no meaning in that context) but regardless of that it doesn't really leave room for ambiguity; The color masks must follow BITMAPV5HEADER.

     

    Since MSDN nowadays is semi-community-edited I didn't take the above as definite proof (I've seen the documentation on this topic change back and forth so many times). Instead I decided to consult "the source" (I'll leave it to you to guess what "the source" is).

     

    So I now have it on good authority that the following is how the Windows clipboard calculates the size of a DIB (brought to you in beautiful Pascal):

     

    Size of the color table

    if (BitmapInfoHeader.biCompression = BI_RGB) then
    begin
      if (BitmapInfoHeader.biClrUsed) then
        ColorTableSize := BitmapInfoHeader.biClrUsed * SizeOf(DWORD)
      else
      if (BitmapInfoHeader.biBitCount <= 8) then
        ColorTableSize := (1 shl BitmapInfoHeader.biBitCount) * SizeOf(RGBQUAD)
      else
        ColorTableSize := 0;
    end else
    if (BitmapInfoHeader.biCompression = BI_BITFIELDS) then
    begin
      if (BitmapInfoHeader.biBitCount = 16) or (BitmapInfoHeader.biBitCount = 32) then
        ColorTableSize := 3 * SizeOf(DWORD)
      else
        ColorTableSize := 0;
    end else
    if (BitmapInfoHeader.biCompression = BI_RLE4) {
      ColorTableSize := 16 * SizeOf(DWORD)
    else 
    if (BitmapInfoHeader.biCompression = BI_RLE8) {
      ColorTableSize := 256 * SizeOf(DWORD)
    else
      ColorTableSize := 0;

     

    Size of the pixel data

    PixelDataSize := (((BitmapInfoheader.biWidth * BitmapInfoheader.biBitCount + 31) and not 31) shr 3) * Abs(BitmapInfoheader.biHeight); // 32 bit aligned
    

     

    Size of DIB

    DIBSize := BitmapInfoHeader.biSize + ColorTableSize + PixelDataSize;

     

     

    Notes:

    • The above only applies for (BitmapInfoHeader.biSize >= SizeOf(BITMAPINFOHEADER)).
      The rules for BITMAPCOREHEADER (i.e an OS/2 DIB) are different.
       
    • For BITMAPV5HEADER, when writing DIBs, the header color masks are duplicated into the color table.
      This is a requirement but I guess it's a good idea since we don't know which of the two set of masks the reader will use.
       

    My guess regarding the cause of this mess is that the DIB spec, internally at Microsoft, has evolved through time from the masks originally being included in the header to them being excluded because that was how most people implemented it.
    This would explain the ever changing documentation and the fact that different Microsoft tools has implemented different formats - and in the case of the clipboard, different formats within the same code. It's also interesting that so many Windows tools are able to correctly read both versions of the format...

  5. Btw, as far as I remember there is a bug, at least in Windows 10, where pasting CF_DIB will corrupt the CF_DIBV5 on the clipboard - or maybe vice versa. I can't remember which it is.

     

    Edit: I see that I already mentioned this in the original thread:

    The comment that mentions the clipboard bugs predates Windows 10 so I probably discovered them in Windows 7 (I only upgraded from Win7 to Win10 a few months ago).

  6. I just searched the Firefox source for CF_DIBV5, BITMAPV4HEADER and BITMAPV5HEADER in order to find out how they handle DIBs - and now I need to book an appointment with my therapist...

     

    As far as I can tell Firefox has used the renowned method of rolling a dice to decide what method to use when reading/writing V4/V5 bitmaps. In most places the DIB data does not contain the extra three color masks, but in a few places it does, and in one gloriously stupid case, when writing a bitmap file, the masks are included but only if the data is specified as having alpha. WTH?

    • Haha 2
  7. On 11/26/2023 at 2:58 AM, Rick Brewster said:

    @AndersM might have some extra insight, if he's got notifications enabled for mentions

    I didn't 🙂  - but I have now.

     

    I don't really have anything to add beyond what was discussed in the Graphics32 vs Paint.NET thread; My position is still that the three color masks are included in BITMAPV5HEADER and thus should not be added after the header. I.e. Paint.NET is correct.

     

    • Upvote 2
  8. 10 minutes ago, Rick Brewster said:

    Some apps use premultiplied alpha for some terrible reason, and I had to add code in PDN to detect that (if all R,G,B are <=A, then it's premultiplied) and then unpremultiply.

    Your patience is admirable 🙂

    I personally wouldn't try to handle broken input to that degree. Especially since it's impossible to guarantee that the fix wouldn't break valid input. For example, the following bitmap is valid and not premultiplied but all RGB <= A:

    Oh... It seems I can't paste BMPs. Anyway, you get the picture - so to say.

  9. 2 minutes ago, Rick Brewster said:

    Do you have a source for this? From what I see, BI_BITFIELDS only talks about specifying the masks for red, green, blue.

    I can't remember anymore and I'm afraid I don't have time to pursue it now.

     

    You can deduce it from the structure format though. The bV5RedMask, bV5GreenMask, and bV5BlueMask fields are only used with BI_BITFIELDS. It follows that the bV5AlphaMask field, which is the only method there is to specify where to find the alpha value in the color DWORDs, is also only used with BI_BITFIELDS.

     

     

    10 minutes ago, Rick Brewster said:

    I can't find anything that says BI_BITFIELDS is required for using alpha.

    I think you'll find that you can't find any mention of alpha at all in the BMP documentation.

    There's nothing stopping you from putting alpha in a BI_RGB DWORD, but if the readers don't recognize it as alpha then what's the point? You can give it a try with Windows Explorer. The easiest test is probably just to place a 32-bit RGBA BMP on the desktop and see if it displays as opaque or transparent.

    image.png.7a5d3e344edf5d2b4e72547732263f5d.png

    • Like 1
  10. 1 hour ago, Rick Brewster said:

    So you look at the total size, and if it's got 12 extra bytes you assume it was has an "extra" "bmiColors" ?

    Yep.

    Here's the code:

      // Work around BMPs with an extra color mask after the header:
      // If there's exactly 12 bytes too many then we assume that these are an extra
      // color mask and skip it.
      // For example the Windows resource compiler will add these 12 bytes to the header
      // of RT_BITMAP resources. This goes for both BI_RGB and BI_BITFIELDS.
      if (InfoHeaderVersion >= InfoHeaderVersion1) then
      begin
        ChunkSize := 3 * SizeOf(DWORD);
        if (Size = ChunkSize) then
          Stream.Seek(ChunkSize, soFromCurrent);
      end;

     

     

    1 hour ago, Rick Brewster said:

    Even if I fix this, there's a high probability that it will break pasting from some other app -- not because of a bug in PDN or Graphics32, but because of the other app. Every app seems to have some kind of different bug with regard to all of this.

    Yes, with this it seems there's no "fixed". Just various degrees of broken.

     

    2 hours ago, Rick Brewster said:

    The other format I'm seeing is CF_DIBV5 + BI_RGB, I haven't found anything on my system that uses BI_BITFIELDS.

    There's no point in using CF_DIBV5 unless you're also using BI_BITFIELDS as BI_BITFIELDS is the only way to specify that the bitmap contains alpha. Without BI_BITFIELDS you might as well just use CF_DIB.

     

    Graphics32 originally used CF_DIB + BI_RGB for both clipboard and BMP files and while this worked if the target assumed that the bitmaps contained alpha, many applications rightly treated the bitmaps as 24-bit RGB. For example Windows Explorer; Only a v3+header with BI_BITFIELDS will make Windows Explorer treat a BMP as a 32-bit RGBA bitmap.

     

    FWIW, the following comment from the Graphics32 bitmap/clipboard adapter code might provide some additional clues. The Windows clipboard isn't exactly makes things easier for us:

      (*
      ** We place the following data on the clipboard:
      **
      ** - CF_BITMAP
      **   This is the source bitmap rendered onto a white background.
      **   Transparency is not retained.
      **   For use by applications that doesn't support Alpha.
      **
      ** - CF_DIBV5
      **   A 32 bit DIB with alpha. This alone can be used to recreate the original
      **   32 bit bitmap, including alpha.
      **   This format provides round trip support.
      **
      ** Since Windows can synthesize between any of CF_DIB, CF_BITMAP and CF_DIBV5
      ** theoretically we could just supply the most capable format (CF_DIBV5) and
      ** let Windows supply the others. Unfortunately we need to supply both CF_DIBV5
      ** and CF_BITMAP/CF_DIB in order to work around various Windows bugs:
      **
      ** - When the clipboard synthesizes CF_DIBV5 from CF_DIB it uses BI_BITFIELDS.
      **   However, if the clipboard synthesizes CF_DIB from CF_DIBV5 with
      **   BI_BITFIELDS compression, the clipboard apparently forgets to take the
      **   extra 3 mask DWORDs into account which messes up the resulting DIB.
      **
      ** - When the clipboard synthesizes CF_DIB or CF_BITMAP from CF_DIBV5 it
      **   appears to require 68 extra bytes after the bitmap header.
      **   Inserting these 68 bytes would fix that but would also make the bitmap
      **   stream invalid for everything else.
      **   FWIW, 68 = SizeOf(BITMAPV4HEADER)-SizeOf(BITMAPINFOHEADER)...
      **
      ** As a bonus we get to control the background color of the CF_DIB/CF_BITMAP
      ** bitmap instead of the black one Windows would use.
      *)

     

  11. 19 hours ago, null54 said:

    Quoting from https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapv4header:

    Quote

    If the bV4Compression member of the BITMAPV4HEADER is BI_BITFIELDS, the bmiColors member contains three DWORD color masks that specify the red, green, and blue components of each pixel. Each DWORD in the bitmap array represents a single pixel.

     

    Hi, Anders Melander, the current Graphics32 maintainer here.

     

    The GDI documentation is correct, albeit very poorly worded, but from my POW it's your interpretation of bmiColors that's the problem.

    As I understand it, your assumption is that the bmiColors array should always follow the BITMAPV4HEADER structure. My claim is that it's part of the structure.

     

    If you look at the documentation, bmiColors comes from the BITMAPINFO structure; The first member of the structure is BITMAPINFOHEADER and the second member is the bmiColors DWORD array.

     

    Now if you look at all the different BMP header versions, they all extend on the previous version by adding fields to the end of it. We start with the v1 BITMAPINFOHEADER. The (undocumented) v2 header adds 3 DWORD RGB masks. The v3 header adds a DWORD alpha mask. the v4 adds gamma and color space stuff, etc. etc. The point here is that bmiColors really only applies when you are using a v1 header. All the later versions include bmiColors in their structure.

     

    A clue is that the documentation first state that bmiColors specify the color masks:

    Quote

    the bmiColors member contains three DWORD color masks that specify the red, green, and blue components of each pixel.

    and then later state (for BI_BITFIELDS):

    Quote

    The members bV4RedMask, bV4GreenMask, and bV4BlueMask specify the red, green, and blue components for each pixel. This is valid when used with 16- and 32-bpp bitmaps.

     

    Ergo: bmiColors=bV4RedMask, bV4GreenMask, bV4BlueMask -> The 3 DWORDs are included in the header

     

    Now, even though I claim my interpretation of the structures to be "the right one" it can also be argued that they are both correct. The documentation has been changed so many times with regard to this and it's still broken, some parts of Windows write one variant, other parts write another and it can usually read them both.

     

    If possible, I suggest Paint.NET try to handle both variants (when reading). That's what I do at my end and it seems that that is also what Windows itself does.

×
×
  • Create New...