0

Create png_fix and document its use

Remove png_check and png_make_srgb, and replace them with png_fix. It
aggressively rebuilds png files by discarding chunks.

Bug: 1289511
Change-Id: I3489e0d02ff9ffb58bd19fa48850c78b476fb701
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3422234
Auto-Submit: Avi Drissman <avi@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
Commit-Queue: Mark Mentovai <mark@chromium.org>
Cr-Commit-Position: refs/heads/main@{#964776}
This commit is contained in:
Avi Drissman
2022-01-28 21:08:13 +00:00
committed by Chromium LUCI CQ
parent 73265ee7c7
commit d899445d1d
4 changed files with 121 additions and 153 deletions

@ -48,8 +48,7 @@ in PNG format: 16×16, 32×32, 128×128, 256×256, and 512×512, named `16.png`,
1. Process the `.png` files with:
1. `optipng -o7 -zm1-9`
1. `advpng -z4 -i50`
1. `png_make_srgb.py`
1. `png_check.py`
1. `png_fix.py`
1. Create the `.icns` file with the `makeicns2` you compiled:
`makeicns2 <name>.iconset`

@ -1,65 +0,0 @@
#!/usr/bin/env python
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Does a quick parse of a png file, to make sure that no unneeded png chunks are
# present in the file.
import struct
import sys
def verify(path):
file = open(path, 'rb')
magic = file.read(8)
assert magic == b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a'
color_type = None
chunk_types = []
while True:
chunk_header = file.read(8)
(chunk_length, chunk_type) = struct.unpack('>I4s', chunk_header)
if chunk_type == b'IHDR':
ihdr = file.read(chunk_length)
(width, height, bit_depth, color_type, compression_method,
filter_method, interlace_method) = struct.unpack('>2I5b', ihdr)
else:
file.seek(chunk_length, 1)
chunk_footer = file.read(4)
(chunk_crc,) = struct.unpack('>I', chunk_footer)
chunk_types.append(chunk_type)
if chunk_type == b'IEND':
break
# The only two color types that have transparency and can be used for icons
# are types 3 (indexed) and 6 (direct RGBA).
assert color_type in (3, 6), "Disallowed color type {}".format(color_type)
allowed_chunk_types = [b'IHDR', b'sRGB', b'IDAT', b'IEND']
if color_type == 3:
allowed_chunk_types.extend([b'PLTE', b'tRNS'])
for chunk_type in chunk_types:
assert (chunk_type in allowed_chunk_types
), "Disallowed chunk of type {}".format(chunk_type)
eof = file.read(1)
assert len(eof) == 0
file.close()
def main(args):
for path in args:
print(path)
verify(path)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

120
tools/mac/icons/png_fix.py Executable file

@ -0,0 +1,120 @@
#!/usr/bin/env python3
# Copyright 2022 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Rebuilds PNG files intended for use as icon images on the Mac. It is
# reasonably robust, but is not designed to be robust against adversarially-
# constructed files.
#
# This is an opinionated script and makes assumptions about the desired
# characteristics of a PNG file for use as a Mac icon. All users of this script
# must verify that those are the correct assumptions for their use case before
# using it.
import argparse
import binascii
import os
import struct
import sys
_PNG_MAGIC = b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a'
_CHUNK_HEADER_STRUCT = struct.Struct('>I4s')
_CHUNK_CRC_STRUCT = struct.Struct('>I')
class FormatError(Exception):
pass
def _process_path(path):
with open(path, 'r+b') as file:
return _process_file(file)
def _process_file(file):
magic = file.read(len(_PNG_MAGIC))
if magic != _PNG_MAGIC:
raise FormatError(file, file.tell(), 'bad magic', magic, _PNG_MAGIC)
chunks = {}
while True:
chunk_header = file.read(_CHUNK_HEADER_STRUCT.size)
(chunk_length, chunk_type) = _CHUNK_HEADER_STRUCT.unpack(chunk_header)
chunk = chunk_header + file.read(chunk_length + _CHUNK_CRC_STRUCT.size)
if chunk_type in chunks:
raise FormatError(file, file.tell(), 'duplicate chunk', chunk_type)
chunks[chunk_type] = chunk
if chunk_type == b'IEND':
break
eof = file.read(1)
if len(eof) != 0:
raise FormatError(file, '\'IEND\' chunk not at end')
ihdr = chunks[b'IHDR'][_CHUNK_HEADER_STRUCT.size:-_CHUNK_CRC_STRUCT.size]
(ihdr_width, ihdr_height, ihdr_bit_depth, ihdr_color_type,
ihdr_compression_method, ihdr_filter_method,
ihdr_interlace_method) = struct.unpack('>2I5b', ihdr)
# The only two color types that have transparency and can be used for icons
# are types 3 (indexed) and 6 (direct RGBA).
if ihdr_color_type not in (3, 6):
raise FormatError(file, 'disallowed color type', color_type)
if ihdr_color_type == 3 and b'PLTE' not in chunks:
raise FormatError(file, 'indexed color requires \'PLTE\' chunk')
if ihdr_color_type == 3 and b'tRNS' not in chunks:
raise FormatError(file, 'indexed color requires \'tRNS\' chunk')
if b'IDAT' not in chunks:
raise FormatError(file, 'missing \'IDAT\' chunk')
if b'iCCP' in chunks:
raise FormatError(file, 'disallowed color profile; sRGB only')
if b'sRGB' not in chunks:
# Note that a value of 0 is a perceptual rendering intent (e.g.
# photographs) while a value of 1 is a relative colorimetric rendering
# intent (e.g. icons). Every macOS icon that has an 'sRGB' chunk uses 0
# so that is what is used here. Please forgive us, UX.
#
# Reference:
# http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.sRGB
srgb_chunk_type = struct.pack('>4s', b'sRGB')
srgb_chunk_data = struct.pack('>b', 0) # Perceptual
srgb_chunk_length = struct.pack('>I', len(srgb_chunk_data))
srgb_chunk_crc = struct.pack(
'>I', binascii.crc32(srgb_chunk_type + srgb_chunk_data))
chunks[b'sRGB'] = (
srgb_chunk_length + srgb_chunk_type + srgb_chunk_data +
srgb_chunk_crc)
file.seek(len(_PNG_MAGIC), os.SEEK_SET)
file.write(chunks[b'IHDR'])
file.write(chunks[b'sRGB'])
if ihdr_color_type == 3:
file.write(chunks[b'PLTE'])
file.write(chunks[b'tRNS'])
file.write(chunks[b'IDAT'])
file.write(chunks[b'IEND'])
file.truncate()
def main(args):
parser = argparse.ArgumentParser()
parser.add_argument('paths', nargs='+', metavar='path')
parsed = parser.parse_args(args)
for path in parsed.paths:
print(path)
_process_path(path)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

@ -1,86 +0,0 @@
#!/usr/bin/env python
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Adds an sRGB chunk to a png file if one isn't already there.
#
# This is quick and dirty. It doesn't check for iCCP or other chunks that might
# conflict with the sRGB. Obviously, only use this if it's desirable for the
# png file to be treated as sRGB.
import binascii
import struct
import sys
def make_srgb(path):
file = open(path, 'r+b')
magic = file.read(8)
assert magic == b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a'
found_ihdr = False
after_ihdr = 0
found_srgb = False
while True:
chunk_header = file.read(8)
(chunk_length, chunk_type) = struct.unpack('>I4s', chunk_header)
file.seek(chunk_length, 1)
chunk_footer = file.read(4)
(chunk_crc,) = struct.unpack('>I', chunk_footer)
if chunk_type == b'IHDR':
assert not found_ihdr
found_ihdr = True
after_ihdr = file.tell()
else:
assert found_ihdr
if chunk_type == b'sRGB':
assert not found_srgb
found_srgb = True
if chunk_type == b'IEND':
break
eof = file.read(1)
assert len(eof) == 0
if not found_srgb:
file.seek(after_ihdr)
remainder = file.read()
file.seek(after_ihdr)
# Note that a value of 0 is a perceptual rendering intent (e.g.
# photographs) while a value of 1 is a relative colorimetric rendering
# intent (e.g. icons). Every macOS icon that has an 'sRGB' chunk uses 0
# so that is what is used here. Please forgive us, UX.
#
# Reference:
# http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.sRGB
srgb_chunk_type = struct.pack('>4s', b'sRGB')
srgb_chunk_data = struct.pack('>b', 0) # Perceptual
srgb_chunk_length = struct.pack('>I', len(srgb_chunk_data))
srgb_chunk_crc = struct.pack(
'>I',
binascii.crc32(srgb_chunk_type + srgb_chunk_data) & 0xffffffff)
srgb_chunk = (
srgb_chunk_length + srgb_chunk_type + srgb_chunk_data +
srgb_chunk_crc)
file.write(srgb_chunk)
file.write(remainder)
file.close()
def main(args):
for path in args:
print(path)
make_srgb(path)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))