/*
* Copyright © 2009-2011 Rebecca G. Bettencourt / Kreative Software
* <p>
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* <a href="http://www.mozilla.org/MPL/">http://www.mozilla.org/MPL/</a>
* <p>
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
* <p>
* Alternatively, the contents of this file may be used under the terms
* of the GNU Lesser General Public License (the "LGPL License"), in which
* case the provisions of LGPL License are applicable instead of those
* above. If you wish to allow use of your version of this file only
* under the terms of the LGPL License and not to allow others to use
* your version of this file under the MPL, indicate your decision by
* deleting the provisions above and replace them with the notice and
* other provisions required by the LGPL License. If you do not delete
* the provisions above, a recipient may use your version of this file
* under either the MPL or the LGPL License.
* @since PowerPaint 1.0
* @author Rebecca G. Bettencourt, Kreative Software
*/
package com.kreative.paint.tool;
import java.awt.Cursor;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.kreative.paint.Layer;
import com.kreative.paint.document.tile.PaintSurface;
import com.kreative.paint.geom.BitmapShape;
import com.kreative.paint.util.CursorUtils;
public class MagicWandTool extends MarqueeTool {
private static final int K = 0xFF000000;
private static final int W = 0xFFFFFFFF;
private static final Image icon = ToolUtilities.makeIcon(
16, 16,
new int[] {
0,0,0,K,0,0,0,0,0,0,0,0,0,0,0,0,
0,K,0,0,0,K,0,0,0,0,0,0,0,0,0,0,
0,0,K,0,K,0,0,0,0,0,0,0,0,0,0,0,
K,0,0,K,0,0,K,0,0,0,0,0,0,0,0,0,
0,0,K,0,K,0,0,0,0,0,0,0,0,0,0,0,
0,K,0,0,0,K,0,0,0,0,0,0,0,0,0,0,
0,0,0,K,0,0,0,K,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,K,0,K,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,K,0,K,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,K,K,K,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,K,K,K,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,K,K,K,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,K,K,K,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,K,K,K,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,K,K,K,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,K,0,
}
);
private static final Cursor curs = CursorUtils.makeCursor(
16, 16,
new int[] {
0,0,0,K,0,0,0,0,0,0,0,0,0,0,0,0,
0,K,0,W,0,K,0,0,0,0,0,0,0,0,0,0,
0,0,K,W,K,0,0,0,0,0,0,0,0,0,0,0,
K,W,W,K,W,W,K,0,0,0,0,0,0,0,0,0,
0,0,K,W,K,0,0,0,0,0,0,0,0,0,0,0,
0,K,0,W,0,K,0,0,0,0,0,0,0,0,0,0,
0,0,0,K,0,0,0,K,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,K,W,K,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,K,W,K,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,K,K,K,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,K,K,K,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,K,K,K,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,K,K,K,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,K,K,K,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,K,K,K,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,K,0,
},
3, 3,
"Wand"
);
protected Image getBWIcon() {
return icon;
}
protected boolean selectAllLasso() {
return true;
}
protected boolean mousePressedImpl(ToolEvent e) {
beginSelection(e);
Layer layer = e.getLayer();
if (layer == null) {
commitSelection(e, null);
} else {
Rectangle bounds = new Rectangle(-layer.getX(), -layer.getY(), e.getCanvas().getWidth(), e.getCanvas().getHeight());
int x = (int)Math.floor(e.getCanvasX()-layer.getX());
int y = (int)Math.floor(e.getCanvasY()-layer.getY());
Point seedPoint = new Point(x, y);
if (e.isCtrlDown()) {
commitSelection(e, allSel(bounds, seedPoint, layer));
} else {
commitSelection(e, seedSel(bounds, seedPoint, layer));
}
}
return true;
}
protected Cursor getDefaultCursor() {
return curs;
}
private static boolean colorCmp(int c, int targetcolor) {
// in the future this could be refactored to support "tolerance"
return c == targetcolor;
}
private BitmapShape allSel(Rectangle bounds, Point seedPoint, PaintSurface p) {
int targetcolor = p.getRGB(seedPoint.x, seedPoint.y);
int[] rgb = new int[bounds.width*bounds.height];
p.getRGB(bounds.x, bounds.y, bounds.width, bounds.height, rgb, 0, bounds.width);
int[] prgb = new int[bounds.width*bounds.height];
int cbx1 = bounds.width, cby1 = bounds.height, cbx2 = -1, cby2 = -1;
for (int ay = 0, by = 0; ay < rgb.length; ay += bounds.width, by++) {
for (int ax = 0, bx = 0; ax < bounds.width; ax++, bx++) {
if (colorCmp(rgb[ay+ax], targetcolor)) {
prgb[ay+ax] = 0xFF000000;
if (bx < cbx1) cbx1 = bx;
if (by < cby1) cby1 = by;
if (bx > cbx2) cbx2 = bx;
if (by > cby2) cby2 = by;
}
}
}
if (cbx2 >= cbx1 && cby2 >= cby1) {
int cw = cbx2-cbx1+1;
int ch = cby2-cby1+1;
int[] nrgb = new int[cw*ch];
for (
int ny = 0, py = cby1*bounds.width;
ny < nrgb.length && py < prgb.length;
ny += cw, py += bounds.width
) {
for (
int nx = 0, px = cbx1;
nx < cw && px < bounds.width;
nx++, px++
) {
nrgb[ny+nx] = prgb[py+px];
}
}
return new BitmapShape(nrgb, cbx1, cby1, cw, ch);
} else {
return null;
}
}
private BitmapShape seedSel(Rectangle bounds, Point node, PaintSurface p) {
int xmin = bounds.x, xmax = bounds.x+bounds.width-1, ymin = bounds.y, ymax = bounds.y+bounds.height-1;
int bx = bounds.x, by = bounds.y, rc = bounds.width;
int targetcolor = p.getRGB(node.x, node.y);
int[] rgb = new int[bounds.width*bounds.height];
p.getRGB(bounds.x, bounds.y, bounds.width, bounds.height, rgb, 0, bounds.width);
int[] prgb = new int[bounds.width*bounds.height];
int cbx1 = bounds.width, cby1 = bounds.height, cbx2 = -1, cby2 = -1;
List<Long> Q = new LinkedList<Long>();
Set<Long> V = new LongBitSet();
Q.add(((node.x & 0xFFFFFFFFL) << 32L) | (node.y & 0xFFFFFFFFL));
while (!Q.isEmpty()) {
long n = Q.remove(0);
if (!V.contains(n)) {
int nx = (int)(n >> 32L);
int ny = (int)(n);
if (colorCmp(rgb[(ny-by)*rc + (nx-bx)], targetcolor)) {
int w = nx;
int e = nx;
int y = ny;
while (w > xmin && colorCmp(rgb[(y-by)*rc + ((w-1)-bx)], targetcolor)) w--;
while (e < xmax && colorCmp(rgb[(y-by)*rc + ((e+1)-bx)], targetcolor)) e++;
for (int x = w; x <= e; x++) {
prgb[(y-by)*rc + (x-bx)] = 0xFF000000;
if (x-bx < cbx1) cbx1 = x-bx;
if (y-by < cby1) cby1 = y-by;
if (x-bx > cbx2) cbx2 = x-bx;
if (y-by > cby2) cby2 = y-by;
V.add(((x & 0xFFFFFFFFL) << 32L) | (y & 0xFFFFFFFFL));
}
if (y > ymin) {
for (int x = w; x <= e; x++) {
if (colorCmp(rgb[((y-1)-by)*rc + (x-bx)], targetcolor)) {
long q = ((x & 0xFFFFFFFFL) << 32L) | ((y-1) & 0xFFFFFFFFL);
if (!V.contains(q)) Q.add(q);
}
}
}
if (y < ymax) {
for (int x = w; x <= e; x++) {
if (colorCmp(rgb[((y+1)-by)*rc + (x-bx)], targetcolor)) {
long q = ((x & 0xFFFFFFFFL) << 32L) | ((y+1) & 0xFFFFFFFFL);
if (!V.contains(q)) Q.add(q);
}
}
}
}
}
}
if (cbx2 >= cbx1 && cby2 >= cby1) {
int cw = cbx2-cbx1+1;
int ch = cby2-cby1+1;
int[] nrgb = new int[cw*ch];
for (
int ny = 0, py = cby1*bounds.width;
ny < nrgb.length && py < prgb.length;
ny += cw, py += bounds.width
) {
for (
int nx = 0, px = cbx1;
nx < cw && px < bounds.width;
nx++, px++
) {
nrgb[ny+nx] = prgb[py+px];
}
}
return new BitmapShape(nrgb, cbx1, cby1, cw, ch);
} else {
return null;
}
}
private static class LongBitSet implements Set<Long> {
private Map<Long,BitSet> m = new HashMap<Long,BitSet>();
public boolean add(Long o) {
if (m.containsKey(o >> 24)) {
m.get(o >> 24).set((int)(o & 0xFFFFFF));
} else {
BitSet b = new BitSet();
b.set((int)(o & 0xFFFFFF));
m.put(o >> 24, b);
}
return true;
}
public boolean addAll(Collection<? extends Long> c) {
boolean ret = false;
for (long l : c) {
if (add(l)) ret = true;
}
return ret;
}
public void clear() {
m.clear();
}
public boolean contains(Object o) {
if (m.containsKey((Long)o >> 24)) {
return m.get((Long)o >> 24).get((int)((Long)o & 0xFFFFFF));
} else {
return false;
}
}
public boolean containsAll(Collection<?> c) {
for (Object o : c) {
if (!contains(o)) return false;
}
return true;
}
public boolean isEmpty() {
for (BitSet b : m.values()) {
if (!b.isEmpty()) return false;
}
return true;
}
public Iterator<Long> iterator() {
throw new UnsupportedOperationException("LongBitSet.iterator");
}
public boolean remove(Object o) {
if (m.containsKey((Long)o >> 24)) {
m.get((Long)o >> 24).clear((int)((Long)o & 0xFFFFFF));
return true;
} else {
return false;
}
}
public boolean removeAll(Collection<?> c) {
boolean ret = false;
for (Object o : c) {
if (remove(o)) ret = true;
}
return ret;
}
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException("LongBitSet.retainAll");
}
public int size() {
int size = 0;
for (BitSet b : m.values()) {
size += b.cardinality();
}
return size;
}
public Object[] toArray() {
throw new UnsupportedOperationException("LongBitSet.toArray");
}
public <T> T[] toArray(T[] a) {
throw new UnsupportedOperationException("LongBitSet.toArray");
}
}
}