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
18package android.media.effect;
19
20import java.lang.reflect.Constructor;
21
22/**
23 * <p>The EffectFactory class defines the list of available Effects, and provides functionality to
24 * inspect and instantiate them. Some effects may not be available on all platforms, so before
25 * creating a certain effect, the application should confirm that the effect is supported on this
26 * platform by calling {@link #isEffectSupported(String)}.</p>
27 */
28public class EffectFactory {
29
30    private EffectContext mEffectContext;
31
32    private final static String[] EFFECT_PACKAGES = {
33        "android.media.effect.effects.",  // Default effect package
34        ""                                // Allows specifying full class path
35    };
36
37    /** List of Effects */
38    /**
39     * <p>Copies the input texture to the output.</p>
40     * <p>Available parameters: None</p>
41     * @hide
42     */
43    public final static String EFFECT_IDENTITY = "IdentityEffect";
44
45    /**
46     * <p>Adjusts the brightness of the image.</p>
47     * <p>Available parameters:</p>
48     * <table>
49     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
50     * <tr><td><code>brightness</code></td>
51     *     <td>The brightness multiplier.</td>
52     *     <td>Positive float. 1.0 means no change;
53               larger values will increase brightness.</td>
54     * </tr>
55     * </table>
56     */
57    public final static String EFFECT_BRIGHTNESS =
58            "android.media.effect.effects.BrightnessEffect";
59
60    /**
61     * <p>Adjusts the contrast of the image.</p>
62     * <p>Available parameters:</p>
63     * <table>
64     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
65     * <tr><td><code>contrast</code></td>
66     *     <td>The contrast multiplier.</td>
67     *     <td>Float. 1.0 means no change;
68               larger values will increase contrast.</td>
69     * </tr>
70     * </table>
71     */
72    public final static String EFFECT_CONTRAST =
73            "android.media.effect.effects.ContrastEffect";
74
75    /**
76     * <p>Applies a fisheye lens distortion to the image.</p>
77     * <p>Available parameters:</p>
78     * <table>
79     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
80     * <tr><td><code>scale</code></td>
81     *     <td>The scale of the distortion.</td>
82     *     <td>Float, between 0 and 1. Zero means no distortion.</td>
83     * </tr>
84     * </table>
85     */
86    public final static String EFFECT_FISHEYE =
87            "android.media.effect.effects.FisheyeEffect";
88
89    /**
90     * <p>Replaces the background of the input frames with frames from a
91     * selected video.  Requires an initial learning period with only the
92     * background visible before the effect becomes active. The effect will wait
93     * until it does not see any motion in the scene before learning the
94     * background and starting the effect.</p>
95     *
96     * <p>Available parameters:</p>
97     * <table>
98     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
99     * <tr><td><code>source</code></td>
100     *     <td>A URI for the background video to use. This parameter must be
101     *         supplied before calling apply() for the first time.</td>
102     *     <td>String, such as from
103     *         {@link android.net.Uri#toString Uri.toString()}</td>
104     * </tr>
105     * </table>
106     *
107     * <p>If the update listener is set for this effect using
108     * {@link Effect#setUpdateListener}, it will be called when the effect has
109     * finished learning the background, with a null value for the info
110     * parameter.</p>
111     */
112    public final static String EFFECT_BACKDROPPER =
113            "android.media.effect.effects.BackDropperEffect";
114
115    /**
116     * <p>Attempts to auto-fix the image based on histogram equalization.</p>
117     * <p>Available parameters:</p>
118     * <table>
119     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
120     * <tr><td><code>scale</code></td>
121     *     <td>The scale of the adjustment.</td>
122     *     <td>Float, between 0 and 1. Zero means no adjustment, while 1 indicates the maximum
123     *     amount of adjustment.</td>
124     * </tr>
125     * </table>
126     */
127    public final static String EFFECT_AUTOFIX =
128            "android.media.effect.effects.AutoFixEffect";
129
130    /**
131     * <p>Adjusts the range of minimal and maximal color pixel intensities.</p>
132     * <p>Available parameters:</p>
133     * <table>
134     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
135     * <tr><td><code>black</code></td>
136     *     <td>The value of the minimal pixel.</td>
137     *     <td>Float, between 0 and 1.</td>
138     * </tr>
139     * <tr><td><code>white</code></td>
140     *     <td>The value of the maximal pixel.</td>
141     *     <td>Float, between 0 and 1.</td>
142     * </tr>
143     * </table>
144     */
145    public final static String EFFECT_BLACKWHITE =
146            "android.media.effect.effects.BlackWhiteEffect";
147
148    /**
149     * <p>Crops an upright rectangular area from the image. If the crop region falls outside of
150     * the image bounds, the results are undefined.</p>
151     * <p>Available parameters:</p>
152     * <table>
153     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
154     * <tr><td><code>xorigin</code></td>
155     *     <td>The origin's x-value.</td>
156     *     <td>Integer, between 0 and width of the image.</td>
157     * </tr>
158     * <tr><td><code>yorigin</code></td>
159     *     <td>The origin's y-value.</td>
160     *     <td>Integer, between 0 and height of the image.</td>
161     * </tr>
162     * <tr><td><code>width</code></td>
163     *     <td>The width of the cropped image.</td>
164     *     <td>Integer, between 1 and the width of the image minus xorigin.</td>
165     * </tr>
166     * <tr><td><code>height</code></td>
167     *     <td>The height of the cropped image.</td>
168     *     <td>Integer, between 1 and the height of the image minus yorigin.</td>
169     * </tr>
170     * </table>
171     */
172    public final static String EFFECT_CROP =
173            "android.media.effect.effects.CropEffect";
174
175    /**
176     * <p>Applies a cross process effect on image, in which the red and green channels are
177     * enhanced while the blue channel is restricted.</p>
178     * <p>Available parameters: None</p>
179     */
180    public final static String EFFECT_CROSSPROCESS =
181            "android.media.effect.effects.CrossProcessEffect";
182
183    /**
184     * <p>Applies black and white documentary style effect on image..</p>
185     * <p>Available parameters: None</p>
186     */
187    public final static String EFFECT_DOCUMENTARY =
188            "android.media.effect.effects.DocumentaryEffect";
189
190
191    /**
192     * <p>Overlays a bitmap (with premultiplied alpha channel) onto the input image. The bitmap
193     * is stretched to fit the input image.</p>
194     * <p>Available parameters:</p>
195     * <table>
196     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
197     * <tr><td><code>bitmap</code></td>
198     *     <td>The overlay bitmap.</td>
199     *     <td>A non-null Bitmap instance.</td>
200     * </tr>
201     * </table>
202     */
203    public final static String EFFECT_BITMAPOVERLAY =
204            "android.media.effect.effects.BitmapOverlayEffect";
205
206    /**
207     * <p>Representation of photo using only two color tones.</p>
208     * <p>Available parameters:</p>
209     * <table>
210     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
211     * <tr><td><code>first_color</code></td>
212     *     <td>The first color tone.</td>
213     *     <td>Integer, representing an ARGB color with 8 bits per channel. May be created using
214     *     {@link android.graphics.Color Color} class.</td>
215     * </tr>
216     * <tr><td><code>second_color</code></td>
217     *     <td>The second color tone.</td>
218     *     <td>Integer, representing an ARGB color with 8 bits per channel. May be created using
219     *     {@link android.graphics.Color Color} class.</td>
220     * </tr>
221     * </table>
222     */
223    public final static String EFFECT_DUOTONE =
224            "android.media.effect.effects.DuotoneEffect";
225
226    /**
227     * <p>Applies back-light filling to the image.</p>
228     * <p>Available parameters:</p>
229     * <table>
230     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
231     * <tr><td><code>strength</code></td>
232     *     <td>The strength of the backlight.</td>
233     *     <td>Float, between 0 and 1. Zero means no change.</td>
234     * </tr>
235     * </table>
236     */
237    public final static String EFFECT_FILLLIGHT =
238            "android.media.effect.effects.FillLightEffect";
239
240    /**
241     * <p>Flips image vertically and/or horizontally.</p>
242     * <p>Available parameters:</p>
243     * <table>
244     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
245     * <tr><td><code>vertical</code></td>
246     *     <td>Whether to flip image vertically.</td>
247     *     <td>Boolean</td>
248     * </tr>
249     * <tr><td><code>horizontal</code></td>
250     *     <td>Whether to flip image horizontally.</td>
251     *     <td>Boolean</td>
252     * </tr>
253     * </table>
254     */
255    public final static String EFFECT_FLIP =
256            "android.media.effect.effects.FlipEffect";
257
258    /**
259     * <p>Applies film grain effect to image.</p>
260     * <p>Available parameters:</p>
261     * <table>
262     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
263     * <tr><td><code>strength</code></td>
264     *     <td>The strength of the grain effect.</td>
265     *     <td>Float, between 0 and 1. Zero means no change.</td>
266     * </tr>
267     * </table>
268     */
269    public final static String EFFECT_GRAIN =
270            "android.media.effect.effects.GrainEffect";
271
272    /**
273     * <p>Converts image to grayscale.</p>
274     * <p>Available parameters: None</p>
275     */
276    public final static String EFFECT_GRAYSCALE =
277            "android.media.effect.effects.GrayscaleEffect";
278
279    /**
280     * <p>Applies lomo-camera style effect to image.</p>
281     * <p>Available parameters: None</p>
282     */
283    public final static String EFFECT_LOMOISH =
284            "android.media.effect.effects.LomoishEffect";
285
286    /**
287     * <p>Inverts the image colors.</p>
288     * <p>Available parameters: None</p>
289     */
290    public final static String EFFECT_NEGATIVE =
291            "android.media.effect.effects.NegativeEffect";
292
293    /**
294     * <p>Applies posterization effect to image.</p>
295     * <p>Available parameters: None</p>
296     */
297    public final static String EFFECT_POSTERIZE =
298            "android.media.effect.effects.PosterizeEffect";
299
300    /**
301     * <p>Removes red eyes on specified region.</p>
302     * <p>Available parameters:</p>
303     * <table>
304     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
305     * <tr><td><code>centers</code></td>
306     *     <td>Multiple center points (x, y) of the red eye regions.</td>
307     *     <td>An array of floats, where (f[2*i], f[2*i+1]) specifies the center of the i'th eye.
308     *     Coordinate values are expected to be normalized between 0 and 1.</td>
309     * </tr>
310     * </table>
311     */
312    public final static String EFFECT_REDEYE =
313            "android.media.effect.effects.RedEyeEffect";
314
315    /**
316     * <p>Rotates the image. The output frame size must be able to fit the rotated version of
317     * the input image. Note that the rotation snaps to a the closest multiple of 90 degrees.</p>
318     * <p>Available parameters:</p>
319     * <table>
320     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
321     * <tr><td><code>angle</code></td>
322     *     <td>The angle of rotation in degrees.</td>
323     *     <td>Integer value. This will be rounded to the nearest multiple of 90.</td>
324     * </tr>
325     * </table>
326     */
327    public final static String EFFECT_ROTATE =
328            "android.media.effect.effects.RotateEffect";
329
330    /**
331     * <p>Adjusts color saturation of image.</p>
332     * <p>Available parameters:</p>
333     * <table>
334     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
335     * <tr><td><code>scale</code></td>
336     *     <td>The scale of color saturation.</td>
337     *     <td>Float, between -1 and 1. 0 means no change, while -1 indicates full desaturation,
338     *     i.e. grayscale.</td>
339     * </tr>
340     * </table>
341     */
342    public final static String EFFECT_SATURATE =
343            "android.media.effect.effects.SaturateEffect";
344
345    /**
346     * <p>Converts image to sepia tone.</p>
347     * <p>Available parameters: None</p>
348     */
349    public final static String EFFECT_SEPIA =
350            "android.media.effect.effects.SepiaEffect";
351
352    /**
353     * <p>Sharpens the image.</p>
354     * <p>Available parameters:</p>
355     * <table>
356     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
357     * <tr><td><code>scale</code></td>
358     *     <td>The degree of sharpening.</td>
359     *     <td>Float, between 0 and 1. 0 means no change.</td>
360     * </tr>
361     * </table>
362     */
363    public final static String EFFECT_SHARPEN =
364            "android.media.effect.effects.SharpenEffect";
365
366    /**
367     * <p>Rotates the image according to the specified angle, and crops the image so that no
368     * non-image portions are visible.</p>
369     * <p>Available parameters:</p>
370     * <table>
371     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
372     * <tr><td><code>angle</code></td>
373     *     <td>The angle of rotation.</td>
374     *     <td>Float, between -45 and +45.</td>
375     * </tr>
376     * </table>
377     */
378    public final static String EFFECT_STRAIGHTEN =
379            "android.media.effect.effects.StraightenEffect";
380
381    /**
382     * <p>Adjusts color temperature of the image.</p>
383     * <p>Available parameters:</p>
384     * <table>
385     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
386     * <tr><td><code>scale</code></td>
387     *     <td>The value of color temperature.</td>
388     *     <td>Float, between 0 and 1, with 0 indicating cool, and 1 indicating warm. A value of
389     *     of 0.5 indicates no change.</td>
390     * </tr>
391     * </table>
392     */
393    public final static String EFFECT_TEMPERATURE =
394            "android.media.effect.effects.ColorTemperatureEffect";
395
396    /**
397     * <p>Tints the photo with specified color.</p>
398     * <p>Available parameters:</p>
399     * <table>
400     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
401     * <tr><td><code>tint</code></td>
402     *     <td>The color of the tint.</td>
403     *     <td>Integer, representing an ARGB color with 8 bits per channel. May be created using
404     *     {@link android.graphics.Color Color} class.</td>
405     * </tr>
406     * </table>
407     */
408    public final static String EFFECT_TINT =
409            "android.media.effect.effects.TintEffect";
410
411    /**
412     * <p>Adds a vignette effect to image, i.e. fades away the outer image edges.</p>
413     * <p>Available parameters:</p>
414     * <table>
415     * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr>
416     * <tr><td><code>scale</code></td>
417     *     <td>The scale of vignetting.</td>
418     *     <td>Float, between 0 and 1. 0 means no change.</td>
419     * </tr>
420     * </table>
421     */
422    public final static String EFFECT_VIGNETTE =
423            "android.media.effect.effects.VignetteEffect";
424
425    EffectFactory(EffectContext effectContext) {
426        mEffectContext = effectContext;
427    }
428
429    /**
430     * Instantiate a new effect with the given effect name.
431     *
432     * <p>The effect's parameters will be set to their default values.</p>
433     *
434     * <p>Note that the EGL context associated with the current EffectContext need not be made
435     * current when creating an effect. This allows the host application to instantiate effects
436     * before any EGL context has become current.</p>
437     *
438     * @param effectName The name of the effect to create.
439     * @return A new Effect instance.
440     * @throws IllegalArgumentException if the effect with the specified name is not supported or
441     *         not known.
442     */
443    public Effect createEffect(String effectName) {
444        Class effectClass = getEffectClassByName(effectName);
445        if (effectClass == null) {
446            throw new IllegalArgumentException("Cannot instantiate unknown effect '" +
447                effectName + "'!");
448        }
449        return instantiateEffect(effectClass, effectName);
450    }
451
452    /**
453     * Check if an effect is supported on this platform.
454     *
455     * <p>Some effects may only be available on certain platforms. Use this method before
456     * instantiating an effect to make sure it is supported.</p>
457     *
458     * @param effectName The name of the effect.
459     * @return true, if the effect is supported on this platform.
460     * @throws IllegalArgumentException if the effect name is not known.
461     */
462    public static boolean isEffectSupported(String effectName) {
463        return getEffectClassByName(effectName) != null;
464    }
465
466    private static Class getEffectClassByName(String className) {
467        Class effectClass = null;
468
469        // Get context's classloader; otherwise cannot load non-framework effects
470        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
471
472        // Look for the class in the imported packages
473        for (String packageName : EFFECT_PACKAGES) {
474            try {
475                effectClass = contextClassLoader.loadClass(packageName + className);
476            } catch (ClassNotFoundException e) {
477                continue;
478            }
479            // Exit loop if class was found.
480            if (effectClass != null) {
481                break;
482            }
483        }
484        return effectClass;
485    }
486
487    private Effect instantiateEffect(Class effectClass, String name) {
488        // Make sure this is an Effect subclass
489        try {
490            effectClass.asSubclass(Effect.class);
491        } catch (ClassCastException e) {
492            throw new IllegalArgumentException("Attempting to allocate effect '" + effectClass
493                + "' which is not a subclass of Effect!", e);
494        }
495
496        // Look for the correct constructor
497        Constructor effectConstructor = null;
498        try {
499            effectConstructor = effectClass.getConstructor(EffectContext.class, String.class);
500        } catch (NoSuchMethodException e) {
501            throw new RuntimeException("The effect class '" + effectClass + "' does not have "
502                + "the required constructor.", e);
503        }
504
505        // Construct the effect
506        Effect effect = null;
507        try {
508            effect = (Effect)effectConstructor.newInstance(mEffectContext, name);
509        } catch (Throwable t) {
510            throw new RuntimeException("There was an error constructing the effect '" + effectClass
511                + "'!", t);
512        }
513
514        return effect;
515    }
516}
517