Binary Palette Chunk in PNGs

Started by blue-j, March 22, 2024, 01:38:31 AM

Previous topic - Next topic

blue-j

I am not getting intelligible decoded output for binary extraction of the palette inside PNGs.  It should be a flat list of RGB values.  I can make it work using PIL in Python, so the data is there.  For the sample image (attached), ExifTool outputs:

␀␀␀=5(95$ämm9-$]]e5- $(-1-␜eUEeME"í9aMAIIQfl≤ ∂û∆QEE($  û≤¶û aI-$$␜û}ä $␘  ␘I==QA1␜ ␔␜␜␔≤ñÆM=-äuä-((␘␜␐␘␜␐∆ñöA9559=␘␔␐=5115999-␔␔␌␔␔␌␐␘␈95-␐␔␈␐␐␈␌␌␄␌␈␄öuueeyE9(ieiuaa11(1-(-1$m]Y5-␘™y5äeQ]MIYMEaM9III$$(QI= $$QE=EEE␜$ ñ}é( ␘mmäMA9IA5 ␜␐E=1-( ␐␘␔í鬬¬˚␐␔␔555515151---␄␌␈ymq␄␈␈¬≤Á␀␈␄␀␄␄ûÜíE5 ¢¶Ô91(a]muaYi]ae]]a]Yu]EUUa ™ YQQMMYUQMÆÇi∫¶ŒUMMQMI(($yuéYI=MIE$$ =AIEE=  ␜ä}é␜ ␘E==␜␜␘␜␜␘¶ñ∫␘␜␔==5␘␘␔␘␘␔␘␘␔␔␘␐␔␘␐␔␔␐␔␔␐"æÔ951Î"˜-$␘␐␔␌␐␐␌␐␐␌¬∫Û51-␌␐␈ym}¢ä¢␌␌␈␈␌␄íܶ␈␈␄␄␈␀(- fl¬Î␄␄␀yeiäu}␄␀␀ueeÜqy--(UYm((-iYYaUQUUY]QMMMQUMEUIEÇ}¶ME=AEE$$␘AAEIA9␘␜ ==AEA59==E95␐␘␘-$$␐␔␘=9-␔␔␈␈␔␐115-11␈␌␐␐␐␄␄␌␌␌␐␀␌␌␀™Üé␀␄␈ueq55(5-(iYQ]UYeQM$$$   QA9A==␘␜␘␘␘␘␔␜␔=99E=-1( ␔␘␔␔␘␔␐␜␐E9-995995␔␔␔␐␘␐¶í™-$␜␐␔␐␐␔␐␐␐␐␌␔␌␌␐␌11-␌␌␌␈␐␈∂äí◊ ˚␈␌␈␈␈␈␄␌␄␈␄␈␄␈␄␀␌␀␄␄␄␀␈␀∂ÆÎ␀␄␀

my Python script:

from PIL import Image

def extract_palette(png_file_path):
    # Open the image
    with Image.open(png_file_path) as img:
        # Ensure the image has a palette
        if not img.mode == 'P':
            raise ValueError("This image does not use a palette (not in 'P' mode).")

        # Get the palette
        palette = img.getpalette()

        # The palette is a flat list of RGB values, so group it into tuples of (R, G, B)
        colors = [tuple(palette[i:i+3]) for i in range(0, len(palette), 3)]

        return colors

# Example usage
png_file_path = '~/OwlAlpha-0.5.png'
palette = extract_palette(png_file_path)
print(palette)


the output is:

[(0, 0, 0), (61, 53, 40), (57, 53, 36), (138, 109, 109), (57, 45, 36), (93, 93, 101), (53, 45, 32), (36, 40, 45), (49, 45, 28), (101, 85, 69), (101, 77, 69), (210, 146, 57), (97, 77, 65), (73, 73, 81), (223, 178, 202), (182, 158, 198), (81, 69, 69), (40, 36, 32), (202, 158, 178), (166, 158, 202), (97, 73, 45), (36, 36, 28), (158, 125, 138), (32, 36, 24), (32, 32, 24), (73, 61, 61), (81, 65, 49), (28, 32, 20), (28, 28, 20), (178, 150, 174), (77, 61, 45), (138, 117, 138), (45, 40, 40), (24, 28, 16), (24, 28, 16), (198, 150, 154), (65, 57, 53), (53, 57, 61), (24, 20, 16), (61, 53, 49), (49, 53, 57), (57, 57, 45), (20, 20, 12), (20, 20, 12), (16, 24, 8), (57, 53, 45), (16, 20, 8), (16, 16, 8), (12, 12, 4), (12, 8, 4), (154, 117, 117), (101, 101, 121), (69, 57, 40), (105, 101, 105), (117, 97, 97), (49, 49, 40), (49, 45, 40), (45, 49, 36), (109, 93, 89), (53, 45, 24), (170, 121, 53), (138, 101, 81), (93, 77, 73), (89, 77, 69), (97, 77, 57), (73, 73, 73), (36, 36, 40), (81, 73, 61), (32, 36, 36), (81, 69, 61), (69, 69, 69), (28, 36, 32), (150, 125, 142), (40, 32, 24), (109, 109, 138), (77, 65, 57), (73, 65, 53), (32, 28, 16), (69, 61, 49), (45, 40, 32), (16, 24, 20), (146, 142, 194), (194, 194, 251), (16, 20, 20), (53, 53, 53), (53, 49, 53), (49, 53, 49), (45, 45, 45), (4, 12, 8), (121, 109, 113), (4, 8, 8), (194, 178, 231), (0, 8, 4), (0, 4, 4), (158, 134, 146), (69, 53, 32), (162, 166, 239), (57, 49, 40), (97, 93, 109), (117, 97, 89), (105, 93, 97), (101, 93, 93), (97, 93, 89), (117, 93, 69), (85, 85, 97), (202, 170, 202), (89, 81, 81), (77, 77, 89), (85, 81, 77), (174, 130, 105), (186, 166, 206), (85, 77, 77), (81, 77, 73), (40, 40, 36), (121, 117, 142), (89, 73, 61), (77, 73, 69), (36, 36, 32), (61, 65, 73), (69, 69, 61), (32, 32, 28), (138, 125, 142), (28, 32, 24), (69, 61, 61), (28, 28, 24), (28, 28, 24), (166, 150, 186), (24, 28, 20), (61, 61, 53), (24, 24, 20), (24, 24, 20), (24, 24, 20), (20, 24, 16), (20, 24, 16), (20, 20, 16), (20, 20, 16), (210, 190, 239), (57, 53, 49), (235, 210, 247), (45, 36, 24), (16, 20, 12), (16, 16, 12), (16, 16, 12), (194, 186, 243), (53, 49, 45), (12, 16, 8), (121, 109, 125), (162, 138, 162), (12, 12, 8), (8, 12, 4), (146, 134, 166), (8, 8, 4), (4, 8, 0), (40, 45, 32), (223, 194, 235), (4, 4, 0), (121, 101, 105), (138, 117, 125), (4, 0, 0), (117, 101, 101), (134, 113, 121), (45, 45, 40), (85, 89, 109), (40, 40, 45), (105, 89, 89), (97, 85, 81), (85, 85, 89), (93, 81, 77), (77, 77, 81), (85, 77, 69), (85, 73, 69), (130, 125, 166), (77, 69, 61), (65, 69, 69), (36, 36, 24), (65, 65, 69), (73, 65, 57), (24, 28, 32), (61, 61, 65), (69, 65, 53), (57, 61, 61), (69, 57, 53), (16, 24, 24), (45, 36, 36), (16, 20, 24), (61, 57, 45), (20, 20, 8), (8, 20, 16), (49, 49, 53), (45, 49, 49), (8, 12, 16), (16, 16, 4), (4, 12, 12), (12, 16, 0), (12, 12, 0), (170, 134, 142), (0, 4, 8), (117, 101, 113), (53, 53, 40), (53, 45, 40), (105, 89, 81), (93, 85, 89), (101, 81, 77), (36, 36, 36), (32, 32, 32), (81, 65, 57), (65, 61, 61), (24, 28, 24), (24, 24, 24), (20, 28, 20), (61, 57, 57), (69, 61, 45), (49, 40, 32), (20, 24, 20), (20, 24, 20), (16, 28, 16), (69, 57, 45), (57, 57, 53), (57, 57, 53), (20, 20, 20), (16, 24, 16), (166, 146, 170), (45, 36, 28), (16, 20, 16), (16, 20, 16), (16, 16, 16), (12, 20, 12), (12, 16, 12), (49, 49, 45), (12, 12, 12), (8, 16, 8), (182, 138, 146), (215, 202, 251), (8, 12, 8), (8, 8, 8), (4, 12, 4), (8, 4, 8), (4, 8, 4), (0, 12, 0), (4, 4, 4), (0, 8, 0), (182, 174, 235), (0, 4, 0)]
of course i would always rather use ExifTool.  is this achievable?  I pray it might be an easy change. 

- J


Phil Harvey

Here is a config file that will give you that exact format:

%Image::ExifTool::UserDefined = (
    'Image::ExifTool::PNG::Main' => {
        PLTE => {
            Name => 'Palette',
            ValueConv => q{
                my $str = '[' . join(', ', unpack('C*',$val)) . ']';
                $str =~ s/(\d+, \d+, \d+)/($1)/g;
                return $str;
            },
        },
    },
);

1;

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

blue-j

that's very generous, Phil!  thanks!  - J

blue-j

hey Phil, i am giving a try at doing the same with the tRNS chunk now.


%Image::ExifTool::UserDefined = (
    'Image::ExifTool::PNG::Main' => {
        tRNS => {
            Name => 'Transparency',
            ValueConv => q{
                my @transparency = unpack('C*',$val);
                my $colorType = GetTagValue('ColorType'); # pseudocode
                if ($colorType == 3) { # Indexed-color
                    return '[' . join(',', @transparency) . ']';
                } elsif ($colorType == 0 || $colorType == 4) { # Grayscale
                    return unpack('n', $val); # Assuming $val is the 2-byte grayscale value
                } elsif ($colorType == 2) { # Truecolor
                    my @rgbTransparency = unpack('n3', $val);
                    return '[' . join(',', @rgbTransparency) . ']';
                }
                return '';
            },
        },
    },
);


i just don't know how to get a value from another field in here (pseudocode abounding).  not sure i have ever done that! (note i futzed with the formatting to make an array of triplets in JSON, like [ [1,2,3], [4,5,6] ])




Phil Harvey

You need to use a Composite tag if you want to combine the values of multiple tags.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

blue-j

i put this in the composite section and it fails silently (-composite:PNGTransparency).  not sure whats going on... do you see the gaffe?

        PNGTransparency => {
            Require => {
                0 => 'PNG:Transparency',
                1 => 'PNG:ColorType',
            },
            ValueConv => q{
                my @transparencyData = unpack('C*', $val[0]);
                if ($val[1] == 3) { # Indexed-color/palette
                    return '[' . join(',', @transparencyData) . ']';
                } elsif ($val[1] == 0) { # Grayscale
                    # Unpack as a single 2-byte value
                    my ($transparency) = unpack('n', $val[0]);
                    return $transparency;
                } elsif ($val[1] == 2) { # RGB
                    # Unpack the first three 2-byte values from transparency data
                    my @rgbaTransparency = unpack('n3', $val[0]);
                    return '[' . join(',', @rgbaTransparency) . ']';
                }
                return '';
            },
        },

- J

Phil Harvey

This is how your code works for me:

> exiftool -config test.config OwlAlpha-0.5.png -PNGTransparency
PNG Transparency                : [83,67,65,76,65,82,40,48,120,55,102,101,99,98,101,48,97,51,97,51,56,41]

I've attached the attached config file.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

blue-j

Sigh.  It was a nesting issue.  You'd think with my being blue-j that would not occur.  Thanks, Phil!

blue-j

#8
the config actually was not working.  it did indeed export the values shown above, but it always exported something along those lines.  it was exporting the wrong text!  i pounded on this a lot and arrived here, which worked for me:

   
    'Image::ExifTool::PNG::Main' => {
        tRNS => {
            Name => 'Transparency',
            ValueConv => q{
                my @transparencyData = unpack('C*', $val);
                my $str = '[';
                $str .= join(',', @transparencyData);
                $str .= ']';  # Add closing bracket here, outside the join
                return $str;
            },
        },
    },

i just was not able to get a composite tag to work, but this meets my needs. not sure where i went wrong.

- J

Phil Harvey

Ah, yes.  Sorry.  I didn't check the value to see if it made sense.  I think that replacing $val[0] with $$val should fix this, but I don't have time to test it right now.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

blue-j

I gave that a try, but it didn't seem to change the output.  Thank you for your attention to this!  - J

Phil Harvey

At any rate, the reason for the incorrect output is because a scalar reference wasn't dereferenced.  The output is the byte sequence for "SCALAR...".  Not sure if it is worth spending time to fix it at this point though.

- phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

blue-j

#12
this seems to work, though i have not tested every permutation!  thanks for the tip!

        PNGTransparency => {
            Require => {
                0 => 'PNG:Transparency',
                1 => 'PNG:ColorType',
            },
            ValueConv => q{
                my @transparencyData = unpack('C*', ${$val[o]});
                if ($val[1] == 3) { # Indexed-color/palette
                    return '[' . join(',', @transparencyData) . ']';
                } elsif ($val[1] == 0) { # Grayscale
                    # Unpack as a single 2-byte value
                    my ($transparency) = unpack('n', ${$val[o]});
                    return $transparency;
                } elsif ($val[1] == 2) { # RGB
                    # Unpack the first three 2-byte values from transparency data
                    my @rgbaTransparency = unpack('n3', ${$val[o]});
                    return '[' . join(',', @rgbaTransparency) . ']';
                }
                return '';
            },
        },

- J


Phil Harvey

Yes, that does it.  But I thought $$val should be equivalent to $$val[0] (because I thought $val should be set to $val[0]).  Not sure why this didn't work, but again I don't have time to look into it right now.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

blue-j

strange.  i ran into a fail case and can't see the reason.  the hex is very simple:

00000000 89 50 4E 47:0D 0A 1A 0A|00 00 00 0D:49 48 44 52
00000010 00 00 00 20:00 00 00 20|02 03 00 00:00 0E 14 92
00000020 67 00 00 00:0C 50 4C 54|45 00 00 FF:00 00 FF 00
00000030 00 FF 00 00:FF B5 9F 43|CE 00 00 00:03 74 52 4E
00000040 53 00 55 AA:0B B9 27 39|00 00 00 14:49 44 41 54
00000050 78 5E 63 64:00 82 50 20|1E 2C 8C 55:83 8B 01 00
00000060 2B 17 15 61:16 33 2E 41|00 00 00 00:49 45 4E 44
00000070 AE 42 60 82

the tRNS chunk is 00 55 AA which is just 0 85 170, proper transparency values.  i don't see anything vexing in the data... ExifTool DOES print this PNG:Transparency as "0 85 170" with no binary flag... perhaps this points the way?

- J