1 /*
2  * Copyright (C) 2013 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.ide.eclipse.adt.internal.editors.draw9patch.graphics;
18 
19 import static com.android.SdkConstants.DOT_9PNG;
20 import static com.android.SdkConstants.DOT_PNG;
21 import com.android.ide.eclipse.adt.AdtPlugin;
22 
23 import org.eclipse.swt.graphics.Image;
24 import org.eclipse.swt.graphics.ImageData;
25 import org.eclipse.swt.graphics.Rectangle;
26 
27 import java.io.InputStream;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.List;
31 
32 /**
33  * The model of 9-patched image.
34  */
35 public class NinePatchedImage {
36     private static final boolean DEBUG = false;
37 
38     /**
39      * Get 9-patched filename as like image.9.png .
40      */
getNinePatchedFileName(String fileName)41     public static String getNinePatchedFileName(String fileName) {
42         if (fileName.endsWith(DOT_9PNG)) {
43             return fileName;
44         }
45         return fileName.substring(0, fileName.lastIndexOf(DOT_PNG)) + DOT_9PNG;
46     }
47 
48     // For stretch regions and padding
49     public static final int BLACK_TICK = 0xFF000000;
50     // For Layout Bounds
51     public static final int RED_TICK = 0xFFFF0000;
52     // Blank
53     public static final int TRANSPARENT_TICK = 0x00000000;
54 
55     private ImageData mBaseImageData;
56 
57     private Image mBaseImage = null;
58 
59     private boolean mHasNinePatchExtension = false;
60 
61     private boolean mDirtyFlag = false;
62 
63     private int[] mHorizontalPatchPixels = null;
64     private int[] mVerticalPatchPixels = null;
65 
66     private int[] mHorizontalContentPixels = null;
67     private int[] mVerticalContentPixels = null;
68 
69     // for Prevent unexpected stretch in StretchsView
70     private boolean mRedTickOnlyInHorizontalFlag = false;
71     private boolean mRedTickOnlyInVerticalFlag = false;
72 
73     private final List<Tick> mHorizontalPatches = new ArrayList<Tick>();
74     private final List<Tick> mVerticalPatches = new ArrayList<Tick>();
75 
76     private final List<Tick> mHorizontalContents = new ArrayList<Tick>();
77     private final List<Tick> mVerticalContents = new ArrayList<Tick>();
78 
79 
80     private static final int CHUNK_BIN_SIZE = 100;
81     private final List<Chunk> mChunkBin = new ArrayList<Chunk>(CHUNK_BIN_SIZE);
82 
83     private int mHorizontalFixedPatchSum = 0;
84     private int mVerticalFixedPatchSum = 0;
85 
86     private static final int PROJECTION_BIN_SIZE = 100;
87     private final List<Projection> mProjectionBin = new ArrayList<Projection>(PROJECTION_BIN_SIZE);
88 
89     private Chunk[][] mPatchChunks = null;
90 
getImageData()91     public ImageData getImageData() {
92         return mBaseImageData;
93     }
94 
getWidth()95     public int getWidth() {
96         return mBaseImageData.width;
97     }
98 
getHeight()99     public int getHeight() {
100         return mBaseImageData.height;
101     }
102 
getImage()103     public Image getImage() {
104         if (mBaseImage == null) {
105             mBaseImage = new Image(AdtPlugin.getDisplay(), mBaseImageData);
106         }
107         return mBaseImage;
108     }
109 
hasNinePatchExtension()110     public boolean hasNinePatchExtension() {
111         return mHasNinePatchExtension;
112     }
113 
114     /**
115      * Get the image has/hasn't been edited flag.
116      * @return If has been edited, return true
117      */
isDirty()118     public boolean isDirty() {
119         return mDirtyFlag;
120     }
121 
122     /**
123      * Clear dirty(edited) flag.
124      */
clearDirtyFlag()125     public void clearDirtyFlag() {
126         mDirtyFlag = false;
127     }
128 
NinePatchedImage(String fileName)129     public NinePatchedImage(String fileName) {
130         boolean hasNinePatchExtension = fileName.endsWith(DOT_9PNG);
131         ImageData data = new ImageData(fileName);
132 
133         initNinePatchedImage(data, hasNinePatchExtension);
134     }
135 
NinePatchedImage(InputStream inputStream, String fileName)136     public NinePatchedImage(InputStream inputStream, String fileName) {
137         boolean hasNinePatchExtension = fileName.endsWith(DOT_9PNG);
138         ImageData data = new ImageData(inputStream);
139 
140         initNinePatchedImage(data, hasNinePatchExtension);
141     }
142 
getChunk()143     private Chunk getChunk() {
144         if (mChunkBin.size() > 0) {
145             Chunk chunk = mChunkBin.remove(0);
146             chunk.init();
147             return chunk;
148         }
149         return new Chunk();
150     }
151 
recycleChunks(Chunk[][] patchChunks, List<Chunk> bin)152     private static final void recycleChunks(Chunk[][] patchChunks, List<Chunk> bin) {
153         int yLen = patchChunks.length;
154         int xLen = patchChunks[0].length;
155 
156         for (int y = 0; y < yLen; y++) {
157             for (int x = 0; x < xLen; x++) {
158                 if (bin.size() < CHUNK_BIN_SIZE) {
159                     bin.add(patchChunks[y][x]);
160                 }
161                 patchChunks[y][x] = null;
162             }
163         }
164     }
165 
getProjection()166     private Projection getProjection() {
167         if (mProjectionBin.size() > 0) {
168             Projection projection = mProjectionBin.remove(0);
169             return projection;
170         }
171         return new Projection();
172     }
173 
recycleProjections(Projection[][] projections, List<Projection> bin)174     private static final void recycleProjections(Projection[][] projections, List<Projection> bin) {
175         int yLen = projections.length;
176         int xLen = 0;
177         if (yLen > 0) {
178             xLen = projections[0].length;
179         }
180 
181         for (int y = 0; y < yLen; y++) {
182             for (int x = 0; x < xLen; x++) {
183                 if (bin.size() < CHUNK_BIN_SIZE) {
184                     bin.add(projections[y][x]);
185                 }
186                 projections[y][x] = null;
187             }
188         }
189     }
190 
initArray(int[] array)191     private static final int[] initArray(int[] array) {
192         int len = array.length;
193         for (int i = 0; i < len; i++) {
194             array[i] = TRANSPARENT_TICK;
195         }
196         return array;
197     }
198 
199     /**
200      * Get one pixel with alpha from the image.
201      * @return packed integer value as ARGB8888
202      */
getPixel(ImageData image, int x, int y)203     private static final int getPixel(ImageData image, int x, int y) {
204         return (image.getAlpha(x, y) << 24) + image.getPixel(x, y);
205     }
206 
isTransparentPixel(ImageData image, int x, int y)207     private static final boolean isTransparentPixel(ImageData image, int x, int y) {
208         return image.getAlpha(x, y) == 0x0;
209     }
210 
isValidTickColor(int pixel)211     private static final boolean isValidTickColor(int pixel) {
212         return (pixel == BLACK_TICK || pixel == RED_TICK);
213     }
214 
initNinePatchedImage(ImageData imageData, boolean hasNinePatchExtension)215     private void initNinePatchedImage(ImageData imageData, boolean hasNinePatchExtension) {
216         mBaseImageData = imageData;
217         mHasNinePatchExtension = hasNinePatchExtension;
218     }
219 
ensurePixel(int x, int y, int[] pixels, int index)220     private boolean ensurePixel(int x, int y, int[] pixels, int index) {
221         boolean isValid = true;
222         int pixel = getPixel(mBaseImageData, x, y);
223         if (!isTransparentPixel(mBaseImageData, x, y)) {
224             if (index == 0 || index == pixels.length - 1) {
225                 isValid = false;
226             }
227             if (isValidTickColor(pixel)) {
228                 pixels[index] = pixel;
229             } else {
230                 isValid = false;
231             }
232             // clear pixel
233             mBaseImageData.setAlpha(x, y, 0x0);
234         }
235         return isValid;
236     }
237 
ensureHorizontalPixel(int x, int y, int[] pixels)238     private boolean ensureHorizontalPixel(int x, int y, int[] pixels) {
239         return ensurePixel(x, y, pixels, x);
240     }
241 
ensureVerticalPixel(int x, int y, int[] pixels)242     private boolean ensureVerticalPixel(int x, int y, int[] pixels) {
243         return ensurePixel(x, y, pixels, y);
244     }
245 
246     /**
247      * Ensure that image data is 9-patch.
248      */
ensure9Patch()249     public boolean ensure9Patch() {
250         boolean isValid = true;
251 
252         int width = mBaseImageData.width;
253         int height = mBaseImageData.height;
254 
255         createPatchArray();
256         createContentArray();
257 
258         // horizontal
259         for (int x = 0; x < width; x++) {
260             // top row
261             if (!ensureHorizontalPixel(x, 0, mHorizontalPatchPixels)) {
262                 isValid = false;
263             }
264             // bottom row
265             if (!ensureHorizontalPixel(x, height - 1, mHorizontalContentPixels)) {
266                 isValid = false;
267             }
268         }
269         // vertical
270         for (int y = 0; y < height; y++) {
271             // left column
272             if (!ensureVerticalPixel(0, y, mVerticalPatchPixels)) {
273                 isValid = false;
274             }
275             // right column
276             if (!ensureVerticalPixel(width -1, y, mVerticalContentPixels)) {
277                 isValid = false;
278             }
279         }
280         findPatches();
281         findContentsArea();
282 
283         return isValid;
284     }
285 
createPatchArray()286     private void createPatchArray() {
287         mHorizontalPatchPixels = initArray(new int[mBaseImageData.width]);
288         mVerticalPatchPixels = initArray(new int[mBaseImageData.height]);
289     }
290 
createContentArray()291     private void createContentArray() {
292         mHorizontalContentPixels = initArray(new int[mBaseImageData.width]);
293         mVerticalContentPixels = initArray(new int[mBaseImageData.height]);
294     }
295 
296     /**
297      * Convert to 9-patch image.
298      * <p>
299      * This method doesn't consider that target image is already 9-patched or
300      * not.
301      * </p>
302      */
convertToNinePatch()303     public void convertToNinePatch() {
304         mBaseImageData = GraphicsUtilities.convertToNinePatch(mBaseImageData);
305         mHasNinePatchExtension = true;
306 
307         createPatchArray();
308         createContentArray();
309 
310         findPatches();
311         findContentsArea();
312     }
313 
isValid(int x, int y)314     public boolean isValid(int x, int y) {
315         return (x == 0) ^ (y == 0)
316                 ^ (x == mBaseImageData.width - 1) ^ (y == mBaseImageData.height - 1);
317     }
318 
319     /**
320      * Set patch or content.
321      */
setPatch(int x, int y, int color)322     public void setPatch(int x, int y, int color) {
323         if (isValid(x, y)) {
324             if (x == 0) {
325                 mVerticalPatchPixels[y] = color;
326             } else if (y == 0) {
327                 mHorizontalPatchPixels[x] = color;
328             } else if (x == mBaseImageData.width - 1) {
329                 mVerticalContentPixels[y] = color;
330             } else if (y == mBaseImageData.height - 1) {
331                 mHorizontalContentPixels[x] = color;
332             }
333 
334             // Mark as dirty
335             mDirtyFlag = true;
336         }
337     }
338 
339     /**
340      * Erase the pixel.
341      */
erase(int x, int y)342     public void erase(int x, int y) {
343         if (isValid(x, y)) {
344             int color = TRANSPARENT_TICK;
345             if (x == 0) {
346                 mVerticalPatchPixels[y] = color;
347             } else if (y == 0) {
348                 mHorizontalPatchPixels[x] = color;
349             } else if (x == mBaseImageData.width - 1) {
350                 mVerticalContentPixels[y] = color;
351             } else if (y == mBaseImageData.height - 1) {
352                 mHorizontalContentPixels[x] = color;
353             }
354 
355             // Mark as dirty
356             mDirtyFlag = true;
357         }
358     }
359 
getHorizontalPatches()360     public List<Tick> getHorizontalPatches() {
361         return mHorizontalPatches;
362     }
363 
getVerticalPatches()364     public List<Tick> getVerticalPatches() {
365         return mVerticalPatches;
366     }
367 
368     /**
369      * Find patches from pixels array.
370      * @param pixels Target of seeking ticks.
371      * @param out Add the found ticks.
372      * @return If BlackTick is not found but only RedTick is found, returns true
373      */
findPatches(int[] pixels, List<Tick> out)374     private static boolean findPatches(int[] pixels, List<Tick> out) {
375         boolean redTickOnly = true;
376         Tick patch = null;
377         int len = 0;
378 
379         // find patches
380         out.clear();
381         len = pixels.length - 1;
382         for (int i = 1; i < len; i++) {
383             int pixel = pixels[i];
384 
385             if (redTickOnly && pixel != TRANSPARENT_TICK && pixel != RED_TICK) {
386                 redTickOnly = false;
387             }
388 
389             if (patch != null) {
390                 if (patch.color != pixel) {
391                     patch.end = i;
392                     out.add(patch);
393                     patch = null;
394                 }
395             }
396             if (patch == null) {
397                 patch = new Tick(pixel);
398                 patch.start = i;
399             }
400         }
401 
402         if (patch != null) {
403             patch.end = len;
404             out.add(patch);
405         }
406         return redTickOnly;
407     }
408 
findPatches()409     public void findPatches() {
410 
411         // find horizontal patches
412         mRedTickOnlyInHorizontalFlag = findPatches(mHorizontalPatchPixels, mHorizontalPatches);
413 
414         // find vertical patches
415         mRedTickOnlyInVerticalFlag = findPatches(mVerticalPatchPixels, mVerticalPatches);
416     }
417 
getContentArea()418     public Rectangle getContentArea() {
419         Tick horizontal = getContentArea(mHorizontalContents);
420         Tick vertical = getContentArea(mVerticalContents);
421 
422         Rectangle rect = new Rectangle(0, 0, 0, 0);
423         rect.x = 1;
424         rect.width = mBaseImageData.width - 1;
425         rect.y = 1;
426         rect.height = mBaseImageData.height - 1;
427 
428         if (horizontal != null) {
429             rect.x = horizontal.start;
430             rect.width = horizontal.getLength();
431         }
432         if (vertical != null) {
433             rect.y = vertical.start;
434             rect.height = vertical.getLength();
435         }
436 
437         return rect;
438     }
439 
getContentArea(List<Tick> list)440     private Tick getContentArea(List<Tick> list) {
441         int size = list.size();
442         if (size == 0) {
443             return null;
444         }
445         if (size == 1) {
446             return list.get(0);
447         }
448 
449         Tick start = null;
450         Tick end = null;
451 
452         for (int i = 0; i < size; i++) {
453             Tick t = list.get(i);
454             if (t.color == BLACK_TICK) {
455                 if (start == null) {
456                     start = t;
457                     end = t;
458                 } else {
459                     end = t;
460                 }
461             }
462         }
463 
464         // red tick only
465         if (start == null) {
466             return null;
467         }
468 
469         Tick result = new Tick(start.color);
470         result.start = start.start;
471         result.end = end.end;
472 
473         return result;
474     }
475 
476     /**
477      * This is for unit test use only.
478      * @see com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImageTest
479      */
getHorizontalContents()480     public List<Tick> getHorizontalContents() {
481         return mHorizontalContents;
482     }
483 
484     /**
485      * This is for unit test use only.
486      * @see com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImageTest
487      */
getVerticalContents()488     public List<Tick> getVerticalContents() {
489         return mVerticalContents;
490     }
491 
findContentArea(int[] pixels, List<Tick> out)492     private static void findContentArea(int[] pixels, List<Tick> out) {
493         Tick contents = null;
494         int len = 0;
495 
496         // find horizontal contents area
497         out.clear();
498         len = pixels.length - 1;
499         for (int x = 1; x < len; x++) {
500             if (contents != null) {
501                 if (contents.color != pixels[x]) {
502                     contents.end = x;
503                     out.add(contents);
504                     contents = null;
505                 }
506             }
507             if (contents == null) {
508                 contents = new Tick(pixels[x]);
509                 contents.start = x;
510             }
511         }
512 
513         if (contents != null) {
514             contents.end = len;
515             out.add(contents);
516         }
517     }
518 
findContentsArea()519     public void findContentsArea() {
520 
521         // find horizontal contents area
522         findContentArea(mHorizontalContentPixels, mHorizontalContents);
523 
524         // find vertical contents area
525         findContentArea(mVerticalContentPixels, mVerticalContents);
526     }
527 
528     /**
529      * Get raw image data.
530      * <p>
531      * The raw image data is applicable for save.
532      * </p>
533      */
getRawImageData()534     public ImageData getRawImageData() {
535         ImageData image = GraphicsUtilities.copy(mBaseImageData);
536 
537         final int width = image.width;
538         final int height = image.height;
539         int len = 0;
540 
541         len = mHorizontalPatchPixels.length;
542         for (int x = 0; x < len; x++) {
543             int pixel = mHorizontalPatchPixels[x];
544             if (pixel != TRANSPARENT_TICK) {
545                 image.setAlpha(x, 0, 0xFF);
546                 image.setPixel(x, 0, pixel);
547             }
548         }
549 
550         len = mVerticalPatchPixels.length;
551         for (int y = 0; y < len; y++) {
552             int pixel = mVerticalPatchPixels[y];
553             if (pixel != TRANSPARENT_TICK) {
554                 image.setAlpha(0, y, 0xFF);
555                 image.setPixel(0, y, pixel);
556             }
557         }
558 
559         len = mHorizontalContentPixels.length;
560         for (int x = 0; x < len; x++) {
561             int pixel = mHorizontalContentPixels[x];
562             if (pixel != TRANSPARENT_TICK) {
563                 image.setAlpha(x, height - 1, 0xFF);
564                 image.setPixel(x, height - 1, pixel);
565             }
566         }
567 
568         len = mVerticalContentPixels.length;
569         for (int y = 0; y < len; y++) {
570             int pixel = mVerticalContentPixels[y];
571             if (pixel != TRANSPARENT_TICK) {
572                 image.setAlpha(width - 1, y, 0xFF);
573                 image.setPixel(width - 1, y, pixel);
574             }
575         }
576 
577         return image;
578     }
579 
getChunks(Chunk[][] chunks)580     public Chunk[][] getChunks(Chunk[][] chunks) {
581         int lenY = mVerticalPatches.size();
582         int lenX = mHorizontalPatches.size();
583 
584         if (lenY == 0 || lenX == 0) {
585             return null;
586         }
587 
588         if (chunks == null) {
589             chunks = new Chunk[lenY][lenX];
590         } else {
591             int y = chunks.length;
592             int x = chunks[0].length;
593             if (lenY != y || lenX != x) {
594                 recycleChunks(chunks, mChunkBin);
595                 chunks = new Chunk[lenY][lenX];
596             }
597         }
598 
599         // for calculate weights
600         float horizontalPatchSum = 0;
601         float verticalPatchSum = 0;
602 
603         mVerticalFixedPatchSum = 0;
604         mHorizontalFixedPatchSum = 0;
605 
606         for (int y = 0; y < lenY; y++) {
607             Tick yTick = mVerticalPatches.get(y);
608 
609             for (int x = 0; x < lenX; x++) {
610                 Tick xTick = mHorizontalPatches.get(x);
611                 Chunk t = getChunk();
612                 chunks[y][x] = t;
613 
614                 t.rect.x = xTick.start;
615                 t.rect.width = xTick.getLength();
616                 t.rect.y = yTick.start;
617                 t.rect.height = yTick.getLength();
618 
619                 if (mRedTickOnlyInHorizontalFlag
620                         || xTick.color == BLACK_TICK || lenX == 1) {
621                     t.type += Chunk.TYPE_HORIZONTAL;
622                     if (y == 0) {
623                         horizontalPatchSum += t.rect.width;
624                     }
625                 }
626                 if (mRedTickOnlyInVerticalFlag
627                         || yTick.color == BLACK_TICK || lenY == 1) {
628                     t.type += Chunk.TYPE_VERTICAL;
629                     if (x == 0) {
630                         verticalPatchSum += t.rect.height;
631                     }
632                 }
633 
634                 if ((t.type & Chunk.TYPE_HORIZONTAL) == 0 && lenX > 1 && y == 0) {
635                     mHorizontalFixedPatchSum += t.rect.width;
636                 }
637                 if ((t.type & Chunk.TYPE_VERTICAL) == 0 && lenY > 1 && x == 0) {
638                     mVerticalFixedPatchSum += t.rect.height;
639                 }
640 
641             }
642         }
643 
644         // calc weights
645         for (int y = 0; y < lenY; y++) {
646             for (int x = 0; x < lenX; x++) {
647                 Chunk chunk = chunks[y][x];
648                 if ((chunk.type & Chunk.TYPE_HORIZONTAL) != 0) {
649                     chunk.horizontalWeight = chunk.rect.width / horizontalPatchSum;
650                 }
651                 if ((chunk.type & Chunk.TYPE_VERTICAL) != 0) {
652                     chunk.verticalWeight = chunk.rect.height / verticalPatchSum;
653 
654                 }
655             }
656         }
657 
658         return chunks;
659     }
660 
getCorruptedChunks(Chunk[][] chunks)661     public Chunk[][] getCorruptedChunks(Chunk[][] chunks) {
662         chunks = getChunks(chunks);
663 
664         if (chunks != null) {
665             int yLen = chunks.length;
666             int xLen = chunks[0].length;
667 
668             for (int yPos = 0; yPos < yLen; yPos++) {
669                 for (int xPos = 0; xPos < xLen; xPos++) {
670                     Chunk c = chunks[yPos][xPos];
671                     Rectangle r = c.rect;
672                     if ((c.type & Chunk.TYPE_HORIZONTAL) != 0
673                             && isHorizontalCorrupted(mBaseImageData, r)) {
674                         c.type |= Chunk.TYPE_CORRUPT;
675                     }
676                     if ((c.type & Chunk.TYPE_VERTICAL) != 0
677                             && isVerticalCorrupted(mBaseImageData, r)) {
678                         c.type |= Chunk.TYPE_CORRUPT;
679                     }
680                 }
681             }
682         }
683         return chunks;
684     }
685 
isVerticalCorrupted(ImageData data, Rectangle r)686     private static boolean isVerticalCorrupted(ImageData data, Rectangle r) {
687         int[] column = new int[r.width];
688         int[] sample = new int[r.width];
689 
690         GraphicsUtilities.getHorizontalPixels(data, r.x, r.y, r.width, column);
691 
692         int lenY = r.y + r.height;
693         for (int y = r.y; y < lenY; y++) {
694             GraphicsUtilities.getHorizontalPixels(data, r.x, y, r.width, sample);
695             if (!Arrays.equals(column, sample)) {
696                 return true;
697             }
698         }
699         return false;
700     }
701 
isHorizontalCorrupted(ImageData data, Rectangle r)702     private static boolean isHorizontalCorrupted(ImageData data, Rectangle r) {
703         int[] column = new int[r.height];
704         int[] sample = new int[r.height];
705         GraphicsUtilities.getVerticalPixels(data, r.x, r.y, r.height, column);
706 
707         int lenX = r.x + r.width;
708         for (int x = r.x; x < lenX; x++) {
709             GraphicsUtilities.getVerticalPixels(data, x, r.y, r.height, sample);
710             if (!Arrays.equals(column, sample)) {
711                 return true;
712             }
713         }
714         return false;
715     }
716 
getProjections(int width, int height, Projection[][] projections)717     public Projection[][] getProjections(int width, int height, Projection[][] projections) {
718         mPatchChunks = getChunks(mPatchChunks);
719         if (mPatchChunks == null) {
720             return null;
721         }
722 
723         if (DEBUG) {
724             System.out.println(String.format("width:%d, height:%d", width, height));
725         }
726 
727         int lenY = mPatchChunks.length;
728         int lenX = mPatchChunks[0].length;
729 
730         if (projections == null) {
731             projections = new Projection[lenY][lenX];
732         } else {
733             int y = projections.length;
734             int x = projections[0].length;
735             if (lenY != y || lenX != x) {
736                 recycleProjections(projections, mProjectionBin);
737                 projections = new Projection[lenY][lenX];
738             }
739         }
740 
741         float xZoom = ((float) width / mBaseImageData.width);
742         float yZoom = ((float) height / mBaseImageData.height);
743 
744         if (DEBUG) {
745             System.out.println(String.format("xZoom:%f, yZoom:%f", xZoom, yZoom));
746         }
747 
748         int destX = 0;
749         int destY = 0;
750         int streatchableWidth = width - mHorizontalFixedPatchSum;
751         streatchableWidth = streatchableWidth > 0 ? streatchableWidth : 1;
752 
753         int streatchableHeight = height - mVerticalFixedPatchSum;
754         streatchableHeight = streatchableHeight > 0 ? streatchableHeight : 1;
755 
756         if (DEBUG) {
757             System.out.println(String.format("streatchable %d %d", streatchableWidth,
758                     streatchableHeight));
759         }
760 
761         for (int yPos = 0; yPos < lenY; yPos++) {
762             destX = 0;
763             Projection p = null;
764             for (int xPos = 0; xPos < lenX; xPos++) {
765                 Chunk chunk = mPatchChunks[yPos][xPos];
766 
767                 if (DEBUG) {
768                     System.out.println(String.format("Tile[%d, %d] = %s",
769                             yPos, xPos, chunk.toString()));
770                 }
771 
772                 p = getProjection();
773                 projections[yPos][xPos] = p;
774 
775                 p.chunk = chunk;
776                 p.src = chunk.rect;
777                 p.dest.x = destX;
778                 p.dest.y = destY;
779 
780                 // fixed size
781                 p.dest.width = chunk.rect.width;
782                 p.dest.height = chunk.rect.height;
783 
784                 // horizontal stretch
785                 if ((chunk.type & Chunk.TYPE_HORIZONTAL) != 0) {
786                     p.dest.width = Math.round(streatchableWidth * chunk.horizontalWeight);
787                 }
788                 // vertical stretch
789                 if ((chunk.type & Chunk.TYPE_VERTICAL) != 0) {
790                     p.dest.height = Math.round(streatchableHeight * chunk.verticalWeight);
791                 }
792 
793                 destX += p.dest.width;
794             }
795             destY += p.dest.height;
796         }
797         return projections;
798     }
799 
800     /**
801      * Projection class for make relation between chunked image and resized image.
802      */
803     public static class Projection {
804         public Chunk chunk = null;
805         public Rectangle src = null;
806         public final Rectangle dest = new Rectangle(0, 0, 0, 0);
807 
808         @Override
toString()809         public String toString() {
810             return String.format("src[%d, %d, %d, %d] => dest[%d, %d, %d, %d]",
811                     src.x, src.y, src.width, src.height,
812                     dest.x, dest.y, dest.width, dest.height);
813         }
814     }
815 
816     public static class Chunk {
817         public static final int TYPE_FIXED = 0x0;
818         public static final int TYPE_HORIZONTAL = 0x1;
819         public static final int TYPE_VERTICAL = 0x2;
820         public static final int TYPE_CORRUPT = 0x80000000;
821 
822         public int type = TYPE_FIXED;
823 
824         public Rectangle rect = new Rectangle(0, 0, 0, 0);
825 
826         public float horizontalWeight = 0.0f;
827         public float verticalWeight = 0.0f;
828 
init()829         void init() {
830             type = Chunk.TYPE_FIXED;
831             horizontalWeight = 0.0f;
832             verticalWeight = 0.0f;
833             rect.x = 0;
834             rect.y = 0;
835             rect.width = 0;
836             rect.height = 0;
837         }
838 
typeToString()839         private String typeToString() {
840             switch (type) {
841                 case TYPE_FIXED:
842                     return "FIXED";
843                 case TYPE_HORIZONTAL:
844                     return "HORIZONTAL";
845                 case TYPE_VERTICAL:
846                     return "VERTICAL";
847                 case TYPE_HORIZONTAL + TYPE_VERTICAL:
848                     return "BOTH";
849                 default:
850                     return "UNKNOWN";
851             }
852         }
853 
854         @Override
toString()855         public String toString() {
856             return String.format("%s %f/%f %s", typeToString(), horizontalWeight, verticalWeight,
857                     rect.toString());
858         }
859     }
860 
861     public static class Tick {
862         public int start;
863         public int end;
864         public int color;
865 
866         /**
867          * Get the tick length.
868          */
getLength()869         public int getLength() {
870             return end - start;
871         }
872 
Tick(int tickColor)873         public Tick(int tickColor) {
874             color = tickColor;
875         }
876 
877         @Override
toString()878         public String toString() {
879             return String.format("%d tick: %d to %d", color, start, end);
880         }
881     }
882 }
883