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 
18 package android.media.effect;
19 
20 import 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  */
28 public 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 
EffectFactory(EffectContext effectContext)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      */
createEffect(String effectName)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      */
isEffectSupported(String effectName)462     public static boolean isEffectSupported(String effectName) {
463         return getEffectClassByName(effectName) != null;
464     }
465 
getEffectClassByName(String className)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 
instantiateEffect(Class effectClass, String name)487     private Effect instantiateEffect(Class effectClass, String name) {
488         // Make sure this is an Effect subclass
489         if (!Effect.class.isAssignableFrom(effectClass)) {
490             throw new IllegalArgumentException("Attempting to allocate effect '" + effectClass
491                 + "' which is not a subclass of Effect!");
492         }
493 
494         // Look for the correct constructor
495         Constructor effectConstructor = null;
496         try {
497             effectConstructor = effectClass.getConstructor(EffectContext.class, String.class);
498         } catch (NoSuchMethodException e) {
499             throw new RuntimeException("The effect class '" + effectClass + "' does not have "
500                 + "the required constructor.", e);
501         }
502 
503         // Construct the effect
504         Effect effect = null;
505         try {
506             effect = (Effect)effectConstructor.newInstance(mEffectContext, name);
507         } catch (Throwable t) {
508             throw new RuntimeException("There was an error constructing the effect '" + effectClass
509                 + "'!", t);
510         }
511 
512         return effect;
513     }
514 }
515