# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from resources.types.resource_object import ResourceObject from resources.resource_map.resource_generator import ResourceGenerator from pebble_sdk_platform import pebble_platforms import bitmapgen import png2pblpng import re PNG_MIN_APP_MEMORY = 0x8000 # 32k, fairly arbitrarily class BitmapResourceGenerator(ResourceGenerator): type = 'bitmap' @staticmethod def definitions_from_dict(bld, definition_dict, resource_source_path): definitions = ResourceGenerator.definitions_from_dict(bld, definition_dict, resource_source_path) for d in definitions: d.memory_format = definition_dict.get('memoryFormat', 'smallest') d.space_optimization = definition_dict.get('spaceOptimization', None) d.storage_format = definition_dict.get('storageFormat', None) return definitions @classmethod def generate_object(cls, task, definition): env = task.generator.env memory_format = definition.memory_format.lower() # Some options are mutually exclusive. if definition.space_optimization == 'memory' and definition.storage_format == 'png': task.generator.bld.fatal("{}: spaceOptimization: memory and storageFormat: " "png are mutually exclusive.".format(definition.name)) if definition.space_optimization == 'storage' and definition.storage_format == 'pbi': task.generator.bld.fatal("{}: spaceOptimization: storage and storageFormat: " "pbi are mutually exclusive.".format(definition.name)) if definition.storage_format == 'png' and memory_format == '1bit': task.generator.bld.fatal("{}: PNG storage does not support non-palettised 1-bit images." .format(definition.name)) # If storage_format is not specified, it is completely determined by space_optimization. if definition.storage_format is None and definition.space_optimization is not None: format_mapping = { 'storage': 'png', 'memory': 'pbi', } try: definition.storage_format = format_mapping[definition.space_optimization] except KeyError: task.generator.bld.fatal("{}: Invalid spaceOptimization '{}'. Use one of {}." .format(definition.name, definition.space_optimization, ', '.join(format_mapping.keys()))) if definition.storage_format is None: if pebble_platforms[env.PLATFORM_NAME]['MAX_APP_MEMORY_SIZE'] < PNG_MIN_APP_MEMORY: definition.storage_format = 'pbi' else: definition.storage_format = 'png' # At this point, what we want to do should be completely determined (though, depending on # image content, not necessarily actually possible); begin figuring out what to actually do. is_color = 'color' in pebble_platforms[env.PLATFORM_NAME]['TAGS'] palette_name = png2pblpng.get_ideal_palette(is_color) _, _, bitdepth, _ = png2pblpng.get_palette_for_png(task.inputs[0].abspath(), palette_name, png2pblpng.DEFAULT_COLOR_REDUCTION) formats = { '1bit', '8bit', 'smallest', 'smallestpalette', '1bitpalette', '2bitpalette', '4bitpalette', } if memory_format not in formats: task.generator.bld.fatal("{}: Invalid memoryFormat {} (pick one of {})." .format(definition.name, definition.memory_format, ', '.join(formats))) # "smallest" is always palettised, unless the image has too many colours, in which case it # cannot be. if memory_format == 'smallest': if bitdepth <= 4: memory_format = 'smallestpalette' else: memory_format = '8bit' if 'palette' in memory_format: if memory_format == 'smallestpalette': # If they asked for "smallestpalette", replace that with its actual value. if bitdepth > 4: task.generator.bld.fatal("{} has too many colours for a palettised image" "(max 16), but 'SmallestPalette' specified." .format(definition.name)) else: memory_format = '{}bitpalette'.format(bitdepth) # Pull out however many bits we're supposed to use (which is exact, not a min or max) bits = int(re.match(r'^(\d+)bitpalette$', memory_format).group(1)) if bits < bitdepth: task.generator.bld.fatal("{}: requires at least {} bits.".format(definition.name, bitdepth)) if bits > 2 and not is_color: task.generator.bld.fatal("{}: can't use more than two bits on a black-and-white" "platform." .format(definition.name)) if definition.storage_format == 'pbi': pb = bitmapgen.PebbleBitmap(task.inputs[0].abspath(), bitmap_format='color', bitdepth=bits, crop=False, palette_name=palette_name) return ResourceObject(definition, pb.convert_to_pbi()) else: image_bytes = png2pblpng.convert_png_to_pebble_png_bytes(task.inputs[0].abspath(), palette_name, bitdepth=bits) return ResourceObject(definition, image_bytes) else: if memory_format == '1bit': pb = bitmapgen.PebbleBitmap(task.inputs[0].abspath(), bitmap_format='bw', crop=False) return ResourceObject(definition, pb.convert_to_pbi()) elif memory_format == '8bit': if not is_color: task.generator.bld.fatal("{}: can't use more than two bits on a black-and-white" "platform.".format(definition.name)) # generate an 8-bit pbi or png, as appropriate. if definition.storage_format == 'pbi': pb = bitmapgen.PebbleBitmap(task.inputs[0].abspath(), bitmap_format='color_raw', crop=False, palette_name=palette_name) return ResourceObject(definition, pb.convert_to_pbi()) else: image_bytes = png2pblpng.convert_png_to_pebble_png_bytes( task.inputs[0].abspath(), palette_name, bitdepth=8) return ResourceObject(definition, image_bytes) raise Exception("Got to the end without doing anything?")