| Index: tools/grit/grit/gather/chrome_scaled_image.py
|
| diff --git a/tools/grit/grit/gather/chrome_scaled_image.py b/tools/grit/grit/gather/chrome_scaled_image.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..864c9bddf602b0e18083868298f1fa920af1785a
|
| --- /dev/null
|
| +++ b/tools/grit/grit/gather/chrome_scaled_image.py
|
| @@ -0,0 +1,157 @@
|
| +#!/usr/bin/env python
|
| +# Copyright (c) 2012 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.
|
| +
|
| +'''Gatherer for <structure type="chrome_scaled_image">.
|
| +'''
|
| +
|
| +import os
|
| +import struct
|
| +
|
| +from grit import exception
|
| +from grit import lazy_re
|
| +from grit import util
|
| +from grit.gather import interface
|
| +
|
| +
|
| +_PNG_SCALE_CHUNK = '\0\0\0\0csCl\xc1\x30\x60\x4d'
|
| +
|
| +
|
| +def _RescaleImage(data, from_scale, to_scale):
|
| + if from_scale != to_scale:
|
| + assert from_scale == 100
|
| + # Rather than rescaling the image we add a custom chunk directing Chrome to
|
| + # rescale it on load. Just append it to the PNG data since
|
| + # _MoveSpecialChunksToFront will move it later anyway.
|
| + data += _PNG_SCALE_CHUNK
|
| + return data
|
| +
|
| +
|
| +_PNG_MAGIC = '\x89PNG\r\n\x1a\n'
|
| +
|
| +'''Mandatory first chunk in order for the png to be valid.'''
|
| +_FIRST_CHUNK = 'IHDR'
|
| +
|
| +'''Special chunks to move immediately after the IHDR chunk. (so that the PNG
|
| +remains valid.)
|
| +'''
|
| +_SPECIAL_CHUNKS = frozenset('csCl npTc'.split())
|
| +
|
| +'''Any ancillary chunk not in this list is deleted from the PNG.'''
|
| +_ANCILLARY_CHUNKS_TO_LEAVE = frozenset(
|
| + 'bKGD cHRM gAMA iCCP pHYs sBIT sRGB tRNS'.split())
|
| +
|
| +
|
| +def _MoveSpecialChunksToFront(data):
|
| + '''Move special chunks immediately after the IHDR chunk (so that the PNG
|
| + remains valid). Also delete ancillary chunks that are not on our whitelist.
|
| + '''
|
| + first = [_PNG_MAGIC]
|
| + special_chunks = []
|
| + rest = []
|
| + for chunk in _ChunkifyPNG(data):
|
| + type = chunk[4:8]
|
| + critical = type < 'a'
|
| + if type == _FIRST_CHUNK:
|
| + first.append(chunk)
|
| + elif type in _SPECIAL_CHUNKS:
|
| + special_chunks.append(chunk)
|
| + elif critical or type in _ANCILLARY_CHUNKS_TO_LEAVE:
|
| + rest.append(chunk)
|
| + return ''.join(first + special_chunks + rest)
|
| +
|
| +
|
| +def _ChunkifyPNG(data):
|
| + '''Given a PNG image, yield its chunks in order.'''
|
| + assert data.startswith(_PNG_MAGIC)
|
| + pos = 8
|
| + while pos != len(data):
|
| + length = 12 + struct.unpack_from('>I', data, pos)[0]
|
| + assert 12 <= length <= len(data) - pos
|
| + yield data[pos:pos+length]
|
| + pos += length
|
| +
|
| +
|
| +def _MakeBraceGlob(strings):
|
| + '''Given ['foo', 'bar'], return '{foo,bar}', for error reporting.
|
| + '''
|
| + if len(strings) == 1:
|
| + return strings[0]
|
| + else:
|
| + return '{' + ','.join(strings) + '}'
|
| +
|
| +
|
| +class ChromeScaledImage(interface.GathererBase):
|
| + '''Represents an image that exists in multiple layout variants
|
| + (e.g. "default", "touch") and multiple scale variants
|
| + (e.g. "100_percent", "200_percent").
|
| + '''
|
| +
|
| + split_context_re_ = lazy_re.compile(r'(.+)_(\d+)_percent\Z')
|
| +
|
| + def _FindInputFile(self):
|
| + output_context = self.grd_node.GetRoot().output_context
|
| + match = self.split_context_re_.match(output_context)
|
| + if not match:
|
| + raise exception.MissingMandatoryAttribute(
|
| + 'All <output> nodes must have an appropriate context attribute'
|
| + ' (e.g. context="touch_200_percent")')
|
| + req_layout, req_scale = match.group(1), int(match.group(2))
|
| +
|
| + layouts = [req_layout]
|
| + try_default_layout = self.grd_node.GetRoot().fallback_to_default_layout
|
| + if try_default_layout and 'default' not in layouts:
|
| + layouts.append('default')
|
| +
|
| + # TODO(tdanderson): Search in descending order of all image scales
|
| + # instead of immediately falling back to 100.
|
| + # See crbug.com/503643.
|
| + scales = [req_scale]
|
| + try_low_res = self.grd_node.FindBooleanAttribute(
|
| + 'fallback_to_low_resolution', default=False, skip_self=False)
|
| + if try_low_res and 100 not in scales:
|
| + scales.append(100)
|
| +
|
| + for layout in layouts:
|
| + for scale in scales:
|
| + dir = '%s_%s_percent' % (layout, scale)
|
| + path = os.path.join(dir, self.rc_file)
|
| + if os.path.exists(self.grd_node.ToRealPath(path)):
|
| + return path, scale, req_scale
|
| +
|
| + if not try_default_layout:
|
| + # The file was not found in the specified output context and it was
|
| + # explicitly indicated that the default context should not be searched
|
| + # as a fallback, so return an empty path.
|
| + return None, 100, req_scale
|
| +
|
| + # The file was found in neither the specified context nor the default
|
| + # context, so raise an exception.
|
| + dir = "%s_%s_percent" % (_MakeBraceGlob(layouts),
|
| + _MakeBraceGlob(map(str, scales)))
|
| + raise exception.FileNotFound(
|
| + 'Tried ' + self.grd_node.ToRealPath(os.path.join(dir, self.rc_file)))
|
| +
|
| + def GetInputPath(self):
|
| + path, scale, req_scale = self._FindInputFile()
|
| + return path
|
| +
|
| + def Parse(self):
|
| + pass
|
| +
|
| + def GetTextualIds(self):
|
| + return [self.extkey]
|
| +
|
| + def GetData(self, *args):
|
| + path, scale, req_scale = self._FindInputFile()
|
| + if path is None:
|
| + return None
|
| +
|
| + data = util.ReadFile(self.grd_node.ToRealPath(path), util.BINARY)
|
| + data = _RescaleImage(data, scale, req_scale)
|
| + data = _MoveSpecialChunksToFront(data)
|
| + return data
|
| +
|
| + def Translate(self, *args, **kwargs):
|
| + return self.GetData()
|
|
|