def create_persona_preview_images(src, full_dst, **kw): """ Creates a 680x100 thumbnail used for the Persona preview and a 32x32 thumbnail used for search suggestions/detail pages. """ log.info('[1@None] Resizing persona images: %s' % full_dst) preview, full = amo.PERSONA_IMAGE_SIZES['header'] preview_w, preview_h = preview orig_w, orig_h = full with storage.open(src) as fp: i_orig = i = Image.open(fp) # Crop image from the right. i = i.crop((orig_w - (preview_w * 2), 0, orig_w, orig_h)) # Resize preview. i = i.resize(preview, Image.ANTIALIAS) i.load() with storage.open(full_dst[0], 'wb') as fp: i.save(fp, 'png') _, icon_size = amo.PERSONA_IMAGE_SIZES['icon'] icon_w, icon_h = icon_size # Resize icon. i = i_orig i.load() i = i.crop((orig_w - (preview_h * 2), 0, orig_w, orig_h)) i = i.resize(icon_size, Image.ANTIALIAS) i.load() with storage.open(full_dst[1], 'wb') as fp: i.save(fp, 'png') pngcrush_image(full_dst[0]) pngcrush_image(full_dst[1]) return True
def generate_static_theme_preview(theme_manifest, version_pk): tmpl = loader.get_template( 'devhub/addons/includes/static_theme_preview_svg.xml') file_ = File.objects.filter(version_id=version_pk).first() if not file_: return context = _build_static_theme_preview_context(theme_manifest, file_) sizes = sorted( amo.THEME_PREVIEW_SIZES.values(), lambda x, y: x['position'] - y['position']) for size in sizes: # Create a Preview for this size. preview = VersionPreview.objects.create( version_id=version_pk, position=size['position']) # Add the size to the context and render context.update(svg_render_size=size['full']) svg = tmpl.render(context).encode('utf-8') if write_svg_to_png(svg, preview.image_path): resize_image( preview.image_path, preview.thumbnail_path, size['thumbnail']) pngcrush_image(preview.image_path) preview_sizes = {} preview_sizes['image'] = size['full'] preview_sizes['thumbnail'] = size['thumbnail'] preview.update(sizes=preview_sizes) addon_id = Version.objects.values_list( 'addon_id', flat=True).get(id=version_pk) index_addons.delay([addon_id])
def pngcrush_existing_preview(preview_id): """ Call pngcrush_image() on the images of a given add-on Preview object. """ log.info('Crushing images for Preview %s', preview_id) preview = Preview.objects.get(pk=preview_id) pngcrush_image(preview.thumbnail_path) pngcrush_image(preview.image_path)
def render_to_png(template, context, preview, thumbnail_dimensions): svg = template.render(context).encode('utf-8') if write_svg_to_png(svg, preview.image_path): resize_image( preview.image_path, preview.thumbnail_path, thumbnail_dimensions, format=preview.get_format('thumbnail'), quality=35, # It's ignored for png format, so it's fine to always set. ) pngcrush_image(preview.image_path) return True
def save_persona_image(src, full_dst, **kw): """Creates a PNG of a Persona header image.""" log.info('[1@None] Saving persona image: %s' % full_dst) img = ImageCheck(storage.open(src)) if not img.is_image(): log.error('Not an image: %s' % src, exc_info=True) return with storage.open(src, 'rb') as fp: i = Image.open(fp) with storage.open(full_dst, 'wb') as fp: i.save(fp, 'png') pngcrush_image(full_dst) return True
def save_persona_image(src, full_dst, **kw): """Creates a PNG of a Persona header image.""" log.info('[1@None] Saving persona image: %s' % full_dst) img = ImageCheck(storage.open(src)) if not img.is_image(): log.error('Not an image: %s' % src, exc_info=True) return with storage.open(src, 'rb') as fp: i = Image.open(fp) with storage.open(full_dst, 'wb') as fp: i.save(fp, 'png') pngcrush_image(full_dst) return True
def generate_static_theme_preview(theme_manifest, header_root, preview): tmpl = loader.get_template( 'devhub/addons/includes/static_theme_preview_svg.xml') context = {'amo': amo} context.update({ process_color_value(prop, color) for prop, color in theme_manifest.get('colors', {}).items() }) images_dict = theme_manifest.get('images', {}) header_url = images_dict.get('headerURL', images_dict.get('theme_frame', '')) header_src, header_width, header_height = encode_header_image( os.path.join(header_root, header_url)) meet_or_slice = ('meet' if header_width < amo.THEME_PREVIEW_SIZE.width else 'slice') preserve_aspect_ratio = '%s %s' % ('xMaxYMin', meet_or_slice) context.update(header_src=header_src, header_src_height=header_height, preserve_aspect_ratio=preserve_aspect_ratio) # Limit the srcs rendered to 15 to ameliorate DOSing somewhat. # https://bugzilla.mozilla.org/show_bug.cgi?id=1435191 for background. additional_srcs = images_dict.get('additional_backgrounds', [])[:15] additional_alignments = (theme_manifest.get('properties', {}).get( 'additional_backgrounds_alignment', [])) additional_tiling = (theme_manifest.get('properties', {}).get( 'additional_backgrounds_tiling', [])) additional_backgrounds = [ AdditionalBackground(path, alignment, tiling, header_root) for (path, alignment, tiling) in izip_longest( additional_srcs, additional_alignments, additional_tiling) if path is not None ] context.update(additional_backgrounds=additional_backgrounds) svg = tmpl.render(context).encode('utf-8') image_size = write_svg_to_png(svg, preview.image_path) if image_size: pngcrush_image(preview.image_path) sizes = { # We mimic what resize_preview() does, but in our case, 'image' # dimensions are not amo.ADDON_PREVIEW_SIZES[1] but something # specific to static themes automatic preview. 'image': image_size, 'thumbnail': resize_image(preview.image_path, preview.thumbnail_path, amo.ADDON_PREVIEW_SIZES[0])[0] } preview.update(sizes=sizes)
def generate_static_theme_preview(theme_manifest, version_pk): # Make sure we import `index_addons` late in the game to avoid having # a "copy" of it here that won't get mocked by our ESTestCase from olympia.addons.tasks import index_addons tmpl = loader.get_template( 'devhub/addons/includes/static_theme_preview_svg.xml') file_ = File.objects.filter(version_id=version_pk).first() if not file_: return context = _build_static_theme_preview_context(theme_manifest, file_) renderings = sorted(amo.THEME_PREVIEW_RENDERINGS.values(), key=operator.itemgetter('position')) colors = None for rendering in renderings: # Create a Preview for this size. preview = VersionPreview.objects.create( version_id=version_pk, position=rendering['position'], sizes={'thumbnail_format': rendering['thumbnail_format']}, ) # Add the size to the context and render context.update(svg_render_size=rendering['full']) svg = tmpl.render(context).encode('utf-8') if write_svg_to_png(svg, preview.image_path): resize_image( preview.image_path, preview.thumbnail_path, rendering['thumbnail'], format=rendering['thumbnail_format'], quality= 35, # It's ignored for png format, so it's fine to always set. ) pngcrush_image(preview.image_path) # Extract colors once and store it for all previews. # Use the thumbnail for extra speed, we don't need to be super accurate. if colors is None: colors = extract_colors_from_image(preview.thumbnail_path) data = { 'sizes': { 'image': rendering['full'], 'thumbnail': rendering['thumbnail'], 'thumbnail_format': rendering['thumbnail_format'], }, 'colors': colors, } preview.update(**data) addon_id = Version.objects.values_list('addon_id', flat=True).get(id=version_pk) index_addons.delay([addon_id])
def test_pngcrush_image(subprocess_mock): subprocess_mock.Popen.return_value.communicate.return_value = ('', '') subprocess_mock.Popen.return_value.returncode = 0 # success assert pngcrush_image('/tmp/some_file.png') assert subprocess_mock.Popen.call_count == 1 assert subprocess_mock.Popen.call_args_list[0][0][0] == [ settings.PNGCRUSH_BIN, '-q', '-reduce', '-ow', '/tmp/some_file.png', '/tmp/some_file.crush.png', ] assert subprocess_mock.Popen.call_args_list[0][1] == { 'stdout': subprocess_mock.PIPE, 'stderr': subprocess_mock.PIPE, } # Make sure that exceptions for this are silent. subprocess_mock.Popen.side_effect = Exception assert not pngcrush_image('/tmp/some_other_file.png')
def test_pngcrush_image(subprocess_mock): subprocess_mock.Popen.return_value.communicate.return_value = ('', '') subprocess_mock.Popen.return_value.returncode = 0 # success assert pngcrush_image('/tmp/some_file.png') assert subprocess_mock.Popen.call_count == 1 assert subprocess_mock.Popen.call_args_list[0][0][0] == [ settings.PNGCRUSH_BIN, '-q', '-reduce', '-ow', '/tmp/some_file.png', '/tmp/some_file.crush.png', ] assert subprocess_mock.Popen.call_args_list[0][1] == { 'stdout': subprocess_mock.PIPE, 'stderr': subprocess_mock.PIPE, } # Make sure that exceptions for this are silent. subprocess_mock.Popen.side_effect = Exception assert not pngcrush_image('/tmp/some_other_file.png')
def pngcrush_existing_icons(addon_id): """ Call pngcrush_image() on the icons of a given add-on. """ log.info('Crushing icons for add-on %s', addon_id) addon = Addon.objects.get(pk=addon_id) if addon.icon_type != 'image/png': log.info('Aborting icon crush for add-on %s, icon type is not a PNG.', addon_id) return icon_dir = addon.get_icon_dir() pngcrush_image(os.path.join(icon_dir, '%s-64.png' % addon_id)) pngcrush_image(os.path.join(icon_dir, '%s-32.png' % addon_id)) # Return an icon hash that set_modified_on decorator will set on the add-on # after a small delay. This is normally done with the true md5 hash of the # original icon, but we don't necessarily have it here. We could read one # of the icons we modified but it does not matter just fake a hash to # indicate it was "manually" crushed. return {'icon_hash': 'mcrushed'}
def generate_static_theme_preview(theme_manifest, header_root, version_pk): tmpl = loader.get_template( 'devhub/addons/includes/static_theme_preview_svg.xml') context = _build_static_theme_preview_context(theme_manifest, header_root) for size in sorted(amo.THEME_PREVIEW_SIZES.values()): # Create a Preview for this size. preview = VersionPreview.objects.create( version_id=version_pk, position=size['position']) # Add the size to the context and render context.update(svg_render_size=size['full']) svg = tmpl.render(context).encode('utf-8') if write_svg_to_png(svg, preview.image_path): resize_image( preview.image_path, preview.thumbnail_path, size['thumbnail']) pngcrush_image(preview.image_path) preview_sizes = {} preview_sizes['image'] = size['full'] preview_sizes['thumbnail'] = size['thumbnail'] preview.update(sizes=preview_sizes)
def generate_static_theme_preview(theme_manifest, header_root, version_pk): tmpl = loader.get_template( 'devhub/addons/includes/static_theme_preview_svg.xml') context = _build_static_theme_preview_context(theme_manifest, header_root) for size in sorted(amo.THEME_PREVIEW_SIZES.values()): # Create a Preview for this size. preview = VersionPreview.objects.create(version_id=version_pk, position=size['position']) # Add the size to the context and render context.update(svg_render_size=size['full']) svg = tmpl.render(context).encode('utf-8') if write_svg_to_png(svg, preview.image_path): resize_image(preview.image_path, preview.thumbnail_path, size['thumbnail']) pngcrush_image(preview.image_path) preview_sizes = {} preview_sizes['image'] = size['full'] preview_sizes['thumbnail'] = size['thumbnail'] preview.update(sizes=preview_sizes)
def generate_static_theme_preview(theme_manifest, version_pk): # Make sure we import `index_addons` late in the game to avoid having # a "copy" of it here that won't get mocked by our ESTestCase from olympia.addons.tasks import index_addons tmpl = loader.get_template( 'devhub/addons/includes/static_theme_preview_svg.xml') file_ = File.objects.filter(version_id=version_pk).first() if not file_: return context = _build_static_theme_preview_context(theme_manifest, file_) sizes = sorted( amo.THEME_PREVIEW_SIZES.values(), key=operator.itemgetter('position')) colors = None for size in sizes: # Create a Preview for this size. preview = VersionPreview.objects.create( version_id=version_pk, position=size['position']) # Add the size to the context and render context.update(svg_render_size=size['full']) svg = tmpl.render(context).encode('utf-8') if write_svg_to_png(svg, preview.image_path): resize_image( preview.image_path, preview.thumbnail_path, size['thumbnail']) pngcrush_image(preview.image_path) # Extract colors once and store it for all previews. # Use the thumbnail for extra speed, we don't need to be super # accurate. if colors is None: colors = extract_colors_from_image(preview.thumbnail_path) data = { 'sizes': { 'image': size['full'], 'thumbnail': size['thumbnail'], }, 'colors': colors, } preview.update(**data) addon_id = Version.objects.values_list( 'addon_id', flat=True).get(id=version_pk) index_addons.delay([addon_id])
def pngcrush_existing_icons(addon_id): """ Call pngcrush_image() on the icons of a given add-on. """ log.info('Crushing icons for add-on %s', addon_id) addon = Addon.objects.get(pk=addon_id) if addon.icon_type != 'image/png': log.info('Aborting icon crush for add-on %s, icon type is not a PNG.', addon_id) return icon_dir = addon.get_icon_dir() pngcrush_image(os.path.join(icon_dir, '%s-64.png' % addon_id)) pngcrush_image(os.path.join(icon_dir, '%s-32.png' % addon_id)) # Return an icon hash that set_modified_on decorator will set on the add-on # after a small delay. This is normally done with the true md5 hash of the # original icon, but we don't necessarily have it here. We could read one # of the icons we modified but it does not matter just fake a hash to # indicate it was "manually" crushed. return { 'icon_hash': 'mcrushed' }
def generate_static_theme_preview(theme_manifest, header_root, preview_pk): preview = VersionPreview.objects.get(pk=preview_pk) tmpl = loader.get_template( 'devhub/addons/includes/static_theme_preview_svg.xml') context = _build_static_theme_preview_context(theme_manifest, header_root) # Then add the size and render context.update(svg_render_size=amo.THEME_PREVIEW_SIZES['full']) svg = tmpl.render(context).encode('utf-8') preview_sizes = {} if write_svg_to_png(svg, preview.image_path): pngcrush_image(preview.image_path) preview_sizes['image'] = amo.THEME_PREVIEW_SIZES['full'] # Then rerender at a different size context.update(svg_render_size=amo.THEME_PREVIEW_SIZES['thumb']) svg = tmpl.render(context).encode('utf-8') if write_svg_to_png(svg, preview.thumbnail_path): pngcrush_image(preview.thumbnail_path) preview_sizes['thumbnail'] = amo.THEME_PREVIEW_SIZES['thumb'] if preview_sizes: preview.update(sizes=preview_sizes)
def generate_static_theme_preview(theme_manifest, version_pk): tmpl = loader.get_template( 'devhub/addons/includes/static_theme_preview_svg.xml') file_ = File.objects.filter(version_id=version_pk).first() if not file_: return context = _build_static_theme_preview_context(theme_manifest, file_) sizes = sorted(amo.THEME_PREVIEW_SIZES.values(), lambda x, y: x['position'] - y['position']) colors = None for size in sizes: # Create a Preview for this size. preview = VersionPreview.objects.create(version_id=version_pk, position=size['position']) # Add the size to the context and render context.update(svg_render_size=size['full']) svg = tmpl.render(context).encode('utf-8') if write_svg_to_png(svg, preview.image_path): resize_image(preview.image_path, preview.thumbnail_path, size['thumbnail']) pngcrush_image(preview.image_path) # Extract colors once and store it for all previews. # Use the thumbnail for extra speed, we don't need to be super # accurate. if colors is None: colors = extract_colors_from_image(preview.thumbnail_path) data = { 'sizes': { 'image': size['full'], 'thumbnail': size['thumbnail'], }, 'colors': colors, } preview.update(**data) addon_id = Version.objects.values_list('addon_id', flat=True).get(id=version_pk) index_addons.delay([addon_id])
def pngcrush_existing_theme(persona_id): """ Call pngcrush_image() on the images of a given Persona object. """ log.info('Crushing images for Persona %s', persona_id) persona = Persona.objects.get(pk=persona_id) # Only do this on "new" Personas with persona_id = 0, the older ones (with # a persona_id) have jpeg and not pngs. if not persona.is_new(): log.info('Aborting images crush for Persona %s (too old).', persona_id) return pngcrush_image(persona.preview_path) # No need to crush thumb_path, it's the same as preview_path for "new" # Personas. pngcrush_image(persona.icon_path) if persona.header: pngcrush_image(persona.header_path) if persona.footer: pngcrush_image(persona.footer_path)
def pngcrush_existing_theme(persona_id): """ Call pngcrush_image() on the images of a given Persona object. """ log.info('Crushing images for Persona %s', persona_id) persona = Persona.objects.get(pk=persona_id) # Only do this on "new" Personas with persona_id = 0, the older ones (with # a persona_id) have jpeg and not pngs. if not persona.is_new(): log.info('Aborting images crush for Persona %s (too old).', persona_id) return pngcrush_image(persona.preview_path) # No need to crush thumb_path, it's the same as preview_path for "new" # Personas. pngcrush_image(persona.icon_path) if persona.header: pngcrush_image(persona.header_path) if persona.footer: pngcrush_image(persona.footer_path)