aboutsummaryrefslogtreecommitdiffstats
path: root/assetstudio/src/com/android/assetstudiolib/GraphicGenerator.java
blob: 706adc809ab9b37d8c3087e8d793b55f85839942 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * 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.
 */

package com.android.assetstudiolib;

import com.android.resources.Density;
import com.google.common.io.Closeables;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.imageio.ImageIO;

/**
 * The base Generator class.
 */
public abstract class GraphicGenerator {
    /**
     * Options used for all generators.
     */
    public static class Options {
        /** Minimum version (API level) of the SDK to generate icons for */
        public int minSdk = 1;

        /** Source image to use as a basis for the icon */
        public BufferedImage sourceImage;

        /** The density to generate the icon with */
        public Density density = Density.XHIGH;
    }

    /** Shapes that can be used for icon backgrounds */
    public static enum Shape {
        /** No background */
        NONE("none"),
        /** Circular background */
        CIRCLE("circle"),
        /** Square background */
        SQUARE("square");

        /** Id, used in filenames to identify associated stencils */
        public final String id;

        Shape(String id) {
            this.id = id;
        }
    }

    /** Foreground effects styles */
    public static enum Style {
        /** No effects */
        SIMPLE("fore1");

        /** Id, used in filenames to identify associated stencils */
        public final String id;

        Style(String id) {
            this.id = id;
        }
    }

    /**
     * Generate a single icon using the given options
     *
     * @param context render context to use for looking up resources etc
     * @param options options controlling the appearance of the icon
     * @return a {@link BufferedImage} with the generated icon
     */
    public abstract BufferedImage generate(GraphicGeneratorContext context, Options options);

    /**
     * Computes the target filename (relative to the Android project folder)
     * where an icon rendered with the given options should be stored. This is
     * also used as the map keys in the result map used by
     * {@link #generate(String, Map, GraphicGeneratorContext, Options, String)}.
     *
     * @param options the options object used by the generator for the current
     *            image
     * @param name the base name to use when creating the path
     * @return a path relative to the res/ folder where the image should be
     *         stored (will always use / as a path separator, not \ on Windows)
     */
    protected String getIconPath(Options options, String name) {
        return getIconFolder(options) + '/' + getIconName(options, name);
    }

    /**
     * Gets name of the file itself. It is sometimes modified by options, for
     * example in unselected tabs we change foo.png to foo-unselected.png
     */
    protected String getIconName(Options options, String name) {
        return name + ".png"; //$NON-NLS-1$
    }

    /**
     * Gets name of the folder to contain the resource. It usually includes the
     * density, but is also sometimes modified by options. For example, in some
     * notification icons we add in -v9 or -v11.
     */
    protected String getIconFolder(Options options) {
        return "res/drawable-" + options.density.getResourceValue(); //$NON-NLS-1$
    }

    /**
     * Generates a full set of icons into the given map. The values in the map
     * will be the generated images, and each value is keyed by the
     * corresponding relative path of the image, which is determined by the
     * {@link #getIconPath(Options, String)} method.
     *
     * @param category the current category to place images into (if null the
     *            density name will be used)
     * @param categoryMap the map to put images into, should not be null. The
     *            map is a map from a category name, to a map from file path to
     *            image.
     * @param context a generator context which for example can load resources
     * @param options options to apply to this generator
     * @param name the base name of the icons to generate
     */
    public void generate(String category, Map<String, Map<String, BufferedImage>> categoryMap,
            GraphicGeneratorContext context, Options options, String name) {
        Density[] densityValues = Density.values();
        // Sort density values into ascending order
        Arrays.sort(densityValues, new Comparator<Density>() {
            @Override
            public int compare(Density d1, Density d2) {
                return d1.getDpiValue() - d2.getDpiValue();
            }
        });

        for (Density density : densityValues) {
            if (!density.isValidValueForDevice()) {
                continue;
            }
            if (density == Density.LOW || density == Density.TV ||
                    (density == Density.XXHIGH && !(this instanceof LauncherIconGenerator))) {
                // TODO don't manually check and instead gracefully handle missing stencils.
                // Not yet supported -- missing stencil image
                continue;
            }
            options.density = density;
            BufferedImage image = generate(context, options);
            if (image != null) {
                String mapCategory = category;
                if (mapCategory == null) {
                    mapCategory = options.density.getResourceValue();
                }
                Map<String, BufferedImage> imageMap = categoryMap.get(mapCategory);
                if (imageMap == null) {
                    imageMap = new LinkedHashMap<String, BufferedImage>();
                    categoryMap.put(mapCategory, imageMap);
                }
                imageMap.put(getIconPath(options, name), image);
            }
        }
    }

