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