# Split to Special PNG's v0.8 # (copyLEFT) 2015 Andrey "Efenstor" Pivovarov. No rights reserved # Copy freely, modify freely, spread the word and remember me in your prayers # This script intended to be used on images consisting of one opaque top layer # containing a sharp-edged object (e.g. logo, text, cut-out, etc.) with # anti-aliased edges and one or more additional smooth layers (shadow, glow, # etc.). The script saves such an image as three PNG files (layers), intended # to be stacked (e.g. on a web-page using absolute CSS positioning). The result # achieved by this is significantly smaller amount of data needed to be # downloaded with a visually similar result. # NOTE: before running the script ensure that there is no background layer! # History: # v0.8 - Initial release # v0.9 - "Layer 1 (background) downscale" method added + cosmetic improvements from gimpfu import * import os.path def deinit(img): # Deinitialize the plugin pdb.gimp_undo_push_group_end(img) pdb.gimp_progress_end() pdb.gimp_displays_flush() def object_with_shadow_to_png_layers(img, active, base_filename, layer1_alpha_quant, \ layer1_downscale, layer2_alpha_quant, layer3_colors, layer3_dither, png_compression, \ png_interlace): # Check the requirements if len(base_filename) == 0: pdb.gimp_message("Please specify the base filename!") return if len(img.layers) < 2: pdb.gimp_message("The image must contain at least two layers!") return # Fix the extension (if needed) last_dot = str.rfind(base_filename, ".") if last_dot > 0: ext = str.lower(base_filename[last_dot:]) if len(ext) == 0: bfilename = base_filename[:last_dot] elif ext != "png": bfilename = base_filename else: bfilename = base_filename # Initialization gimp.progress_init("Preparing...") pdb.gimp_undo_push_group_start(img) # Layer #3 (object without edges, indexed) new_img = gimp.Image(img.width, img.height) new_layer = pdb.gimp_layer_new_from_drawable(img.layers[0], new_img) new_img.add_layer(new_layer, -1) if new_layer.mask == None: new_mask = new_layer.create_mask(3) pdb.gimp_layer_add_mask(new_layer, new_mask) # Save alpha as channel orig_alpha = pdb.gimp_channel_new_from_component(new_img, 5, "Original Alpha") new_img.add_channel(orig_alpha) # Cut out the edges pdb.gimp_threshold(new_layer.mask, 255, 255) # Apply the mask and save alpha pdb.gimp_layer_remove_mask(new_layer, 0) cut_alpha = pdb.gimp_channel_new_from_component(new_img, 5, "Cut Alpha") new_img.add_channel(cut_alpha) # Convert to indexed pdb.gimp_image_convert_indexed(new_img, layer3_dither, 0, layer3_colors, False, True, "") # Save pdb.gimp_layer_resize_to_image_size(new_layer) fname = bfilename+"_layer3.png" pdb.file_png_save2(new_img, new_layer, fname, os.path.basename(fname), png_interlace, png_compression, False, False, False, False, False, False, False) # Delete the layer (now it's empty but there are some channels saved) new_img.remove_layer(new_layer) # Convert to RGB pdb.gimp_image_convert_rgb(new_img) # Layer #2 (edges, RGBA) new_layer = pdb.gimp_layer_new_from_drawable(img.layers[0], new_img) new_img.add_layer(new_layer, -1) if new_layer.mask != None: pdb.gimp_layer_remove_mask(new_layer, 0) # Leave edges only pdb.gimp_image_select_item(new_img, 2, orig_alpha) pdb.gimp_selection_invert(new_img) pdb.gimp_image_select_item(new_img, 0, cut_alpha) pdb.gimp_edit_clear(new_layer) pdb.gimp_selection_none(new_img) # Quantize alpha new_mask = new_layer.create_mask(3) pdb.gimp_layer_add_mask(new_layer, new_mask) pdb.gimp_posterize(new_mask, layer2_alpha_quant) pdb.gimp_layer_remove_mask(new_layer, 0) # Save pdb.gimp_layer_resize_to_image_size(new_layer) fname = bfilename+"_layer2.png" pdb.file_png_save2(new_img, new_layer, fname, os.path.basename(fname), png_interlace, png_compression, False, False, False, False, False, False, False) # Delete the image gimp.delete(new_img) # Layer #1 (background, RGBA) new_img = pdb.gimp_image_duplicate(img) new_img.remove_layer(new_img.layers[0]) new_layer = pdb.gimp_image_merge_visible_layers(new_img, 1) # Downscale if layer1_downscale>1: orig_int = pdb.gimp_context_get_interpolation() pdb.gimp_context_set_interpolation(1) new_width = float(new_img.width) / layer1_downscale if new_width<1: new_width = 1 new_height = float(new_img.height) / layer1_downscale if new_height<1: new_height = 1 pdb.gimp_image_scale(new_img, int(new_width+.5), int(new_height+.5)) pdb.gimp_context_set_interpolation(orig_int) # Quantize alpha new_mask = new_layer.create_mask(3) pdb.gimp_layer_add_mask(new_layer, new_mask) pdb.gimp_posterize(new_mask, layer1_alpha_quant) pdb.gimp_layer_remove_mask(new_layer, 0) # Save pdb.gimp_layer_resize_to_image_size(new_layer) fname = bfilename+"_layer1.png" pdb.file_png_save2(new_img, new_layer, fname, os.path.basename(fname), png_interlace, png_compression, False, False, False, False, False, False, False) # Delete the image gimp.delete(new_img) # Deinitialize deinit(img) register( "split_to_special_pngs", "Splits an image into several specially-optimized PNG files (see the script file for details)", "This function splits an image containing a sharp-edged object with a smooth translucent background (e.g. drop shadow) into several images, optimizes them differently and saves as PNG files intended for stacking on a web page.", "Efesntor", "(copyLEFT) Andrey \"Efenstor\" Pivovarov", "2015", "/Filters/Web/Split to Special _PNG's...", "RGB*, GRAY*", [ (PF_FILE, "base_filename", "_Base filename (extension not required)", ""), (PF_SPINNER, "layer1_alpha_quant", "Layer 1 (background) alpha quantization", 128, (2,255,1)), (PF_SPINNER, "layer1_downscale", "Layer1 (background) downscale factor", 1, (1,100,1)), (PF_SPINNER, "layer2_alpha_quant", "Layer 2 (edges) alpha quantization", 8, (2,255,1)), (PF_SPINNER, "layer3_colors", "Layer 3 (object) _number of palette colors", 64, (2,256,1)), (PF_OPTION, "layer3_dither", "Layer 3 (object) _dithering", 0, ("None", "Floyd-Steinberg", "Floyd-Steinberg (reduced bleed)", "Positioned")), (PF_SLIDER, "png_compression", "PNG _compression level", 9, (0,9,1)), (PF_TOGGLE, "png_interlace", "PNG _interlaced", False) ], [], object_with_shadow_to_png_layers) main()