1 /*
2  * Copyright (C) 2006 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 android.graphics;
18 
19 import android.annotation.ColorInt;
20 import android.annotation.ColorLong;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 
24 import libcore.util.NativeAllocationRegistry;
25 
26 /**
27  * Shader is the base class for objects that return horizontal spans of colors
28  * during drawing. A subclass of Shader is installed in a Paint calling
29  * paint.setShader(shader). After that any object (other than a bitmap) that is
30  * drawn with that paint will get its color(s) from the shader.
31  */
32 public class Shader {
33 
34     private static class NoImagePreloadHolder {
35         public static final NativeAllocationRegistry sRegistry =
36                 NativeAllocationRegistry.createMalloced(
37                 Shader.class.getClassLoader(), nativeGetFinalizer());
38     }
39 
40     /**
41      * @deprecated Use subclass constructors directly instead.
42      */
43     @Deprecated
Shader()44     public Shader() {
45         mColorSpace = null;
46     }
47 
48     /**
49      * @hide Only to be used by subclasses in android.graphics.
50      */
Shader(ColorSpace colorSpace)51     protected Shader(ColorSpace colorSpace) {
52         mColorSpace = colorSpace;
53         if (colorSpace == null) {
54             throw new IllegalArgumentException(
55                     "Use Shader() to create a Shader with no ColorSpace");
56         }
57 
58         // This just ensures that if the ColorSpace is invalid, the Exception will be thrown now.
59         mColorSpace.getNativeInstance();
60     }
61 
62     private final ColorSpace mColorSpace;
63 
64     /**
65      * @hide Only to be used by subclasses in android.graphics.
66      */
colorSpace()67     protected ColorSpace colorSpace() {
68         return mColorSpace;
69     }
70 
71     /**
72      * Current native shader instance. Created and updated lazily when {@link #getNativeInstance()}
73      * is called - otherwise may be out of date with java setters/properties.
74      */
75     private long mNativeInstance;
76     // Runnable to do immediate destruction
77     private Runnable mCleaner;
78 
79     /**
80      * Current matrix - always set to null if local matrix is identity.
81      */
82     private Matrix mLocalMatrix;
83 
84     public enum TileMode {
85         /**
86          * Replicate the edge color if the shader draws outside of its
87          * original bounds.
88          */
89         CLAMP   (0),
90         /**
91          * Repeat the shader's image horizontally and vertically.
92          */
93         REPEAT  (1),
94         /**
95          * Repeat the shader's image horizontally and vertically, alternating
96          * mirror images so that adjacent images always seam.
97          */
98         MIRROR(2),
99         /**
100          * Render the shader's image pixels only within its original bounds. If the shader
101          * draws outside of its original bounds, transparent black is drawn instead.
102          */
103         DECAL(3);
104 
TileMode(int nativeInt)105         TileMode(int nativeInt) {
106             this.nativeInt = nativeInt;
107         }
108         final int nativeInt;
109     }
110 
111     /**
112      * Return true if the shader has a non-identity local matrix.
113      * @param localM Set to the local matrix of the shader, if the shader's matrix is non-null.
114      * @return true if the shader has a non-identity local matrix
115      */
getLocalMatrix(@onNull Matrix localM)116     public boolean getLocalMatrix(@NonNull Matrix localM) {
117         if (mLocalMatrix != null) {
118             localM.set(mLocalMatrix);
119             return true; // presence of mLocalMatrix means it's not identity
120         }
121         return false;
122     }
123 
124     /**
125      * Set the shader's local matrix. Passing null will reset the shader's
126      * matrix to identity. If the matrix has scale value as 0, the drawing
127      * result is undefined.
128      *
129      * @param localM The shader's new local matrix, or null to specify identity
130      */
setLocalMatrix(@ullable Matrix localM)131     public void setLocalMatrix(@Nullable Matrix localM) {
132         if (localM == null || localM.isIdentity()) {
133             if (mLocalMatrix != null) {
134                 mLocalMatrix = null;
135                 discardNativeInstance();
136             }
137         } else {
138             if (mLocalMatrix == null) {
139                 mLocalMatrix = new Matrix(localM);
140                 discardNativeInstance();
141             } else if (!mLocalMatrix.equals(localM)) {
142                 mLocalMatrix.set(localM);
143                 discardNativeInstance();
144             }
145         }
146     }
147 
148     /**
149      *  @hide Only to be used by subclasses in the graphics package.
150      */
createNativeInstance(long nativeMatrix, boolean filterFromPaint)151     protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) {
152         return 0;
153     }
154 
155     /**
156      *  @hide Only to be used by subclasses in the graphics package.
157      */
discardNativeInstance()158     protected synchronized final void discardNativeInstance() {
159         discardNativeInstanceLocked();
160     }
161 
162     // For calling inside a synchronized method.
discardNativeInstanceLocked()163     private void discardNativeInstanceLocked() {
164         if (mNativeInstance != 0) {
165             mCleaner.run();
166             mCleaner = null;
167             mNativeInstance = 0;
168         }
169     }
170 
171     /**
172      * Callback for subclasses to specify whether the most recently
173      * constructed native instance is still valid.
174      *  @hide Only to be used by subclasses in the graphics package.
175      */
shouldDiscardNativeInstance(boolean filterBitmap)176     protected boolean shouldDiscardNativeInstance(boolean filterBitmap) {
177         return false;
178     }
179 
180 
181     /**
182      * @hide so it can be called by android.graphics.drawable but must not be called from outside
183      * the module.
184      */
getNativeInstance(boolean filterFromPaint)185     public final synchronized long getNativeInstance(boolean filterFromPaint) {
186         if (shouldDiscardNativeInstance(filterFromPaint)) {
187             discardNativeInstanceLocked();
188         }
189 
190         if (mNativeInstance == 0) {
191             mNativeInstance = createNativeInstance(mLocalMatrix == null
192                     ? 0 : mLocalMatrix.ni(), filterFromPaint);
193             if (mNativeInstance != 0) {
194                 mCleaner = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
195                         this, mNativeInstance);
196             }
197         }
198         return mNativeInstance;
199     }
200 
201     /**
202      * @hide so it can be called by android.graphics.drawable but must not be called from outside
203      * the module.
204      */
getNativeInstance()205     public final long getNativeInstance() {
206         // If the caller has no paint flag for filtering bitmaps, we just pass false
207         return getNativeInstance(false);
208     }
209 
210     /**
211      * @hide Only to be called by subclasses in the android.graphics package.
212      */
convertColors(@onNull @olorInt int[] colors)213     protected static @ColorLong long[] convertColors(@NonNull @ColorInt int[] colors) {
214         if (colors.length < 2) {
215             throw new IllegalArgumentException("needs >= 2 number of colors");
216         }
217 
218         long[] colorLongs = new long[colors.length];
219         for (int i = 0; i < colors.length; ++i) {
220             colorLongs[i] = Color.pack(colors[i]);
221         }
222 
223         return colorLongs;
224     }
225 
226     /**
227      * Detect the ColorSpace that the {@code colors} share.
228      *
229      * @throws IllegalArgumentException if the colors do not all share the same,
230      *      valid ColorSpace, or if there are less than 2 colors.
231      *
232      * @hide Only to be called by subclasses in the android.graphics package.
233      */
detectColorSpace(@onNull @olorLong long[] colors)234     protected static ColorSpace detectColorSpace(@NonNull @ColorLong long[] colors) {
235         if (colors.length < 2) {
236             throw new IllegalArgumentException("needs >= 2 number of colors");
237         }
238         final ColorSpace colorSpace = Color.colorSpace(colors[0]);
239         for (int i = 1; i < colors.length; ++i) {
240             if (Color.colorSpace(colors[i]) != colorSpace) {
241                 throw new IllegalArgumentException("All colors must be in the same ColorSpace!");
242             }
243         }
244         return colorSpace;
245     }
246 
nativeGetFinalizer()247     private static native long nativeGetFinalizer();
248 
249 }
250 
251