Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(312)

Unified Diff: tools/resources/optimize-ico-files.py

Issue 1377713002: optimize-ico-files: Move the logic for this script into a module. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@optimize-ico-files-fixmasks
Patch Set: Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/resources/ico_tools.py ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/resources/optimize-ico-files.py
diff --git a/tools/resources/optimize-ico-files.py b/tools/resources/optimize-ico-files.py
index 71185f1368611d1ca29a82f4124835a49ca5d3a2..2635e9c509ba5cf6c3cec048e97c584b9670de8e 100755
--- a/tools/resources/optimize-ico-files.py
+++ b/tools/resources/optimize-ico-files.py
@@ -17,204 +17,11 @@ image, it will not get smaller. 256x256 icons should be PNG-formatted first.
import argparse
import logging
-import math
import os
import StringIO
-import struct
-import subprocess
import sys
-import tempfile
-OPTIMIZE_PNG_FILES = 'tools/resources/optimize-png-files.sh'
-
-logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
-
-class InvalidFile(Exception):
- """Represents an invalid ICO file."""
-
-def IsPng(png_data):
- """Determines whether a sequence of bytes is a PNG."""
- return png_data.startswith('\x89PNG\r\n\x1a\n')
-
-def OptimizePngFile(temp_dir, png_filename, optimization_level=None):
- """Optimize a PNG file.
-
- Args:
- temp_dir: The directory containing the PNG file. Must be the only file in
- the directory.
- png_filename: The full path to the PNG file to optimize.
-
- Returns:
- The raw bytes of a PNG file, an optimized version of the input.
- """
- logging.debug('Crushing PNG image...')
- args = [OPTIMIZE_PNG_FILES]
- if optimization_level is not None:
- args.append('-o%d' % optimization_level)
- args.append(temp_dir)
- result = subprocess.call(args, stdout=sys.stderr)
- if result != 0:
- logging.warning('Warning: optimize-png-files failed (%d)', result)
- else:
- logging.debug('optimize-png-files succeeded')
-
- with open(png_filename, 'rb') as png_file:
- return png_file.read()
-
-def OptimizePng(png_data, optimization_level=None):
- """Optimize a PNG.
-
- Args:
- png_data: The raw bytes of a PNG file.
-
- 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)
- png_filename = os.path.join(temp_dir, 'image.png')
- with open(png_filename, 'wb') as png_file:
- png_file.write(png_data)
- return OptimizePngFile(temp_dir, png_filename,
- optimization_level=optimization_level)
-
- finally:
- 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 = []
- for y in range(height):
- bit_count = 0
- current_byte = 0
- for x in range(width):
- alpha = image_data[(y * width + x) * 4 + 3]
- current_byte <<= 1
- if ord(alpha) == 0:
- current_byte |= 1
- bit_count += 1
- if bit_count == 8:
- and_bytes.append(current_byte)
- bit_count = 0
- current_byte = 0
-
- # At the end of a row, pad the current byte.
- if bit_count > 0:
- current_byte <<= (8 - bit_count)
- and_bytes.append(current_byte)
- # And keep padding until a multiple of 4 bytes.
- while len(and_bytes) % 4 != 0:
- and_bytes.append(0)
-
- and_bytes = ''.join(map(chr, and_bytes))
- return and_bytes
-
-def RebuildANDMask(iconimage):
- """Rebuild the AND mask in an icon image.
-
- GIMP (<=2.8.14) creates a bad AND mask on 32-bit icon images (pixels with <50%
- opacity are marked as transparent, which end up looking black on Windows). So,
- if this is a 32-bit image, throw the mask away and recompute it from the alpha
- data. (See: https://bugzilla.gnome.org/show_bug.cgi?id=755200)
-
- Args:
- iconimage: Bytes of an icon image (the BMP data for an entry in an ICO
- file). Must be in BMP format, not PNG. Does not need to be 32-bit (if it
- is not 32-bit, this is a no-op).
-
- Returns:
- An updated |iconimage|, with the AND mask re-computed using
- ComputeANDMaskFromAlpha.
- """
- # Parse BITMAPINFOHEADER.
- (_, width, height, _, bpp, _, _, _, _, num_colors, _) = struct.unpack(
- '<LLLHHLLLLLL', iconimage[:40])
-
- if bpp != 32:
- # No alpha channel, so the mask cannot be "wrong" (it is the only source of
- # transparency information).
- return iconimage
-
- height /= 2
- xor_size = int(math.ceil(width * bpp / 32.0)) * 4 * height
-
- # num_colors can be 0, implying 2^bpp colors.
- xor_palette_size = (num_colors or (1 << bpp if bpp < 24 else 0)) * 4
- xor_data = iconimage[40 + xor_palette_size :
- 40 + xor_palette_size + xor_size]
-
- and_data = ComputeANDMaskFromAlpha(xor_data, width, height)
-
- # Replace the AND mask in the original icon data.
- return iconimage[:40 + xor_palette_size + xor_size] + and_data
-
-def OptimizeIcoFile(infile, outfile, optimization_level=None):
- """Read an ICO file, optimize its PNGs, and write the output to outfile.
-
- Args:
- infile: The file to read from. Must be a seekable file-like object
- containing a Microsoft ICO file.
- outfile: The file to write to.
- """
- filename = os.path.basename(infile.name)
- icondir = infile.read(6)
- zero, image_type, num_images = struct.unpack('<HHH', icondir)
- if zero != 0:
- raise InvalidFile('First word must be 0.')
- if image_type not in (1, 2):
- raise InvalidFile('Image type must be 1 or 2.')
-
- # Read and unpack each ICONDIRENTRY.
- icon_dir_entries = []
- for i in range(num_images):
- icondirentry = infile.read(16)
- icon_dir_entries.append(struct.unpack('<BBBBHHLL', icondirentry))
-
- # Read each icon's bitmap data, crush PNGs, and update icon dir entries.
- current_offset = infile.tell()
- icon_bitmap_data = []
- for i in range(num_images):
- width, height, num_colors, r1, r2, r3, size, _ = icon_dir_entries[i]
- width = width or 256
- height = height or 256
- offset = current_offset
- icon_data = infile.read(size)
- if len(icon_data) != size:
- raise EOFError()
-
- entry_is_png = IsPng(icon_data)
- logging.info('%s entry #%d: %dx%d, %d bytes (%s)', filename, i + 1, width,
- height, size, 'PNG' if entry_is_png else 'BMP')
-
- if entry_is_png:
- icon_data = OptimizePng(icon_data, optimization_level=optimization_level)
- 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,
- new_size, offset)
- icon_bitmap_data.append(icon_data)
-
- # Write the data back to outfile.
- outfile.write(icondir)
- for icon_dir_entry in icon_dir_entries:
- outfile.write(struct.pack('<BBBBHHLL', *icon_dir_entry))
- for icon_bitmap in icon_bitmap_data:
- outfile.write(icon_bitmap)
+import ico_tools
def main(args=None):
if args is None:
@@ -238,7 +45,7 @@ def main(args=None):
file.seek(0, os.SEEK_END)
old_length = file.tell()
file.seek(0, os.SEEK_SET)
- OptimizeIcoFile(file, buf, args.optimization_level)
+ ico_tools.OptimizeIcoFile(file, buf, args.optimization_level)
new_length = len(buf.getvalue())
« no previous file with comments | « tools/resources/ico_tools.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698