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