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