Home | History | Annotate | Download | only in resource_check
      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.
      4 
      5 """Presubmit script for Chromium browser resources.
      6 
      7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
      8 for more details about the presubmit API built into gcl/git cl, and see
      9 http://www.chromium.org/developers/web-development-style-guide for the rules
     10 we're checking against here.
     11 """
     12 
     13 
     14 import os
     15 import struct
     16 
     17 
     18 class InvalidPNGException(Exception):
     19   pass
     20 
     21 
     22 class ResourceScaleFactors(object):
     23   """Verifier of image dimensions for Chromium resources.
     24 
     25   This class verifies the image dimensions of resources in the various
     26   resource subdirectories.
     27 
     28   Attributes:
     29       paths: An array of tuples giving the folders to check and their
     30           relevant scale factors. For example:
     31 
     32           [(100, 'default_100_percent'), (200, 'default_200_percent')]
     33   """
     34 
     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
     39     self.paths = paths
     40 
     41   def RunChecks(self):
     42     """Verifies the scale factors of resources being added or modified.
     43 
     44     Returns:
     45         An array of presubmit errors if any images were detected not
     46         having the correct dimensions.
     47     """
     48     def ImageSize(filename):
     49       with open(filename, 'rb', buffering=0) as f:
     50         data = f.read(24)
     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])
     54 
     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]))
     62 
     63     repository_path = self.input_api.os_path.relpath(
     64         self.input_api.PresubmitLocalPath(),
     65         self.input_api.change.RepositoryRoot())
     66     results = []
     67 
     68     # Check for affected files in any of the paths specified.
     69     affected_files = self.input_api.AffectedFiles(include_deletes=False)
     70     files = []
     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(),
     79               path_root)
     80           if relative_path not in files:
     81             files.append(relative_path)
     82 
     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.')
     86     for f in files:
     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)))
     92         continue
     93       try:
     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)))
     98         continue
     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):
    103           continue
    104         # Ensure that each image for a particular scale factor is the
    105         # correct scale of the base image.
    106         try:
    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)))
    111           continue
    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),
    119                 dimension_name,
    120                 scaled_size,
    121                 ' or '.join(map(str, valid_sizes)))))
    122     return results
    123