1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.assetstudiolib; 18 19 import com.android.resources.Density; 20 21 import java.awt.image.BufferedImage; 22 import java.io.File; 23 import java.io.IOException; 24 import java.io.InputStream; 25 import java.net.URISyntaxException; 26 import java.net.URL; 27 import java.security.ProtectionDomain; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.Comparator; 31 import java.util.Enumeration; 32 import java.util.Iterator; 33 import java.util.LinkedHashMap; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.jar.JarFile; 37 import java.util.zip.ZipEntry; 38 import java.util.zip.ZipFile; 39 40 import javax.imageio.ImageIO; 41 42 /** 43 * The base Generator class. 44 */ 45 public abstract class GraphicGenerator { 46 /** 47 * Options used for all generators. 48 */ 49 public static class Options { 50 /** Minimum version (API level) of the SDK to generate icons for */ 51 public int minSdk = 1; 52 53 /** Source image to use as a basis for the icon */ 54 public BufferedImage sourceImage; 55 56 /** The density to generate the icon with */ 57 public Density density = Density.XHIGH; 58 } 59 60 /** Shapes that can be used for icon backgrounds */ 61 public static enum Shape { 62 /** Circular background */ 63 CIRCLE("circle"), 64 /** Square background */ 65 SQUARE("square"); 66 67 /** Id, used in filenames to identify associated stencils */ 68 public final String id; 69 Shape(String id)70 Shape(String id) { 71 this.id = id; 72 } 73 } 74 75 /** Foreground effects styles */ 76 public static enum Style { 77 /** No effects */ 78 SIMPLE("fore1"), 79 /** "Fancy" effects */ 80 FANCY("fore2"), 81 /** A glossy look */ 82 GLOSSY("fore3"); 83 84 /** Id, used in filenames to identify associated stencils */ 85 public final String id; 86 Style(String id)87 Style(String id) { 88 this.id = id; 89 } 90 } 91 92 /** 93 * Generate a single icon using the given options 94 * 95 * @param context render context to use for looking up resources etc 96 * @param options options controlling the appearance of the icon 97 * @return a {@link BufferedImage} with the generated icon 98 */ generate(GraphicGeneratorContext context, Options options)99 public abstract BufferedImage generate(GraphicGeneratorContext context, Options options); 100 101 /** 102 * Computes the target filename (relative to the Android project folder) 103 * where an icon rendered with the given options should be stored. This is 104 * also used as the map keys in the result map used by 105 * {@link #generate(String, Map, GraphicGeneratorContext, Options, String)}. 106 * 107 * @param options the options object used by the generator for the current 108 * image 109 * @param name the base name to use when creating the path 110 * @return a path relative to the res/ folder where the image should be 111 * stored (will always use / as a path separator, not \ on Windows) 112 */ getIconPath(Options options, String name)113 protected String getIconPath(Options options, String name) { 114 return getIconFolder(options) + '/' + getIconName(options, name); 115 } 116 117 /** 118 * Gets name of the file itself. It is sometimes modified by options, for 119 * example in unselected tabs we change foo.png to foo-unselected.png 120 */ getIconName(Options options, String name)121 protected String getIconName(Options options, String name) { 122 return name + ".png"; //$NON-NLS-1$ 123 } 124 125 /** 126 * Gets name of the folder to contain the resource. It usually includes the 127 * density, but is also sometimes modified by options. For example, in some 128 * notification icons we add in -v9 or -v11. 129 */ getIconFolder(Options options)130 protected String getIconFolder(Options options) { 131 return "res/drawable-" + options.density.getResourceValue(); //$NON-NLS-1$ 132 } 133 134 /** 135 * Generates a full set of icons into the given map. The values in the map 136 * will be the generated images, and each value is keyed by the 137 * corresponding relative path of the image, which is determined by the 138 * {@link #getIconPath(Options, String)} method. 139 * 140 * @param category the current category to place images into (if null the 141 * density name will be used) 142 * @param categoryMap the map to put images into, should not be null. The 143 * map is a map from a category name, to a map from file path to 144 * image. 145 * @param context a generator context which for example can load resources 146 * @param options options to apply to this generator 147 * @param name the base name of the icons to generate 148 */ generate(String category, Map<String, Map<String, BufferedImage>> categoryMap, GraphicGeneratorContext context, Options options, String name)149 public void generate(String category, Map<String, Map<String, BufferedImage>> categoryMap, 150 GraphicGeneratorContext context, Options options, String name) { 151 Density[] densityValues = Density.values(); 152 // Sort density values into ascending order 153 Arrays.sort(densityValues, new Comparator<Density>() { 154 @Override 155 public int compare(Density d1, Density d2) { 156 return d1.getDpiValue() - d2.getDpiValue(); 157 } 158 }); 159 160 for (Density density : densityValues) { 161 if (!density.isValidValueForDevice()) { 162 continue; 163 } 164 if (density == Density.TV) { 165 // Not yet supported -- missing stencil image 166 continue; 167 } 168 options.density = density; 169 BufferedImage image = generate(context, options); 170 if (image != null) { 171 String mapCategory = category; 172 if (mapCategory == null) { 173 mapCategory = options.density.getResourceValue(); 174 } 175 Map<String, BufferedImage> imageMap = categoryMap.get(mapCategory); 176 if (imageMap == null) { 177 imageMap = new LinkedHashMap<String, BufferedImage>(); 178 categoryMap.put(mapCategory, imageMap); 179 } 180 imageMap.put(getIconPath(options, name), image); 181 } 182 } 183 } 184 185 /** 186 * Returns the scale factor to apply for a given MDPI density to compute the 187 * absolute pixel count to use to draw an icon of the given target density 188 * 189 * @param density the density 190 * @return a factor to multiple mdpi distances with to compute the target density 191 */ getMdpiScaleFactor(Density density)192 public static float getMdpiScaleFactor(Density density) { 193 return density.getDpiValue() / (float) Density.MEDIUM.getDpiValue(); 194 } 195 196 /** 197 * Returns one of the built in stencil images, or null 198 * 199 * @param relativePath stencil path such as "launcher-stencil/square/web/back.png" 200 * @return the image, or null 201 * @throws IOException if an unexpected I/O error occurs 202 */ getStencilImage(String relativePath)203 public static BufferedImage getStencilImage(String relativePath) throws IOException { 204 InputStream is = GraphicGenerator.class.getResourceAsStream(relativePath); 205 return ImageIO.read(is); 206 } 207 208 /** 209 * Returns the icon (32x32) for a given clip art image. 210 * 211 * @param name the name of the image to be loaded (which can be looked up via 212 * {@link #getClipartNames()}) 213 * @return the icon image 214 * @throws IOException if the image cannot be loaded 215 */ getClipartIcon(String name)216 public static BufferedImage getClipartIcon(String name) throws IOException { 217 InputStream is = GraphicGenerator.class.getResourceAsStream( 218 "/images/clipart/small/" + name); 219 return ImageIO.read(is); 220 } 221 222 /** 223 * Returns the full size clip art image for a given image name. 224 * 225 * @param name the name of the image to be loaded (which can be looked up via 226 * {@link #getClipartNames()}) 227 * @return the clip art image 228 * @throws IOException if the image cannot be loaded 229 */ getClipartImage(String name)230 public static BufferedImage getClipartImage(String name) throws IOException { 231 InputStream is = GraphicGenerator.class.getResourceAsStream( 232 "/images/clipart/big/" + name); 233 return ImageIO.read(is); 234 } 235 236 /** 237 * Returns the names of available clip art images which can be obtained by passing the 238 * name to {@link #getClipartIcon(String)} or 239 * {@link GraphicGenerator#getClipartImage(String)} 240 * 241 * @return an iterator for the available image names 242 */ getClipartNames()243 public static Iterator<String> getClipartNames() { 244 List<String> names = new ArrayList<String>(80); 245 try { 246 String pathPrefix = "images/clipart/big/"; //$NON-NLS-1$ 247 ProtectionDomain protectionDomain = GraphicGenerator.class.getProtectionDomain(); 248 URL url = protectionDomain.getCodeSource().getLocation(); 249 File file; 250 try { 251 file = new File(url.toURI()); 252 } catch (URISyntaxException e) { 253 file = new File(url.getPath()); 254 } 255 final ZipFile zipFile = new JarFile(file); 256 Enumeration<? extends ZipEntry> enumeration = zipFile.entries(); 257 while (enumeration.hasMoreElements()) { 258 ZipEntry zipEntry = enumeration.nextElement(); 259 String name = zipEntry.getName(); 260 if (!name.startsWith(pathPrefix) || !name.endsWith(".png")) { //$NON-NLS-1$ 261 continue; 262 } 263 264 int lastSlash = name.lastIndexOf('/'); 265 if (lastSlash != -1) { 266 name = name.substring(lastSlash + 1); 267 } 268 names.add(name); 269 } 270 } catch (final Exception e) { 271 e.printStackTrace(); 272 } 273 274 return names.iterator(); 275 } 276 } 277