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:

committed by
Chromium LUCI CQ

parent
73265ee7c7
commit
d899445d1d
@ -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
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:]))
|
Reference in New Issue
Block a user