1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Presubmit script for Chromium browser resources.
7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8 for more details about the presubmit API built into depot_tools, and see
9 http://www.chromium.org/developers/web-development-style-guide for the rules
10 we're checking against here.
18 class InvalidPNGException(Exception):
22 class ResourceScaleFactors(object):
23 """Verifier of image dimensions for Chromium resources.
25 This class verifies the image dimensions of resources in the various
26 resource subdirectories.
29 paths: An array of tuples giving the folders to check and their
30 relevant scale factors. For example:
32 [(100, 'default_100_percent'), (200, 'default_200_percent')]
35 def __init__(self
, input_api
, output_api
, paths
):
36 """ Initializes ResourceScaleFactors with paths."""
37 self
.input_api
= input_api
38 self
.output_api
= output_api
42 """Verifies the scale factors of resources being added or modified.
45 An array of presubmit errors if any images were detected not
46 having the correct dimensions.
48 def ImageSize(filename
):
49 with
open(filename
, 'rb', buffering
=0) as f
:
51 if data
[:8] != '\x89PNG\r\n\x1A\n' or data
[12:16] != 'IHDR':
52 raise InvalidPNGException
53 return struct
.unpack('>ii', data
[16:24])
55 # Returns a list of valid scaled image sizes. The valid sizes are the
56 # floor and ceiling of (base_size * scale_percent / 100). This is equivalent
57 # to requiring that the actual scaled size is less than one pixel away from
58 # the exact scaled size.
59 def ValidSizes(base_size
, scale_percent
):
60 return sorted(set([(base_size
* scale_percent
) / 100,
61 (base_size
* scale_percent
+ 99) / 100]))
63 repository_path
= self
.input_api
.os_path
.relpath(
64 self
.input_api
.PresubmitLocalPath(),
65 self
.input_api
.change
.RepositoryRoot())
68 # Check for affected files in any of the paths specified.
69 affected_files
= self
.input_api
.AffectedFiles(include_deletes
=False)
71 for f
in affected_files
:
72 for path_spec
in self
.paths
:
73 path_root
= self
.input_api
.os_path
.join(
74 repository_path
, path_spec
[1])
75 if (f
.LocalPath().endswith('.png') and
76 f
.LocalPath().startswith(path_root
)):
77 # Only save the relative path from the resource directory.
78 relative_path
= self
.input_api
.os_path
.relpath(f
.LocalPath(),
80 if relative_path
not in files
:
81 files
.append(relative_path
)
83 corrupt_png_error
= ('Corrupt PNG in file %s. Note that binaries are not '
84 'correctly uploaded to the code review tool and must be directly '
85 'submitted using the dcommit command.')
87 base_image
= self
.input_api
.os_path
.join(self
.paths
[0][1], f
)
88 if not os
.path
.exists(base_image
):
89 results
.append(self
.output_api
.PresubmitError(
90 'Base image %s does not exist' % self
.input_api
.os_path
.join(
91 repository_path
, base_image
)))
94 base_dimensions
= ImageSize(base_image
)
95 except InvalidPNGException
:
96 results
.append(self
.output_api
.PresubmitError(corrupt_png_error
%
97 self
.input_api
.os_path
.join(repository_path
, base_image
)))
99 # Find all scaled versions of the base image and verify their sizes.
100 for i
in range(1, len(self
.paths
)):
101 image_path
= self
.input_api
.os_path
.join(self
.paths
[i
][1], f
)
102 if not os
.path
.exists(image_path
):
104 # Ensure that each image for a particular scale factor is the
105 # correct scale of the base image.
107 scaled_dimensions
= ImageSize(image_path
)
108 except InvalidPNGException
:
109 results
.append(self
.output_api
.PresubmitError(corrupt_png_error
%
110 self
.input_api
.os_path
.join(repository_path
, image_path
)))
112 for dimension_name
, base_size
, scaled_size
in zip(
113 ('width', 'height'), base_dimensions
, scaled_dimensions
):
114 valid_sizes
= ValidSizes(base_size
, self
.paths
[i
][0])
115 if scaled_size
not in valid_sizes
:
116 results
.append(self
.output_api
.PresubmitError(
117 'Image %s has %s %d, expected to be %s' % (
118 self
.input_api
.os_path
.join(repository_path
, image_path
),
121 ' or '.join(map(str, valid_sizes
)))))