Chromium Code Reviews| Index: tools/resources/ico_tools.py |
| diff --git a/tools/resources/ico_tools.py b/tools/resources/ico_tools.py |
| index 259f7f197612b6dcc92e924e413729d78d280a19..d0a9aba900c1bed970601a28ce4d5cf1c4c27e3f 100644 |
| --- a/tools/resources/ico_tools.py |
| +++ b/tools/resources/ico_tools.py |
| @@ -11,6 +11,7 @@ import sys |
| import tempfile |
| OPTIMIZE_PNG_FILES = 'tools/resources/optimize-png-files.sh' |
| +IMAGEMAGICK_CONVERT = 'convert' |
| logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') |
| @@ -69,6 +70,79 @@ def OptimizePng(png_data, optimization_level=None): |
| os.unlink(png_filename) |
| os.rmdir(temp_dir) |
| +def ExportSingleEntry(icon_dir_entry, icon_data, outfile): |
| + """Export a single icon dir entry to its own ICO file. |
| + |
| + Args: |
| + icon_dir_entry: Struct containing the fields of an ICONDIRENTRY. |
| + icon_data: Raw pixel data of the icon. |
| + outfile: File object to write to. |
| + """ |
| + # Write the ICONDIR header. |
| + logging.debug('len(icon_data) = %d', len(icon_data)) |
| + outfile.write(struct.pack('<HHH', 0, 1, 1)) |
| + |
| + # Write the ICONDIRENTRY header. |
| + width, height, num_colors, r1, r2, r3, size, _ = icon_dir_entry |
| + offset = 22; |
| + icon_dir_entry = width, height, num_colors, r1, r2, r3, size, offset |
| + outfile.write(struct.pack('<BBBBHHLL', *icon_dir_entry)) |
| + |
| + # Write the image data. |
| + outfile.write(icon_data) |
| + |
| +def ConvertIcoToPng(ico_filename, png_filename): |
| + """Convert a single-entry ICO file to a PNG image. |
| + |
| + Requires that the user has `convert` (ImageMagick) installed. |
| + |
| + Raises: |
| + OSError: If ImageMagick was not found. |
| + subprocess.CalledProcessError: If convert failed. |
| + """ |
| + logging.debug('Converting BMP image to PNG...') |
| + args = [IMAGEMAGICK_CONVERT, ico_filename, png_filename] |
| + result = subprocess.check_call(args, stdout=sys.stderr) |
| + logging.info('Converted BMP image to PNG format') |
| + |
| +def OptimizeBmp(icon_dir_entry, icon_data): |
| + """Convert a BMP file to PNG and optimize it. |
| + |
| + Args: |
| + icon_dir_entry: Struct containing the fields of an ICONDIRENTRY. |
| + icon_data: Raw pixel data of the icon. |
| + |
| + Returns: |
| + The raw bytes of a PNG file, an optimized version of the input. |
| + """ |
| + temp_dir = tempfile.mkdtemp() |
| + try: |
| + logging.debug('temp_dir = %s', temp_dir) |
| + ico_filename = os.path.join(temp_dir, 'image.ico') |
| + png_filename = os.path.join(temp_dir, 'image.png') |
| + with open(ico_filename, 'wb') as ico_file: |
| + logging.debug('writing %s', ico_filename) |
| + ExportSingleEntry(icon_dir_entry, icon_data, ico_file) |
| + |
| + try: |
| + ConvertIcoToPng(ico_filename, png_filename) |
| + except Exception as e: |
| + logging.warning('Could not convert BMP to PNG format: %s', e) |
| + if isinstance(e, OSError): |
| + logging.info('This is because ImageMagick (`convert`) was not found. ' |
| + 'Please install it, or manually convert large BMP images ' |
| + 'into PNG before running this utility.') |
| + return icon_data |
| + |
| + return OptimizePngFile(temp_dir, png_filename) |
| + |
| + finally: |
| + if os.path.exists(ico_filename): |
| + os.unlink(ico_filename) |
| + if os.path.exists(png_filename): |
| + os.unlink(png_filename) |
| + os.rmdir(temp_dir) |
| + |
| def ComputeANDMaskFromAlpha(image_data, width, height): |
| """Compute an AND mask from 32-bit BGRA image data.""" |
| and_bytes = [] |
| @@ -175,19 +249,19 @@ def OptimizeIcoFile(infile, outfile, optimization_level=None): |
| height, size, 'PNG' if entry_is_png else 'BMP') |
| if entry_is_png: |
| + # It is a PNG. Crush it. |
| icon_data = OptimizePng(icon_data, optimization_level=optimization_level) |
| + elif width >= 256 or height >= 256: |
| + # It is a large BMP. Reformat as a PNG, then crush it. |
| + # Note: Smaller images must be kept uncompressed, for compatibility |
| + # with Windows XP. |
|
scottmg
2016/11/07 17:45:35
If this is really the only reason for this restric
Matt Giuca
2016/11/08 00:06:31
That's a great suggestion. I think so, but I'd hav
|
| + icon_data = OptimizeBmp(icon_dir_entries[i], icon_data) |
| else: |
| new_icon_data = RebuildANDMask(icon_data) |
| if new_icon_data != icon_data: |
| logging.info(' * Rebuilt AND mask for this image from alpha channel.') |
| icon_data = new_icon_data |
| - if width >= 256 or height >= 256: |
| - # TODO(mgiuca): Automatically convert large BMP images to PNGs. |
| - logging.warning('Entry #%d is a large image in uncompressed BMP ' |
| - 'format. Please manually convert to PNG format before ' |
| - 'running this utility.', i + 1) |
| - |
| new_size = len(icon_data) |
| current_offset += new_size |
| icon_dir_entries[i] = (width % 256, height % 256, num_colors, r1, r2, r3, |