Home | History | Annotate | Download | only in graphics
      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      */
     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 
     91     public ImageData getImageData() {
     92         return mBaseImageData;
     93     }
     94 
     95     public int getWidth() {
     96         return mBaseImageData.width;
     97     }
     98 
     99     public int getHeight() {
    100         return mBaseImageData.height;
    101     }
    102 
    103     public Image getImage() {
    104         if (mBaseImage == null) {
    105             mBaseImage = new Image(AdtPlugin.getDisplay(), mBaseImageData);
    106         }
    107         return mBaseImage;
    108     }
    109 
    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      */
    118     public boolean isDirty() {
    119         return mDirtyFlag;
    120     }
    121 
    122     /**
    123      * Clear dirty(edited) flag.
    124      */
    125     public void clearDirtyFlag() {
    126         mDirtyFlag = false;
    127     }
    128 
    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 
    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 
    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 
    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 
    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 
    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 
    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      */
    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 
    207     private static final boolean isTransparentPixel(ImageData image, int x, int y) {
    208         return image.getAlpha(x, y) == 0x0;
    209     }
    210 
    211     private static final boolean isValidTickColor(int pixel) {
    212         return (pixel == BLACK_TICK || pixel == RED_TICK);
    213     }
    214 
    215     private void initNinePatchedImage(ImageData imageData, boolean hasNinePatchExtension) {
    216         mBaseImageData = imageData;
    217         mHasNinePatchExtension = hasNinePatchExtension;
    218     }
    219 
    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 
    238     private boolean ensureHorizontalPixel(int x, int y, int[] pixels) {
    239         return ensurePixel(x, y, pixels, x);
    240     }
    241 
    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      */
    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 
    286     private void createPatchArray() {
    287         mHorizontalPatchPixels = initArray(new int[mBaseImageData.width]);
    288         mVerticalPatchPixels = initArray(new int[mBaseImageData.height]);
    289     }
    290 
    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      */
    303     public void convertToNinePatch() {
    304         mBaseImageData = GraphicsUtilities.convertToNinePatch(mBaseImageData);
    305         mHasNinePatchExtension = true;
    306 
    307         createPatchArray();
    308         createContentArray();
    309 
    310         findPatches();
    311         findContentsArea();
    312     }
    313 
    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      */
    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      */
    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 
    360     public List<Tick> getHorizontalPatches() {
    361         return mHorizontalPatches;
    362     }
    363 
    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      */
    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 
    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 
    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 
    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      */
    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      */
    488     public List<Tick> getVerticalContents() {
    489         return mVerticalContents;
    490     }
    491 
    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 
    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      */
    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 
    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 
    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 
    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 
    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 
    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
    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 
    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 
    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
    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          */
    869         public int getLength() {
    870             return end - start;
    871         }
    872 
    873         public Tick(int tickColor) {
    874             color = tickColor;
    875         }
    876 
    877         @Override
    878         public String toString() {
    879             return String.format("%d tick: %d to %d", color, start, end);
    880         }
    881     }
    882 }
    883