1 /*
2  * Copyright (C) 2007 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.IntDef;
20 import android.annotation.IntRange;
21 import android.annotation.NonNull;
22 
23 import java.lang.annotation.Retention;
24 import java.lang.annotation.RetentionPolicy;
25 
26 /**
27  * Shader used to draw a bitmap as a texture. The bitmap can be repeated or
28  * mirrored by setting the tiling mode.
29  */
30 public class BitmapShader extends Shader {
31     /**
32      * Prevent garbage collection.
33      */
34     /*package*/ Bitmap mBitmap;
35 
36     private int mTileX;
37     private int mTileY;
38 
39     /** @hide */
40     @IntDef(prefix = {"FILTER_MODE"}, value = {
41             FILTER_MODE_DEFAULT,
42             FILTER_MODE_NEAREST,
43             FILTER_MODE_LINEAR
44     })
45     @Retention(RetentionPolicy.SOURCE)
46     public @interface FilterMode {}
47 
48     /**
49      * This FilterMode value will respect the value of the Paint#isFilterBitmap flag while the
50      * shader is attached to the Paint.
51      *
52      * <p>The exception to this rule is when a Shader is attached as input to a RuntimeShader. In
53      *    that case this mode will default to FILTER_MODE_NEAREST.</p>
54      *
55      * @see #setFilterMode(int)
56      */
57     public static final int FILTER_MODE_DEFAULT = 0;
58     /**
59      * This FilterMode value will cause the shader to sample from the nearest pixel to the requested
60      * sample point.
61      *
62      * <p>This value will override the effect of Paint#isFilterBitmap.</p>
63      *
64      * @see #setFilterMode(int)
65      */
66     public static final int FILTER_MODE_NEAREST = 1;
67     /**
68      * This FilterMode value will cause the shader to interpolate the output of the shader from a
69      * 2x2 grid of pixels nearest to the sample point (i.e. bilinear interpolation).
70      *
71      * <p>This value will override the effect of Paint#isFilterBitmap.</p>
72      *
73      * @see #setFilterMode(int)
74      */
75     public static final int FILTER_MODE_LINEAR = 2;
76 
77     @FilterMode
78     private int mFilterMode;
79 
80     /*
81      *  This is cache of the last value from the Paint of bitmap-filtering.
82      *  In the future, BitmapShaders will carry their own (expanded) data for this
83      *  (e.g. including mipmap options, or bicubic weights)
84      *
85      *  When that happens, this bool will become those extended values, and we will
86      *  need to track whether this Shader was created with those new constructors,
87      *  or from the current "legacy" constructor, which (for compatibility) will
88      *  still need to know the Paint's setting.
89      *
90      *  When the filter Paint setting is finally gone, we will be able to remove
91      *  the filterFromPaint parameter currently being passed to createNativeInstance()
92      *  and shouldDiscardNativeInstance(), as shaders will always know their filter
93      *  settings.
94      */
95     private boolean mFilterFromPaint;
96 
97     /**
98      *  Stores whether or not the contents of this shader's bitmap will be sampled
99      *  without modification or if the bitmap's properties, like colorspace and
100      *  premultiplied alpha, will be respected when sampling from the bitmap's buffer.
101      */
102     private boolean mIsDirectSampled;
103 
104     private boolean mRequestDirectSampling;
105 
106     private int mMaxAniso = 0;
107 
108     /**
109      * Call this to create a new shader that will draw with a bitmap.
110      *
111      * @param bitmap The bitmap to use inside the shader
112      * @param tileX The tiling mode for x to draw the bitmap in.
113      * @param tileY The tiling mode for y to draw the bitmap in.
114      */
BitmapShader(@onNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY)115     public BitmapShader(@NonNull Bitmap bitmap, @NonNull TileMode tileX, @NonNull TileMode tileY) {
116         this(bitmap, tileX.nativeInt, tileY.nativeInt);
117     }
118 
BitmapShader(Bitmap bitmap, int tileX, int tileY)119     private BitmapShader(Bitmap bitmap, int tileX, int tileY) {
120         if (bitmap == null) {
121             throw new IllegalArgumentException("Bitmap must be non-null");
122         }
123         bitmap.checkRecycled("Cannot create BitmapShader for recycled bitmap");
124         mBitmap = bitmap;
125         mTileX = tileX;
126         mTileY = tileY;
127         mFilterMode = FILTER_MODE_DEFAULT;
128         mFilterFromPaint = false;
129         mIsDirectSampled = false;
130         mRequestDirectSampling = false;
131     }
132 
133     /**
134      * Returns the filter mode used when sampling from this shader
135      */
136     @FilterMode
getFilterMode()137     public int getFilterMode() {
138         return mFilterMode;
139     }
140 
141     /**
142      * Set the filter mode to be used when sampling from this shader. If this is configured
143      * then the anisotropic filtering value specified in any previous call to
144      * {@link #setMaxAnisotropy(int)} is ignored.
145      */
setFilterMode(@ilterMode int mode)146     public void setFilterMode(@FilterMode int mode) {
147         if (mode != mFilterMode) {
148             mFilterMode = mode;
149             mMaxAniso = 0;
150             discardNativeInstance();
151         }
152     }
153 
154     /**
155      * Enables and configures the max anisotropy sampling value. If this value is configured,
156      * {@link #setFilterMode(int)} is ignored.
157      *
158      * Anisotropic filtering can enhance visual quality by removing aliasing effects of images
159      * that are at oblique viewing angles. This value is typically consumed as a power of 2 and
160      * anisotropic values of the next power of 2 typically provide twice the quality improvement
161      * as the previous value. For example, a sampling value of 4 would provide twice the improvement
162      * of a sampling value of 2. It is important to note that higher sampling values reach
163      * diminishing returns as the improvements between 8 and 16 can be slight.
164      *
165      * @param maxAnisotropy The Anisotropy value to use for filtering. Must be greater than 0.
166      */
setMaxAnisotropy(@ntRangefrom = 1) int maxAnisotropy)167     public void setMaxAnisotropy(@IntRange(from = 1) int maxAnisotropy) {
168         if (mMaxAniso != maxAnisotropy && maxAnisotropy > 0) {
169             mMaxAniso = maxAnisotropy;
170             mFilterMode = FILTER_MODE_DEFAULT;
171             discardNativeInstance();
172         }
173     }
174 
175     /**
176      * Returns the current max anisotropic filtering value configured by
177      * {@link #setFilterMode(int)}. If {@link #setFilterMode(int)} is invoked this returns zero.
178      */
getMaxAnisotropy()179     public int getMaxAnisotropy() {
180         return mMaxAniso;
181     }
182 
183     /** @hide */
getNativeInstanceWithDirectSampling()184     /* package */ synchronized long getNativeInstanceWithDirectSampling() {
185         mRequestDirectSampling = true;
186         return getNativeInstance();
187     }
188 
189     /** @hide */
190     @Override
createNativeInstance(long nativeMatrix, boolean filterFromPaint)191     protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) {
192         mBitmap.checkRecycled("BitmapShader's bitmap has been recycled");
193 
194         boolean enableLinearFilter = mFilterMode == FILTER_MODE_LINEAR;
195         if (mFilterMode == FILTER_MODE_DEFAULT) {
196             mFilterFromPaint = filterFromPaint;
197             enableLinearFilter = mFilterFromPaint;
198         }
199 
200         mIsDirectSampled = mRequestDirectSampling;
201         mRequestDirectSampling = false;
202 
203         if (mMaxAniso > 0) {
204             return nativeCreateWithMaxAniso(nativeMatrix, mBitmap.getNativeInstance(), mTileX,
205                     mTileY, mMaxAniso, mIsDirectSampled);
206         } else {
207             return nativeCreate(nativeMatrix, mBitmap.getNativeInstance(), mTileX, mTileY,
208                     enableLinearFilter, mIsDirectSampled);
209         }
210     }
211 
212     /** @hide */
213     @Override
shouldDiscardNativeInstance(boolean filterFromPaint)214     protected boolean shouldDiscardNativeInstance(boolean filterFromPaint) {
215         return mIsDirectSampled != mRequestDirectSampling
216                 || (mFilterMode == FILTER_MODE_DEFAULT && mFilterFromPaint != filterFromPaint);
217     }
218 
nativeCreate(long nativeMatrix, long bitmapHandle, int shaderTileModeX, int shaderTileModeY, boolean filter, boolean isDirectSampled)219     private static native long nativeCreate(long nativeMatrix, long bitmapHandle,
220             int shaderTileModeX, int shaderTileModeY, boolean filter, boolean isDirectSampled);
221 
nativeCreateWithMaxAniso(long nativeMatrix, long bitmapHandle, int shaderTileModeX, int shaderTileModeY, int maxAniso, boolean isDirectSampled)222     private static native long nativeCreateWithMaxAniso(long nativeMatrix, long bitmapHandle,
223             int shaderTileModeX, int shaderTileModeY, int maxAniso, boolean isDirectSampled);
224 }
225 
226