• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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 com.android.ninepatch;
18 
19 import java.awt.Graphics2D;
20 import java.awt.Rectangle;
21 import java.awt.RenderingHints;
22 import java.awt.image.BufferedImage;
23 import java.io.Serializable;
24 import java.util.ArrayList;
25 import java.util.List;
26 
27 /**
28  * The chunk information for a nine patch.
29  *
30  * This does not represent the bitmap, only the chunk info responsible for the padding and the
31  * stretching areas.
32  *
33  * Since android.graphics.drawable.NinePatchDrawable and android.graphics.NinePatch both deal with
34  * the nine patch chunk as a byte[], this class is converted to and from byte[] through
35  * serialization.
36  *
37  * This is meant to be used with the NinePatch_Delegate in Layoutlib API 5+.
38  */
39 public class NinePatchChunk implements Serializable {
40 
41     /** Generated Serial Version UID */
42     private static final long serialVersionUID = -7353439224505296217L;
43 
44     private static final int[] sPaddingRect = new int[4];
45 
46     private boolean mVerticalStartWithPatch;
47     private boolean mHorizontalStartWithPatch;
48 
49     private List<Rectangle> mFixed;
50     private List<Rectangle> mPatches;
51     private List<Rectangle> mHorizontalPatches;
52     private List<Rectangle> mVerticalPatches;
53 
54     private Pair<Integer> mHorizontalPadding;
55     private Pair<Integer> mVerticalPadding;
56 
57 
58     /**
59      * Data computed during drawing.
60      */
61     static final class DrawingData {
62         private int mRemainderHorizontal;
63         private int mRemainderVertical;
64         private float mHorizontalPatchesSum;
65         private float mVerticalPatchesSum;
66     }
67 
68     /**
69      * Computes and returns the 9-patch chunks.
70      * @param image the image containing both the content and the control outer line.
71      * @return the {@link NinePatchChunk}.
72      */
create(BufferedImage image)73     public static NinePatchChunk create(BufferedImage image) {
74         NinePatchChunk chunk = new NinePatchChunk();
75         chunk.findPatches(image);
76         return chunk;
77     }
78 
draw(BufferedImage image, Graphics2D graphics2D, int x, int y, int scaledWidth, int scaledHeight, int destDensity, int srcDensity)79     public void draw(BufferedImage image, Graphics2D graphics2D, int x, int y, int scaledWidth,
80             int scaledHeight, int destDensity, int srcDensity) {
81 
82         boolean scaling = destDensity != srcDensity && destDensity != 0 && srcDensity != 0;
83 
84         if (scaling) {
85             try {
86                 graphics2D = (Graphics2D) graphics2D.create();
87 
88                 // scale and transform
89                 float densityScale = (float) destDensity / srcDensity;
90 
91                 // translate/rotate the canvas.
92                 graphics2D.translate(x, y);
93                 graphics2D.scale(densityScale, densityScale);
94 
95                 // sets the new drawing bounds.
96                 scaledWidth /= densityScale;
97                 scaledHeight /= densityScale;
98                 x = y = 0;
99 
100                 // draw
101                 draw(image, graphics2D, x, y, scaledWidth, scaledHeight);
102             } finally {
103                 graphics2D.dispose();
104             }
105         } else {
106             // non density-scaled rendering
107             draw(image, graphics2D, x, y, scaledWidth, scaledHeight);
108         }
109     }
110 
draw(BufferedImage image, Graphics2D graphics2D, int x, int y, int scaledWidth, int scaledHeight)111     private void draw(BufferedImage image, Graphics2D graphics2D, int x, int y, int scaledWidth,
112             int scaledHeight) {
113         if (scaledWidth <= 1 || scaledHeight <= 1) {
114             return;
115         }
116 
117         Graphics2D g = (Graphics2D)graphics2D.create();
118         g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
119                 RenderingHints.VALUE_INTERPOLATION_BILINEAR);
120 
121         try {
122             if (mPatches.size() == 0) {
123                 g.drawImage(image, x, y, scaledWidth, scaledHeight, null);
124                 return;
125             }
126 
127             g.translate(x, y);
128             x = y = 0;
129 
130             DrawingData data = computePatches(scaledWidth, scaledHeight);
131 
132             int fixedIndex = 0;
133             int horizontalIndex = 0;
134             int verticalIndex = 0;
135             int patchIndex = 0;
136 
137             boolean hStretch;
138             boolean vStretch;
139 
140             float vWeightSum = 1.0f;
141             float vRemainder = data.mRemainderVertical;
142 
143             vStretch = mVerticalStartWithPatch;
144             while (y < scaledHeight - 1) {
145                 hStretch = mHorizontalStartWithPatch;
146 
147                 int height = 0;
148                 float vExtra = 0.0f;
149 
150                 float hWeightSum = 1.0f;
151                 float hRemainder = data.mRemainderHorizontal;
152 
153                 while (x < scaledWidth - 1) {
154                     Rectangle r;
155                     if (!vStretch) {
156                         if (hStretch) {
157                             r = mHorizontalPatches.get(horizontalIndex++);
158                             float extra = r.width / data.mHorizontalPatchesSum;
159                             int width = (int) (extra * hRemainder / hWeightSum);
160                             hWeightSum -= extra;
161                             hRemainder -= width;
162                             g.drawImage(image, x, y, x + width, y + r.height, r.x, r.y,
163                                     r.x + r.width, r.y + r.height, null);
164                             x += width;
165                         } else {
166                             r = mFixed.get(fixedIndex++);
167                             g.drawImage(image, x, y, x + r.width, y + r.height, r.x, r.y,
168                                     r.x + r.width, r.y + r.height, null);
169                             x += r.width;
170                         }
171                         height = r.height;
172                     } else {
173                         if (hStretch) {
174                             r = mPatches.get(patchIndex++);
175                             vExtra = r.height / data.mVerticalPatchesSum;
176                             height = (int) (vExtra * vRemainder / vWeightSum);
177                             float extra = r.width / data.mHorizontalPatchesSum;
178                             int width = (int) (extra * hRemainder / hWeightSum);
179                             hWeightSum -= extra;
180                             hRemainder -= width;
181                             g.drawImage(image, x, y, x + width, y + height, r.x, r.y,
182                                     r.x + r.width, r.y + r.height, null);
183                             x += width;
184                         } else {
185                             r = mVerticalPatches.get(verticalIndex++);
186                             vExtra = r.height / data.mVerticalPatchesSum;
187                             height = (int) (vExtra * vRemainder / vWeightSum);
188                             g.drawImage(image, x, y, x + r.width, y + height, r.x, r.y,
189                                     r.x + r.width, r.y + r.height, null);
190                             x += r.width;
191                         }
192 
193                     }
194                     hStretch = !hStretch;
195                 }
196                 x = 0;
197                 y += height;
198                 if (vStretch) {
199                     vWeightSum -= vExtra;
200                     vRemainder -= height;
201                 }
202                 vStretch = !vStretch;
203             }
204 
205         } finally {
206             g.dispose();
207         }
208     }
209 
210     /**
211      * Fills the given array with the nine patch padding.
212      *
213      * @param padding array of left, top, right, bottom padding
214      */
getPadding(int[] padding)215     public void getPadding(int[] padding) {
216         padding[0] = mHorizontalPadding.mFirst; // left
217         padding[2] = mHorizontalPadding.mSecond; // right
218         padding[1] = mVerticalPadding.mFirst; // top
219         padding[3] = mVerticalPadding.mSecond; // bottom
220     }
221 
222     /**
223      * Returns the padding as an int[] describing left, top, right, bottom.
224      *
225      * This method is not thread-safe and returns an array owned by the {@link NinePatchChunk}
226      * class.
227      * @return an internal array filled with the padding.
228      */
getPadding()229     public int[] getPadding() {
230         getPadding(sPaddingRect);
231         return sPaddingRect;
232     }
233 
computePatches(int scaledWidth, int scaledHeight)234     private DrawingData computePatches(int scaledWidth, int scaledHeight) {
235         DrawingData data = new DrawingData();
236         boolean measuredWidth = false;
237         boolean endRow = true;
238 
239         int remainderHorizontal = 0;
240         int remainderVertical = 0;
241 
242         if (mFixed.size() > 0) {
243             int start = mFixed.get(0).y;
244             for (Rectangle rect : mFixed) {
245                 if (rect.y > start) {
246                     endRow = true;
247                     measuredWidth = true;
248                 }
249                 if (!measuredWidth) {
250                     remainderHorizontal += rect.width;
251                 }
252                 if (endRow) {
253                     remainderVertical += rect.height;
254                     endRow = false;
255                     start = rect.y;
256                 }
257             }
258         }
259 
260         data.mRemainderHorizontal = scaledWidth - remainderHorizontal;
261         data.mRemainderVertical = scaledHeight - remainderVertical;
262 
263         data.mHorizontalPatchesSum = 0;
264         if (mHorizontalPatches.size() > 0) {
265             int start = -1;
266             for (Rectangle rect : mHorizontalPatches) {
267                 if (rect.x > start) {
268                     data.mHorizontalPatchesSum += rect.width;
269                     start = rect.x;
270                 }
271             }
272         } else {
273             int start = -1;
274             for (Rectangle rect : mPatches) {
275                 if (rect.x > start) {
276                     data.mHorizontalPatchesSum += rect.width;
277                     start = rect.x;
278                 }
279             }
280         }
281 
282         data.mVerticalPatchesSum = 0;
283         if (mVerticalPatches.size() > 0) {
284             int start = -1;
285             for (Rectangle rect : mVerticalPatches) {
286                 if (rect.y > start) {
287                     data.mVerticalPatchesSum += rect.height;
288                     start = rect.y;
289                 }
290             }
291         } else {
292             int start = -1;
293             for (Rectangle rect : mPatches) {
294                 if (rect.y > start) {
295                     data.mVerticalPatchesSum += rect.height;
296                     start = rect.y;
297                 }
298             }
299         }
300 
301         return data;
302     }
303 
304 
305     /**
306      * Finds the 9-patch patches and padding from a {@link BufferedImage} image that contains
307      * both the image content and the control outer lines.
308      */
findPatches(BufferedImage image)309     private void findPatches(BufferedImage image) {
310         // the size of the actual image content
311         int width = image.getWidth() - 2;
312         int height = image.getHeight() - 2;
313 
314         int[] row = null;
315         int[] column = null;
316 
317         // extract the patch line. Make sure to start at 1 and be only as long as the image content,
318         // to not include the outer control line.
319         row = GraphicsUtilities.getPixels(image, 1, 0, width, 1, row);
320         column = GraphicsUtilities.getPixels(image, 0, 1, 1, height, column);
321 
322         boolean[] result = new boolean[1];
323         Pair<List<Pair<Integer>>> left = getPatches(column, result);
324         mVerticalStartWithPatch = result[0];
325 
326         result = new boolean[1];
327         Pair<List<Pair<Integer>>> top = getPatches(row, result);
328         mHorizontalStartWithPatch = result[0];
329 
330         mFixed = getRectangles(left.mFirst, top.mFirst);
331         mPatches = getRectangles(left.mSecond, top.mSecond);
332 
333         if (mFixed.size() > 0) {
334             mHorizontalPatches = getRectangles(left.mFirst, top.mSecond);
335             mVerticalPatches = getRectangles(left.mSecond, top.mFirst);
336         } else {
337             if (top.mFirst.size() > 0) {
338                 mHorizontalPatches = new ArrayList<Rectangle>(0);
339                 mVerticalPatches = getVerticalRectangles(height, top.mFirst);
340             } else if (left.mFirst.size() > 0) {
341                 mHorizontalPatches = getHorizontalRectangles(width, left.mFirst);
342                 mVerticalPatches = new ArrayList<Rectangle>(0);
343             } else {
344                 mHorizontalPatches = mVerticalPatches = new ArrayList<Rectangle>(0);
345             }
346         }
347 
348         // extract the padding line. Make sure to start at 1 and be only as long as the image
349         // content, to not include the outer control line.
350         row = GraphicsUtilities.getPixels(image, 1, height + 1, width, 1, row);
351         column = GraphicsUtilities.getPixels(image, width + 1, 1, 1, height, column);
352 
353         top = getPatches(row, result);
354         mHorizontalPadding = getPadding(top.mFirst);
355 
356         left = getPatches(column, result);
357         mVerticalPadding = getPadding(left.mFirst);
358     }
359 
getVerticalRectangles(int imageHeight, List<Pair<Integer>> topPairs)360     private List<Rectangle> getVerticalRectangles(int imageHeight,
361             List<Pair<Integer>> topPairs) {
362         List<Rectangle> rectangles = new ArrayList<Rectangle>();
363         for (Pair<Integer> top : topPairs) {
364             int x = top.mFirst;
365             int width = top.mSecond - top.mFirst;
366 
367             rectangles.add(new Rectangle(x, 0, width, imageHeight));
368         }
369         return rectangles;
370     }
371 
getHorizontalRectangles(int imageWidth, List<Pair<Integer>> leftPairs)372     private List<Rectangle> getHorizontalRectangles(int imageWidth,
373             List<Pair<Integer>> leftPairs) {
374         List<Rectangle> rectangles = new ArrayList<Rectangle>();
375         for (Pair<Integer> left : leftPairs) {
376             int y = left.mFirst;
377             int height = left.mSecond - left.mFirst;
378 
379             rectangles.add(new Rectangle(0, y, imageWidth, height));
380         }
381         return rectangles;
382     }
383 
getPadding(List<Pair<Integer>> pairs)384     private Pair<Integer> getPadding(List<Pair<Integer>> pairs) {
385         if (pairs.size() == 0) {
386             return new Pair<Integer>(0, 0);
387         } else if (pairs.size() == 1) {
388             if (pairs.get(0).mFirst == 0) {
389                 return new Pair<Integer>(pairs.get(0).mSecond - pairs.get(0).mFirst, 0);
390             } else {
391                 return new Pair<Integer>(0, pairs.get(0).mSecond - pairs.get(0).mFirst);
392             }
393         } else {
394             int index = pairs.size() - 1;
395             return new Pair<Integer>(pairs.get(0).mSecond - pairs.get(0).mFirst,
396                     pairs.get(index).mSecond - pairs.get(index).mFirst);
397         }
398     }
399 
getRectangles(List<Pair<Integer>> leftPairs, List<Pair<Integer>> topPairs)400     private List<Rectangle> getRectangles(List<Pair<Integer>> leftPairs,
401             List<Pair<Integer>> topPairs) {
402         List<Rectangle> rectangles = new ArrayList<Rectangle>();
403         for (Pair<Integer> left : leftPairs) {
404             int y = left.mFirst;
405             int height = left.mSecond - left.mFirst;
406             for (Pair<Integer> top : topPairs) {
407                 int x = top.mFirst;
408                 int width = top.mSecond - top.mFirst;
409 
410                 rectangles.add(new Rectangle(x, y, width, height));
411             }
412         }
413         return rectangles;
414     }
415 
416     /**
417      * Computes a list of Patch based on a pixel line.
418      *
419      * This returns both the fixed areas, and the patches (stretchable) areas.
420      *
421      * The return value is a pair of list. The first list ({@link Pair#mFirst}) is the list
422      * of fixed area. The second list ({@link Pair#mSecond}) is the list of stretchable areas.
423      *
424      * Each area is defined as a Pair of (start, end) coordinate in the given line.
425      *
426      * @param pixels the pixels of the control line. The line should have the same length as the
427      *           content (i.e. it should be stripped of the first/last control pixel which are not
428      *           used)
429      * @param startWithPatch a boolean array of size 1 used to return the boolean value of whether
430      *           a patch (stretchable area) is first or not.
431      * @return
432      */
getPatches(int[] pixels, boolean[] startWithPatch)433     private Pair<List<Pair<Integer>>> getPatches(int[] pixels, boolean[] startWithPatch) {
434         int lastIndex = 0;
435         int lastPixel = pixels[0];
436         boolean first = true;
437 
438         List<Pair<Integer>> fixed = new ArrayList<Pair<Integer>>();
439         List<Pair<Integer>> patches = new ArrayList<Pair<Integer>>();
440 
441         for (int i = 0; i < pixels.length; i++) {
442             int pixel = pixels[i];
443             if (pixel != lastPixel) {
444                 if (lastPixel == 0xFF000000) {
445                     if (first) startWithPatch[0] = true;
446                     patches.add(new Pair<Integer>(lastIndex, i));
447                 } else {
448                     fixed.add(new Pair<Integer>(lastIndex, i));
449                 }
450                 first = false;
451 
452                 lastIndex = i;
453                 lastPixel = pixel;
454             }
455         }
456         if (lastPixel == 0xFF000000) {
457             if (first) startWithPatch[0] = true;
458             patches.add(new Pair<Integer>(lastIndex, pixels.length));
459         } else {
460             fixed.add(new Pair<Integer>(lastIndex, pixels.length));
461         }
462 
463         if (patches.size() == 0) {
464             patches.add(new Pair<Integer>(1, pixels.length));
465             startWithPatch[0] = true;
466             fixed.clear();
467         }
468 
469         return new Pair<List<Pair<Integer>>>(fixed, patches);
470     }
471 
472     /**
473      * A pair of values.
474      *
475      * @param <E>
476      */
477     /*package*/ static class Pair<E> implements Serializable {
478         /** Generated Serial Version UID */
479         private static final long serialVersionUID = -2204108979541762418L;
480         E mFirst;
481         E mSecond;
482 
Pair(E first, E second)483         Pair(E first, E second) {
484             mFirst = first;
485             mSecond = second;
486         }
487 
488         @Override
toString()489         public String toString() {
490             return "Pair[" + mFirst + ", " + mSecond + "]";
491         }
492     }
493 
494 }
495