diff options
author | Keiji Ariyama <keiji_ariyama@c-lis.co.jp> | 2013-01-30 06:46:08 +0900 |
---|---|---|
committer | Keiji Ariyama <keiji_ariyama@c-lis.co.jp> | 2013-04-13 01:53:57 +0900 |
commit | ab78b7e3ea4b7aced78a05ec273995588e094671 (patch) | |
tree | 16200f17136399666d6373b814cd17cbc13c016d /eclipse | |
parent | d4d130da5b9421155bf9997aaca0f9d3cc626221 (diff) | |
download | sdk-ab78b7e3ea4b7aced78a05ec273995588e094671.zip sdk-ab78b7e3ea4b7aced78a05ec273995588e094671.tar.gz sdk-ab78b7e3ea4b7aced78a05ec273995588e094671.tar.bz2 |
Add "Draw 9-patch editor" to ADT.
Change-Id: I1a60ac1e683887dc4cecf8b18bdcf9def48f19a4
Diffstat (limited to 'eclipse')
30 files changed, 4093 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/checker.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/checker.png Binary files differnew file mode 100644 index 0000000..78908f4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/checker.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index 77108c8..d04a1f4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -732,6 +732,14 @@ <contentTypeBinding contentTypeId="com.android.ide.eclipse.adt.binaryXml"> </contentTypeBinding> </editor> + <editor + class="com.android.ide.eclipse.adt.internal.editors.draw9patch.Draw9PatchEditor" + contributorClass="com.android.ide.eclipse.adt.internal.editors.common.CommonActionContributor" + icon="icons/android_file.png" + id="draw9patchplugin.editors.Draw9PatchEditor" + name="Android Draw 9-patch Editor" + extensions="9.png"> + </editor> </extension> <extension point="org.eclipse.ui.views"> <view diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/Draw9PatchEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/Draw9PatchEditor.java new file mode 100644 index 0000000..48ef7c3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/Draw9PatchEditor.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.draw9patch; + +import static com.android.SdkConstants.DOT_9PNG; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.ui.ImageViewer; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.ui.MainFrame; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.ImageLoader; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorSite; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.dialogs.SaveAsDialog; +import org.eclipse.ui.part.EditorPart; +import org.eclipse.ui.part.FileEditorInput; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +/** + * Draw9Patch editor part. + */ +public class Draw9PatchEditor extends EditorPart implements ImageViewer.UpdateListener { + + private IProject mProject = null; + + private FileEditorInput mFileEditorInput = null; + + private String mFileName = null; + + private NinePatchedImage mNinePatchedImage = null; + + private MainFrame mMainFrame = null; + + @Override + public void init(IEditorSite site, IEditorInput input) throws PartInitException { + setSite(site); + setInput(input); + setPartName(input.getName()); + + // The contract of init() mentions we need to fail if we can't + // understand the input. + if (input instanceof FileEditorInput) { + // We try to open a file that is part of the current workspace + mFileEditorInput = (FileEditorInput) input; + mFileName = mFileEditorInput.getName(); + mProject = mFileEditorInput.getFile().getProject(); + } else { + throw new PartInitException("Input is not of type FileEditorInput " + //$NON-NLS-1$ + "nor FileStoreEditorInput: " + //$NON-NLS-1$ + input == null ? "null" : input.toString()); //$NON-NLS-1$ + } + + } + + @Override + public boolean isSaveAsAllowed() { + return true; + } + + @Override + public void doSaveAs() { + IPath relativePath = null; + if ((relativePath = showSaveAsDialog()) != null) { + mFileEditorInput = new FileEditorInput(ResourcesPlugin.getWorkspace().getRoot() + .getFile(relativePath)); + mFileName = mFileEditorInput.getName(); + setInput(mFileEditorInput); + + doSave(new NullProgressMonitor()); + } + } + + @Override + public void doSave(final IProgressMonitor monitor) { + boolean hasNinePatchExtension = mFileName.endsWith(DOT_9PNG); + boolean doConvert = false; + + if (!hasNinePatchExtension) { + String patchedName = NinePatchedImage.getNinePatchedFileName(mFileName); + doConvert = MessageDialog + .openQuestion(AdtPlugin.getDisplay().getActiveShell(), + "Warning", + String.format( + "The file \"%s\" doesn't seem to be a 9-patch file. \n" + + "Do you want to convert and save as \"%s\" ?", + mFileName, patchedName)); + + if (doConvert) { + IFile destFile = mProject.getFile(NinePatchedImage.getNinePatchedFileName( + mFileEditorInput.getFile().getProjectRelativePath().toOSString())); + if (!destFile.exists()) { + mFileEditorInput = new FileEditorInput(destFile); + mFileName = mFileEditorInput.getName(); + } else { + IPath relativePath = null; + if ((relativePath = showSaveAsDialog()) != null) { + mFileEditorInput = new FileEditorInput(ResourcesPlugin.getWorkspace() + .getRoot().getFile(relativePath)); + mFileName = mFileEditorInput.getName(); + } else { + doConvert = false; + } + } + } + } + + if (hasNinePatchExtension || doConvert) { + ImageLoader loader = new ImageLoader(); + loader.data = new ImageData[] { + mNinePatchedImage.getRawImageData() + }; + + IFile file = mFileEditorInput.getFile(); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + loader.save(outputStream, SWT.IMAGE_PNG); + byte[] byteArray = outputStream.toByteArray(); + + try { + if (file.exists()) { + file.setContents(new ByteArrayInputStream(byteArray), true, false, monitor); + } else { + file.create(new ByteArrayInputStream(byteArray), true, monitor); + } + + mNinePatchedImage.clearDirtyFlag(); + + AdtPlugin.getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + setPartName(mFileName); + firePropertyChange(PROP_DIRTY); + } + }); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } + } + } + + @Override + public void createPartControl(Composite parent) { + mMainFrame = new MainFrame(parent, SWT.NULL); + + ImageViewer imageViewer = mMainFrame.getImageEditorPanel().getImageViewer(); + imageViewer.addUpdateListener(this); + + mNinePatchedImage = imageViewer.loadFile(mFileEditorInput.getPath().toOSString()); + if (mNinePatchedImage.hasNinePatchExtension()) { + if (!mNinePatchedImage.ensure9Patch() && showConvertMessageBox(mFileName)) { + // Reload image + mNinePatchedImage = imageViewer.loadFile(mFileEditorInput.getPath().toOSString()); + mNinePatchedImage.convertToNinePatch(); + } + } else { + mNinePatchedImage.convertToNinePatch(); + } + + imageViewer.startDisplay(); + + parent.layout(); + } + + @Override + public void setFocus() { + mMainFrame.forceFocus(); + } + + @Override + public boolean isDirty() { + return mNinePatchedImage.isDirty(); + } + + @Override + public void update(NinePatchedImage image) { + if (image.isDirty()) { + firePropertyChange(PROP_DIRTY); + } + } + + private IPath showSaveAsDialog() { + SaveAsDialog dialog = new SaveAsDialog(AdtPlugin.getDisplay().getActiveShell()); + + IFile dest = mProject.getFile(NinePatchedImage.getNinePatchedFileName( + mFileEditorInput.getFile().getProjectRelativePath().toOSString())); + dialog.setOriginalFile(dest); + + dialog.create(); + + if (dialog.open() == Window.CANCEL) { + return null; + } + + return dialog.getResult(); + } + + private static boolean showConvertMessageBox(String fileName) { + return MessageDialog.openQuestion( + AdtPlugin.getDisplay().getActiveShell(), + "Warning", + String.format("The file \"%s\" doesn't seem to be a 9-patch file. \n" + + "Do you want to convert?", fileName)); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/graphics/GraphicsUtilities.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/graphics/GraphicsUtilities.java new file mode 100644 index 0000000..74c2f04 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/graphics/GraphicsUtilities.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics; + +import org.eclipse.swt.graphics.ImageData; + +/** + * The utility class for SWT Image and ImageData manipulation. + */ +public class GraphicsUtilities { + + /** + * Convert normal image to 9-patched. + * @return Returns 9-patched ImageData object. If image is null, returns null. + */ + public static ImageData convertToNinePatch(ImageData image) { + if (image == null) { + return null; + } + ImageData result = new ImageData(image.width + 2, image.height + 2, image.depth, + image.palette); + + final int[] colors = new int[image.width]; + final byte[] alpha = new byte[image.width]; + + for (int y = 0; y < image.height; y++) { + + // Copy pixels + image.getPixels(0, y, image.width, colors, 0); + result.setPixels(1, y + 1, image.width, colors, 0); + + // Copy alpha + image.getAlphas(0, y, image.width, alpha, 0); + result.setAlphas(1, y + 1, image.width, alpha, 0); + } + + return result; + } + + /** + * Wipe all color and alpha pixels. + */ + public static void clearImageData(ImageData imageData) { + if (imageData == null) { + throw new IllegalArgumentException("image data must not be null"); + } + int width = imageData.width; + int height = imageData.height; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + imageData.setPixel(x, y, 0x00000000); + imageData.setAlpha(x, y, 0x00); + } + } + } + + /** + * Duplicate the image data. + * @return If image is null, return null. + */ + public static ImageData copy(ImageData image) { + if (image == null) { + return null; + } + ImageData result = new ImageData(image.width, image.height, image.depth, + image.palette); + + final int[] colors = new int[image.width]; + final byte[] alpha = new byte[image.width]; + + for (int y = 0; y < image.height; y++) { + + // Copy pixels + image.getPixels(0, y, image.width, colors, 0); + result.setPixels(0, y, image.width, colors, 0); + + // Copy alpha + image.getAlphas(0, y, image.width, alpha, 0); + result.setAlphas(0, y, image.width, alpha, 0); + } + + return result; + } + + /** + * Get column pixels. + * @return length of obtained pixels. + */ + public static int getVerticalPixels(ImageData data, int x, int y, int height, int[] out) { + if (data == null) { + throw new IllegalArgumentException("data must not be null"); + } + if (out == null) { + throw new IllegalArgumentException("out array must not be null"); + } + if (height > out.length) { + throw new IllegalArgumentException("out array length must be > height"); + } + if (data.height < (y + height)) { + throw new IllegalArgumentException("image height must be > (y + height)"); + } + if (x < 0 || y < 0) { + throw new IllegalArgumentException("argument x, y must be >= 0"); + } + if (x >= data.width) { + throw new IllegalArgumentException("argument x must be < data.width"); + } + if (y >= data.height) { + throw new IllegalArgumentException("argument y must be < data.height"); + } + if (height <= 0) { + throw new IllegalArgumentException("argument height must be > 0"); + } + + int idx = 0; + while (idx < height) { + data.getPixels(x, (y + idx), 1, out, idx); + idx++; + } + return idx; + } + + /** + * Get row pixels. + */ + public static void getHorizontalPixels(ImageData data, int x, int y, int width, int[] out) { + if (data == null) { + throw new IllegalArgumentException("data must not be null"); + } + if (out == null) { + throw new IllegalArgumentException("out array must not be null"); + } + if (width > out.length) { + throw new IllegalArgumentException("out array length must be > width"); + } + if (data.width < (x + width)) { + throw new IllegalArgumentException("image height must be > (x + width)"); + } + if (x < 0 || y < 0) { + throw new IllegalArgumentException("argument x, y must be >= 0"); + } + if (x >= data.width) { + throw new IllegalArgumentException("argument x must be < data.width"); + } + if (y >= data.height) { + throw new IllegalArgumentException("argument y must be < data.height"); + } + if (width <= 0) { + throw new IllegalArgumentException("argument width must be > 0"); + } + + data.getPixels(x, y, width, out, 0); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/graphics/NinePatchedImage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/graphics/NinePatchedImage.java new file mode 100644 index 0000000..f1022c3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/graphics/NinePatchedImage.java @@ -0,0 +1,882 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics; + +import static com.android.SdkConstants.DOT_9PNG; +import static com.android.SdkConstants.DOT_PNG; +import com.android.ide.eclipse.adt.AdtPlugin; + +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Rectangle; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * The model of 9-patched image. + */ +public class NinePatchedImage { + private static final boolean DEBUG = false; + + /** + * Get 9-patched filename as like image.9.png . + */ + public static String getNinePatchedFileName(String fileName) { + if (fileName.endsWith(DOT_9PNG)) { + return fileName; + } + return fileName.substring(0, fileName.lastIndexOf(DOT_PNG)) + DOT_9PNG; + } + + // For stretch regions and padding + public static final int BLACK_TICK = 0xFF000000; + // For Layout Bounds + public static final int RED_TICK = 0xFFFF0000; + // Blank + public static final int TRANSPARENT_TICK = 0x00000000; + + private ImageData mBaseImageData; + + private Image mBaseImage = null; + + private boolean mHasNinePatchExtension = false; + + private boolean mDirtyFlag = false; + + private int[] mHorizontalPatchPixels = null; + private int[] mVerticalPatchPixels = null; + + private int[] mHorizontalContentPixels = null; + private int[] mVerticalContentPixels = null; + + // for Prevent unexpected stretch in StretchsView + private boolean mRedTickOnlyInHorizontalFlag = false; + private boolean mRedTickOnlyInVerticalFlag = false; + + private final List<Tick> mHorizontalPatches = new ArrayList<Tick>(); + private final List<Tick> mVerticalPatches = new ArrayList<Tick>(); + + private final List<Tick> mHorizontalContents = new ArrayList<Tick>(); + private final List<Tick> mVerticalContents = new ArrayList<Tick>(); + + + private static final int CHUNK_BIN_SIZE = 100; + private final List<Chunk> mChunkBin = new ArrayList<Chunk>(CHUNK_BIN_SIZE); + + private int mHorizontalFixedPatchSum = 0; + private int mVerticalFixedPatchSum = 0; + + private static final int PROJECTION_BIN_SIZE = 100; + private final List<Projection> mProjectionBin = new ArrayList<Projection>(PROJECTION_BIN_SIZE); + + private Chunk[][] mPatchChunks = null; + + public ImageData getImageData() { + return mBaseImageData; + } + + public int getWidth() { + return mBaseImageData.width; + } + + public int getHeight() { + return mBaseImageData.height; + } + + public Image getImage() { + if (mBaseImage == null) { + mBaseImage = new Image(AdtPlugin.getDisplay(), mBaseImageData); + } + return mBaseImage; + } + + public boolean hasNinePatchExtension() { + return mHasNinePatchExtension; + } + + /** + * Get the image has/hasn't been edited flag. + * @return If has been edited, return true + */ + public boolean isDirty() { + return mDirtyFlag; + } + + /** + * Clear dirty(edited) flag. + */ + public void clearDirtyFlag() { + mDirtyFlag = false; + } + + public NinePatchedImage(String fileName) { + boolean hasNinePatchExtension = fileName.endsWith(DOT_9PNG); + ImageData data = new ImageData(fileName); + + initNinePatchedImage(data, hasNinePatchExtension); + } + + public NinePatchedImage(InputStream inputStream, String fileName) { + boolean hasNinePatchExtension = fileName.endsWith(DOT_9PNG); + ImageData data = new ImageData(inputStream); + + initNinePatchedImage(data, hasNinePatchExtension); + } + + private Chunk getChunk() { + if (mChunkBin.size() > 0) { + Chunk chunk = mChunkBin.remove(0); + chunk.init(); + return chunk; + } + return new Chunk(); + } + + private static final void recycleChunks(Chunk[][] patchChunks, List<Chunk> bin) { + int yLen = patchChunks.length; + int xLen = patchChunks[0].length; + + for (int y = 0; y < yLen; y++) { + for (int x = 0; x < xLen; x++) { + if (bin.size() < CHUNK_BIN_SIZE) { + bin.add(patchChunks[y][x]); + } + patchChunks[y][x] = null; + } + } + } + + private Projection getProjection() { + if (mProjectionBin.size() > 0) { + Projection projection = mProjectionBin.remove(0); + return projection; + } + return new Projection(); + } + + private static final void recycleProjections(Projection[][] projections, List<Projection> bin) { + int yLen = projections.length; + int xLen = 0; + if (yLen > 0) { + xLen = projections[0].length; + } + + for (int y = 0; y < yLen; y++) { + for (int x = 0; x < xLen; x++) { + if (bin.size() < CHUNK_BIN_SIZE) { + bin.add(projections[y][x]); + } + projections[y][x] = null; + } + } + } + + private static final int[] initArray(int[] array) { + int len = array.length; + for (int i = 0; i < len; i++) { + array[i] = TRANSPARENT_TICK; + } + return array; + } + + /** + * Get one pixel with alpha from the image. + * @return packed integer value as ARGB8888 + */ + private static final int getPixel(ImageData image, int x, int y) { + return (image.getAlpha(x, y) << 24) + image.getPixel(x, y); + } + + private static final boolean isTransparentPixel(ImageData image, int x, int y) { + return image.getAlpha(x, y) == 0x0; + } + + private static final boolean isValidTickColor(int pixel) { + return (pixel == BLACK_TICK || pixel == RED_TICK); + } + + private void initNinePatchedImage(ImageData imageData, boolean hasNinePatchExtension) { + mBaseImageData = imageData; + mHasNinePatchExtension = hasNinePatchExtension; + } + + private boolean ensurePixel(int x, int y, int[] pixels, int index) { + boolean isValid = true; + int pixel = getPixel(mBaseImageData, x, y); + if (!isTransparentPixel(mBaseImageData, x, y)) { + if (index == 0 || index == pixels.length - 1) { + isValid = false; + } + if (isValidTickColor(pixel)) { + pixels[index] = pixel; + } else { + isValid = false; + } + // clear pixel + mBaseImageData.setAlpha(x, y, 0x0); + } + return isValid; + } + + private boolean ensureHorizontalPixel(int x, int y, int[] pixels) { + return ensurePixel(x, y, pixels, x); + } + + private boolean ensureVerticalPixel(int x, int y, int[] pixels) { + return ensurePixel(x, y, pixels, y); + } + + /** + * Ensure that image data is 9-patch. + */ + public boolean ensure9Patch() { + boolean isValid = true; + + int width = mBaseImageData.width; + int height = mBaseImageData.height; + + createPatchArray(); + createContentArray(); + + // horizontal + for (int x = 0; x < width; x++) { + // top row + if (!ensureHorizontalPixel(x, 0, mHorizontalPatchPixels)) { + isValid = false; + } + // bottom row + if (!ensureHorizontalPixel(x, height - 1, mHorizontalContentPixels)) { + isValid = false; + } + } + // vertical + for (int y = 0; y < height; y++) { + // left column + if (!ensureVerticalPixel(0, y, mVerticalPatchPixels)) { + isValid = false; + } + // right column + if (!ensureVerticalPixel(width -1, y, mVerticalContentPixels)) { + isValid = false; + } + } + findPatches(); + findContentsArea(); + + return isValid; + } + + private void createPatchArray() { + mHorizontalPatchPixels = initArray(new int[mBaseImageData.width]); + mVerticalPatchPixels = initArray(new int[mBaseImageData.height]); + } + + private void createContentArray() { + mHorizontalContentPixels = initArray(new int[mBaseImageData.width]); + mVerticalContentPixels = initArray(new int[mBaseImageData.height]); + } + + /** + * Convert to 9-patch image. + * <p> + * This method doesn't consider that target image is already 9-patched or + * not. + * </p> + */ + public void convertToNinePatch() { + mBaseImageData = GraphicsUtilities.convertToNinePatch(mBaseImageData); + mHasNinePatchExtension = true; + + createPatchArray(); + createContentArray(); + + findPatches(); + findContentsArea(); + } + + public boolean isValid(int x, int y) { + return (x == 0) ^ (y == 0) + ^ (x == mBaseImageData.width - 1) ^ (y == mBaseImageData.height - 1); + } + + /** + * Set patch or content. + */ + public void setPatch(int x, int y, int color) { + if (isValid(x, y)) { + if (x == 0) { + mVerticalPatchPixels[y] = color; + } else if (y == 0) { + mHorizontalPatchPixels[x] = color; + } else if (x == mBaseImageData.width - 1) { + mVerticalContentPixels[y] = color; + } else if (y == mBaseImageData.height - 1) { + mHorizontalContentPixels[x] = color; + } + + // Mark as dirty + mDirtyFlag = true; + } + } + + /** + * Erase the pixel. + */ + public void erase(int x, int y) { + if (isValid(x, y)) { + int color = TRANSPARENT_TICK; + if (x == 0) { + mVerticalPatchPixels[y] = color; + } else if (y == 0) { + mHorizontalPatchPixels[x] = color; + } else if (x == mBaseImageData.width - 1) { + mVerticalContentPixels[y] = color; + } else if (y == mBaseImageData.height - 1) { + mHorizontalContentPixels[x] = color; + } + + // Mark as dirty + mDirtyFlag = true; + } + } + + public List<Tick> getHorizontalPatches() { + return mHorizontalPatches; + } + + public List<Tick> getVerticalPatches() { + return mVerticalPatches; + } + + /** + * Find patches from pixels array. + * @param pixels Target of seeking ticks. + * @param out Add the found ticks. + * @return If BlackTick is not found but only RedTick is found, returns true + */ + private static boolean findPatches(int[] pixels, List<Tick> out) { + boolean redTickOnly = true; + Tick patch = null; + int len = 0; + + // find patches + out.clear(); + len = pixels.length - 1; + for (int i = 1; i < len; i++) { + int pixel = pixels[i]; + + if (redTickOnly && pixel != TRANSPARENT_TICK && pixel != RED_TICK) { + redTickOnly = false; + } + + if (patch != null) { + if (patch.color != pixel) { + patch.end = i; + out.add(patch); + patch = null; + } + } + if (patch == null) { + patch = new Tick(pixel); + patch.start = i; + } + } + + if (patch != null) { + patch.end = len; + out.add(patch); + } + return redTickOnly; + } + + public void findPatches() { + + // find horizontal patches + mRedTickOnlyInHorizontalFlag = findPatches(mHorizontalPatchPixels, mHorizontalPatches); + + // find vertical patches + mRedTickOnlyInVerticalFlag = findPatches(mVerticalPatchPixels, mVerticalPatches); + } + + public Rectangle getContentArea() { + Tick horizontal = getContentArea(mHorizontalContents); + Tick vertical = getContentArea(mVerticalContents); + + Rectangle rect = new Rectangle(0, 0, 0, 0); + rect.x = 1; + rect.width = mBaseImageData.width - 1; + rect.y = 1; + rect.height = mBaseImageData.height - 1; + + if (horizontal != null) { + rect.x = horizontal.start; + rect.width = horizontal.getLength(); + } + if (vertical != null) { + rect.y = vertical.start; + rect.height = vertical.getLength(); + } + + return rect; + } + + private Tick getContentArea(List<Tick> list) { + int size = list.size(); + if (size == 0) { + return null; + } + if (size == 1) { + return list.get(0); + } + + Tick start = null; + Tick end = null; + + for (int i = 0; i < size; i++) { + Tick t = list.get(i); + if (t.color == BLACK_TICK) { + if (start == null) { + start = t; + end = t; + } else { + end = t; + } + } + } + + // red tick only + if (start == null) { + return null; + } + + Tick result = new Tick(start.color); + result.start = start.start; + result.end = end.end; + + return result; + } + + /** + * This is for unit test use only. + * @see com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImageTest + */ + public List<Tick> getHorizontalContents() { + return mHorizontalContents; + } + + /** + * This is for unit test use only. + * @see com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImageTest + */ + public List<Tick> getVerticalContents() { + return mVerticalContents; + } + + private static void findContentArea(int[] pixels, List<Tick> out) { + Tick contents = null; + int len = 0; + + // find horizontal contents area + out.clear(); + len = pixels.length - 1; + for (int x = 1; x < len; x++) { + if (contents != null) { + if (contents.color != pixels[x]) { + contents.end = x; + out.add(contents); + contents = null; + } + } + if (contents == null) { + contents = new Tick(pixels[x]); + contents.start = x; + } + } + + if (contents != null) { + contents.end = len; + out.add(contents); + } + } + + public void findContentsArea() { + + // find horizontal contents area + findContentArea(mHorizontalContentPixels, mHorizontalContents); + + // find vertical contents area + findContentArea(mVerticalContentPixels, mVerticalContents); + } + + /** + * Get raw image data. + * <p> + * The raw image data is applicable for save. + * </p> + */ + public ImageData getRawImageData() { + ImageData image = GraphicsUtilities.copy(mBaseImageData); + + final int width = image.width; + final int height = image.height; + int len = 0; + + len = mHorizontalPatchPixels.length; + for (int x = 0; x < len; x++) { + int pixel = mHorizontalPatchPixels[x]; + if (pixel != TRANSPARENT_TICK) { + image.setAlpha(x, 0, 0xFF); + image.setPixel(x, 0, pixel); + } + } + + len = mVerticalPatchPixels.length; + for (int y = 0; y < len; y++) { + int pixel = mVerticalPatchPixels[y]; + if (pixel != TRANSPARENT_TICK) { + image.setAlpha(0, y, 0xFF); + image.setPixel(0, y, pixel); + } + } + + len = mHorizontalContentPixels.length; + for (int x = 0; x < len; x++) { + int pixel = mHorizontalContentPixels[x]; + if (pixel != TRANSPARENT_TICK) { + image.setAlpha(x, height - 1, 0xFF); + image.setPixel(x, height - 1, pixel); + } + } + + len = mVerticalContentPixels.length; + for (int y = 0; y < len; y++) { + int pixel = mVerticalContentPixels[y]; + if (pixel != TRANSPARENT_TICK) { + image.setAlpha(width - 1, y, 0xFF); + image.setPixel(width - 1, y, pixel); + } + } + + return image; + } + + public Chunk[][] getChunks(Chunk[][] chunks) { + int lenY = mVerticalPatches.size(); + int lenX = mHorizontalPatches.size(); + + if (lenY == 0 || lenX == 0) { + return null; + } + + if (chunks == null) { + chunks = new Chunk[lenY][lenX]; + } else { + int y = chunks.length; + int x = chunks[0].length; + if (lenY != y || lenX != x) { + recycleChunks(chunks, mChunkBin); + chunks = new Chunk[lenY][lenX]; + } + } + + // for calculate weights + float horizontalPatchSum = 0; + float verticalPatchSum = 0; + + mVerticalFixedPatchSum = 0; + mHorizontalFixedPatchSum = 0; + + for (int y = 0; y < lenY; y++) { + Tick yTick = mVerticalPatches.get(y); + + for (int x = 0; x < lenX; x++) { + Tick xTick = mHorizontalPatches.get(x); + Chunk t = getChunk(); + chunks[y][x] = t; + + t.rect.x = xTick.start; + t.rect.width = xTick.getLength(); + t.rect.y = yTick.start; + t.rect.height = yTick.getLength(); + + if (mRedTickOnlyInHorizontalFlag + || xTick.color == BLACK_TICK || lenX == 1) { + t.type += Chunk.TYPE_HORIZONTAL; + if (y == 0) { + horizontalPatchSum += t.rect.width; + } + } + if (mRedTickOnlyInVerticalFlag + || yTick.color == BLACK_TICK || lenY == 1) { + t.type += Chunk.TYPE_VERTICAL; + if (x == 0) { + verticalPatchSum += t.rect.height; + } + } + + if ((t.type & Chunk.TYPE_HORIZONTAL) == 0 && lenX > 1 && y == 0) { + mHorizontalFixedPatchSum += t.rect.width; + } + if ((t.type & Chunk.TYPE_VERTICAL) == 0 && lenY > 1 && x == 0) { + mVerticalFixedPatchSum += t.rect.height; + } + + } + } + + // calc weights + for (int y = 0; y < lenY; y++) { + for (int x = 0; x < lenX; x++) { + Chunk chunk = chunks[y][x]; + if ((chunk.type & Chunk.TYPE_HORIZONTAL) != 0) { + chunk.horizontalWeight = chunk.rect.width / horizontalPatchSum; + } + if ((chunk.type & Chunk.TYPE_VERTICAL) != 0) { + chunk.verticalWeight = chunk.rect.height / verticalPatchSum; + + } + } + } + + return chunks; + } + + public Chunk[][] getCorruptedChunks(Chunk[][] chunks) { + chunks = getChunks(chunks); + + if (chunks != null) { + int yLen = chunks.length; + int xLen = chunks[0].length; + + for (int yPos = 0; yPos < yLen; yPos++) { + for (int xPos = 0; xPos < xLen; xPos++) { + Chunk c = chunks[yPos][xPos]; + Rectangle r = c.rect; + if ((c.type & Chunk.TYPE_HORIZONTAL) != 0 + && isHorizontalCorrupted(mBaseImageData, r)) { + c.type |= Chunk.TYPE_CORRUPT; + } + if ((c.type & Chunk.TYPE_VERTICAL) != 0 + && isVerticalCorrupted(mBaseImageData, r)) { + c.type |= Chunk.TYPE_CORRUPT; + } + } + } + } + return chunks; + } + + private static boolean isVerticalCorrupted(ImageData data, Rectangle r) { + int[] column = new int[r.width]; + int[] sample = new int[r.width]; + + GraphicsUtilities.getHorizontalPixels(data, r.x, r.y, r.width, column); + + int lenY = r.y + r.height; + for (int y = r.y; y < lenY; y++) { + GraphicsUtilities.getHorizontalPixels(data, r.x, y, r.width, sample); + if (!Arrays.equals(column, sample)) { + return true; + } + } + return false; + } + + private static boolean isHorizontalCorrupted(ImageData data, Rectangle r) { + int[] column = new int[r.height]; + int[] sample = new int[r.height]; + GraphicsUtilities.getVerticalPixels(data, r.x, r.y, r.height, column); + + int lenX = r.x + r.width; + for (int x = r.x; x < lenX; x++) { + GraphicsUtilities.getVerticalPixels(data, x, r.y, r.height, sample); + if (!Arrays.equals(column, sample)) { + return true; + } + } + return false; + } + + public Projection[][] getProjections(int width, int height, Projection[][] projections) { + mPatchChunks = getChunks(mPatchChunks); + if (mPatchChunks == null) { + return null; + } + + if (DEBUG) { + System.out.println(String.format("width:%d, height:%d", width, height)); + } + + int lenY = mPatchChunks.length; + int lenX = mPatchChunks[0].length; + + if (projections == null) { + projections = new Projection[lenY][lenX]; + } else { + int y = projections.length; + int x = projections[0].length; + if (lenY != y || lenX != x) { + recycleProjections(projections, mProjectionBin); + projections = new Projection[lenY][lenX]; + } + } + + float xZoom = ((float) width / mBaseImageData.width); + float yZoom = ((float) height / mBaseImageData.height); + + if (DEBUG) { + System.out.println(String.format("xZoom:%f, yZoom:%f", xZoom, yZoom)); + } + + int destX = 0; + int destY = 0; + int streatchableWidth = width - mHorizontalFixedPatchSum; + streatchableWidth = streatchableWidth > 0 ? streatchableWidth : 1; + + int streatchableHeight = height - mVerticalFixedPatchSum; + streatchableHeight = streatchableHeight > 0 ? streatchableHeight : 1; + + if (DEBUG) { + System.out.println(String.format("streatchable %d %d", streatchableWidth, + streatchableHeight)); + } + + for (int yPos = 0; yPos < lenY; yPos++) { + destX = 0; + Projection p = null; + for (int xPos = 0; xPos < lenX; xPos++) { + Chunk chunk = mPatchChunks[yPos][xPos]; + + if (DEBUG) { + System.out.println(String.format("Tile[%d, %d] = %s", + yPos, xPos, chunk.toString())); + } + + p = getProjection(); + projections[yPos][xPos] = p; + + p.chunk = chunk; + p.src = chunk.rect; + p.dest.x = destX; + p.dest.y = destY; + + // fixed size + p.dest.width = chunk.rect.width; + p.dest.height = chunk.rect.height; + + // horizontal stretch + if ((chunk.type & Chunk.TYPE_HORIZONTAL) != 0) { + p.dest.width = Math.round(streatchableWidth * chunk.horizontalWeight); + } + // vertical stretch + if ((chunk.type & Chunk.TYPE_VERTICAL) != 0) { + p.dest.height = Math.round(streatchableHeight * chunk.verticalWeight); + } + + destX += p.dest.width; + } + destY += p.dest.height; + } + return projections; + } + + /** + * Projection class for make relation between chunked image and resized image. + */ + public static class Projection { + public Chunk chunk = null; + public Rectangle src = null; + public final Rectangle dest = new Rectangle(0, 0, 0, 0); + + @Override + public String toString() { + return String.format("src[%d, %d, %d, %d] => dest[%d, %d, %d, %d]", + src.x, src.y, src.width, src.height, + dest.x, dest.y, dest.width, dest.height); + } + } + + public static class Chunk { + public static final int TYPE_FIXED = 0x0; + public static final int TYPE_HORIZONTAL = 0x1; + public static final int TYPE_VERTICAL = 0x2; + public static final int TYPE_CORRUPT = 0x80000000; + + public int type = TYPE_FIXED; + + public Rectangle rect = new Rectangle(0, 0, 0, 0); + + public float horizontalWeight = 0.0f; + public float verticalWeight = 0.0f; + + void init() { + type = Chunk.TYPE_FIXED; + horizontalWeight = 0.0f; + verticalWeight = 0.0f; + rect.x = 0; + rect.y = 0; + rect.width = 0; + rect.height = 0; + } + + private String typeToString() { + switch (type) { + case TYPE_FIXED: + return "FIXED"; + case TYPE_HORIZONTAL: + return "HORIZONTAL"; + case TYPE_VERTICAL: + return "VERTICAL"; + case TYPE_HORIZONTAL + TYPE_VERTICAL: + return "BOTH"; + default: + return "UNKNOWN"; + } + } + + @Override + public String toString() { + return String.format("%s %f/%f %s", typeToString(), horizontalWeight, verticalWeight, + rect.toString()); + } + } + + public static class Tick { + public int start; + public int end; + public int color; + + /** + * Get the tick length. + */ + public int getLength() { + return end - start; + } + + public Tick(int tickColor) { + color = tickColor; + } + + @Override + public String toString() { + return String.format("%d tick: %d to %d", color, start, end); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/ImageEditorPanel.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/ImageEditorPanel.java new file mode 100644 index 0000000..7c45230 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/ImageEditorPanel.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.draw9patch.ui; + +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DropTarget; +import org.eclipse.swt.dnd.DropTargetListener; +import org.eclipse.swt.dnd.FileTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; + +/** + * Image editor pane. + */ +public class ImageEditorPanel extends Composite implements ImageViewer.UpdateListener, + StatusPanel.StatusChangedListener { + + private static final int WEIGHT_VIEWER = 3; + private static final int WEIGHT_PREVIEW = 1; + + private final ImageViewer mImageViewer; + private final StretchesViewer mStretchesViewer; + + public ImageViewer getImageViewer() { + return mImageViewer; + } + + public ImageEditorPanel(Composite parent, int style) { + super(parent, style); + + setLayout(new FillLayout()); + SashForm sashForm = new SashForm(this, SWT.HORIZONTAL); + + mImageViewer = new ImageViewer(sashForm, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); + mImageViewer.addUpdateListener(this); + + mStretchesViewer = new StretchesViewer(sashForm, SWT.BORDER); + + sashForm.setWeights(new int[] { + WEIGHT_VIEWER, WEIGHT_PREVIEW + }); + } + + @Override + public void zoomChanged(int zoom) { + mImageViewer.setZoom(zoom); + } + + @Override + public void scaleChanged(int scale) { + mStretchesViewer.setScale(scale); + } + + @Override + public void lockVisibilityChanged(boolean visible) { + mImageViewer.setShowLock(visible); + } + + @Override + public void patchesVisibilityChanged(boolean visible) { + mImageViewer.setShowPatchesArea(visible); + } + + @Override + public void badPatchesVisibilityChanged(boolean visible) { + mImageViewer.setShowBadPatchesArea(visible); + } + + @Override + public void contentAreaVisibilityChanged(boolean visible) { + mStretchesViewer.setShowContentArea(visible); + } + + @Override + public void update(NinePatchedImage image) { + mStretchesViewer.updatePreview(image); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/ImageViewer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/ImageViewer.java new file mode 100644 index 0000000..2414a39 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/ImageViewer.java @@ -0,0 +1,774 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.draw9patch.ui; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage.Chunk; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage.Tick; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.ScrollBar; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; + +/** + * View and edit Draw 9-patch image. + */ +public class ImageViewer extends Canvas implements PaintListener, KeyListener, MouseListener, + MouseMoveListener { + private static final boolean DEBUG = false; + + public static final String HELP_MESSAGE_KEY_TIPS = "Press Shift to erase pixels." + + " Press Control to draw layout bounds."; + + public static final String HELP_MESSAGE_KEY_TIPS2 = "Release Shift to draw pixels."; + + private static final Color BLACK_COLOR = AdtPlugin.getDisplay().getSystemColor(SWT.COLOR_BLACK); + private static final Color RED_COLOR = AdtPlugin.getDisplay().getSystemColor(SWT.COLOR_RED); + + private static final Color BACK_COLOR + = new Color(AdtPlugin.getDisplay(), new RGB(0x00, 0xFF, 0x00)); + private static final Color LOCK_COLOR + = new Color(AdtPlugin.getDisplay(), new RGB(0xFF, 0x00, 0x00)); + private static final Color PATCH_COLOR + = new Color(AdtPlugin.getDisplay(), new RGB(0xFF, 0xFF, 0x00)); + private static final Color PATCH_ONEWAY_COLOR + = new Color(AdtPlugin.getDisplay(), new RGB(0x00, 0x00, 0xFF)); + private static final Color CORRUPTED_COLOR + = new Color(AdtPlugin.getDisplay(), new RGB(0xFF, 0x00, 0x00)); + + private static final int NONE_ALPHA = 0xFF; + private static final int LOCK_ALPHA = 50; + private static final int PATCH_ALPHA = 50; + private static final int GUIDE_ALPHA = 60; + + private static final int MODE_NONE = 0x00; + private static final int MODE_BLACK_TICK = 0x01; + private static final int MODE_RED_TICK = 0x02; + private static final int MODE_ERASE = 0xFF; + + private int mDrawMode = MODE_NONE; + + private static final int MARGIN = 5; + private static final String CHECKER_PNG_PATH = "/icons/checker.png"; + + private Image mBackgroundLayer = null; + + private NinePatchedImage mNinePatchedImage = null; + + private Chunk[][] mChunks = null; + private Chunk[][] mBadChunks = null; + + private boolean mIsLockShown = true; + private boolean mIsPatchesShown = false; + private boolean mIsBadPatchesShown = false; + + private ScrollBar mHorizontalBar; + private ScrollBar mVerticalBar; + + private int mZoom = 500; + + private int mHorizontalScroll = 0; + private int mVerticalScroll = 0; + + private final Rectangle mPadding = new Rectangle(0, 0, 0, 0); + + // one pixel size that considered zoom + private int mZoomedPixelSize = 1; + + private Image mBufferImage = null; + + private boolean isCtrlPressed = false; + private boolean isShiftPressed = false; + + private final List<UpdateListener> mUpdateListenerList + = new ArrayList<UpdateListener>(); + + private final Point mCursorPoint = new Point(0, 0); + + private static final int DEFAULT_UPDATE_QUEUE_SIZE = 10; + + private final ArrayBlockingQueue<NinePatchedImage> mUpdateQueue + = new ArrayBlockingQueue<NinePatchedImage>(DEFAULT_UPDATE_QUEUE_SIZE); + + private final Runnable mUpdateRunnable = new Runnable() { + @Override + public void run() { + if (isDisposed()) { + return; + } + + redraw(); + + fireUpdateEvent(); + } + }; + + private final Thread mUpdateThread = new Thread() { + @Override + public void run() { + while (!isDisposed()) { + try { + mUpdateQueue.take(); + mNinePatchedImage.findPatches(); + mNinePatchedImage.findContentsArea(); + + mChunks = mNinePatchedImage.getChunks(mChunks); + mBadChunks = mNinePatchedImage.getCorruptedChunks(mBadChunks); + + AdtPlugin.getDisplay().asyncExec(mUpdateRunnable); + + } catch (InterruptedException e) { + } + } + } + }; + + private StatusChangedListener mStatusChangedListener = null; + + public void addUpdateListener(UpdateListener l) { + mUpdateListenerList.add(l); + } + + public void removeUpdateListener(UpdateListener l) { + mUpdateListenerList.remove(l); + } + + private void fireUpdateEvent() { + int len = mUpdateListenerList.size(); + for(int i=0; i < len; i++) { + mUpdateListenerList.get(i).update(mNinePatchedImage); + } + } + + public void setStatusChangedListener(StatusChangedListener l) { + mStatusChangedListener = l; + if (mStatusChangedListener != null) { + mStatusChangedListener.helpTextChanged(HELP_MESSAGE_KEY_TIPS); + } + } + + void setShowLock(boolean show) { + mIsLockShown = show; + redraw(); + } + + void setShowPatchesArea(boolean show) { + mIsPatchesShown = show; + redraw(); + } + + void setShowBadPatchesArea(boolean show) { + mIsBadPatchesShown = show; + redraw(); + } + + void setZoom(int zoom) { + mZoom = zoom; + mZoomedPixelSize = getZoomedPixelSize(1); + redraw(); + } + + public ImageViewer(Composite parent, int style) { + super(parent, style); + + mUpdateThread.start(); + + mBackgroundLayer = AdtPlugin.getImageDescriptor(CHECKER_PNG_PATH).createImage(); + + addMouseListener(this); + addMouseMoveListener(this); + addPaintListener(this); + + mHorizontalBar = getHorizontalBar(); + mHorizontalBar.setThumb(1); + mHorizontalBar.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + super.widgetSelected(event); + ScrollBar bar = (ScrollBar) event.widget; + if (mHorizontalBar.isVisible() + && mHorizontalScroll != bar.getSelection()) { + mHorizontalScroll = bar.getSelection(); + redraw(); + } + } + }); + + mVerticalBar = getVerticalBar(); + mVerticalBar.setThumb(1); + mVerticalBar.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + super.widgetSelected(event); + ScrollBar bar = (ScrollBar) event.widget; + if (mVerticalBar.isVisible() + && mVerticalScroll != bar.getSelection()) { + mVerticalScroll = bar.getSelection(); + redraw(); + } + } + }); + } + + /** + * Load the image file. + * + * @param fileName must be absolute path + */ + public NinePatchedImage loadFile(String fileName) { + mNinePatchedImage = new NinePatchedImage(fileName); + + return mNinePatchedImage; + } + + /** + * Start displaying the image. + */ + public void startDisplay() { + mZoomedPixelSize = getZoomedPixelSize(1); + + scheduleUpdate(); + } + + private void draw(int x, int y, int drawMode) { + if (drawMode == MODE_ERASE) { + erase(x, y); + } else { + int color = (drawMode == MODE_RED_TICK) ? NinePatchedImage.RED_TICK + : NinePatchedImage.BLACK_TICK; + mNinePatchedImage.setPatch(x, y, color); + redraw(); + + scheduleUpdate(); + } + } + + private void erase(int x, int y) { + mNinePatchedImage.erase(x, y); + redraw(); + + scheduleUpdate(); + } + + private void scheduleUpdate() { + try { + mUpdateQueue.put(mNinePatchedImage); + } catch (InterruptedException e) { + } + } + + @Override + public void mouseDown(MouseEvent event) { + if (event.button == 1 && !isShiftPressed) { + mDrawMode = !isCtrlPressed ? MODE_BLACK_TICK : MODE_RED_TICK; + draw(mCursorPoint.x, mCursorPoint.y, mDrawMode); + } else if (event.button == 3 || isShiftPressed) { + mDrawMode = MODE_ERASE; + erase(mCursorPoint.x, mCursorPoint.y); + } + } + + @Override + public void mouseUp(MouseEvent event) { + mDrawMode = MODE_NONE; + } + + @Override + public void mouseDoubleClick(MouseEvent event) { + } + + private int getLogicalPositionX(int x) { + return Math.round((x - mPadding.x + mHorizontalScroll) / ((float) mZoom / 100)); + } + + private int getLogicalPositionY(int y) { + return Math.round((y - mPadding.y + mVerticalScroll) / ((float) mZoom / 100)); + } + + @Override + public void mouseMove(MouseEvent event) { + int posX = getLogicalPositionX(event.x); + int posY = getLogicalPositionY(event.y); + + int width = mNinePatchedImage.getWidth(); + int height = mNinePatchedImage.getHeight(); + + if (posX < 0) { + posX = 0; + } + if (posX >= width) { + posX = width - 1; + } + if (posY < 0) { + posY = 0; + } + if (posY >= height) { + posY = height - 1; + } + + if (mDrawMode != MODE_NONE) { + int drawMode = mDrawMode; + if (isShiftPressed) { + drawMode = MODE_ERASE; + } else if (mDrawMode != MODE_ERASE) { + drawMode = !isCtrlPressed ? MODE_BLACK_TICK : MODE_RED_TICK; + } + + /* + * Consider the previous cursor position because mouseMove events are + * scatter. + */ + int x = mCursorPoint.x; + int y = mCursorPoint.y; + for (; y != posY; y += (y > posY) ? -1 : 1) { + draw(x, y, drawMode); + } + + x = mCursorPoint.x; + y = mCursorPoint.y; + for (; x != posX; x += (x > posX) ? -1 : 1) { + draw(x, y, drawMode); + } + } + mCursorPoint.x = posX; + mCursorPoint.y = posY; + + redraw(); + + if (mStatusChangedListener != null) { + // Update position on status panel if position is in logical size. + if (posX >= 0 && posY >= 0 + && posX <= mNinePatchedImage.getWidth() + && posY <= mNinePatchedImage.getHeight()) { + mStatusChangedListener.cursorPositionChanged(posX + 1, posY + 1); + } + } + } + + private synchronized void calcPaddings(int width, int height) { + Point canvasSize = getSize(); + + mPadding.width = getZoomedPixelSize(width); + mPadding.height = getZoomedPixelSize(height); + + int margin = getZoomedPixelSize(MARGIN); + + if (mPadding.width < canvasSize.x) { + mPadding.x = (canvasSize.x - mPadding.width) / 2; + } else { + mPadding.x = margin; + } + + if (mPadding.height < canvasSize.y) { + mPadding.y = (canvasSize.y - mPadding.height) / 2; + } else { + mPadding.y = margin; + } + } + + private void calcScrollBarSettings() { + Point size = getSize(); + int screenWidth = size.x; + int screenHeight = size.y; + + int imageWidth = getZoomedPixelSize(mNinePatchedImage.getWidth() + 1); + int imageHeight = getZoomedPixelSize(mNinePatchedImage.getHeight() + 1); + + // consider the scroll bar sizes + int verticalBarSize = mVerticalBar.getSize().x; + int horizontalBarSize = mHorizontalBar.getSize().y; + + int horizontalScroll = imageWidth - (screenWidth - verticalBarSize); + int verticalScroll = imageHeight - (screenHeight - horizontalBarSize); + + int margin = getZoomedPixelSize(MARGIN) * 2; + + if (horizontalScroll > 0) { + mHorizontalBar.setVisible(true); + + // horizontal maximum + int max = horizontalScroll + verticalBarSize + margin; + mHorizontalBar.setMaximum(max); + + // set corrected scroll size + int value = mHorizontalBar.getSelection(); + value = max < value ? max : value; + + mHorizontalBar.setSelection(value); + mHorizontalScroll = value; + + } else { + mHorizontalBar.setSelection(0); + mHorizontalBar.setMaximum(0); + mHorizontalBar.setVisible(false); + } + + if (verticalScroll > 0) { + mVerticalBar.setVisible(true); + + // vertical maximum + int max = verticalScroll + horizontalBarSize + margin; + mVerticalBar.setMaximum(max); + + // set corrected scroll size + int value = mVerticalBar.getSelection(); + value = max < value ? max : value; + + mVerticalBar.setSelection(value); + mVerticalScroll = value; + + } else { + mVerticalBar.setSelection(0); + mVerticalBar.setMaximum(0); + mVerticalBar.setVisible(false); + } + } + + private int getZoomedPixelSize(int val) { + return Math.round(val * (float) mZoom / 100); + } + + @Override + public void paintControl(PaintEvent pe) { + if (mNinePatchedImage == null) { + return; + } + + // Use buffer + GC bufferGc = null; + if (mBufferImage == null) { + mBufferImage = new Image(AdtPlugin.getDisplay(), pe.width, pe.height); + } else { + int width = mBufferImage.getBounds().width; + int height = mBufferImage.getBounds().height; + if (width != pe.width || height != pe.height) { + mBufferImage = new Image(AdtPlugin.getDisplay(), pe.width, pe.height); + } + } + + // Draw previous image once for prevent flicking + pe.gc.drawImage(mBufferImage, 0, 0); + + bufferGc = new GC(mBufferImage); + bufferGc.setAdvanced(true); + + // Make interpolation disable + bufferGc.setInterpolation(SWT.NONE); + + // clear buffer + bufferGc.fillRectangle(0, 0, pe.width, pe.height); + + calcScrollBarSettings(); + + // padding from current zoom + int width = mNinePatchedImage.getWidth(); + int height = mNinePatchedImage.getHeight(); + calcPaddings(width, height); + + int baseX = mPadding.x - mHorizontalScroll; + int baseY = mPadding.y - mVerticalScroll; + + // draw checker image + bufferGc.drawImage(mBackgroundLayer, + 0, 0, mBackgroundLayer.getImageData().width, + mBackgroundLayer.getImageData().height, + baseX, baseY, mPadding.width, mPadding.height); + + if (DEBUG) { + System.out.println(String.format("%d,%d %d,%d %d,%d", + width, height, baseX, baseY, mPadding.width, mPadding.height)); + } + + // draw image + /* TODO: Do not draw invisible area, for better performance. */ + bufferGc.drawImage(mNinePatchedImage.getImage(), 0, 0, width, height, baseX, baseY, + mPadding.width, mPadding.height); + + bufferGc.setBackground(BLACK_COLOR); + + // draw patch ticks + drawHorizontalPatches(bufferGc, baseX, baseY); + drawVerticalPatches(bufferGc, baseX, baseY); + + // draw content ticks + drawHorizontalContentArea(bufferGc, baseX, baseY); + drawVerticalContentArea(bufferGc, baseX, baseY); + + if (mNinePatchedImage.isValid(mCursorPoint.x, mCursorPoint.y)) { + bufferGc.setForeground(BLACK_COLOR); + } else if (mIsLockShown) { + drawLockArea(bufferGc, baseX, baseY); + } + + // Patches + if (mIsPatchesShown) { + drawPatchAreas(bufferGc, baseX, baseY); + } + + // Bad patches + if (mIsBadPatchesShown) { + drawBadPatchAreas(bufferGc, baseX, baseY); + } + + if (mNinePatchedImage.isValid(mCursorPoint.x, mCursorPoint.y)) { + bufferGc.setForeground(BLACK_COLOR); + } else { + bufferGc.setForeground(RED_COLOR); + } + + drawGuideLine(bufferGc, baseX, baseY); + + bufferGc.dispose(); + + pe.gc.drawImage(mBufferImage, 0, 0); + } + + private static final Color getColor(int color) { + switch (color) { + case NinePatchedImage.RED_TICK: + return RED_COLOR; + default: + return BLACK_COLOR; + } + } + + private void drawVerticalPatches(GC gc, int baseX, int baseY) { + List<Tick> verticalPatches = mNinePatchedImage.getVerticalPatches(); + for (Tick t : verticalPatches) { + if (t.color != NinePatchedImage.TRANSPARENT_TICK) { + gc.setBackground(getColor(t.color)); + gc.fillRectangle( + baseX, + baseY + getZoomedPixelSize(t.start), + mZoomedPixelSize, + getZoomedPixelSize(t.getLength())); + } + } + } + + private void drawHorizontalPatches(GC gc, int baseX, int baseY) { + List<Tick> horizontalPatches = mNinePatchedImage.getHorizontalPatches(); + for (Tick t : horizontalPatches) { + if (t.color != NinePatchedImage.TRANSPARENT_TICK) { + gc.setBackground(getColor(t.color)); + gc.fillRectangle( + baseX + getZoomedPixelSize(t.start), + baseY, + getZoomedPixelSize(t.getLength()), + mZoomedPixelSize); + } + } + } + + private void drawHorizontalContentArea(GC gc, int baseX, int baseY) { + List<Tick> horizontalContentArea = mNinePatchedImage.getHorizontalContents(); + for (Tick t : horizontalContentArea) { + if (t.color != NinePatchedImage.TRANSPARENT_TICK) { + gc.setBackground(getColor(t.color)); + gc.fillRectangle( + baseX + getZoomedPixelSize(t.start), + baseY + getZoomedPixelSize(mNinePatchedImage.getHeight() - 1), + getZoomedPixelSize(t.getLength()), + mZoomedPixelSize); + } + } + + } + + private void drawVerticalContentArea(GC gc, int baseX, int baseY) { + List<Tick> verticalContentArea = mNinePatchedImage.getVerticalContents(); + for (Tick t : verticalContentArea) { + if (t.color != NinePatchedImage.TRANSPARENT_TICK) { + gc.setBackground(getColor(t.color)); + gc.fillRectangle( + baseX + getZoomedPixelSize(mNinePatchedImage.getWidth() - 1), + baseY + getZoomedPixelSize(t.start), + mZoomedPixelSize, + getZoomedPixelSize(t.getLength())); + } + } + } + + private void drawLockArea(GC gc, int baseX, int baseY) { + gc.setAlpha(LOCK_ALPHA); + gc.setForeground(LOCK_COLOR); + gc.setBackground(LOCK_COLOR); + + gc.fillRectangle( + baseX + mZoomedPixelSize, + baseY + mZoomedPixelSize, + getZoomedPixelSize(mNinePatchedImage.getWidth() - 2), + getZoomedPixelSize(mNinePatchedImage.getHeight() - 2)); + gc.setAlpha(NONE_ALPHA); + + } + + private void drawPatchAreas(GC gc, int baseX, int baseY) { + if (mChunks != null) { + int yLen = mChunks.length; + int xLen = mChunks[0].length; + + gc.setAlpha(PATCH_ALPHA); + + for (int yPos = 0; yPos < yLen; yPos++) { + for (int xPos = 0; xPos < xLen; xPos++) { + Chunk c = mChunks[yPos][xPos]; + + if (c.type == Chunk.TYPE_FIXED) { + gc.setBackground(BACK_COLOR); + } else if (c.type == Chunk.TYPE_HORIZONTAL) { + gc.setBackground(PATCH_ONEWAY_COLOR); + } else if (c.type == Chunk.TYPE_VERTICAL) { + gc.setBackground(PATCH_ONEWAY_COLOR); + } else if (c.type == Chunk.TYPE_HORIZONTAL + Chunk.TYPE_VERTICAL) { + gc.setBackground(PATCH_COLOR); + } + Rectangle r = c.rect; + gc.fillRectangle( + baseX + getZoomedPixelSize(r.x), + baseY + getZoomedPixelSize(r.y), + getZoomedPixelSize(r.width), + getZoomedPixelSize(r.height)); + } + } + } + gc.setAlpha(NONE_ALPHA); + } + + private void drawBadPatchAreas(GC gc, int baseX, int baseY) { + if (mBadChunks != null) { + int yLen = mBadChunks.length; + int xLen = mBadChunks[0].length; + + gc.setAlpha(NONE_ALPHA); + gc.setForeground(CORRUPTED_COLOR); + gc.setBackground(CORRUPTED_COLOR); + + for (int yPos = 0; yPos < yLen; yPos++) { + for (int xPos = 0; xPos < xLen; xPos++) { + Chunk c = mBadChunks[yPos][xPos]; + if ((c.type & Chunk.TYPE_CORRUPT) != 0) { + Rectangle r = c.rect; + gc.drawRectangle( + baseX + getZoomedPixelSize(r.x), + baseY + getZoomedPixelSize(r.y), + getZoomedPixelSize(r.width), + getZoomedPixelSize(r.height)); + } + } + } + } + } + + private void drawGuideLine(GC gc, int baseX, int baseY) { + gc.setAntialias(SWT.ON); + gc.setInterpolation(SWT.HIGH); + + int x = Math.round((mCursorPoint.x * ((float) mZoom / 100) + baseX) + + ((float) mZoom / 100 / 2)); + int y = Math.round((mCursorPoint.y * ((float) mZoom / 100) + baseY) + + ((float) mZoom / 100 / 2)); + gc.setAlpha(GUIDE_ALPHA); + + Point size = getSize(); + gc.drawLine(x, 0, x, size.y); + gc.drawLine(0, y, size.x, y); + + gc.setAlpha(NONE_ALPHA); + } + + @Override + public void keyPressed(KeyEvent event) { + int keycode = event.keyCode; + if (keycode == SWT.CTRL) { + isCtrlPressed = true; + } + if (keycode == SWT.SHIFT) { + isShiftPressed = true; + if (mStatusChangedListener != null) { + mStatusChangedListener.helpTextChanged(HELP_MESSAGE_KEY_TIPS2); + } + } + } + + @Override + public void keyReleased(KeyEvent event) { + int keycode = event.keyCode; + if (keycode == SWT.CTRL) { + isCtrlPressed = false; + } + if (keycode == SWT.SHIFT) { + isShiftPressed = false; + if (mStatusChangedListener != null) { + mStatusChangedListener.helpTextChanged(HELP_MESSAGE_KEY_TIPS); + } + } + } + + @Override + public void dispose() { + mBackgroundLayer.dispose(); + super.dispose(); + } + + /** + * Listen image updated event. + */ + public interface UpdateListener { + /** + * 9-patched image has been updated. + */ + public void update(NinePatchedImage image); + } + + /** + * Listen status changed event. + */ + public interface StatusChangedListener { + /** + * Mouse cursor position has been changed. + */ + public void cursorPositionChanged(int x, int y); + + /** + * Help text has been changed. + */ + public void helpTextChanged(String text); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/MainFrame.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/MainFrame.java new file mode 100644 index 0000000..71845c1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/MainFrame.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.draw9patch.ui; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.layout.FormLayout; +import org.eclipse.swt.widgets.Composite; + +/** + * Main frame. + */ +public class MainFrame extends Composite implements ImageViewer.StatusChangedListener { + + private final StatusPanel mStatusPanel; + private final ImageEditorPanel mImageEditorPanel; + + public StatusPanel getStatusPanel() { + return mStatusPanel; + } + + public ImageEditorPanel getImageEditorPanel() { + return mImageEditorPanel; + } + + public MainFrame(Composite parent, int style) { + super(parent, style); + setLayout(new FormLayout()); + + mStatusPanel = new StatusPanel(this, SWT.NULL); + + FormData bottom = new FormData(); + bottom.bottom = new FormAttachment(100, 0); + bottom.left = new FormAttachment(0, 0); + bottom.right = new FormAttachment(100, 0); + mStatusPanel.setLayoutData(bottom); + + mImageEditorPanel = new ImageEditorPanel(this, SWT.NULL); + mImageEditorPanel.getImageViewer().setStatusChangedListener(this); + + mStatusPanel.setStatusChangedListener(mImageEditorPanel); + + FormData top = new FormData(); + top.top = new FormAttachment(0); + top.left = new FormAttachment(0); + top.right = new FormAttachment(100); + top.bottom = new FormAttachment(mStatusPanel); + mImageEditorPanel.setLayoutData(top); + + addKeyListener(mStatusPanel); + addKeyListener(mImageEditorPanel.getImageViewer()); + } + + @Override + public void cursorPositionChanged(int x, int y) { + mStatusPanel.setPosition(x, y); + } + + @Override + public void helpTextChanged(String text) { + mStatusPanel.setHelpText(text); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/StatusPanel.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/StatusPanel.java new file mode 100644 index 0000000..6ad258e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/StatusPanel.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.draw9patch.ui; + +import com.android.ide.eclipse.adt.AdtPlugin; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.layout.FormLayout; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Scale; + +/** + * Status and control pane. + */ +public class StatusPanel extends Composite implements KeyListener { + + public static final int SCALE_MIN = 2; + public static final int SCALE_MAX = 6; + + public static final int ZOOM_MIN = 100; + public static final int ZOOM_MAX = 800; + + public static final int PADDING_TOP = 12; + public static final int PADDING_RIGHT = 0; + public static final int PADDING_BOTTOM = 5; + public static final int PADDING_LEFT = 10; + + public static final int MIN_WIDTH = 800; + + private Button mShowLock = null; + private Button mShowPatches = null; + private Button mShowBadPatches = null; + private Button mShowContent = null; + + private Label mHelpLabel = null; + + private Label mXPosLabel = null; + private Label mYPosLabel = null; + + private ZoomControl mZoomControl = null; + private ZoomControl mScaleControl = null; + + private StatusChangedListener mListener = null; + + public void setStatusChangedListener(StatusChangedListener l) { + mListener = l; + } + + public void setHelpText(String text) { + Point size = getSize(); + // check window width + if (MIN_WIDTH < size.x) { + mHelpLabel.setText(text); + mHelpLabel.setVisible(true); + } else { + mHelpLabel.setText("N/A"); + mHelpLabel.setVisible(false); + } + } + + /** + * Set mouse cursor position. + */ + public void setPosition(int x, int y) { + mXPosLabel.setText(String.format("X: %4d px", x)); + mYPosLabel.setText(String.format("Y: %4d px", y)); + } + + public StatusPanel(Composite parent, int style) { + super(parent, style); + setLayout(new FormLayout()); + + final Composite container = new Composite(this, SWT.NULL); + container.setLayout(new FormLayout()); + + FormData innerForm = new FormData(); + innerForm.left = new FormAttachment(0, PADDING_LEFT); + innerForm.top = new FormAttachment(0, PADDING_TOP); + innerForm.right = new FormAttachment(100, PADDING_RIGHT); + innerForm.bottom = new FormAttachment(100, -PADDING_BOTTOM); + container.setLayoutData(innerForm); + + buildPosition(container); + + Composite zoomPanels = new Composite(container, SWT.NULL); + zoomPanels.setLayout(new GridLayout(3, false)); + + buildZoomControl(zoomPanels); + buildScaleControl(zoomPanels); + + Composite checkPanel = new Composite(container, SWT.NULL); + checkPanel.setLayout(new GridLayout(2, false)); + FormData checkPanelForm = new FormData(); + checkPanelForm.left = new FormAttachment(zoomPanels, 0); + checkPanelForm.bottom = new FormAttachment(100, -PADDING_BOTTOM); + checkPanel.setLayoutData(checkPanelForm); + + buildCheckboxes(checkPanel); + + mHelpLabel = new Label(container, SWT.BORDER_SOLID | SWT.BOLD | SWT.WRAP); + mHelpLabel.setBackground(new Color(AdtPlugin.getDisplay(), 0xFF, 0xFF, 0xFF)); + FormData hintForm = new FormData(); + hintForm.left = new FormAttachment(checkPanel, 5); + hintForm.right = new FormAttachment(mXPosLabel, -10); + hintForm.top = new FormAttachment(PADDING_TOP); + hintForm.bottom = new FormAttachment(100, -PADDING_BOTTOM); + mHelpLabel.setLayoutData(hintForm); + + /* + * If the window width is not much, the "help label" will break the window. + * Because that is wrapped automatically. + * + * This listener catch resized events and reset help text. + * + * setHelpText method checks window width. + * If window is too narrow, help text will be set invisible. + */ + container.addControlListener(new ControlListener() { + @Override + public void controlResized(ControlEvent event) { + // reset text + setHelpText(ImageViewer.HELP_MESSAGE_KEY_TIPS); + } + @Override + public void controlMoved(ControlEvent event) { + } + }); + + } + + private void buildPosition(Composite parent) { + mXPosLabel = new Label(parent, SWT.NULL); + mYPosLabel = new Label(parent, SWT.NULL); + + mXPosLabel.setText(String.format("X: %4d px", 1000)); + mYPosLabel.setText(String.format("Y: %4d px", 1000)); + + FormData bottomRight = new FormData(); + bottomRight.bottom = new FormAttachment(100, 0); + bottomRight.right = new FormAttachment(100, 0); + mYPosLabel.setLayoutData(bottomRight); + + FormData aboveYPosLabel = new FormData(); + aboveYPosLabel.bottom = new FormAttachment(mYPosLabel); + aboveYPosLabel.right = new FormAttachment(100, 0); + mXPosLabel.setLayoutData(aboveYPosLabel); + } + + private void buildScaleControl(Composite parent) { + mScaleControl = new ZoomControl(parent); + mScaleControl.maxLabel.setText("6x"); + mScaleControl.minLabel.setText("2x"); + mScaleControl.scale.setMinimum(SCALE_MIN); + mScaleControl.scale.setMaximum(SCALE_MAX); + mScaleControl.scale.setSelection(2); + mScaleControl.scale.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + super.widgetSelected(event); + if (mListener != null) { + Scale scale = (Scale) event.widget; + mListener.scaleChanged(scale.getSelection()); + } + } + }); + } + + private void buildZoomControl(Composite parent) { + mZoomControl = new ZoomControl(parent); + mZoomControl.maxLabel.setText("800%"); + mZoomControl.minLabel.setText("100%"); + mZoomControl.scale.setMinimum(ZOOM_MIN); + mZoomControl.scale.setMaximum(ZOOM_MAX - ZOOM_MIN); + mZoomControl.scale.setSelection(400); + mZoomControl.scale.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + super.widgetSelected(event); + if (mListener != null) { + Scale scale = (Scale) event.widget; + mListener.zoomChanged(scale.getSelection() + ZOOM_MIN); + } + } + }); + + } + + private void buildCheckboxes(Composite parent) { + // check lock + mShowLock = new Button(parent, SWT.CHECK); + mShowLock.setText("show Lock"); + mShowLock.setSelection(true); + mShowLock.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + super.widgetSelected(event); + if (mListener != null) { + mListener.lockVisibilityChanged(mShowLock.getSelection()); + } + } + }); + + // check patches + mShowPatches = new Button(parent, SWT.CHECK); + mShowPatches.setText("show Patches"); + mShowPatches.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + super.widgetSelected(event); + if (mListener != null) { + mListener.patchesVisibilityChanged(mShowPatches.getSelection()); + } + } + }); + + // check patches + mShowBadPatches = new Button(parent, SWT.CHECK); + mShowBadPatches.setText("show Bad patches"); + mShowBadPatches.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + super.widgetSelected(event); + if (mListener != null) { + mListener.badPatchesVisibilityChanged(mShowBadPatches.getSelection()); + } + } + }); + + // check contents(padding) + mShowContent = new Button(parent, SWT.CHECK); + mShowContent.setText("show Contents"); + mShowContent.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + super.widgetSelected(event); + if (mListener != null) { + mListener.contentAreaVisibilityChanged(mShowContent.getSelection()); + } + } + }); + } + + @Override + public void keyPressed(KeyEvent event) { + switch (event.character) { + case 'c': + mShowContent.setSelection(!mShowContent.getSelection()); + if (mListener != null) { + mListener.contentAreaVisibilityChanged(mShowContent.getSelection()); + } + break; + case 'l': + mShowLock.setSelection(!mShowLock.getSelection()); + if (mListener != null) { + mListener.lockVisibilityChanged(mShowLock.getSelection()); + } + break; + case 'p': + mShowPatches.setSelection(!mShowPatches.getSelection()); + if (mListener != null) { + mListener.patchesVisibilityChanged(mShowPatches.getSelection()); + } + break; + case 'b': + mShowBadPatches.setSelection(!mShowBadPatches.getSelection()); + if (mListener != null) { + mListener.badPatchesVisibilityChanged(mShowBadPatches.getSelection()); + } + break; + } + } + + @Override + public void keyReleased(KeyEvent event) { + } + + private static class ZoomControl { + + private Label minLabel; + private Label maxLabel; + Scale scale; + + public ZoomControl(Composite composite) { + minLabel = new Label(composite, SWT.RIGHT); + scale = new Scale(composite, SWT.HORIZONTAL); + maxLabel = new Label(composite, SWT.LEFT); + } + } + + /** + * Status changed events listener. + */ + public interface StatusChangedListener { + /** + * Zoom level has been changed. + * @param zoom + */ + public void zoomChanged(int zoom); + + /** + * Scale has been changed. + * @param scale + */ + public void scaleChanged(int scale); + + /** + * Lock visibility has been changed. + * @param visible + */ + public void lockVisibilityChanged(boolean visible); + + /** + * Patches visibility has been changed. + * @param visible + */ + public void patchesVisibilityChanged(boolean visible); + + /** + * BadPatches visibility has been changed. + * @param visible + */ + public void badPatchesVisibilityChanged(boolean visible); + + /** + * Content visibility has been changed. + * @param visible + */ + public void contentAreaVisibilityChanged(boolean visible); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/StretchesViewer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/StretchesViewer.java new file mode 100644 index 0000000..353312c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/StretchesViewer.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.draw9patch.ui; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.GraphicsUtilities; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage.Projection; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.PaletteData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; + +/** + * Preview 9-patched image pane. + */ +public class StretchesViewer extends Composite { + private static final boolean DEBUG = false; + + private static final int PADDING_COLOR = 0x0000CC; + + private static final int PADDING_COLOR_ALPHA = 100; + + private static final PaletteData PADDING_PALLET + = new PaletteData(new RGB[] {new RGB(0x00, 0x00, 0xCC)}); + + private static final String CHECKER_PNG_PATH = "/icons/checker.png"; + + private Image mBackgroundLayer = null; + + private final StretchView mHorizontal; + private final StretchView mVertical; + + private final StretchView mBoth; + + private NinePatchedImage mNinePatchedImage = null; + + private ImageData mContentAreaImageData = null; + + private boolean mIsContentAreaShown = false; + + private Image mContentAreaImage = null; + + private int mScale = 2; + + public StretchesViewer(Composite parent, int style) { + super(parent, style); + + mBackgroundLayer = AdtPlugin.getImageDescriptor(CHECKER_PNG_PATH).createImage(); + + setLayout(new FillLayout(SWT.VERTICAL)); + + mHorizontal = new StretchView(this, SWT.NULL); + mVertical = new StretchView(this, SWT.NULL); + mBoth = new StretchView(this, SWT.NULL); + } + + /** + * Set show/not show content area. + * @param If show, true + */ + public void setShowContentArea(boolean show) { + mIsContentAreaShown = show; + redraw(); + } + + private static final boolean equalSize(ImageData image1, ImageData image2) { + return (image1.width == image2.width && image1.height == image2.height); + } + + /** + * Update preview image. + */ + public void updatePreview(NinePatchedImage image) { + mNinePatchedImage = image; + ImageData base = mNinePatchedImage.getImageData(); + + if (mContentAreaImageData == null + || (mContentAreaImageData != null && !equalSize(base, mContentAreaImageData))) { + mContentAreaImageData = new ImageData( + base.width, + base.height, + 1, + PADDING_PALLET); + } else { + GraphicsUtilities.clearImageData(mContentAreaImageData); + } + + mHorizontal.setImage(mNinePatchedImage); + mVertical.setImage(mNinePatchedImage); + mBoth.setImage(mNinePatchedImage); + + mContentAreaImage = buildContentAreaPreview(); + + setScale(mScale); + } + + private Image buildContentAreaPreview() { + if (mContentAreaImage != null) { + mContentAreaImage.dispose(); + } + + Rectangle rect = mNinePatchedImage.getContentArea(); + + int yLen = rect.y + rect.height; + for (int y = rect.y; y < yLen; y++) { + int xLen = rect.x + rect.width; + for (int x = rect.x; x < xLen; x++) { + mContentAreaImageData.setPixel(x, y, PADDING_COLOR); + mContentAreaImageData.setAlpha(x, y, PADDING_COLOR_ALPHA); + } + } + return new Image(AdtPlugin.getDisplay(), mContentAreaImageData); + } + + public void setScale(int scale) { + if (DEBUG) { + System.out.println("scale = " + scale); + } + + mScale = scale; + int imageWidth = mNinePatchedImage.getWidth(); + int imageHeight = mNinePatchedImage.getHeight(); + + mHorizontal.setSize(imageWidth * scale, imageHeight); + mVertical.setSize(imageWidth, imageHeight * scale); + mBoth.setSize(imageWidth * scale, imageHeight * scale); + + redraw(); + } + + @Override + public void dispose() { + mBackgroundLayer.dispose(); + super.dispose(); + } + + private class StretchView extends Canvas implements PaintListener { + + private final Point mSize = new Point(0, 0); + private final Rectangle mPadding = new Rectangle(0, 0, 0, 0); + private Projection[][] mProjection = null; + + public StretchView(Composite parent, int style) { + super(parent, style); + addPaintListener(this); + } + + private void setImage(NinePatchedImage image) { + setSize(image.getWidth(), image.getHeight()); + } + + @Override + public void setSize(int width, int heigh) { + mSize.x = width; + mSize.y = heigh; + mProjection = mNinePatchedImage.getProjections(mSize.x, mSize.y, mProjection); + } + + private synchronized void calcPaddings(int width, int height) { + Point canvasSize = getSize(); + + mPadding.x = (canvasSize.x - width) / 2; + mPadding.y = (canvasSize.y - height) / 2; + + mPadding.width = width; + mPadding.height = height; + } + + @Override + public void paintControl(PaintEvent pe) { + if (mNinePatchedImage == null || mProjection == null) { + return; + } + + Point size = getSize(); + + // relative scaling + float ratio = 1.0f; + float wRatio = ((float) size.x / mSize.x); + ratio = Math.min(wRatio, ratio); + float hRatio = ((float) size.y / mSize.y); + ratio = Math.min(hRatio, ratio); + + int width = Math.round(mSize.x * ratio); + int height = Math.round(mSize.y * ratio); + + calcPaddings(width, height); + + Rectangle dest = new Rectangle(0, 0, 0, 0); + + GC gc = pe.gc; + + int backgroundLayerWidth = mBackgroundLayer.getImageData().width; + int backgroundLayerHeight = mBackgroundLayer.getImageData().height; + + int yCount = size.y / backgroundLayerHeight + + ((size.y % backgroundLayerHeight) > 0 ? 1 : 0); + int xCount = size.x / backgroundLayerWidth + + ((size.x % backgroundLayerWidth) > 0 ? 1 : 0); + + // draw background layer + for (int y = 0; y < yCount; y++) { + for (int x = 0; x < xCount; x++) { + gc.drawImage(mBackgroundLayer, + x * backgroundLayerWidth, + y * backgroundLayerHeight); + } + } + + // draw the border line + gc.setAlpha(0x88); + gc.drawRectangle(0, 0, size.x, size.y); + gc.setAlpha(0xFF); + + int yLen = mProjection.length; + int xLen = mProjection[0].length; + for (int yPos = 0; yPos < yLen; yPos++) { + for (int xPos = 0; xPos < xLen; xPos++) { + Projection p = mProjection[yPos][xPos]; + + // consider the scale + dest.x = (int) Math.ceil(p.dest.x * ratio); + dest.y = (int) Math.ceil(p.dest.y * ratio); + dest.width = (int) Math.ceil(p.dest.width * ratio); + dest.height = (int) Math.ceil(p.dest.height * ratio); + + gc.drawImage(mNinePatchedImage.getImage(), p.src.x, p.src.y, + p.src.width, p.src.height, + (mPadding.x + dest.x), (mPadding.y + dest.y), + dest.width, dest.height); + + if (mIsContentAreaShown) { + gc.drawImage(mContentAreaImage, p.src.x, p.src.y, + p.src.width, p.src.height, + (mPadding.x + dest.x), (mPadding.y + dest.y), + dest.width, dest.height); + } + } + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/draw9patch/graphics/GraphicsUtilitiesTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/draw9patch/graphics/GraphicsUtilitiesTest.java new file mode 100644 index 0000000..4f00097 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/draw9patch/graphics/GraphicsUtilitiesTest.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics; + +import java.util.Arrays; + +import junit.framework.TestCase; + +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.widgets.Display; + +public class GraphicsUtilitiesTest extends TestCase { + private static final int MASK_ALPHA = 0xFF000000; + + private static final String DIR = "/com/android/ide/eclipse/testdata/draw9patch/"; + + public void testConvertToNinePatchNull() throws Exception { + ImageData result = GraphicsUtilities.convertToNinePatch(null); + assertNull(result); + } + + public void testConvertToNinePatch() throws Exception { + String fileName = DIR + "no-patched.png"; + Image image = new Image(Display.getDefault(), + getClass().getResourceAsStream(fileName)); + ImageData baseData = image.getImageData(); + + ImageData result = GraphicsUtilities.convertToNinePatch(baseData); + + assertEquals(baseData.width + 2, result.width); + assertEquals(baseData.height + 2, result.height); + + // horizontal + for (int x = 0; x < result.width; x++) { + + // top row + assertEquals(0x0, result.getPixel(x, 0) & MASK_ALPHA); + + // bottom row + assertEquals(0x0, result.getPixel(x, result.height - 1) & MASK_ALPHA); + } + + // vertical + for (int y = 0; y < result.height; y++) { + + // left column + assertEquals(0x0, result.getPixel(0, y) & MASK_ALPHA); + + // right column + assertEquals(0x0, result.getPixel(result.width - 1, y) & MASK_ALPHA); + } + } + + public void testClearImageDataNull() throws Exception { + try { + GraphicsUtilities.clearImageData(null); + fail(); + } catch (IllegalArgumentException e) { + } + } + + public void testClearImageData() throws Exception { + String fileName = DIR + "no-patched.png"; + Image image = new Image(Display.getDefault(), + getClass().getResourceAsStream(fileName)); + + ImageData baseData = image.getImageData(); + GraphicsUtilities.clearImageData(baseData); + for (int y = 0; y < baseData.height; y++) { + for (int x = 0; x < baseData.width; x++) { + assertEquals(0x000000, baseData.getPixel(x, y)); + assertEquals(0x00, baseData.getAlpha(x, y)); + } + } + + } + + public void testCopyNull() throws Exception { + ImageData result = GraphicsUtilities.copy(null); + assertNull(result); + } + + public void testCopy() throws Exception { + String fileName = DIR + "no-patched.png"; + Image image = new Image(Display.getDefault(), + getClass().getResourceAsStream(fileName)); + + ImageData baseData = image.getImageData(); + ImageData copiedData = GraphicsUtilities.copy(baseData); + + assertEquals(baseData.width, copiedData.width); + assertEquals(baseData.height, copiedData.height); + assertEquals(baseData.depth, copiedData.depth); + assertEquals(baseData.transparentPixel, copiedData.transparentPixel); + assertEquals(baseData.alpha, copiedData.alpha); + assertTrue(baseData.palette.equals(copiedData.palette)); + + final int[] baseColors = new int[baseData.width]; + final byte[] baseAlpha = new byte[baseData.width]; + + final int[] copiedColors = new int[copiedData.width]; + final byte[] copiedAlpha = new byte[copiedData.width]; + + for (int y = 0; y < baseData.height; y++) { + + baseData.getPixels(0, y, baseData.width, baseColors, 0); + baseData.getPixels(0, y, baseData.width, copiedColors, 0); + assertTrue(Arrays.equals(baseColors, copiedColors)); + + baseData.getAlphas(0, y, baseData.width, baseAlpha, 0); + baseData.getAlphas(0, y, baseData.width, copiedAlpha, 0); + assertTrue(Arrays.equals(baseAlpha, copiedAlpha)); + + } + } + + public void testGetVerticalPixelsIllegalArgument() throws Exception { + String fileName = DIR + "no-patched.png"; + Image image = new Image(Display.getDefault(), + getClass().getResourceAsStream(fileName)); + + ImageData baseData = image.getImageData(); + int[] temp = new int[baseData.width]; + + // data must not be null + try { + GraphicsUtilities.getVerticalPixels(null, 0, 0, 1, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + // out must not be null + try { + GraphicsUtilities.getVerticalPixels(baseData, 0, 0, 1, null); + fail(); + } catch (IllegalArgumentException e) { + } + + // out length must be > height + try { + GraphicsUtilities.getVerticalPixels(baseData, 0, 0, 1, new int[0]); + fail(); + } catch (IllegalArgumentException e) { + } + + // x must be > 0 + try { + GraphicsUtilities.getVerticalPixels(baseData, -1, 0, 1, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + // y must be > 0 + try { + GraphicsUtilities.getVerticalPixels(baseData, 0, -1, 1, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + // height must be >= 0 + try { + GraphicsUtilities.getVerticalPixels(baseData, 0, 0, 0, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + // argument x must be < data.width + try { + GraphicsUtilities.getVerticalPixels(baseData, baseData.width, 0, baseData.height, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + // argument y must be < data.height + try { + GraphicsUtilities + .getVerticalPixels(baseData, 0, baseData.height, baseData.height, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + // argument height must be > (y + data.height) + try { + GraphicsUtilities.getVerticalPixels(baseData, 0, 1, baseData.height, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + } + + public void testGetVerticalPixels() throws Exception { + String fileName = DIR + "no-patched.png"; + Image image = new Image(Display.getDefault(), + getClass().getResourceAsStream(fileName)); + + ImageData baseData = image.getImageData(); + int[] temp = new int[baseData.width]; + + GraphicsUtilities.getVerticalPixels(baseData, 0, 0, baseData.height, temp); + + int height = baseData.height; + for (int y = 0; y < height; y++) { + assertEquals(baseData.getPixel(0, y), temp[y]); + } + } + + public void testGetHorizontalPixelsIllegalArgument() throws Exception { + String fileName = DIR + "no-patched.png"; + Image image = new Image(Display.getDefault(), + getClass().getResourceAsStream(fileName)); + + ImageData baseData = image.getImageData(); + int[] temp = new int[baseData.width]; + + // data must not be null + try { + GraphicsUtilities.getHorizontalPixels(null, 0, 0, 1, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + // out must not be null + try { + GraphicsUtilities.getHorizontalPixels(baseData, 0, 0, 1, null); + fail(); + } catch (IllegalArgumentException e) { + } + + // out length must be > width + try { + GraphicsUtilities.getHorizontalPixels(baseData, 0, 0, 1, new int[0]); + fail(); + } catch (IllegalArgumentException e) { + } + + // x must be > 0 + try { + GraphicsUtilities.getHorizontalPixels(baseData, -1, 0, 1, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + // y must be > 0 + try { + GraphicsUtilities.getHorizontalPixels(baseData, 0, -1, 1, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + // width must be >= 0 + try { + GraphicsUtilities.getHorizontalPixels(baseData, 0, 0, 0, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + // argument x must be < data.width + try { + GraphicsUtilities + .getHorizontalPixels(baseData, baseData.width, 0, baseData.width, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + // argument y must be < data.height + try { + GraphicsUtilities + .getHorizontalPixels(baseData, 0, baseData.height, baseData.width, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + // argument width must be > (x + data.width) + try { + GraphicsUtilities.getHorizontalPixels(baseData, 1, 0, baseData.width, temp); + fail(); + } catch (IllegalArgumentException e) { + } + + } + + public void testGetHorizontalPixels() throws Exception { + String fileName = DIR + "no-patched.png"; + Image image = new Image(Display.getDefault(), + getClass().getResourceAsStream(fileName)); + + ImageData baseData = image.getImageData(); + int[] temp = new int[baseData.width]; + + GraphicsUtilities.getHorizontalPixels(baseData, 0, 0, baseData.width, temp); + + int width = baseData.width; + for (int x = 0; x < width; x++) { + assertEquals(baseData.getPixel(x, 0), temp[x]); + } + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/draw9patch/graphics/NinePatchedImageTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/draw9patch/graphics/NinePatchedImageTest.java new file mode 100644 index 0000000..72c9296 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/draw9patch/graphics/NinePatchedImageTest.java @@ -0,0 +1,914 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; + +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage.Chunk; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage.Projection; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage.Tick; + +public class NinePatchedImageTest extends TestCase { + + private static final String DIR = "/com/android/ide/eclipse/testdata/draw9patch/"; + + public void testReadNoPatchedImage() throws Exception { + String fileName = DIR + "no-patched.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + + ImageData data = image.getImageData(); + int width = data.width; + int height = data.height; + + assertEquals(72, width); + assertEquals(50, height); + + assertFalse(image.hasNinePatchExtension()); + } + + public void testReadNoPatchedInteraceImage() throws Exception { + String fileName = DIR + "no-patched-interlace.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + + ImageData data = image.getImageData(); + int width = data.width; + int height = data.height; + + assertEquals(72, width); + assertEquals(50, height); + + assertFalse(image.hasNinePatchExtension()); + } + + public void testConvert9PatchedImage() throws Exception { + String fileName = DIR + "no-patched.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + + ImageData data = image.getImageData(); + int width = data.width; + int height = data.height; + + assertEquals(72, width); + assertEquals(50, height); + + assertFalse(image.hasNinePatchExtension()); + + image.convertToNinePatch(); + + data = image.getImageData(); + width = data.width; + height = data.height; + + // increased patch size + assertEquals(72 + 2, width); + assertEquals(50 + 2, height); + + assertTrue(image.hasNinePatchExtension()); + assertFalse(image.isDirty()); + + // initialized patches + List<Tick> horizontalPatches = image.getHorizontalPatches(); + List<Tick> verticalPatches = image.getVerticalPatches(); + assertEquals(1, horizontalPatches.size()); + assertEquals(1, verticalPatches.size()); + + // initialized contents area + List<Tick> horizontalContentsArea = image.getHorizontalContents(); + List<Tick> verticalContentsArea = image.getVerticalContents(); + assertEquals(1, horizontalContentsArea.size()); + assertEquals(1, verticalContentsArea.size()); + + // content area rectangle + Rectangle contentsArea = image.getContentArea(); + assertEquals(new Rectangle(1, 1, width - 2, height - 2), contentsArea); + } + + public void testReadInvalidPatchedImageCorners() throws Exception { + + // top-left + String fileName = DIR + "invalid-patched1.9.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + assertTrue(image.hasNinePatchExtension()); + assertFalse(image.ensure9Patch()); + + // top-right + fileName = DIR + "invalid-patched2.9.png"; + image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + assertTrue(image.hasNinePatchExtension()); + assertFalse(image.ensure9Patch()); + + // bottom-left + fileName = DIR + "invalid-patched3.9.png"; + image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + assertTrue(image.hasNinePatchExtension()); + assertFalse(image.ensure9Patch()); + + // bottom-right + fileName = DIR + "invalid-patched4.9.png"; + image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + assertTrue(image.hasNinePatchExtension()); + assertFalse(image.ensure9Patch()); + } + + public void testReadInvalidPatchedImageLine() throws Exception { + + // top + String fileName = DIR + "invalid-patched5.9.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + assertTrue(image.hasNinePatchExtension()); + assertFalse(image.ensure9Patch()); + + // right + fileName = DIR + "invalid-patched6.9.png"; + image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + assertTrue(image.hasNinePatchExtension()); + assertFalse(image.ensure9Patch()); + + // bottom + fileName = DIR + "invalid-patched7.9.png"; + image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + assertTrue(image.hasNinePatchExtension()); + assertFalse(image.ensure9Patch()); + + // left + fileName = DIR + "invalid-patched8.9.png"; + image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + assertTrue(image.hasNinePatchExtension()); + assertFalse(image.ensure9Patch()); + } + + public void testEnsure9PatchIgnoreInvalidPixels() throws Exception { + // top + String fileName = DIR + "invalid-patched5.9.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + + // invalid pixel + int invalidPixel = image.getImageData().getPixel(33, 0); + assertTrue(0x0 != invalidPixel); + + assertTrue(image.hasNinePatchExtension()); + assertFalse(image.ensure9Patch()); + + // ensure9path() ignored invalid pixels + int invalidPixelAlpha = image.getImageData().getAlpha(33, 0); + assertEquals(0x00, invalidPixelAlpha); + } + + public void test9Patch1() throws Exception { + String fileName = DIR + "patched1.9.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + + assertTrue(image.hasNinePatchExtension()); + assertTrue(image.ensure9Patch()); + + // patches + List<Tick> horizontalPatches = image.getHorizontalPatches(); + List<Tick> verticalPatches = image.getVerticalPatches(); + assertEquals(3, horizontalPatches.size()); + assertEquals(3, verticalPatches.size()); + + Chunk[][] chunks = null; + chunks = image.getChunks(chunks); + + // vertical chunk size + assertEquals(3, chunks.length); + + // horizontal chunk size + for (int i = 0; i < chunks.length; i++) { + assertEquals(3, chunks[i].length); + } + + Chunk c = null; + Rectangle rect = null; + + // Row 1 + c = chunks[0][0]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(1, rect.x); + assertEquals(1, rect.y); + assertEquals(1, rect.width); + assertEquals(1, rect.height); + + c = chunks[0][1]; + rect = c.rect; + assertEquals(Chunk.TYPE_VERTICAL, c.type); + assertEquals(2, rect.x); + assertEquals(1, rect.y); + assertEquals(70, rect.width); + assertEquals(1, rect.height); + + c = chunks[0][2]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(72, rect.x); + assertEquals(1, rect.y); + assertEquals(1, rect.width); + assertEquals(1, rect.height); + + // Row 2 + c = chunks[1][0]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL, c.type); + assertEquals(1, rect.x); + assertEquals(2, rect.y); + assertEquals(1, rect.width); + assertEquals(48, rect.height); + + c = chunks[1][1]; + rect = c.rect; + assertEquals(Chunk.TYPE_FIXED, c.type); + assertEquals(2, rect.x); + assertEquals(2, rect.y); + assertEquals(70, rect.width); + assertEquals(48, rect.height); + + c = chunks[1][2]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL, c.type); + assertEquals(72, rect.x); + assertEquals(2, rect.y); + assertEquals(1, rect.width); + assertEquals(48, rect.height); + + // Row 3 + c = chunks[2][0]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(1, rect.x); + assertEquals(50, rect.y); + assertEquals(1, rect.width); + assertEquals(1, rect.height); + + c = chunks[2][1]; + rect = c.rect; + assertEquals(Chunk.TYPE_VERTICAL, c.type); + assertEquals(2, rect.x); + assertEquals(50, rect.y); + assertEquals(70, rect.width); + assertEquals(1, rect.height); + + c = chunks[2][2]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(72, rect.x); + assertEquals(50, rect.y); + assertEquals(1, rect.width); + assertEquals(1, rect.height); + } + + public void test9Patch2() throws Exception { + String fileName = DIR + "patched2.9.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + + assertTrue(image.hasNinePatchExtension()); + assertTrue(image.ensure9Patch()); + + // patches + List<Tick> horizontalPatches = image.getHorizontalPatches(); + List<Tick> verticalPatches = image.getVerticalPatches(); + assertEquals(5, horizontalPatches.size()); + assertEquals(7, verticalPatches.size()); + + NinePatchedImage.Chunk[][] chunks = null; + chunks = image.getChunks(chunks); + + // vertical chunk size + assertEquals(7, chunks.length); + + // horizontal chunk size + for (int i = 0; i < chunks.length; i++) { + assertEquals(5, chunks[i].length); + } + + NinePatchedImage.Chunk c = null; + Rectangle rect = null; + + // Row 1 + c = chunks[0][0]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(1, rect.x); + assertEquals(1, rect.y); + assertEquals(1, rect.width); + assertEquals(1, rect.height); + + c = chunks[0][1]; + rect = c.rect; + assertEquals(Chunk.TYPE_VERTICAL, c.type); + assertEquals(2, rect.x); + assertEquals(1, rect.y); + assertEquals(34, rect.width); + assertEquals(1, rect.height); + + c = chunks[0][2]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(36, rect.x); + assertEquals(1, rect.y); + assertEquals(1, rect.width); + assertEquals(1, rect.height); + + c = chunks[0][3]; + rect = c.rect; + assertEquals(Chunk.TYPE_VERTICAL, c.type); + assertEquals(37, rect.x); + assertEquals(1, rect.y); + assertEquals(35, rect.width); + assertEquals(1, rect.height); + + c = chunks[0][4]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(72, rect.x); + assertEquals(1, rect.y); + assertEquals(1, rect.width); + assertEquals(1, rect.height); + + // Row 2 + c = chunks[1][0]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL, c.type); + assertEquals(1, rect.x); + assertEquals(2, rect.y); + assertEquals(1, rect.width); + assertEquals(7, rect.height); + + c = chunks[1][1]; + rect = c.rect; + assertEquals(Chunk.TYPE_FIXED, c.type); + assertEquals(2, rect.x); + assertEquals(2, rect.y); + assertEquals(34, rect.width); + assertEquals(7, rect.height); + + c = chunks[1][2]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL, c.type); + assertEquals(36, rect.x); + assertEquals(2, rect.y); + assertEquals(1, rect.width); + assertEquals(7, rect.height); + + c = chunks[1][3]; + rect = c.rect; + assertEquals(Chunk.TYPE_FIXED, c.type); + assertEquals(37, rect.x); + assertEquals(2, rect.y); + assertEquals(35, rect.width); + assertEquals(7, rect.height); + + c = chunks[1][4]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL, c.type); + assertEquals(72, rect.x); + assertEquals(2, rect.y); + assertEquals(1, rect.width); + assertEquals(7, rect.height); + + // Row 3 + c = chunks[2][0]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(1, rect.x); + assertEquals(9, rect.y); + assertEquals(1, rect.width); + assertEquals(4, rect.height); + + c = chunks[2][1]; + rect = c.rect; + assertEquals(Chunk.TYPE_VERTICAL, c.type); + assertEquals(2, rect.x); + assertEquals(9, rect.y); + assertEquals(34, rect.width); + assertEquals(4, rect.height); + + c = chunks[2][2]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(36, rect.x); + assertEquals(9, rect.y); + assertEquals(1, rect.width); + assertEquals(4, rect.height); + + c = chunks[2][3]; + rect = c.rect; + assertEquals(Chunk.TYPE_VERTICAL, c.type); + assertEquals(37, rect.x); + assertEquals(9, rect.y); + assertEquals(35, rect.width); + assertEquals(4, rect.height); + + c = chunks[2][4]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(72, rect.x); + assertEquals(9, rect.y); + assertEquals(1, rect.width); + assertEquals(4, rect.height); + + // Row 4 + c = chunks[3][0]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL, c.type); + assertEquals(1, rect.x); + assertEquals(13, rect.y); + assertEquals(1, rect.width); + assertEquals(13, rect.height); + + c = chunks[3][1]; + rect = c.rect; + assertEquals(Chunk.TYPE_FIXED, c.type); + assertEquals(2, rect.x); + assertEquals(13, rect.y); + assertEquals(34, rect.width); + assertEquals(13, rect.height); + + c = chunks[3][2]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL, c.type); + assertEquals(36, rect.x); + assertEquals(13, rect.y); + assertEquals(1, rect.width); + assertEquals(13, rect.height); + + c = chunks[3][3]; + rect = c.rect; + assertEquals(Chunk.TYPE_FIXED, c.type); + assertEquals(37, rect.x); + assertEquals(13, rect.y); + assertEquals(35, rect.width); + assertEquals(13, rect.height); + + c = chunks[3][4]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL, c.type); + assertEquals(72, rect.x); + assertEquals(13, rect.y); + assertEquals(1, rect.width); + assertEquals(13, rect.height); + + // Row 5 + c = chunks[4][0]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(1, rect.x); + assertEquals(26, rect.y); + assertEquals(1, rect.width); + assertEquals(12, rect.height); + + c = chunks[4][1]; + rect = c.rect; + assertEquals(Chunk.TYPE_VERTICAL, c.type); + assertEquals(2, rect.x); + assertEquals(26, rect.y); + assertEquals(34, rect.width); + assertEquals(12, rect.height); + + c = chunks[4][2]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(36, rect.x); + assertEquals(26, rect.y); + assertEquals(1, rect.width); + assertEquals(12, rect.height); + + c = chunks[4][3]; + rect = c.rect; + assertEquals(Chunk.TYPE_VERTICAL, c.type); + assertEquals(37, rect.x); + assertEquals(26, rect.y); + assertEquals(35, rect.width); + assertEquals(12, rect.height); + + c = chunks[4][4]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(72, rect.x); + assertEquals(26, rect.y); + assertEquals(1, rect.width); + assertEquals(12, rect.height); + + // Row 6 + c = chunks[5][0]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL, c.type); + assertEquals(1, rect.x); + assertEquals(38, rect.y); + assertEquals(1, rect.width); + assertEquals(12, rect.height); + + c = chunks[5][1]; + rect = c.rect; + assertEquals(Chunk.TYPE_FIXED, c.type); + assertEquals(2, rect.x); + assertEquals(38, rect.y); + assertEquals(34, rect.width); + assertEquals(12, rect.height); + + c = chunks[5][2]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL, c.type); + assertEquals(36, rect.x); + assertEquals(38, rect.y); + assertEquals(1, rect.width); + assertEquals(12, rect.height); + + c = chunks[5][3]; + rect = c.rect; + assertEquals(Chunk.TYPE_FIXED, c.type); + assertEquals(37, rect.x); + assertEquals(38, rect.y); + assertEquals(35, rect.width); + assertEquals(12, rect.height); + + c = chunks[5][4]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL, c.type); + assertEquals(72, rect.x); + assertEquals(38, rect.y); + assertEquals(1, rect.width); + assertEquals(12, rect.height); + + // Row 7 + c = chunks[6][0]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(1, rect.x); + assertEquals(50, rect.y); + assertEquals(1, rect.width); + assertEquals(1, rect.height); + + c = chunks[6][1]; + rect = c.rect; + assertEquals(Chunk.TYPE_VERTICAL, c.type); + assertEquals(2, rect.x); + assertEquals(50, rect.y); + assertEquals(34, rect.width); + assertEquals(1, rect.height); + + c = chunks[6][2]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(36, rect.x); + assertEquals(50, rect.y); + assertEquals(1, rect.width); + assertEquals(1, rect.height); + + c = chunks[6][3]; + rect = c.rect; + assertEquals(Chunk.TYPE_VERTICAL, c.type); + assertEquals(37, rect.x); + assertEquals(50, rect.y); + assertEquals(35, rect.width); + assertEquals(1, rect.height); + + c = chunks[6][4]; + rect = c.rect; + assertEquals(Chunk.TYPE_HORIZONTAL | Chunk.TYPE_VERTICAL, c.type); + assertEquals(72, rect.x); + assertEquals(50, rect.y); + assertEquals(1, rect.width); + assertEquals(1, rect.height); + } + + public void testContentArea() throws Exception { + String fileName = DIR + "content-area.9.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + + assertTrue(image.hasNinePatchExtension()); + assertTrue(image.ensure9Patch()); + + // contents area + List<Tick> horizontalContentsArea = image.getHorizontalContents(); + List<Tick> verticalContentsArea = image.getVerticalContents(); + assertEquals(3, horizontalContentsArea.size()); + assertEquals(3, verticalContentsArea.size()); + + // content area rectangle + Rectangle contentsArea = image.getContentArea(); + assertEquals(new Rectangle(19, 13, 35, 25), contentsArea); + } + + public void testContentAreaOneDot() throws Exception { + String fileName = DIR + "content-area-one-dot.9.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + + assertTrue(image.hasNinePatchExtension()); + assertTrue(image.ensure9Patch()); + + // contents area + List<Tick> horizontalContentsArea = image.getHorizontalContents(); + List<Tick> verticalContentsArea = image.getVerticalContents(); + assertEquals(3, horizontalContentsArea.size()); + assertEquals(3, verticalContentsArea.size()); + + // content area rectangle + Rectangle contentsArea = image.getContentArea(); + assertEquals(new Rectangle(19, 13, 1, 1), contentsArea); + } + + public void testContentAreaTwoDots() throws Exception { + String fileName = DIR + "content-area-two-dots.9.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + + assertTrue(image.hasNinePatchExtension()); + assertTrue(image.ensure9Patch()); + + // contents area + List<Tick> horizontalContentsArea = image.getHorizontalContents(); + List<Tick> verticalContentsArea = image.getVerticalContents(); + assertEquals(5, horizontalContentsArea.size()); + assertEquals(5, verticalContentsArea.size()); + + // content area rectangle + Rectangle contentsArea = image.getContentArea(); + assertEquals(new Rectangle(19, 13, 35, 25), contentsArea); + + String fileName2 = DIR + "content-area.9.png"; + NinePatchedImage image2 = new NinePatchedImage(getClass() + .getResourceAsStream(fileName2), fileName2); + assertNotNull(image2); + + assertTrue(image2.hasNinePatchExtension()); + assertTrue(image2.ensure9Patch()); + + // content area rectangle + Rectangle contentsArea2 = image2.getContentArea(); + assertEquals(contentsArea2, contentsArea); + } + + public void testBadPatches() throws Exception { + String fileName = DIR + "patched-with-badpatches.9.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + + assertTrue(image.hasNinePatchExtension()); + assertTrue(image.ensure9Patch()); + + Chunk[][] chunks = null; + chunks = image.getChunks(chunks); + + // vertical chunk size + assertEquals(5, chunks.length); + + // horizontal chunk size + for (int i = 0; i < chunks.length; i++) { + assertEquals(7, chunks[i].length); + } + + chunks = image.getCorruptedChunks(chunks); + + Chunk c = null; + + // collect bad patches + List<Point> badPatches = new ArrayList<Point>(5 * 7); + for (int y = 0; y < chunks.length; y++) { + for (int x = 0; x < chunks[0].length; x++) { + c = chunks[y][x]; + if ((c.type & Chunk.TYPE_CORRUPT) != 0x0) { + badPatches.add(new Point(y, x)); + } + } + } + + assertEquals(15, badPatches.size()); + + assertTrue(badPatches.contains(new Point(0, 3))); + + assertTrue(badPatches.contains(new Point(1, 1))); + assertTrue(badPatches.contains(new Point(1, 2))); + assertTrue(badPatches.contains(new Point(1, 3))); + assertTrue(badPatches.contains(new Point(1, 4))); + assertTrue(badPatches.contains(new Point(1, 5))); + + assertTrue(badPatches.contains(new Point(2, 1))); + assertTrue(badPatches.contains(new Point(2, 5))); + + assertTrue(badPatches.contains(new Point(3, 0))); + assertTrue(badPatches.contains(new Point(3, 1))); + assertTrue(badPatches.contains(new Point(3, 5))); + assertTrue(badPatches.contains(new Point(3, 6))); + + assertTrue(badPatches.contains(new Point(4, 1))); + assertTrue(badPatches.contains(new Point(4, 3))); + assertTrue(badPatches.contains(new Point(4, 5))); + } + + public void testProjection() throws Exception { + // top + String fileName = DIR + "patched3.9.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + + assertTrue(image.hasNinePatchExtension()); + assertTrue(image.ensure9Patch()); + + ImageData data = image.getImageData(); + assertEquals(72 + 2, data.width); + assertEquals(50 + 2, data.height); + + int width = 72 * 2; + int height = 50 * 2; + + Chunk[][] chunks = null; + chunks = image.getChunks(chunks); + + Projection[][] projections = null; + projections = image.getProjections(width, height, projections); + + assertEquals(chunks.length, projections.length); + for (int i = 0; i < chunks.length; i++) { + assertEquals(chunks[i].length, projections[i].length); + } + + for (int y = 0; y < projections.length; y++) { + for (int x = 0; x < projections[y].length; x++) { + assertEquals(projections[y][x].src, chunks[y][x].rect); + + // If chunk type is FIXED. Same projection size as original + // chunk. + if (projections[y][x].chunk.type == Chunk.TYPE_FIXED) { + assertEquals(projections[y][x].dest.width, chunks[y][x].rect.width); + assertEquals(projections[y][x].dest.height, chunks[y][x].rect.height); + } + } + } + + Projection p = null; + Rectangle rect = null; + + // Check start position + p = projections[0][0]; + + // src position start from 1, 9-patch row and column included. + assertEquals(1, p.src.x); + assertEquals(1, p.src.y); + + // dest position start from 0, 9-patch row and column ignored. + assertEquals(0, p.dest.x); + assertEquals(0, p.dest.y); + + // row 1 + p = projections[0][0]; + rect = p.dest; + assertEquals(0, rect.x); + assertEquals(0, rect.y); + assertEquals(74, rect.width); + assertEquals(5, rect.height); + + p = projections[0][1]; + rect = p.dest; + assertEquals(74, rect.x); + assertEquals(0, rect.y); + assertEquals(62, rect.width); + assertEquals(5, rect.height); + + p = projections[0][2]; + rect = p.dest; + assertEquals(136, rect.x); + assertEquals(0, rect.y); + assertEquals(8, rect.width); + assertEquals(5, rect.height); + + // row 2 + p = projections[1][0]; + rect = p.dest; + assertEquals(0, rect.x); + assertEquals(5, rect.y); + assertEquals(74, rect.width); + assertEquals(24, rect.height); + + p = projections[1][1]; + rect = p.dest; + assertEquals(74, rect.x); + assertEquals(5, rect.y); + assertEquals(62, rect.width); + assertEquals(24, rect.height); + + p = projections[1][2]; + rect = p.dest; + assertEquals(136, rect.x); + assertEquals(5, rect.y); + assertEquals(8, rect.width); + assertEquals(24, rect.height); + + // row 3 + p = projections[2][0]; + rect = p.dest; + assertEquals(0, rect.x); + assertEquals(29, rect.y); + assertEquals(74, rect.width); + assertEquals(58, rect.height); + + p = projections[2][1]; + rect = p.dest; + assertEquals(74, rect.x); + assertEquals(29, rect.y); + assertEquals(62, rect.width); + assertEquals(58, rect.height); + + p = projections[2][2]; + rect = p.dest; + assertEquals(136, rect.x); + assertEquals(29, rect.y); + assertEquals(8, rect.width); + assertEquals(58, rect.height); + + // row 4 + p = projections[3][0]; + rect = p.dest; + assertEquals(0, rect.x); + assertEquals(87, rect.y); + assertEquals(74, rect.width); + assertEquals(13, rect.height); + + p = projections[3][1]; + rect = p.dest; + assertEquals(74, rect.x); + assertEquals(87, rect.y); + assertEquals(62, rect.width); + assertEquals(13, rect.height); + + p = projections[3][2]; + rect = p.dest; + assertEquals(136, rect.x); + assertEquals(87, rect.y); + assertEquals(8, rect.width); + assertEquals(13, rect.height); + } + + public void testReadLayoutBoundsOnlyImage() throws Exception { + String fileName = DIR + "layout-bounds-only.9.png"; + NinePatchedImage image = new NinePatchedImage(getClass() + .getResourceAsStream(fileName), fileName); + assertNotNull(image); + + ImageData data = image.getImageData(); + int width = data.width; + int height = data.height; + + assertEquals(74, width); + assertEquals(52, height); + + assertTrue(image.hasNinePatchExtension()); + assertTrue(image.ensure9Patch()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/content-area-one-dot.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/content-area-one-dot.9.png Binary files differnew file mode 100644 index 0000000..690d76e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/content-area-one-dot.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/content-area-two-dots.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/content-area-two-dots.9.png Binary files differnew file mode 100644 index 0000000..c3d3e4c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/content-area-two-dots.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/content-area.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/content-area.9.png Binary files differnew file mode 100644 index 0000000..6a7a62c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/content-area.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched1.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched1.9.png Binary files differnew file mode 100644 index 0000000..9e5ffbf --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched1.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched2.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched2.9.png Binary files differnew file mode 100644 index 0000000..cfd9c1d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched2.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched3.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched3.9.png Binary files differnew file mode 100644 index 0000000..0f2c3eb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched3.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched4.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched4.9.png Binary files differnew file mode 100644 index 0000000..ce73210 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched4.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched5.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched5.9.png Binary files differnew file mode 100644 index 0000000..97d9cd8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched5.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched6.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched6.9.png Binary files differnew file mode 100644 index 0000000..f9b8a48 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched6.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched7.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched7.9.png Binary files differnew file mode 100644 index 0000000..bbbb3ad --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched7.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched8.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched8.9.png Binary files differnew file mode 100644 index 0000000..35f04b5 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/invalid-patched8.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/layout-bounds-only.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/layout-bounds-only.9.png Binary files differnew file mode 100644 index 0000000..6f70473 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/layout-bounds-only.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/no-patched-interlace.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/no-patched-interlace.png Binary files differnew file mode 100644 index 0000000..a6d0e94 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/no-patched-interlace.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/no-patched.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/no-patched.png Binary files differnew file mode 100644 index 0000000..742b1e8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/no-patched.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/patched-with-badpatches.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/patched-with-badpatches.9.png Binary files differnew file mode 100644 index 0000000..1a38972 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/patched-with-badpatches.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/patched1.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/patched1.9.png Binary files differnew file mode 100644 index 0000000..6a7a62c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/patched1.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/patched2.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/patched2.9.png Binary files differnew file mode 100644 index 0000000..11a1a6d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/patched2.9.png diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/patched3.9.png b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/patched3.9.png Binary files differnew file mode 100644 index 0000000..8d288c9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/testdata/draw9patch/patched3.9.png |