1ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org#!/usr/bin/env python 2ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org# Copyright (c) 2012 The Chromium Authors. All rights reserved. 3ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org# Use of this source code is governed by a BSD-style license that can be 4ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org# found in the LICENSE file. 5ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 6ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org'''Gatherer for <structure type="chrome_scaled_image">. 7ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org''' 8ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 9ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.orgimport os 10f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.orgimport struct 11ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 12ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.orgfrom grit import exception 13ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.orgfrom grit import lazy_re 14ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.orgfrom grit import util 15b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.orgfrom grit.gather import interface 16ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 17ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 18f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org_PNG_SCALE_CHUNK = '\0\0\0\0csCl\xc1\x30\x60\x4d' 19f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org 20f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org 21ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.orgdef _RescaleImage(data, from_scale, to_scale): 22f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org if from_scale != to_scale: 23f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org assert from_scale == 100 24f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org # Rather than rescaling the image we add a custom chunk directing Chrome to 25f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org # rescale it on load. Just append it to the PNG data since 26f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org # _MoveSpecialChunksToFront will move it later anyway. 27f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org data += _PNG_SCALE_CHUNK 28ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org return data 29ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 30ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 31f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org_PNG_MAGIC = '\x89PNG\r\n\x1a\n' 32f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org 33848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org'''Mandatory first chunk in order for the png to be valid.''' 34848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org_FIRST_CHUNK = 'IHDR' 35f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org 36848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org'''Special chunks to move immediately after the IHDR chunk. (so that the PNG 37848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.orgremains valid.) 38848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org''' 39848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org_SPECIAL_CHUNKS = frozenset('csCl npTc'.split()) 40f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org 41f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org'''Any ancillary chunk not in this list is deleted from the PNG.''' 42f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org_ANCILLARY_CHUNKS_TO_LEAVE = frozenset( 43f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org 'bKGD cHRM gAMA iCCP pHYs sBIT sRGB tRNS'.split()) 44f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org 45f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org 46f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.orgdef _MoveSpecialChunksToFront(data): 47848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org '''Move special chunks immediately after the IHDR chunk (so that the PNG 48848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org remains valid). Also delete ancillary chunks that are not on our whitelist. 49f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org ''' 50848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org first = [_PNG_MAGIC] 51848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org special_chunks = [] 52848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org rest = [] 53f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org for chunk in _ChunkifyPNG(data): 54f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org type = chunk[4:8] 55f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org critical = type < 'a' 56848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org if type == _FIRST_CHUNK: 57f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org first.append(chunk) 58848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org elif type in _SPECIAL_CHUNKS: 59848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org special_chunks.append(chunk) 60f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org elif critical or type in _ANCILLARY_CHUNKS_TO_LEAVE: 61f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org rest.append(chunk) 62848a66248a9cadc7c2adfe27a43c8db4cf534ab7flackr@chromium.org return ''.join(first + special_chunks + rest) 63f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org 64f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org 65f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.orgdef _ChunkifyPNG(data): 66f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org '''Given a PNG image, yield its chunks in order.''' 67f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org assert data.startswith(_PNG_MAGIC) 68f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org pos = 8 69f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org while pos != len(data): 70f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org length = 12 + struct.unpack_from('>I', data, pos)[0] 71f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org assert 12 <= length <= len(data) - pos 72f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org yield data[pos:pos+length] 73f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org pos += length 74f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org 75f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org 76ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.orgdef _MakeBraceGlob(strings): 77ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org '''Given ['foo', 'bar'], return '{foo,bar}', for error reporting. 78ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org ''' 79ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org if len(strings) == 1: 80ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org return strings[0] 81ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org else: 82ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org return '{' + ','.join(strings) + '}' 83ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 84ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 85ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.orgclass ChromeScaledImage(interface.GathererBase): 86ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org '''Represents an image that exists in multiple layout variants 87ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org (e.g. "default", "touch") and multiple scale variants 88ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org (e.g. "100_percent", "200_percent"). 89ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org ''' 90ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 91ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org split_context_re_ = lazy_re.compile(r'(.+)_(\d+)_percent\Z') 92ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 93ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org def _FindInputFile(self): 94ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org output_context = self.grd_node.GetRoot().output_context 95ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org match = self.split_context_re_.match(output_context) 96ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org if not match: 97ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org raise exception.MissingMandatoryAttribute( 98ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 'All <output> nodes must have an appropriate context attribute' 99ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org ' (e.g. context="touch_200_percent")') 100ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org req_layout, req_scale = match.group(1), int(match.group(2)) 101ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 102ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org layouts = [req_layout] 103ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org if 'default' not in layouts: 104ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org layouts.append('default') 105ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org scales = [req_scale] 106ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org try_low_res = self.grd_node.FindBooleanAttribute( 107ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 'fallback_to_low_resolution', default=False, skip_self=False) 108ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org if try_low_res and 100 not in scales: 109ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org scales.append(100) 110ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org for layout in layouts: 111ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org for scale in scales: 112ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org dir = '%s_%s_percent' % (layout, scale) 113ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org path = os.path.join(dir, self.rc_file) 114ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org if os.path.exists(self.grd_node.ToRealPath(path)): 115ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org return path, scale, req_scale 116a0794a012db500b125de83a8676b652e2ddd210abenrg@chromium.org # If we get here then the file is missing, so fail. 117a0794a012db500b125de83a8676b652e2ddd210abenrg@chromium.org dir = "%s_%s_percent" % (_MakeBraceGlob(layouts), 118a0794a012db500b125de83a8676b652e2ddd210abenrg@chromium.org _MakeBraceGlob(map(str, scales))) 119ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org raise exception.FileNotFound( 120ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 'Tried ' + self.grd_node.ToRealPath(os.path.join(dir, self.rc_file))) 121ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 122ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org def GetInputPath(self): 123ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org path, scale, req_scale = self._FindInputFile() 124ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org return path 125ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 126ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org def Parse(self): 127ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org pass 128ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 129ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org def GetTextualIds(self): 130ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org return [self.extkey] 131ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 132ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org def GetData(self, *args): 133ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org path, scale, req_scale = self._FindInputFile() 134b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org data = util.ReadFile(self.grd_node.ToRealPath(path), util.BINARY) 135f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org data = _RescaleImage(data, scale, req_scale) 136f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org data = _MoveSpecialChunksToFront(data) 137f6b78f5aee34cf1624f04c181da55432de41e9efbenrg@chromium.org return data 138ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org 139ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org def Translate(self, *args, **kwargs): 140ec8016c73b3b945b6284746230913d88653f35e7benrg@chromium.org return self.GetData() 141