    /**
     * Returns the scale factor to apply for a given MDPI density to compute the
     * absolute pixel count to use to draw an icon of the given target density
     *
     * @param density the density
     * @return a factor to multiple mdpi distances with to compute the target density
     */
    public static float getMdpiScaleFactor(Density density) {
        return density.getDpiValue() / (float) Density.MEDIUM.getDpiValue();
    }

    /**
     * Returns one of the built in stencil images, or null
     *
     * @param relativePath stencil path such as "launcher-stencil/square/web/back.png"
     * @return the image, or null
     * @throws IOException if an unexpected I/O error occurs
     */
    @SuppressWarnings("resource") // Eclipse doesn't know about Closeables#closeQuietly yet
    public static BufferedImage getStencilImage(String relativePath) throws IOException {
        InputStream is = GraphicGenerator.class.getResourceAsStream(relativePath);
        try {
            return ImageIO.read(is);
        } finally {
            Closeables.closeQuietly(is);
        }
    }

    /**
     * Returns the icon (32x32) for a given clip art image.
     *
     * @param name the name of the image to be loaded (which can be looked up via
     *            {@link #getClipartNames()})
     * @return the icon image
     * @throws IOException if the image cannot be loaded
     */
    @SuppressWarnings("resource") // Eclipse doesn't know about Closeables#closeQuietly yet
    public static BufferedImage getClipartIcon(String name) throws IOException {
        InputStream is = GraphicGenerator.class.getResourceAsStream(
                "/images/clipart/small/" + name);
        try {
            return ImageIO.read(is);
        } finally {
            Closeables.closeQuietly(is);
        }
    }

    /**
     * Returns the full size clip art image for a given image name.
     *
     * @param name the name of the image to be loaded (which can be looked up via
     *            {@link #getClipartNames()})
     * @return the clip art image
     * @throws IOException if the image cannot be loaded
     */
    @SuppressWarnings("resource") // Eclipse doesn't know about Closeables#closeQuietly yet
    public static BufferedImage getClipartImage(String name) throws IOException {
        InputStream is = GraphicGenerator.class.getResourceAsStream(
                "/images/clipart/big/" + name);
        try {
            return ImageIO.read(is);
        } finally {
            Closeables.closeQuietly(is);
        }
    }

    /**
     * Returns the names of available clip art images which can be obtained by passing the
     * name to {@link #getClipartIcon(String)} or
     * {@link GraphicGenerator#getClipartImage(String)}
     *
     * @return an iterator for the available image names
     */
    public static Iterator<String> getClipartNames() {
        List<String> names = new ArrayList<String>(80);
        try {
            String pathPrefix = "images/clipart/big/"; //$NON-NLS-1$
            ProtectionDomain protectionDomain = GraphicGenerator.class.getProtectionDomain();
            URL url = protectionDomain.getCodeSource().getLocation();
            File file;
            try {
                file = new File(url.toURI());
            } catch (URISyntaxException e) {
                file = new File(url.getPath());
            }
            final ZipFile zipFile = new JarFile(file);
            Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
            while (enumeration.hasMoreElements()) {
                ZipEntry zipEntry = enumeration.nextElement();
                String name = zipEntry.getName();
                if (!name.startsWith(pathPrefix) || !name.endsWith(".png")) { //$NON-NLS-1$
                    continue;
                }

                int lastSlash = name.lastIndexOf('/');
                if (lastSlash != -1) {
                    name = name.substring(lastSlash + 1);
                }
                names.add(name);
            }
        } catch (final Exception e) {
            e.printStackTrace();
        }

        return names.iterator();
    }
}