• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.drawable;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.pm.ActivityInfo.Config;
22 import android.content.res.ColorStateList;
23 import android.content.res.Resources;
24 import android.content.res.Resources.Theme;
25 import android.content.res.TypedArray;
26 import android.graphics.Bitmap;
27 import android.graphics.BitmapFactory;
28 import android.graphics.Canvas;
29 import android.graphics.ColorFilter;
30 import android.graphics.Insets;
31 import android.graphics.NinePatch;
32 import android.graphics.Outline;
33 import android.graphics.Paint;
34 import android.graphics.PixelFormat;
35 import android.graphics.PorterDuff;
36 import android.graphics.PorterDuff.Mode;
37 import android.graphics.PorterDuffColorFilter;
38 import android.graphics.Rect;
39 import android.graphics.Region;
40 import android.util.AttributeSet;
41 import android.util.DisplayMetrics;
42 import android.util.LayoutDirection;
43 import android.util.TypedValue;
44 
45 import com.android.internal.R;
46 
47 import org.xmlpull.v1.XmlPullParser;
48 import org.xmlpull.v1.XmlPullParserException;
49 
50 import java.io.IOException;
51 import java.io.InputStream;
52 
53 /**
54  *
55  * A resizeable bitmap, with stretchable areas that you define. This type of image
56  * is defined in a .png file with a special format.
57  *
58  * <div class="special reference">
59  * <h3>Developer Guides</h3>
60  * <p>For more information about how to use a NinePatchDrawable, read the
61  * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch">
62  * Canvas and Drawables</a> developer guide. For information about creating a NinePatch image
63  * file using the draw9patch tool, see the
64  * <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-patch</a> tool guide.</p></div>
65  */
66 public class NinePatchDrawable extends Drawable {
67     // dithering helps a lot, and is pretty cheap, so default is true
68     private static final boolean DEFAULT_DITHER = false;
69 
70     /** Temporary rect used for density scaling. */
71     private Rect mTempRect;
72 
73     private NinePatchState mNinePatchState;
74     private PorterDuffColorFilter mTintFilter;
75     private Rect mPadding;
76     private Insets mOpticalInsets = Insets.NONE;
77     private Rect mOutlineInsets;
78     private float mOutlineRadius;
79     private Paint mPaint;
80     private boolean mMutated;
81 
82     private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
83 
84     // These are scaled to match the target density.
85     private int mBitmapWidth = -1;
86     private int mBitmapHeight = -1;
87 
NinePatchDrawable()88     NinePatchDrawable() {
89         mNinePatchState = new NinePatchState();
90     }
91 
92     /**
93      * Create drawable from raw nine-patch data, not dealing with density.
94      *
95      * @deprecated Use {@link #NinePatchDrawable(Resources, Bitmap, byte[], Rect, String)}
96      *             to ensure that the drawable has correctly set its target density.
97      */
98     @Deprecated
NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName)99     public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) {
100         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null);
101     }
102 
103     /**
104      * Create drawable from raw nine-patch data, setting initial target density
105      * based on the display metrics of the resources.
106      */
NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, String srcName)107     public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
108             Rect padding, String srcName) {
109         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res);
110     }
111 
112     /**
113      * Create drawable from raw nine-patch data, setting initial target density
114      * based on the display metrics of the resources.
115      *
116      * @hide
117      */
NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, Rect opticalInsets, String srcName)118     public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
119             Rect padding, Rect opticalInsets, String srcName) {
120         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, opticalInsets),
121                 res);
122     }
123 
124     /**
125      * Create drawable from existing nine-patch, not dealing with density.
126      *
127      * @deprecated Use {@link #NinePatchDrawable(Resources, NinePatch)}
128      *             to ensure that the drawable has correctly set its target
129      *             density.
130      */
131     @Deprecated
NinePatchDrawable(@onNull NinePatch patch)132     public NinePatchDrawable(@NonNull NinePatch patch) {
133         this(new NinePatchState(patch, new Rect()), null);
134     }
135 
136     /**
137      * Create drawable from existing nine-patch, setting initial target density
138      * based on the display metrics of the resources.
139      */
NinePatchDrawable(@ullable Resources res, @NonNull NinePatch patch)140     public NinePatchDrawable(@Nullable Resources res, @NonNull NinePatch patch) {
141         this(new NinePatchState(patch, new Rect()), res);
142     }
143 
144     /**
145      * Set the density scale at which this drawable will be rendered. This
146      * method assumes the drawable will be rendered at the same density as the
147      * specified canvas.
148      *
149      * @param canvas The Canvas from which the density scale must be obtained.
150      *
151      * @see android.graphics.Bitmap#setDensity(int)
152      * @see android.graphics.Bitmap#getDensity()
153      */
setTargetDensity(@onNull Canvas canvas)154     public void setTargetDensity(@NonNull Canvas canvas) {
155         setTargetDensity(canvas.getDensity());
156     }
157 
158     /**
159      * Set the density scale at which this drawable will be rendered.
160      *
161      * @param metrics The DisplayMetrics indicating the density scale for this drawable.
162      *
163      * @see android.graphics.Bitmap#setDensity(int)
164      * @see android.graphics.Bitmap#getDensity()
165      */
setTargetDensity(@onNull DisplayMetrics metrics)166     public void setTargetDensity(@NonNull DisplayMetrics metrics) {
167         setTargetDensity(metrics.densityDpi);
168     }
169 
170     /**
171      * Set the density at which this drawable will be rendered.
172      *
173      * @param density The density scale for this drawable.
174      *
175      * @see android.graphics.Bitmap#setDensity(int)
176      * @see android.graphics.Bitmap#getDensity()
177      */
setTargetDensity(int density)178     public void setTargetDensity(int density) {
179         if (density == 0) {
180             density = DisplayMetrics.DENSITY_DEFAULT;
181         }
182 
183         if (mTargetDensity != density) {
184             mTargetDensity = density;
185 
186             computeBitmapSize();
187             invalidateSelf();
188         }
189     }
190 
191     @Override
draw(Canvas canvas)192     public void draw(Canvas canvas) {
193         final NinePatchState state = mNinePatchState;
194 
195         Rect bounds = getBounds();
196         int restoreToCount = -1;
197 
198         final boolean clearColorFilter;
199         if (mTintFilter != null && getPaint().getColorFilter() == null) {
200             mPaint.setColorFilter(mTintFilter);
201             clearColorFilter = true;
202         } else {
203             clearColorFilter = false;
204         }
205 
206         final int restoreAlpha;
207         if (state.mBaseAlpha != 1.0f) {
208             restoreAlpha = getPaint().getAlpha();
209             mPaint.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
210         } else {
211             restoreAlpha = -1;
212         }
213 
214         final boolean needsDensityScaling = canvas.getDensity() == 0;
215         if (needsDensityScaling) {
216             restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save();
217 
218             // Apply density scaling.
219             final float scale = mTargetDensity / (float) state.mNinePatch.getDensity();
220             final float px = bounds.left;
221             final float py = bounds.top;
222             canvas.scale(scale, scale, px, py);
223 
224             if (mTempRect == null) {
225                 mTempRect = new Rect();
226             }
227 
228             // Scale the bounds to match.
229             final Rect scaledBounds = mTempRect;
230             scaledBounds.left = bounds.left;
231             scaledBounds.top = bounds.top;
232             scaledBounds.right = bounds.left + Math.round(bounds.width() / scale);
233             scaledBounds.bottom = bounds.top + Math.round(bounds.height() / scale);
234             bounds = scaledBounds;
235         }
236 
237         final boolean needsMirroring = needsMirroring();
238         if (needsMirroring) {
239             restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save();
240 
241             // Mirror the 9patch.
242             final float cx = (bounds.left + bounds.right) / 2.0f;
243             final float cy = (bounds.top + bounds.bottom) / 2.0f;
244             canvas.scale(-1.0f, 1.0f, cx, cy);
245         }
246 
247         state.mNinePatch.draw(canvas, bounds, mPaint);
248 
249         if (restoreToCount >= 0) {
250             canvas.restoreToCount(restoreToCount);
251         }
252 
253         if (clearColorFilter) {
254             mPaint.setColorFilter(null);
255         }
256 
257         if (restoreAlpha >= 0) {
258             mPaint.setAlpha(restoreAlpha);
259         }
260     }
261 
262     @Override
getChangingConfigurations()263     public @Config int getChangingConfigurations() {
264         return super.getChangingConfigurations() | mNinePatchState.getChangingConfigurations();
265     }
266 
267     @Override
getPadding(@onNull Rect padding)268     public boolean getPadding(@NonNull Rect padding) {
269         if (mPadding != null) {
270             padding.set(mPadding);
271             return (padding.left | padding.top | padding.right | padding.bottom) != 0;
272         } else {
273             return super.getPadding(padding);
274         }
275     }
276 
277     @Override
getOutline(@onNull Outline outline)278     public void getOutline(@NonNull Outline outline) {
279         final Rect bounds = getBounds();
280         if (bounds.isEmpty()) {
281             return;
282         }
283 
284         if (mNinePatchState != null && mOutlineInsets != null) {
285             final NinePatch.InsetStruct insets =
286                     mNinePatchState.mNinePatch.getBitmap().getNinePatchInsets();
287             if (insets != null) {
288                 outline.setRoundRect(bounds.left + mOutlineInsets.left,
289                         bounds.top + mOutlineInsets.top,
290                         bounds.right - mOutlineInsets.right,
291                         bounds.bottom - mOutlineInsets.bottom,
292                         mOutlineRadius);
293                 outline.setAlpha(insets.outlineAlpha * (getAlpha() / 255.0f));
294                 return;
295             }
296         }
297 
298         super.getOutline(outline);
299     }
300 
301     /**
302      * @hide
303      */
304     @Override
getOpticalInsets()305     public Insets getOpticalInsets() {
306         final Insets opticalInsets = mOpticalInsets;
307         if (needsMirroring()) {
308             return Insets.of(opticalInsets.right, opticalInsets.top,
309                     opticalInsets.left, opticalInsets.bottom);
310         } else {
311             return opticalInsets;
312         }
313     }
314 
315     @Override
setAlpha(int alpha)316     public void setAlpha(int alpha) {
317         if (mPaint == null && alpha == 0xFF) {
318             // Fast common case -- leave at normal alpha.
319             return;
320         }
321         getPaint().setAlpha(alpha);
322         invalidateSelf();
323     }
324 
325     @Override
getAlpha()326     public int getAlpha() {
327         if (mPaint == null) {
328             // Fast common case -- normal alpha.
329             return 0xFF;
330         }
331         return getPaint().getAlpha();
332     }
333 
334     @Override
setColorFilter(@ullable ColorFilter colorFilter)335     public void setColorFilter(@Nullable ColorFilter colorFilter) {
336         if (mPaint == null && colorFilter == null) {
337             // Fast common case -- leave at no color filter.
338             return;
339         }
340         getPaint().setColorFilter(colorFilter);
341         invalidateSelf();
342     }
343 
344     @Override
setTintList(@ullable ColorStateList tint)345     public void setTintList(@Nullable ColorStateList tint) {
346         mNinePatchState.mTint = tint;
347         mTintFilter = updateTintFilter(mTintFilter, tint, mNinePatchState.mTintMode);
348         invalidateSelf();
349     }
350 
351     @Override
setTintMode(@ullable PorterDuff.Mode tintMode)352     public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
353         mNinePatchState.mTintMode = tintMode;
354         mTintFilter = updateTintFilter(mTintFilter, mNinePatchState.mTint, tintMode);
355         invalidateSelf();
356     }
357 
358     @Override
setDither(boolean dither)359     public void setDither(boolean dither) {
360         //noinspection PointlessBooleanExpression
361         if (mPaint == null && dither == DEFAULT_DITHER) {
362             // Fast common case -- leave at default dither.
363             return;
364         }
365 
366         getPaint().setDither(dither);
367         invalidateSelf();
368     }
369 
370     @Override
setAutoMirrored(boolean mirrored)371     public void setAutoMirrored(boolean mirrored) {
372         mNinePatchState.mAutoMirrored = mirrored;
373     }
374 
needsMirroring()375     private boolean needsMirroring() {
376         return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
377     }
378 
379     @Override
isAutoMirrored()380     public boolean isAutoMirrored() {
381         return mNinePatchState.mAutoMirrored;
382     }
383 
384     @Override
setFilterBitmap(boolean filter)385     public void setFilterBitmap(boolean filter) {
386         getPaint().setFilterBitmap(filter);
387         invalidateSelf();
388     }
389 
390     @Override
isFilterBitmap()391     public boolean isFilterBitmap() {
392         return mPaint != null && getPaint().isFilterBitmap();
393     }
394 
395     @Override
inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)396     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
397             throws XmlPullParserException, IOException {
398         super.inflate(r, parser, attrs, theme);
399 
400         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.NinePatchDrawable);
401         updateStateFromTypedArray(a);
402         a.recycle();
403 
404         updateLocalState(r);
405     }
406 
407     /**
408      * Updates the constant state from the values in the typed array.
409      */
updateStateFromTypedArray(@onNull TypedArray a)410     private void updateStateFromTypedArray(@NonNull TypedArray a) throws XmlPullParserException {
411         final Resources r = a.getResources();
412         final NinePatchState state = mNinePatchState;
413 
414         // Account for any configuration changes.
415         state.mChangingConfigurations |= a.getChangingConfigurations();
416 
417         // Extract the theme attributes, if any.
418         state.mThemeAttrs = a.extractThemeAttrs();
419 
420         state.mDither = a.getBoolean(R.styleable.NinePatchDrawable_dither, state.mDither);
421 
422         final int srcResId = a.getResourceId(R.styleable.NinePatchDrawable_src, 0);
423         if (srcResId != 0) {
424             final BitmapFactory.Options options = new BitmapFactory.Options();
425             options.inDither = !state.mDither;
426             options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi;
427 
428             final Rect padding = new Rect();
429             final Rect opticalInsets = new Rect();
430             Bitmap bitmap = null;
431 
432             try {
433                 final TypedValue value = new TypedValue();
434                 final InputStream is = r.openRawResource(srcResId, value);
435 
436                 bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options);
437 
438                 is.close();
439             } catch (IOException e) {
440                 // Ignore
441             }
442 
443             if (bitmap == null) {
444                 throw new XmlPullParserException(a.getPositionDescription() +
445                         ": <nine-patch> requires a valid src attribute");
446             } else if (bitmap.getNinePatchChunk() == null) {
447                 throw new XmlPullParserException(a.getPositionDescription() +
448                         ": <nine-patch> requires a valid 9-patch source image");
449             }
450 
451             bitmap.getOpticalInsets(opticalInsets);
452 
453             state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk());
454             state.mPadding = padding;
455             state.mOpticalInsets = Insets.of(opticalInsets);
456         }
457 
458         state.mAutoMirrored = a.getBoolean(
459                 R.styleable.NinePatchDrawable_autoMirrored, state.mAutoMirrored);
460         state.mBaseAlpha = a.getFloat(R.styleable.NinePatchDrawable_alpha, state.mBaseAlpha);
461 
462         final int tintMode = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1);
463         if (tintMode != -1) {
464             state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
465         }
466 
467         final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint);
468         if (tint != null) {
469             state.mTint = tint;
470         }
471     }
472 
473     @Override
applyTheme(@onNull Theme t)474     public void applyTheme(@NonNull Theme t) {
475         super.applyTheme(t);
476 
477         final NinePatchState state = mNinePatchState;
478         if (state == null) {
479             return;
480         }
481 
482         if (state.mThemeAttrs != null) {
483             final TypedArray a = t.resolveAttributes(
484                     state.mThemeAttrs, R.styleable.NinePatchDrawable);
485             try {
486                 updateStateFromTypedArray(a);
487             } catch (XmlPullParserException e) {
488                 rethrowAsRuntimeException(e);
489             } finally {
490                 a.recycle();
491             }
492         }
493 
494         if (state.mTint != null && state.mTint.canApplyTheme()) {
495             state.mTint = state.mTint.obtainForTheme(t);
496         }
497 
498         updateLocalState(t.getResources());
499     }
500 
501     @Override
canApplyTheme()502     public boolean canApplyTheme() {
503         return mNinePatchState != null && mNinePatchState.canApplyTheme();
504     }
505 
506     @NonNull
getPaint()507     public Paint getPaint() {
508         if (mPaint == null) {
509             mPaint = new Paint();
510             mPaint.setDither(DEFAULT_DITHER);
511         }
512         return mPaint;
513     }
514 
515     @Override
getIntrinsicWidth()516     public int getIntrinsicWidth() {
517         return mBitmapWidth;
518     }
519 
520     @Override
getIntrinsicHeight()521     public int getIntrinsicHeight() {
522         return mBitmapHeight;
523     }
524 
525     @Override
getOpacity()526     public int getOpacity() {
527         return mNinePatchState.mNinePatch.hasAlpha()
528                 || (mPaint != null && mPaint.getAlpha() < 255) ?
529                         PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
530     }
531 
532     @Override
getTransparentRegion()533     public Region getTransparentRegion() {
534         return mNinePatchState.mNinePatch.getTransparentRegion(getBounds());
535     }
536 
537     @Override
getConstantState()538     public ConstantState getConstantState() {
539         mNinePatchState.mChangingConfigurations = getChangingConfigurations();
540         return mNinePatchState;
541     }
542 
543     @Override
mutate()544     public Drawable mutate() {
545         if (!mMutated && super.mutate() == this) {
546             mNinePatchState = new NinePatchState(mNinePatchState);
547             mMutated = true;
548         }
549         return this;
550     }
551 
552     /**
553      * @hide
554      */
clearMutated()555     public void clearMutated() {
556         super.clearMutated();
557         mMutated = false;
558     }
559 
560     @Override
onStateChange(int[] stateSet)561     protected boolean onStateChange(int[] stateSet) {
562         final NinePatchState state = mNinePatchState;
563         if (state.mTint != null && state.mTintMode != null) {
564             mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
565             return true;
566         }
567 
568         return false;
569     }
570 
571     @Override
isStateful()572     public boolean isStateful() {
573         final NinePatchState s = mNinePatchState;
574         return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
575     }
576 
577     /** @hide */
578     @Override
hasFocusStateSpecified()579     public boolean hasFocusStateSpecified() {
580         return mNinePatchState.mTint != null && mNinePatchState.mTint.hasFocusStateSpecified();
581     }
582 
583     final static class NinePatchState extends ConstantState {
584         @Config int mChangingConfigurations;
585 
586         // Values loaded during inflation.
587         NinePatch mNinePatch = null;
588         ColorStateList mTint = null;
589         Mode mTintMode = DEFAULT_TINT_MODE;
590         Rect mPadding = null;
591         Insets mOpticalInsets = Insets.NONE;
592         float mBaseAlpha = 1.0f;
593         boolean mDither = DEFAULT_DITHER;
594         boolean mAutoMirrored = false;
595 
596         int[] mThemeAttrs;
597 
NinePatchState()598         NinePatchState() {
599             // Empty constructor.
600         }
601 
NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding)602         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding) {
603             this(ninePatch, padding, null, DEFAULT_DITHER, false);
604         }
605 
NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding, @Nullable Rect opticalInsets)606         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding,
607                 @Nullable Rect opticalInsets) {
608             this(ninePatch, padding, opticalInsets, DEFAULT_DITHER, false);
609         }
610 
NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding, @Nullable Rect opticalInsets, boolean dither, boolean autoMirror)611         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding,
612                 @Nullable Rect opticalInsets, boolean dither, boolean autoMirror) {
613             mNinePatch = ninePatch;
614             mPadding = padding;
615             mOpticalInsets = Insets.of(opticalInsets);
616             mDither = dither;
617             mAutoMirrored = autoMirror;
618         }
619 
NinePatchState(@onNull NinePatchState orig)620         NinePatchState(@NonNull NinePatchState orig) {
621             mChangingConfigurations = orig.mChangingConfigurations;
622             mNinePatch = orig.mNinePatch;
623             mTint = orig.mTint;
624             mTintMode = orig.mTintMode;
625             mPadding = orig.mPadding;
626             mOpticalInsets = orig.mOpticalInsets;
627             mBaseAlpha = orig.mBaseAlpha;
628             mDither = orig.mDither;
629             mAutoMirrored = orig.mAutoMirrored;
630             mThemeAttrs = orig.mThemeAttrs;
631         }
632 
633         @Override
canApplyTheme()634         public boolean canApplyTheme() {
635             return mThemeAttrs != null
636                     || (mTint != null && mTint.canApplyTheme())
637                     || super.canApplyTheme();
638         }
639 
640         @Override
newDrawable()641         public Drawable newDrawable() {
642             return new NinePatchDrawable(this, null);
643         }
644 
645         @Override
newDrawable(Resources res)646         public Drawable newDrawable(Resources res) {
647             return new NinePatchDrawable(this, res);
648         }
649 
650         @Override
getChangingConfigurations()651         public @Config int getChangingConfigurations() {
652             return mChangingConfigurations
653                     | (mTint != null ? mTint.getChangingConfigurations() : 0);
654         }
655     }
656 
computeBitmapSize()657     private void computeBitmapSize() {
658         final NinePatch ninePatch = mNinePatchState.mNinePatch;
659         if (ninePatch == null) {
660             return;
661         }
662 
663         final int sourceDensity = ninePatch.getDensity();
664         final int targetDensity = mTargetDensity;
665 
666         final Insets sourceOpticalInsets = mNinePatchState.mOpticalInsets;
667         if (sourceOpticalInsets != Insets.NONE) {
668             final int left = Drawable.scaleFromDensity(
669                     sourceOpticalInsets.left, sourceDensity, targetDensity, true);
670             final int top = Drawable.scaleFromDensity(
671                     sourceOpticalInsets.top, sourceDensity, targetDensity, true);
672             final int right = Drawable.scaleFromDensity(
673                     sourceOpticalInsets.right, sourceDensity, targetDensity, true);
674             final int bottom = Drawable.scaleFromDensity(
675                     sourceOpticalInsets.bottom, sourceDensity, targetDensity, true);
676             mOpticalInsets = Insets.of(left, top, right, bottom);
677         } else {
678             mOpticalInsets = Insets.NONE;
679         }
680 
681         final Rect sourcePadding = mNinePatchState.mPadding;
682         if (sourcePadding != null) {
683             if (mPadding == null) {
684                 mPadding = new Rect();
685             }
686             mPadding.left = Drawable.scaleFromDensity(
687                     sourcePadding.left, sourceDensity, targetDensity, false);
688             mPadding.top = Drawable.scaleFromDensity(
689                     sourcePadding.top, sourceDensity, targetDensity, false);
690             mPadding.right = Drawable.scaleFromDensity(
691                     sourcePadding.right, sourceDensity, targetDensity, false);
692             mPadding.bottom = Drawable.scaleFromDensity(
693                     sourcePadding.bottom, sourceDensity, targetDensity, false);
694         } else {
695             mPadding = null;
696         }
697 
698         mBitmapHeight = Drawable.scaleFromDensity(
699                 ninePatch.getHeight(), sourceDensity, targetDensity, true);
700         mBitmapWidth = Drawable.scaleFromDensity(
701                 ninePatch.getWidth(), sourceDensity, targetDensity, true);
702 
703         final NinePatch.InsetStruct insets = ninePatch.getBitmap().getNinePatchInsets();
704         if (insets != null) {
705             Rect outlineRect = insets.outlineRect;
706             mOutlineInsets = NinePatch.InsetStruct.scaleInsets(outlineRect.left, outlineRect.top,
707                     outlineRect.right, outlineRect.bottom, targetDensity / (float) sourceDensity);
708             mOutlineRadius = Drawable.scaleFromDensity(
709                     insets.outlineRadius, sourceDensity, targetDensity);
710         } else {
711             mOutlineInsets = null;
712         }
713     }
714 
715     /**
716      * The one constructor to rule them all. This is called by all public
717      * constructors to set the state and initialize local properties.
718      *
719      * @param state constant state to assign to the new drawable
720      */
NinePatchDrawable(@onNull NinePatchState state, @Nullable Resources res)721     private NinePatchDrawable(@NonNull NinePatchState state, @Nullable Resources res) {
722         mNinePatchState = state;
723 
724         updateLocalState(res);
725     }
726 
727     /**
728      * Initializes local dynamic properties from state.
729      */
updateLocalState(@ullable Resources res)730     private void updateLocalState(@Nullable Resources res) {
731         final NinePatchState state = mNinePatchState;
732 
733         // If we can, avoid calling any methods that initialize Paint.
734         if (state.mDither != DEFAULT_DITHER) {
735             setDither(state.mDither);
736         }
737 
738         // The nine-patch may have been created without a Resources object, in
739         // which case we should try to match the density of the nine patch (if
740         // available).
741         if (res == null && state.mNinePatch != null) {
742             mTargetDensity = state.mNinePatch.getDensity();
743         } else {
744             mTargetDensity = Drawable.resolveDensity(res, mTargetDensity);
745         }
746         mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
747         computeBitmapSize();
748     }
749 }
750