import java.applet.Applet; import java.awt.Image; import java.awt.Graphics; import java.awt.Rectangle; import java.util.StringTokenizer; import java.util.Vector; import java.util.Hashtable; import java.net.URL; import java.awt.image.*; import java.net.MalformedURLException; /** * An extensible ImageMap applet class. * The active areas on the image are controlled by ImageArea classes * that can be dynamically loaded over the net. * * @author Jim Graham * @version %I%, %G% */ public class ImageMap extends Applet { /** * The unhighlighted image being mapped. */ Image baseImage; /** * The list of image area handling objects; */ ImageMapArea areas[]; /** * The primary highlight mode to be used. */ static final int BRIGHTER = 0; static final int DARKER = 1; int hlmode = BRIGHTER; /** * The percentage of highlight to apply for the primary highlight mode. */ int hlpercent = 50; /** * Get a rectangular region of the baseImage highlighted according to * the primary highlight specification. */ Image getHighlight(int x, int y, int w, int h) { return getHighlight(x, y, w, h, hlmode, hlpercent); } /** * Get a rectangular region of the baseImage with a specific highlight. */ Image getHighlight(int x, int y, int w, int h, int mode, int percent) { return getHighlight(x, y, w, h, new HighlightFilter(mode == BRIGHTER, percent)); } /** * Get a rectangular region of the baseImage modified by an image filter. */ Image getHighlight(int x, int y, int w, int h, ImageFilter filter) { Image cropped = makeImage(baseImage, new CropImageFilter(x, y, w, h)); return makeImage(cropped, filter); } /** * Make the primary highlighted version of the baseImage. */ Image makeImage(Image orig, ImageFilter filter) { return createImage(new FilteredImageSource(orig.getSource(), filter)); } /** * Parse a string representing the desired highlight to be applied. */ void parseHighlight(String s) { if (s == null) { return; } if (s.startsWith("brighter")) { hlmode = BRIGHTER; if (s.length() > "brighter".length()) { hlpercent = Integer.parseInt(s.substring("brighter".length())); } } else if (s.startsWith("darker")) { hlmode = DARKER; if (s.length() > "darker".length()) { hlpercent = Integer.parseInt(s.substring("darker".length())); } } } /** * Initialize the applet. Get attributes. * * Initialize the ImageAreas. * Each ImageArea is a subclass of the class ImageArea, and is * specified with an attribute of the form: * areaN=ImageAreaClassName,arguments... * The ImageAreaClassName is parsed off and a new instance of that * class is created. The initializer for that class is passed a * reference to the applet and the remainder of the attribute * string, from which the class should retrieve any information it * needs about the area it controls and the actions it needs to * take within that area. */ public void init() { String s; parseHighlight(getParameter("highlight")); baseImage = getImage(getDocumentBase(), getParameter("img")); Vector areaVec = new Vector(); int num = 1; while (true) { ImageMapArea newArea; s = getParameter("area"+num); if (s == null) { // Try rect for backwards compatibility. s = getParameter("rect"+num); if (s == null) { break; } String url = getParameter("href"+num); if (url != null) s += "," + url; newArea = new HrefArea(); } else { int classend = s.indexOf(","); try { String name = s.substring(0, classend); newArea = (ImageMapArea) Class.forName(name).newInstance(); } catch (Exception e) { e.printStackTrace(); break; } s = s.substring(classend+1); } newArea.init(this, s); areaVec.addElement(newArea); num++; } areas = new ImageMapArea[areaVec.size()]; areaVec.copyInto(areas); checkSize(); } /** * Check the size of this applet while the image is being loaded. */ synchronized void checkSize() { int w = baseImage.getWidth(this); int h = baseImage.getHeight(this); if (w > 0 && h > 0) { resize(w, h); repaintrect.x = repaintrect.y = 0; repaintrect.width = w; repaintrect.height = h; fullrepaint = true; repaint(); } } private boolean fullrepaint = false; private Rectangle repaintrect = new Rectangle(); private long lastupdate = 0; private final static long UPDATERATE = 100; /** * Handle updates from images being loaded. */ public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { if ((infoflags & (WIDTH | HEIGHT)) != 0) { checkSize(); } if ((infoflags & (SOMEBITS | FRAMEBITS | ALLBITS)) != 0) { repaint(((infoflags & (FRAMEBITS | ALLBITS)) != 0) ? 0 : UPDATERATE, x, y, width, height); } return (infoflags & (ALLBITS | ERROR)) == 0; } /** * Paint the image and all active highlights. */ public void paint(Graphics g) { synchronized(this) { if (fullrepaint) { g = g.create(); g.clipRect(repaintrect.x, repaintrect.y, repaintrect.width, repaintrect.height); fullrepaint = false; } } if (baseImage == null) { return; } g.drawImage(baseImage, 0, 0, this); if (areas != null) { for (int i = areas.length; --i >= 0; ) { if (areas[i].active || areas[i].entered) { areas[i].setState(g, areas[i].entered); } } } } /** * Update the active highlights on the image. */ public void update(Graphics g) { if (fullrepaint) { paint(g); return; } if (baseImage == null) { return; } g.drawImage(baseImage, 0, 0, this); if (areas == null) { return; } // First unhighlight all of the deactivated areas for (int i = areas.length; --i >= 0; ) { if (areas[i].active && !areas[i].entered) { areas[i].setState(g, false); } } // Then highlight all of the activated areas for (int i = areas.length; --i >= 0; ) { if (areas[i].entered) { areas[i].setState(g, true); } } } /** * Make sure that no ImageAreas are highlighted. */ public void mouseExit() { boolean changed = false; for (int i = 0; i < areas.length; i++) { if (areas[i].active) { areas[i].entered = false; changed = true; } } if (changed) { repaint(); } } /** * Find the ImageAreas that the mouse is in. */ public boolean mouseMove(java.awt.Event evt, int x, int y) { boolean changed = false; boolean propagate = true; for (int i = 0; i < areas.length; i++) { if (areas[i].inside(x, y)) { areas[i].entered = propagate; if (areas[i].terminal) { propagate = false; } } else { areas[i].entered = false; } if (areas[i].active != areas[i].entered) { changed = true; } } if (changed) { repaint(); } return true; } int pressX; int pressY; /** * Inform all active ImageAreas of a mouse press. */ public boolean mouseDown(java.awt.Event evt, int x, int y) { pressX = x; pressY = y; for (int i = 0; i < areas.length; i++) { if (areas[i].inside(x, y)) { areas[i].press(x, y); if (areas[i].terminal) { break; } } } return true; } /** * Inform all active ImageAreas of a mouse release. * Only those areas that were inside the original mouseDown() * are informed of the mouseUp. */ public boolean mouseUp(java.awt.Event evt, int x, int y) { for (int i = 0; i < areas.length; i++) { if (areas[i].inside(pressX, pressY)) { areas[i].lift(x, y); if (areas[i].terminal) { break; } } } return true; } /** * Inform all active ImageAreas of a mouse drag. * Only those areas that were inside the original mouseDown() * are informed of the mouseUp. */ public boolean mouseDrag(java.awt.Event evt, int x, int y) { mouseMove(evt, x, y); for (int i = 0; i < areas.length; i++) { if (areas[i].inside(pressX, pressY)) { areas[i].drag(x, y); if (areas[i].terminal) { break; } } } return true; } } /** * The base ImageArea class. * This class performs the basic functions that most ImageArea * classes will need and delegates specific actions to the subclasses. * * @author Jim Graham * @version %I%, %G% */ class ImageMapArea implements ImageObserver { /** The applet parent that contains this ImageArea. */ ImageMap parent; /** The X location of the area (if rectangular). */ int X; /** The Y location of the area (if rectangular). */ int Y; /** The size().width of the area (if rectangular). */ int W; /** The size().height of the area (if rectangular). */ int H; /** * This flag indicates whether the user was in this area during the * last scan of mouse locations. */ boolean entered = false; /** This flag indicates whether the area is currently highlighted. */ boolean active = false; /** * This flag indicates whether the area is terminal. Terminal areas * prevent any areas which are under them from being activated when * the mouse is inside them. Some areas may wish to change this to * false so that they can augment other areas that they are on top of. */ boolean terminal = true; /** * This is the default highlight image if no special effects are * needed to draw the highlighted image. It is created by the * default "makeImages()" method. */ Image hlImage; /** * Initialize this ImageArea as called from the applet. * If the subclass does not override this initializer, then it * will perform the basic functions of setting the parent applet * and parsing out 4 numbers from the argument string which specify * a rectangular region for the ImageArea to act on. * The remainder of the argument string is passed to the handleArg() * method for more specific handling by the subclass. */ public void init(ImageMap parent, String args) { this.parent = parent; StringTokenizer st = new StringTokenizer(args, ", "); X = Integer.parseInt(st.nextToken()); Y = Integer.parseInt(st.nextToken()); W = Integer.parseInt(st.nextToken()); H = Integer.parseInt(st.nextToken()); if (st.hasMoreTokens()) { // hasMoreTokens() Skips the trailing comma handleArg(st.nextToken("")); } else { handleArg(null); } makeImages(); } /** * This method handles the remainder of the argument string after * the standard initializer has parsed off the 4 rectangular * parameters. If the subclass does not override this method, * the remainder will be ignored. */ public void handleArg(String s) { } /** * This method sets the image to be used to render the ImageArea * when it is highlighted. */ public void setHighlight(Image img) { hlImage = img; } /** * This method handles the construction of the various images * used to highlight this particular ImageArea when the user * interacts with it. */ public void makeImages() { setHighlight(parent.getHighlight(X, Y, W, H)); } /** * This method tests to see if a point is inside this ImageArea. * The standard method assumes a rectangular area as parsed by * the standard initializer. If a more complex area is required * then this method will have to be overridden by the subclass. */ public boolean inside(int x, int y) { return (x >= X && x < (X + W) && y >= Y && y < (Y + H)); } /** * This utility method draws a rectangular subset of a highlight * image. */ public void drawImage(Graphics g, Image img, int imgx, int imgy, int x, int y, int w, int h) { Graphics ng = g.create(); ng.clipRect(x, y, w, h); ng.drawImage(img, imgx, imgy, this); } /** * This method handles the updates from drawing the images. */ public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { if (img == hlImage) { return parent.imageUpdate(img, infoflags, x + X, y + Y, width, height); } else { return false; } } /** * This utility method shows a string in the status bar. */ public void showStatus(String msg) { parent.getAppletContext().showStatus(msg); } /** * This utility method tells the browser to visit a URL. */ public void showDocument(URL u) { parent.getAppletContext().showDocument(u); } /** * This method highlights the specified area when the user enters * it with his mouse. The standard highlight method is to replace * the indicated rectangular area of the image with the primary * highlighted image. */ public void highlight(Graphics g, boolean on) { if (on) { g.drawImage(hlImage, X, Y, this); } else { drawImage(g, parent.baseImage, 0, 0, X, Y, W, H); } } /** * This method changes the active state of the ImageArea, which * indicates whether the user is currently "inside" this area. * It turns around and calls the highlight method which is likely * to have been overridden by subclasses seeking a custom highlight. */ public void setState(Graphics g, boolean on) { highlight(g, on); active = on; } /** * The press method is called when the user presses the mouse * button inside the ImageArea. The location is supplied, but * the standard implementation is to call the overloaded method * with no arguments. */ public void press(int x, int y) { press(); } /** * The overloaded press method is called when the user presses the * mouse button inside the ImageArea. This method can be overridden * if the ImageArea does not need to know the location of the press. */ public void press() { } /** * The lift method is called when the user releases the mouse button. * The location is supplied, but the standard implementation is to * call the overloaded method with no arguments. Only those ImageAreas * that were informed of a press will be informed of the corresponding * release. */ public void lift(int x, int y) { lift(); } /** * The overloaded lift method is called when the user releases the * mouse button. This method can be overridden if the ImageArea * does not need to know the location of the release. */ public void lift() { } /** * The drag method is called when the user moves the mouse while * the button is pressed. Only those ImageAreas that were informed * of a press will be informed of the corresponding mouse movements. */ public void drag(int x, int y) { } } /** * The classic "Fetch a URL" ImageArea class. * This class extends the basic ImageArea Class to fetch a URL when * the user clicks in the area. * * @author Jim Graham * @version %I%, %G% */ class HrefArea extends ImageMapArea { /** The URL to be fetched when the user clicks on this area. */ URL anchor; /** * The argument string is the URL to be fetched. */ public void handleArg(String arg) { try { anchor = new URL(parent.getDocumentBase(), arg); } catch (MalformedURLException e) { anchor = null; } } /** * The status message area is updated to show the destination URL. * The default graphics highlight feedback is used. */ public void highlight(Graphics g, boolean on) { super.highlight(g, on); showStatus((on && anchor != null) ? "Go To " + anchor.toExternalForm() : null); } /** * The new URL is fetched when the user releases the mouse button * only if they are still in the area. */ public void lift(int x, int y) { if (inside(x, y) && anchor != null) { showDocument(anchor); } // Note that we should not be active, so no repaint is necessary. } } /** * An audio feedback ImageArea class. * This class extends the basic ImageArea Class to play a sound each * time the user enters the area. * * @author Jim Graham * @version %I%, %G% */ class SoundArea extends ImageMapArea { /** The URL of the sound to be played. */ String sound; /** * The argument is the URL of the sound to be played. * This method also sets this type of area to be non-terminal. */ public void handleArg(String arg) { sound = arg; terminal = false; } /** * The highlight method plays the sound in addition to the usual * graphical highlight feedback. */ public void highlight(Graphics g, boolean on) { if (on && !active) { parent.play(parent.getDocumentBase(), sound); } super.highlight(g, on); } } /** * An click feedback ImageArea class. * This class extends the basic ImageArea Class to show the locations * of clicks in the image in the status message area. This utility * ImageArea class is useful when setting up ImageMaps. * * @author Jim Graham * @version %I%, %G% */ class ClickArea extends ImageMapArea { /** The X location of the last mouse press. */ int startx; /** The Y location of the last mouse press. */ int starty; /** * The argument is ignored, but we use this method to set this type * of area to be non-terminal. */ public void handleArg(String arg) { terminal = false; } /** This class overrides the highlight method to prevent highlighting. */ public void highlight(Graphics g, boolean on) { } String ptstr(int x, int y) { return "("+x+", "+y+")"; } /** * When the user presses the mouse button, start showing coordinate * feedback in the status message line. */ public void press(int x, int y) { showStatus("Clicked at "+ptstr(x, y)); startx = x; starty = y; } /** * Update the coordinate feedback every time the user moves the mouse * while he has the button pressed. */ public void drag(int x, int y) { showStatus("Rectangle from "+ptstr(startx, starty) +" to "+ptstr(x, y) +" is "+(x-startx)+"x"+(y-starty)); } /** * Update the coordinate feedback one last time when the user releases * the mouse button. */ public void lift(int x, int y) { drag(x, y); } } /** * A message feedback ImageArea class. * This class extends the basic ImageArea Class to show the a given * message in the status message area when the user enters this area. * * @author Jim Graham * @version %I%, %G% */ class NameArea extends ImageMapArea { /** The string to be shown in the status message area. */ String name; /** * The argument is the string to be displayed in the status message * area. This method also sets this type of area to be non-terminal. */ public void handleArg(String arg) { name = arg; terminal = false; } /** * The highlight method displays the message in addition to the usual * graphical highlight feedback. */ public void highlight(Graphics g, boolean on) { super.highlight(g, on); showStatus(on ? name : null); } } /** * An improved "Fetch a URL" ImageArea class. * This class extends the basic ImageArea Class to fetch a URL when * the user clicks in the area. In addition, special custom highlights * are used to make the area look and feel like a 3-D button. * * @author Jim Graham * @version %I%, %G% */ class HrefButtonArea extends ImageMapArea { /** The URL to be fetched when the user clicks on this area. */ URL anchor; /** The highlight image for when the button is "UP". */ Image upImage; /** The highlight image for when the button is "DOWN". */ Image downImage; /** This flag indicates if the "button" is currently pressed. */ boolean pressed = false; /** The border size for the 3-D effect. */ int border = 5; /** * The argument string is the URL to be fetched. * This method also constructs the various highlight images needed * to achieve the 3-D effect. */ public void handleArg(String arg) { try { anchor = new URL(parent.getDocumentBase(), arg); } catch (MalformedURLException e) { anchor = null; } if (border * 2 > W || border * 2 > H) { border = Math.min(W, H) / 2; } } public void makeImages() { upImage = parent.getHighlight(X, Y, W, H, new ButtonFilter(false, parent.hlpercent, border, W, H)); downImage = parent.getHighlight(X, Y, W, H, new ButtonFilter(true, parent.hlpercent, border, W, H)); } public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { if (img == (pressed ? downImage : upImage)) { return parent.imageUpdate(img, infoflags, x + X, y + Y, width, height); } else { return (img == downImage || img == upImage); } } /** * The status message area is updated to show the destination URL. * The graphical highlight is achieved using the ButtonFilter. */ public void highlight(Graphics g, boolean on) { if (on) { setHighlight(pressed ? downImage : upImage); } super.highlight(g, on); showStatus((on && anchor != null) ? "Go To " + anchor.toExternalForm() : null); } /** * Since the highlight changes when the button is pressed, we need * to record the "pressed" state and induce a repaint. */ public void press() { parent.repaint(); pressed = true; } /** * The new URL is fetched when the user releases the mouse button * only if they are still in the area. */ public void lift(int x, int y) { pressed = false; parent.repaint(); if (inside(x, y) && anchor != null) { showDocument(anchor); } } } /** * An improved, round, "Fetch a URL" ImageArea class. * This class extends the HrefButtonArea Class to make the 3D button * a rounded ellipse. All of the same feedback and operational * charactistics as the HrefButtonArea apply. * * @author Jim Graham * @version %I%, %G% */ class RoundHrefButtonArea extends HrefButtonArea { public void makeImages() { upImage = parent.getHighlight(X, Y, W, H, new RoundButtonFilter(false, parent.hlpercent, border, W, H)); downImage = parent.getHighlight(X, Y, W, H, new RoundButtonFilter(true, parent.hlpercent, border, W, H)); } } class HighlightFilter extends RGBImageFilter { boolean brighter; int percent; public HighlightFilter(boolean b, int p) { brighter = b; percent = p; canFilterIndexColorModel = true; } public int filterRGB(int x, int y, int rgb) { int r = (rgb >> 16) & 0xff; int g = (rgb >> 8) & 0xff; int b = (rgb >> 0) & 0xff; if (brighter) { r = (255 - ((255 - r) * (100 - percent) / 100)); g = (255 - ((255 - g) * (100 - percent) / 100)); b = (255 - ((255 - b) * (100 - percent) / 100)); } else { r = (r * (100 - percent) / 100); g = (g * (100 - percent) / 100); b = (b * (100 - percent) / 100); } if (r < 0) r = 0; if (r > 255) r = 255; if (g < 0) g = 0; if (g > 255) g = 255; if (b < 0) b = 0; if (b > 255) b = 255; return (rgb & 0xff000000) | (r << 16) | (g << 8) | (b << 0); } } class ButtonFilter extends RGBImageFilter { boolean pressed; int defpercent; int border; int width; int height; ColorModel models[] = new ColorModel[7]; ColorModel origbuttonmodel; public ButtonFilter(boolean press, int p, int b, int w, int h) { pressed = press; defpercent = p; border = b; width = w; height = h; } public void setHints(int hints) { super.setHints(hints & (~ImageConsumer.COMPLETESCANLINES)); } public void setColorModel(ColorModel model) { if (model instanceof IndexColorModel && true) { IndexColorModel icm = (IndexColorModel) model; models[0] = filterIndexColorModel(icm, false, false, 0); models[1] = filterIndexColorModel(icm, true, !pressed, defpercent); models[2] = null; if (pressed) { models[3] = filterIndexColorModel(icm, true, false, defpercent/2); } else { models[3] = models[0]; } models[4] = null; models[5] = filterIndexColorModel(icm, true, pressed, defpercent); models[6] = models[0]; origbuttonmodel = model; consumer.setColorModel(models[3]); } else { super.setColorModel(model); } } public IndexColorModel filterIndexColorModel(IndexColorModel icm, boolean opaque, boolean brighter, int percent) { byte r[] = new byte[256]; byte g[] = new byte[256]; byte b[] = new byte[256]; byte a[] = new byte[256]; int mapsize = icm.getMapSize(); icm.getReds(r); icm.getGreens(g); icm.getBlues(b); if (opaque) { icm.getAlphas(a); for (int i = 0; i < mapsize; i++) { int rgb = filterRGB(icm.getRGB(i), brighter, percent); a[i] = (byte) (rgb >> 24); r[i] = (byte) (rgb >> 16); g[i] = (byte) (rgb >> 8); b[i] = (byte) (rgb >> 0); } } return new IndexColorModel(icm.getPixelSize(), mapsize, r, g, b, a); } /** * Define the ranges of varying highlight for the button. * ranges is an array of 8 values which split up a scanline into * 7 different regions of highlighting effect: * * ranges[0-1] = area outside of left edge of button * ranges[1-2] = area inside UpperLeft highlight region left of center * ranges[2-3] = area requiring custom highlighting left of center * ranges[3-4] = area inside center of button * ranges[4-5] = area requiring custom highlighting right of center * ranges[5-6] = area inside LowerRight highlight region right of center * ranges[6-7] = area outside of right edge of button * * Note that ranges[0-1] and ranges[6-7] are empty where the edges of * the button touch the left and right edges of the image (everywhere * on a square button) and ranges[2-3] and ranges[4-5] are only nonempty * in those regions where the UpperLeft highlighting has leaked over * the "top" of the button onto parts of its right edge or where the * LowerRight highlighting has leaked under the "bottom" of the button * onto parts of its left edge (can't happen on square buttons, happens * occasionally on round buttons). */ public void buttonRanges(int y, int ranges[]) { ranges[0] = ranges[1] = 0; if (y < border) { ranges[2] = ranges[3] = ranges[4] = ranges[5] = width - y; } else if (y > height - border) { ranges[2] = ranges[3] = ranges[4] = ranges[5] = height - y; } else { ranges[2] = ranges[3] = border; ranges[4] = ranges[5] = width - border; } ranges[6] = ranges[7] = width; } public void setPixels(int x, int y, int w, int h, ColorModel model, byte pixels[], int off, int scansize) { if (model == origbuttonmodel) { int ranges[] = new int[8]; int x2 = x + w; for (int cy = y; cy < y + h; cy++) { buttonRanges(cy, ranges); for (int i = 0; i < 7; i++) { if (x2 > ranges[i] && x < ranges[i+1]) { int cx1 = Math.max(x, ranges[i]); int cx2 = Math.min(x2, ranges[i+1]); if (models[i] == null) { super.setPixels(cx1, cy, cx2 - cx1, 1, model, pixels, off + (cx1 - x), scansize); } else { if (cx1 < cx2) { consumer.setPixels(cx1, cy, cx2 - cx1, 1, models[i], pixels, off + (cx1 - x), scansize); } } } } off += scansize; } } else { super.setPixels(x, y, w, h, model, pixels, off, scansize); } } public int filterRGB(int x, int y, int rgb) { boolean brighter; int percent; if ((x < border && y < height - x) || (y < border && x < width - y)) { brighter = !pressed; percent = defpercent; } else if (x >= width - border || y >= height - border) { brighter = pressed; percent = defpercent; } else if (pressed) { brighter = false; percent = defpercent / 2; } else { return rgb & 0x00ffffff; } return filterRGB(rgb, brighter, percent); } public int filterRGB(int rgb, boolean brighter, int percent) { int r = (rgb >> 16) & 0xff; int g = (rgb >> 8) & 0xff; int b = (rgb >> 0) & 0xff; if (brighter) { r = (255 - ((255 - r) * (100 - percent) / 100)); g = (255 - ((255 - g) * (100 - percent) / 100)); b = (255 - ((255 - b) * (100 - percent) / 100)); } else { r = (r * (100 - percent) / 100); g = (g * (100 - percent) / 100); b = (b * (100 - percent) / 100); } if (r < 0) r = 0; if (g < 0) g = 0; if (b < 0) b = 0; if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; return (rgb & 0xff000000) | (r << 16) | (g << 8) | (b << 0); } } class RoundButtonFilter extends ButtonFilter { int Xcenter; int Ycenter; int Yradsq; int innerW; int innerH; int Yrad2sq; public RoundButtonFilter(boolean press, int p, int b, int w, int h) { super(press, p, b, w, h); Xcenter = w/2; Ycenter = h/2; Yradsq = h * h / 4; innerW = w - border * 2; innerH = h - border * 2; Yrad2sq = innerH * innerH / 4; } public void buttonRanges(int y, int ranges[]) { int yrel = Math.abs(Ycenter - y); int xrel = (int) (Math.sqrt(Yradsq - yrel * yrel) * width / height); int xslash = width - (y * width / height); ranges[0] = 0; ranges[1] = Xcenter - xrel; ranges[6] = Xcenter + xrel; ranges[7] = width; if (y < border) { ranges[2] = ranges[3] = ranges[4] = Xcenter; ranges[5] = ranges[6]; } else if (y + border >= height) { ranges[2] = ranges[1]; ranges[3] = ranges[4] = ranges[5] = Xcenter; } else { int xrel2 = (int) (Math.sqrt(Yrad2sq - yrel * yrel) * innerW / innerH); ranges[3] = Xcenter - xrel2; ranges[4] = Xcenter + xrel2; if (y < Ycenter) { ranges[2] = ranges[3]; ranges[5] = ranges[6]; } else { ranges[2] = ranges[1]; ranges[5] = ranges[4]; } } } private int savedranges[]; private int savedy; private synchronized int[] getRanges(int y) { if (savedranges == null || savedy != y) { if (savedranges == null) { savedranges = new int[8]; } buttonRanges(y, savedranges); savedy = y; } return savedranges; } public int filterRGB(int x, int y, int rgb) { boolean brighter; int percent; int i; int ranges[] = getRanges(y); for (i = 0; i < 7; i++) { if (x >= ranges[i] && x < ranges[i+1]) { break; } } double angle; switch (i) { default: case 0: case 6: return rgb & 0x00ffffff; case 1: brighter = !pressed; percent = defpercent; break; case 5: brighter = pressed; percent = defpercent; break; case 2: angle = Math.atan2(y - Ycenter, Xcenter - x); percent = defpercent - ((int) (Math.cos(angle) * 2 * defpercent)); if (!pressed) { percent = -percent; } if (percent == 0) { return rgb; } else if (percent < 0) { percent = -percent; brighter = false; } else { brighter = true; } break; case 4: angle = Math.atan2(Ycenter - y, x - Xcenter); percent = defpercent - ((int) (Math.cos(angle) * 2 * defpercent)); if (pressed) { percent = -percent; } if (percent == 0) { return rgb; } else if (percent < 0) { percent = -percent; brighter = false; } else { brighter = true; } break; case 3: if (!pressed) { return rgb & 0x00ffffff; } brighter = false; percent = defpercent; break; } return filterRGB(rgb, brighter, percent); } }