import java.util.*;
import java.util.zip.*;
import java.util.List;
import java.util.regex.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.table.*;
import java.io.*;
import java.net.*;
import java.lang.reflect.*;
import java.lang.ref.*;
import java.lang.management.*;
import java.security.*;
import java.security.spec.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.imageio.*;
import java.math.*;
public class main {
// see http://stackoverflow.com/questions/12552144/resize-image-in-java-without-losing-transparency
public static void main(String[] args) throws Exception {
  // is 512*512
  BufferedImage img = loadImage2("#1004221");
  
  img = resizeImage(img, 256);
  showImage(img);
  savePNG(img, prepareProgramFile(img.getWidth() + ".png"));
}
static File prepareProgramFile(String name) {
  return mkdirsForFile(getProgramFile(name));
}
static BufferedImage resizeImage(BufferedImage img, int newW, int newH) {
  Image tmp = img.getScaledInstance(newW, newH, Image.SCALE_SMOOTH);
  BufferedImage dimg = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB);
  Graphics2D g2d = dimg.createGraphics();
  g2d.drawImage(tmp, 0, 0, null);
  g2d.dispose();
  return dimg;
}
static BufferedImage resizeImage(BufferedImage img, int newW) {
  int newH = iround(img.getHeight()*(double) newW/img.getWidth());
  return resizeImage(img, newW, newH);
}
static BufferedImage loadImage2(String snippetIDOrURL) {
  return loadBufferedImage(snippetIDOrURL);
}
static BufferedImage loadImage2(File file) {
  return loadBufferedImage(file);
}
  static ImageSurface showImage(String snippetIDOrURL, String title) {
    return showImage(loadImage(snippetIDOrURL), title);
  }
  
  static ImageSurface showImage(final BufferedImage img, final String title) {
    return (ImageSurface) swing(new Object() { Object get() { 
      ImageSurface is = showImage(img);
      getFrame(is).setTitle(title);
      return is;
     }
  public String toString() { return "ImageSurface is = showImage(img);\r\n      getFrame(is).setTitle(title);\r\n      return is;"; }});
  }
  
  static ImageSurface showImage(final BufferedImage img) {
    return (ImageSurface) swing(new Object() { Object get() { 
      JFrame frame = new JFrame("JavaX");
      ImageSurface is = new ImageSurface(img);
      frame.getContentPane().add(new JScrollPane(is));
      frame.pack();
      centerFrame(frame);
      frame.setVisible(true);
      return is;
     }
  public String toString() { return "JFrame frame = new JFrame(\"JavaX\");\r\n      ImageSurface is = new ImageSurface(img);\r\n      frame.getContentPane().add(new JScrollPane(is));\r\n      frame.pack();\r\n      centerFrame(frame);\r\n      frame.setVisible(true);\r\n      return is;"; }});
  }
  
  static ImageSurface showImage(RGBImage img) {
    return showImage(img.getBufferedImage());
  }
  
  static ImageSurface showImage(RGBImage img, String title) {
    ImageSurface is = showImage(img.getBufferedImage());
    getFrame(is).setTitle(title);
    return is;
  }
  
  static ImageSurface showImage(String imageID) {
    return showImage(loadImage(imageID));
  }
  static ImageSurface showImage(RGBImage img, ImageSurface surface) {
    if (surface == null)
      return showImage(img);
    else {
      surface.setImage(img);
      return surface;
    }
  }
static void savePNG(BufferedImage img, File file) { try {
 
  ImageIO.write(img, "png", file);
} catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }}
static File getProgramFile(String progID, String fileName) {
  return new File(getProgramDir(progID), fileName);
}
static File getProgramFile(String fileName) {
  return getProgramFile(getProgramID(), fileName);
}
static int iround(double d) {
  return (int) Math.round(d);
}
public static File mkdirsForFile(File file) {
  File dir = file.getParentFile();
  if (dir != null) // is null if file is in current dir
    dir.mkdirs();
  return file;
}
static JFrame getFrame(Object o) {
  if (!(o instanceof Component)) return null;
  Component c = (Component) o;
  while (c != null) {
    if (c instanceof JFrame) return (JFrame) c;
    c = c.getParent();
  }
  return null;
}
  static RGBImage loadImage(String snippetIDOrURL) {
    return new RGBImage(loadBufferedImage(snippetIDOrURL));
  }
static void centerFrame(JFrame frame) {
  frame.setLocationRelativeTo(null); // magic trick
}
static Object swing(Object f) {
  return swingAndWait(f);
}
static boolean loadBufferedImage_useImageCache = true;
static BufferedImage loadBufferedImage(String snippetIDOrURL) { try {
 
  if (isURL(snippetIDOrURL))
    return ImageIO.read(new URL(snippetIDOrURL));
  if (!isSnippetID(snippetIDOrURL)) throw fail("Not a URL or snippet ID: " + snippetIDOrURL);
  String snippetID = "" + parseSnippetID(snippetIDOrURL);
  
try {
  // TODO: androidify
  File dir = new File(System.getProperty("user.home"), ".tinybrain/image-cache");
  if (loadBufferedImage_useImageCache) {
    dir.mkdirs();
    File file = new File(dir, snippetID + ".png");
    if (file.exists() && file.length() != 0)
      try {
        return ImageIO.read(file);
      } catch (Throwable e) {
        e.printStackTrace();
        // fall back to loading from sourceforge
      }
  }
  String imageURL = snippetImageURL(snippetID);
  System.err.println("Loading image: " + imageURL);
  BufferedImage image = ImageIO.read(new URL(imageURL));
  if (loadBufferedImage_useImageCache) {
    File tempFile = new File(dir, snippetID + ".tmp." + System.currentTimeMillis());
    ImageIO.write(image, "png", tempFile);
    tempFile.renameTo(new File(dir, snippetID + ".png"));
    //Log.info("Cached image.");
  }
  //Log.info("Loaded image.");
  return image;
 } catch (IOException e) {
  throw new RuntimeException(e);
 }
} catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }}
static BufferedImage loadBufferedImage(File file) { try {
 
  return ImageIO.read(file);
} catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }}
static String programID;
static String getProgramID() {
  return nempty(programID) ? formatSnippetID(programID) : "?";
}
// TODO: ask JavaX instead
static String getProgramID(Class c) {
  String id = (String) getOpt(c, "programID");
  if (nempty(id))
    return formatSnippetID(id);
  return "?";
}
static String getProgramID(Object o) {
  return getProgramID(getMainClass(o));
}
 static String snippetImageURL(String snippetID) {
    long id = parseSnippetID(snippetID);
    String url;
    if (id == 1000010 || id == 1000012)
      url = "http://tinybrain.de:8080/tb/show-blobimage.php?id=" + id;
    else
      url = "http://eyeocr.sourceforge.net/filestore/filestore.php?cmd=serve&file=blob_" + id
        + "&contentType=image/png";
    return url;
  }
  public static boolean isSnippetID(String s) {
    try {
      parseSnippetID(s);
      return true;
    } catch (RuntimeException e) {
      return false;
    }
  }
static File getProgramDir() {
  return programDir();
}
static File getProgramDir(String snippetID) {
  return programDir(snippetID);
}
  static RuntimeException fail() {
    throw new RuntimeException("fail");
  }
  
  static RuntimeException fail(Object msg) {
    throw new RuntimeException(String.valueOf(msg));
  }
  
  static RuntimeException fail(String msg) {
    throw new RuntimeException(unnull(msg));
  }
   
  // disabled for now to shorten some programs 
  /*static RuntimeException fail(S msg, O... args) {
    throw new RuntimeException(format(msg, args));
  }*/
public static long parseSnippetID(String snippetID) {
  long id = Long.parseLong(shortenSnippetID(snippetID));
  if (id == 0) throw fail("0 is not a snippet ID");
  return id;
}
static boolean isURL(String s) {
  return s.startsWith("http://") || s.startsWith("https://");
}
static void swingAndWait(Runnable r) { try {
 
  if (isAWTThread())
    r.run();
  else
    EventQueue.invokeAndWait(r);
} catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }}
static Object swingAndWait(final Object f) {
  if (isAWTThread())
    return callF(f);
  else {
    final Var result = new Var();
    swingAndWait(new Runnable() { public void run() { try {
      result.set(callF(f));
    } catch (Exception __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }}});
    return result.get();
  }
}
  static String unnull(String s) {
    return s == null ? "" : s;
  }
  
  static  List unnull(List l) {
    return l == null ? emptyList() : l;
  }
  
  static Object[] unnull(Object[] a) {
    return a == null ? new Object[0] : a;
  }
static Object callF(Object f, Object... args) {
  return callFunction(f, args);
}
static File programDir() {
  return programDir(getProgramID());
}
static File programDir(String snippetID) {
  return new File(javaxDataDir(), formatSnippetID(snippetID));
}
static String formatSnippetID(String id) {
  return "#" + parseSnippetID(id);
}
static String formatSnippetID(long id) {
  return "#" + id;
}
static boolean isAWTThread() {
  return SwingUtilities.isEventDispatchThread();
}
static String shortenSnippetID(String snippetID) {
  if (snippetID.startsWith("#"))
    snippetID = snippetID.substring(1);
  String httpBlaBla = "http://tinybrain.de/";
  if (snippetID.startsWith(httpBlaBla))
    snippetID = snippetID.substring(httpBlaBla.length());
  return "" + parseLong(snippetID);
}
static Class getMainClass() { try {
 
  return Class.forName("main");
} catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }}
static Class getMainClass(Object o) { try {
 
  return (o instanceof Class ? (Class) o : o.getClass()).getClassLoader().loadClass("main");
} catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }}
static boolean nempty(Collection c) {
  return !isEmpty(c);
}
static boolean nempty(CharSequence s) {
  return !isEmpty(s);
}
static boolean nempty(Object[] o) {
  return !isEmpty(o);
}
static Object getOpt(Object o, String field) {
  if (o instanceof String) o = getBot ((String) o);
  if (o == null) return null;
  if (o instanceof Class) return getOpt((Class) o, field);
  
  if (o.getClass().getName().equals("main$DynamicObject"))
    return ((Map) getOpt_raw(o, "fieldValues")).get(field);
  
  if (o instanceof Map) return ((Map) o).get(field);
  
  return getOpt_raw(o, field);
}
static Object getOpt_raw(Object o, String field) {
  try {
    Field f = getOpt_findField(o.getClass(), field);
    if (f == null) return null;
    f.setAccessible(true);
    return f.get(o);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
static Object getOpt(Class c, String field) {
  try {
    if (c == null) return null;
    Field f = getOpt_findStaticField(c, field);
    if (f == null) return null;
    f.setAccessible(true);
    return f.get(null);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
static Field getOpt_findStaticField(Class> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field) && (f.getModifiers() & Modifier.STATIC) != 0)
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}
static Field getOpt_findField(Class> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field))
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  return null;
}
static File javaxDataDir_dir; // can be set to work on different base dir
static File javaxDataDir() {
  return javaxDataDir_dir != null ? javaxDataDir_dir : new File(userHome(), "JavaX-Data");
}
static Object callFunction(Object f, Object... args) {
  if (f == null) return null;
  if (f instanceof Runnable) {
    ((Runnable) f).run();
    return null;
  } else if (f instanceof String)
    return call(mc(), (String) f, args);
  else
    return call(f, "get", args);
  //else throw fail("Can't call a " + getClassName(f));
}
static List emptyList() {
  return new ArrayList();
  //ret Collections.emptyList();
}
static Object getBot(String botID) {
  return callOpt(getMainBot(), "getBot", botID);
}
static boolean isEmpty(Collection c) {
  return c == null || c.isEmpty();
}
static boolean isEmpty(CharSequence s) {
  return s == null || s.length() == 0;
}
static boolean isEmpty(Object[] a) {
  return a == null || a.length == 0;
}
static boolean isEmpty(Map map) {
  return map == null || map.isEmpty();
}
static long parseLong(String s) {
  if (s == null) return 0;
  return Long.parseLong(dropSuffix("L", s));
}
static long parseLong(Object s) {
  return Long.parseLong((String) s);
}
  static Object callOpt(Object o, String method, Object... args) {
    try {
      if (o == null) return null;
      if (o instanceof Class) {
        Method m = callOpt_findStaticMethod((Class) o, method, args, false);
        if (m == null) return null;
        m.setAccessible(true);
        return m.invoke(null, args);
      } else {
        Method m = callOpt_findMethod(o, method, args, false);
        if (m == null) return null;
        m.setAccessible(true);
        return m.invoke(o, args);
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
  static Method callOpt_findStaticMethod(Class c, String method, Object[] args, boolean debug) {
    Class _c = c;
    while (c != null) {
      for (Method m : c.getDeclaredMethods()) {
        if (debug)
          System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");;
        if (!m.getName().equals(method)) {
          if (debug) System.out.println("Method name mismatch: " + method);
          continue;
        }
        if ((m.getModifiers() & Modifier.STATIC) == 0 || !callOpt_checkArgs(m, args, debug))
          continue;
        return m;
      }
      c = c.getSuperclass();
    }
    return null;
  }
  static Method callOpt_findMethod(Object o, String method, Object[] args, boolean debug) {
    Class c = o.getClass();
    while (c != null) {
      for (Method m : c.getDeclaredMethods()) {
        if (debug)
          System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");;
        if (m.getName().equals(method) && callOpt_checkArgs(m, args, debug))
          return m;
      }
      c = c.getSuperclass();
    }
    return null;
  }
  private static boolean callOpt_checkArgs(Method m, Object[] args, boolean debug) {
    Class>[] types = m.getParameterTypes();
    if (types.length != args.length) {
      if (debug)
        System.out.println("Bad parameter length: " + args.length + " vs " + types.length);
      return false;
    }
    for (int i = 0; i < types.length; i++)
      if (!(args[i] == null || isInstanceX(types[i], args[i]))) {
        if (debug)
          System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]);
        return false;
      }
    return true;
  }
static Object mainBot;
static Object getMainBot() {
  return mainBot;
}
static String _userHome;
static String userHome() {
  if (_userHome == null) {
    if (isAndroid())
      _userHome = "/storage/sdcard0/";
    else
      _userHome = System.getProperty("user.home");
    //System.out.println("userHome: " + _userHome);
  }
  return _userHome;
}
static File userHome(String path) {
  return new File(userDir(), path);
}
static Class mc() {
  return getMainClass();
}
static String dropSuffix(String suffix, String s) {
  return s.endsWith(suffix) ? s.substring(0, l(s)-l(suffix)) : s;
}
  static Object call(Object o) {
    return callFunction(o);
  }
  
  // varargs assignment fixer for a single string array argument
  static Object call(Object o, String method, String[] arg) {
    return call(o, method, new Object[] {arg});
  }
  
  static Object call(Object o, String method, Object... args) {
    try {
      if (o instanceof Class) {
        Method m = call_findStaticMethod((Class) o, method, args, false);
        m.setAccessible(true);
        return m.invoke(null, args);
      } else {
        Method m = call_findMethod(o, method, args, false);
        m.setAccessible(true);
        return m.invoke(o, args);
      }
    } catch (Exception e) {
      throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
    }
  }
  static Method call_findStaticMethod(Class c, String method, Object[] args, boolean debug) {
    Class _c = c;
    while (c != null) {
      for (Method m : c.getDeclaredMethods()) {
        if (debug)
          System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");;
        if (!m.getName().equals(method)) {
          if (debug) System.out.println("Method name mismatch: " + method);
          continue;
        }
        if ((m.getModifiers() & Modifier.STATIC) == 0 || !call_checkArgs(m, args, debug))
          continue;
        return m;
      }
      c = c.getSuperclass();
    }
    throw new RuntimeException("Method '" + method + "' (static) with " + args.length + " parameter(s) not found in " + _c.getName());
  }
  static Method call_findMethod(Object o, String method, Object[] args, boolean debug) {
    Class c = o.getClass();
    while (c != null) {
      for (Method m : c.getDeclaredMethods()) {
        if (debug)
          System.out.println("Checking method " + m.getName() + " with " + m.getParameterTypes().length + " parameters");;
        if (m.getName().equals(method) && call_checkArgs(m, args, debug))
          return m;
      }
      c = c.getSuperclass();
    }
    throw new RuntimeException("Method '" + method + "' (non-static) with " + args.length + " parameter(s) not found in " + o.getClass().getName());
  }
  private static boolean call_checkArgs(Method m, Object[] args, boolean debug) {
    Class>[] types = m.getParameterTypes();
    if (types.length != args.length) {
      if (debug)
        System.out.println("Bad parameter length: " + args.length + " vs " + types.length);
      return false;
    }
    for (int i = 0; i < types.length; i++)
      if (!(args[i] == null || isInstanceX(types[i], args[i]))) {
        if (debug)
          System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]);
        return false;
      }
    return true;
  }
// extended over Class.isInstance() to handle primitive types
static boolean isInstanceX(Class type, Object arg) {
  if (type == boolean.class) return arg instanceof Boolean;
  if (type == int.class) return arg instanceof Integer;
  if (type == long.class) return arg instanceof Long;
  if (type == float.class) return arg instanceof Float;
  if (type == short.class) return arg instanceof Short;
  if (type == char.class) return arg instanceof Character;
  if (type == byte.class) return arg instanceof Byte;
  if (type == double.class) return arg instanceof Double;
  return type.isInstance(arg);
}
static int l(Object[] array) {
  return array == null ? 0 : array.length;
}
static int l(byte[] array) {
  return array == null ? 0 : array.length;
}
static int l(int[] array) {
  return array == null ? 0 : array.length;
}
static int l(char[] array) {
  return array == null ? 0 : array.length;
}
static int l(Collection c) {
  return c == null ? 0 : c.size();
}
static int l(Map m) {
  return m == null ? 0 : m.size();
}
static int l(String s) {
  return s == null ? 0 : s.length();
} 
static int l(Object o) {
  return l((List) o); // incomplete
}
static File userDir() {
  return new File(userHome());
}
static File userDir(String path) {
  return new File(userHome(), path);
}
static boolean isAndroid() { return System.getProperty("java.vendor").toLowerCase().indexOf("android") >= 0; }
static class Var {
  A v;
  
  Var() {}
  Var(A v) {
  this.v = v;}
  
  synchronized void set(A a) { v = a; }
  synchronized A get() { return v; }
}
static class RGB {
  public final float r, g, b;
  public RGB(float r, float g, float b) {
    this.r = r;
    this.g = g;
    this.b = b;
  }
  public RGB(double r, double g, double b) {
    this.r = (float) r;
    this.g = (float) g;
    this.b = (float) b;
  }
  public RGB(double brightness) {
    this.r = this.g = this.b = (float) brightness;
  }
  public RGB(Color color) {
    this.r = color.getRed()/255f;
    this.g = color.getGreen()/255f;
    this.b = color.getBlue()/255f;
  }
  public RGB(String hex) {
    r = Integer.parseInt(hex.substring(0, 2), 16)/255f;
    g = Integer.parseInt(hex.substring(2, 4), 16)/255f;
    b = Integer.parseInt(hex.substring(4, 6), 16)/255f;
  }
  public float getComponent(int i) {
    return i == 0 ? r : i == 1 ? g : b;
  }
  public Color getColor() {
    return new Color(r, g, b);
  }
  public static RGB newSafe(float r, float g, float b) {
    return new RGB(Math.max(0, Math.min(1, r)), Math.max(0, Math.min(1, g)), Math.max(0, Math.min(1, b)));
  }
  public int asInt() {
    return getColor().getRGB() & 0xFFFFFF;
  }
  public float getBrightness() {
    return (r+g+b)/3.0f;
  }
  public String getHexString() {
    return Integer.toHexString(asInt() | 0xFF000000).substring(2).toUpperCase();
  }
  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof RGB)) return false;
    RGB rgb = (RGB) o;
    if (Float.compare(rgb.b, b) != 0) return false;
    if (Float.compare(rgb.g, g) != 0) return false;
    if (Float.compare(rgb.r, r) != 0) return false;
    return true;
  }
  @Override
  public int hashCode() {
    int result = (r != +0.0f ? Float.floatToIntBits(r) : 0);
    result = 31 * result + (g != +0.0f ? Float.floatToIntBits(g) : 0);
    result = 31 * result + (b != +0.0f ? Float.floatToIntBits(b) : 0);
    return result;
  }
  public boolean isBlack() {
    return r == 0f && g == 0f && b == 0f;
  }
  public boolean isWhite() {
    return r == 1f && g == 1f && b == 1f;
  }
  public String toString() {
    return getHexString();
  }
}
static class RGBImage {
  private BufferedImage bufferedImage;
  private File file;
  private int width, height;
  private int[] pixels;
  // color returned when getPixel is called with out-of-bounds position
  private int background = 0xFFFFFF;
  public RGBImage(BufferedImage image) {
    this(image, null);
  }
  public RGBImage(BufferedImage image, File file) {
    this.file = file;
    bufferedImage = image;
    width = image.getWidth();
    height = image.getHeight();
    pixels = new int[width*height];
    PixelGrabber pixelGrabber = new PixelGrabber(image, 0, 0, width, height, pixels, 0, width);
    try {
      if (!pixelGrabber.grabPixels())
        throw new RuntimeException("Could not grab pixels");
      cleanPixels(); // set upper byte to 0
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }
  /** We assume it's a file name to load from */
  public RGBImage(String file) throws IOException {
    this(new File(file));
  }
  public RGBImage(Dimension size, Color color) {
    this(size.width, size.height, color);
  }
  public RGBImage(Dimension size, RGB color) {
    this(size.width, size.height, color);
  }
  private void cleanPixels() {
    for (int i = 0; i < pixels.length; i++)
      pixels[i] &= 0xFFFFFF;
  }
  public RGBImage(int width, int height, int[] pixels) {
    this.width = width;
    this.height = height;
    this.pixels = pixels;
  }
  public RGBImage(int w, int h, RGB[] pixels) {
    this.width = w;
    this.height = h;
    this.pixels = asInts(pixels);
  }
  public static int[] asInts(RGB[] pixels) {
    int[] ints = new int[pixels.length];
    for (int i = 0; i < pixels.length; i++)
      ints[i] = pixels[i] == null ? 0 : pixels[i].getColor().getRGB();
    return ints;
  }
  public RGBImage(int w, int h) {
    this(w, h, Color.black);
  }
  
  public RGBImage(int w, int h, RGB rgb) {
    this.width = w;
    this.height = h;
    this.pixels = new int[w*h];
    int col = rgb.asInt();
    if (col != 0)
      for (int i = 0; i < pixels.length; i++)
        pixels[i] = col;
  }
  public RGBImage(RGBImage image) {
    this(image.width, image.height, copyPixels(image.pixels));
  }
  public RGBImage(int width, int height, Color color) {
    this(width, height, new RGB(color));
  }
  public RGBImage(File file) throws IOException {
    this(javax.imageio.ImageIO.read(file));
  }
  private static int[] copyPixels(int[] pixels) {
    int[] copy = new int[pixels.length];
    System.arraycopy(pixels, 0, copy, 0, pixels.length);
    return copy;
  }
  public int getIntPixel(int x, int y) {
    if (inRange(x, y))
      return pixels[y * width + x];
    else
      return background;
  }
  public static RGB asRGB(int packed) {
    int r = (packed >> 16) & 0xFF;
    int g = (packed >> 8) & 0xFF;
    int b = packed & 0xFF;
    return new RGB(r / 255f, g / 255f, b / 255f);
  }
  public RGB getRGB(int x, int y) {
    if (inRange(x, y))
      return asRGB(pixels[y * width + x]);
    else
      return new RGB(background);
  }
  /** alias of getRGB - I kept typing getPixel instead of getRGB all the time, so I finally created it */
  public RGB getPixel(int x, int y) {
    return getRGB(x, y);
  }
  public int getWidth() {
    return width;
  }
  public int getHeight() {
    return height;
  }
  /** Attention: cached, i.e. does not change when image itself changes */
  /** @NotNull */
  public BufferedImage getBufferedImage() {
    if (bufferedImage == null) {
      bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
      //bufferedImage.setData(Raster.createRaster(new SampleModel()));
      for (int y = 0; y < height; y++)
        for (int x = 0; x < width; x++)
          bufferedImage.setRGB(x, y, pixels[y*width+x]);
    }
    return bufferedImage;
  }
  public RGBImage clip(Rectangle r) {
    r = fixClipRect(r);
    int[] newPixels;
    try {
      newPixels = new int[r.width*r.height];
    } catch (RuntimeException e) {
      System.out.println(r);
      throw e;
    }
    for (int y = 0; y < r.height; y++) {
      System.arraycopy(pixels, (y+r.y)*width+r.x, newPixels, y*r.width, r.width);
    }
    return new RGBImage(r.width, r.height, newPixels);
  }
  private Rectangle fixClipRect(Rectangle r) {
    r = r.intersection(new Rectangle(0, 0, width, height));
    if (r.isEmpty())
      r = new Rectangle(r.x, r.y, 0, 0);
    return r;
  }
  public File getFile() {
    return file;
  }
  /** can now also do GIF (not just JPEG) */
  public static RGBImage load(String fileName) {
    return load(new File(fileName));
  }
  /** can now also do GIF (not just JPEG) */
  public static RGBImage load(File file) {
    try {
      BufferedImage bufferedImage = javax.imageio.ImageIO.read(file);
      return new RGBImage(bufferedImage);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
  public int getInt(int x, int y) {
    return pixels[y * width + x];
  }
  public void save(File file) throws IOException {
    String name = file.getName().toLowerCase();
    String type;
    if (name.endsWith(".png")) type = "png";
    else if (name.endsWith(".jpg") || name.endsWith(".jpeg")) type = "jpeg";
    else throw new IOException("Unknown image extension: " + name);
    javax.imageio.ImageIO.write(getBufferedImage(), type, file);
  }
  public static RGBImage dummyImage() {
    return new RGBImage(1, 1, new int[] {0xFFFFFF});
  }
  public int[] getPixels() {
    return pixels;
  }
  public void setPixel(int x, int y, RGB rgb) {
    if (x >= 0 && y >= 0 && x < width && y < height)
      pixels[y*width+x] = rgb.asInt();
  }
  public void setPixel(int x, int y, Color color) {
    setPixel(x, y, new RGB(color));
  }
  public void setPixel(int x, int y, int rgb) {
    if (x >= 0 && y >= 0 && x < width && y < height)
      pixels[y*width+x] = rgb;
  }
  public RGBImage copy() {
    return new RGBImage(this);
  }
  public boolean inRange(int x, int y) {
    return x >= 0 && y >= 0 && x < width && y < height;
  }
  public int getBackground() {
    return background;
  }
  public void setBackground(int background) {
    this.background = background;
  }
  public Dimension getSize() {
    return new Dimension(width, height);
  }
  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    RGBImage rgbImage = (RGBImage) o;
    if (height != rgbImage.height) return false;
    if (width != rgbImage.width) return false;
    if (!Arrays.equals(pixels, rgbImage.pixels)) return false;
    return true;
  }
  @Override
  public int hashCode() {
    int result = width;
    result = 31 * result + height;
    result = 31 * result + Arrays.hashCode(pixels);
    return result;
  }
  public String getHex(int x, int y) {
    return getPixel(x, y).getHexString();
  }
  public RGBImage clip(int x, int y, int width, int height) {
    return clip(new Rectangle(x, y, width, height));
  }
  public RGBImage clipLine(int y) {
    return clip(0, y, width, 1);
  }
  public int numPixels() {
    return width*height;
  }
}
abstract static class Surface extends JPanel {
  public Object AntiAlias = RenderingHints.VALUE_ANTIALIAS_ON;
  public Object Rendering = RenderingHints.VALUE_RENDER_SPEED;
  public AlphaComposite composite;
  public Paint texture;
  public BufferedImage bimg;
  public int imageType;
  public String name;
  public boolean clearSurface = true;
  // Demos using animated gif's that implement ImageObserver set dontThread.
  public boolean dontThread;
  protected long sleepAmount = 50; // max20 fps
  private long orig, start, frame;
  private Toolkit toolkit;
  private boolean perfMonitor, outputPerf;
  private int biw, bih;
  private boolean clearOnce;
  private boolean toBeInitialized = true;
  public Surface() {
    setDoubleBuffered(false);
    toolkit = getToolkit();
    name = this.getClass().getName();
    name = name.substring(name.indexOf(".", 7)+1);
    setImageType(0);
    // To launch an individual demo with the performance str output  :
    //    java -Djava2demo.perf= -cp Java2Demo.jar demos.Clipping.ClipAnim
    try {
      if (System.getProperty("java2demo.perf") != null) {
        perfMonitor = outputPerf = true;
      }
    } catch (Exception ex) { }
  }
    /*protected Image getImage(String name) {
        return DemoImages.getImage(name, this);
    }
    protected Font getFont(String name) {
        return DemoFonts.getFont(name);
    }*/
  public int getImageType() {
    return imageType;
  }
  public void setImageType(int imgType) {
    if (imgType == 0) {
      imageType = 1;
    } else {
      imageType = imgType;
    }
    bimg = null;
  }
  public void setAntiAlias(boolean aa) {
    AntiAlias = aa
      ? RenderingHints.VALUE_ANTIALIAS_ON
      : RenderingHints.VALUE_ANTIALIAS_OFF;
  }
  public void setRendering(boolean rd) {
    Rendering = rd
      ? RenderingHints.VALUE_RENDER_QUALITY
      : RenderingHints.VALUE_RENDER_SPEED;
  }
  public void setTexture(Object obj) {
    if (obj instanceof GradientPaint) {
      texture = new GradientPaint(0, 0, Color.white,
        getSize().width*2, 0, Color.green);
    } else {
      texture = (Paint) obj;
    }
  }
  public void setComposite(boolean cp) {
    composite = cp
      ? AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)
      : null;
  }
  public void setMonitor(boolean pm) {
    perfMonitor = pm;
  }
  public void setSleepAmount(long amount) {
    sleepAmount = amount;
  }
  public long getSleepAmount() {
    return sleepAmount;
  }
  public BufferedImage createBufferedImage(Graphics2D g2,
                                           int w,
                                           int h,
                                           int imgType) {
    BufferedImage bi = null;
    if (imgType == 0) {
      bi = (BufferedImage) g2.getDeviceConfiguration().
        createCompatibleImage(w, h);
    } else if (imgType > 0 && imgType < 14) {
      bi = new BufferedImage(w, h, imgType);
    } else if (imgType == 14) {
      bi = createBinaryImage(w, h, 2);
    } else if (imgType == 15) {
      bi = createBinaryImage(w, h, 4);
    } else if (imgType == 16) {
      bi = createSGISurface(w, h, 32);
    } else if (imgType == 17) {
      bi = createSGISurface(w, h, 16);
    }
    biw = w;
    bih = h;
    return bi;
  }
  // Lookup tables for BYTE_BINARY 1, 2 and 4 bits.
  static byte[] lut1Arr = new byte[] {0, (byte)255 };
  static byte[] lut2Arr = new byte[] {0, (byte)85, (byte)170, (byte)255};
  static byte[] lut4Arr = new byte[] {0, (byte)17, (byte)34, (byte)51,
    (byte)68, (byte)85,(byte) 102, (byte)119,
    (byte)136, (byte)153, (byte)170, (byte)187,
    (byte)204, (byte)221, (byte)238, (byte)255};
  private BufferedImage createBinaryImage(int w, int h, int pixelBits) {
    int bytesPerRow = w * pixelBits / 8;
    if (w * pixelBits % 8 != 0) {
      bytesPerRow++;
    }
    byte[] imageData = new byte[h * bytesPerRow];
    IndexColorModel cm = null;
    switch (pixelBits) {
      case 1:
        cm = new IndexColorModel(pixelBits, lut1Arr.length,
          lut1Arr, lut1Arr, lut1Arr);
        break;
      case 2:
        cm = new IndexColorModel(pixelBits, lut2Arr.length,
          lut2Arr, lut2Arr, lut2Arr);
        break;
      case 4:
        cm = new IndexColorModel(pixelBits, lut4Arr.length,
          lut4Arr, lut4Arr, lut4Arr);
        break;
      default:
      {new Exception("Invalid # of bit per pixel").printStackTrace();}
    }
    DataBuffer db = new DataBufferByte(imageData, imageData.length);
    WritableRaster r = Raster.createPackedRaster(db, w, h, pixelBits, null);
    return new BufferedImage(cm, r, false, null);
  }
  private BufferedImage createSGISurface(int w, int h, int pixelBits) {
    int rMask32 = 0xFF000000;
    int rMask16 = 0xF800;
    int gMask32 = 0x00FF0000;
    int gMask16 = 0x07C0;
    int bMask32 = 0x0000FF00;
    int bMask16 = 0x003E;
    DirectColorModel dcm = null;
    DataBuffer db = null;
    WritableRaster wr = null;
    switch (pixelBits) {
      case 16:
        short[] imageDataUShort = new short[w * h];
        dcm = new DirectColorModel(16, rMask16, gMask16, bMask16);
        db = new DataBufferUShort(imageDataUShort, imageDataUShort.length);
        wr = Raster.createPackedRaster(db, w, h, w,
          new int[] {rMask16, gMask16, bMask16},
          null);
        break;
      case 32:
        int[] imageDataInt = new int[w * h];
        dcm = new DirectColorModel(32, rMask32, gMask32, bMask32);
        db = new DataBufferInt(imageDataInt, imageDataInt.length);
        wr = Raster.createPackedRaster(db, w, h, w,
          new int[] {rMask32, gMask32, bMask32},
          null);
        break;
      default:
      {new Exception("Invalid # of bit per pixel").printStackTrace();}
    }
    return new BufferedImage(dcm, wr, false, null);
  }
  public Graphics2D createGraphics2D(int width,
                                     int height,
                                     BufferedImage bi,
                                     Graphics g) {
    Graphics2D g2 = null;
    if (bi != null) {
      g2 = bi.createGraphics();
    } else {
      g2 = (Graphics2D) g;
    }
    g2.setBackground(getBackground());
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, AntiAlias);
    g2.setRenderingHint(RenderingHints.KEY_RENDERING, Rendering);
    if (clearSurface || clearOnce) {
      g2.clearRect(0, 0, width, height);
      clearOnce = false;
    }
    if (texture != null) {
      // set composite to opaque for texture fills
      g2.setComposite(AlphaComposite.SrcOver);
      g2.setPaint(texture);
      g2.fillRect(0, 0, width, height);
    }
    if (composite != null) {
      g2.setComposite(composite);
    }
    return g2;
  }
  public abstract void render(int w, int h, Graphics2D g);
  /**
   * It's possible to turn off double-buffering for just the repaint
   * calls invoked directly on the non double buffered component.
   * This can be done by overriding paintImmediately() (which is called
   * as a result of repaint) and getting the current RepaintManager and
   * turning off double buffering in the RepaintManager before calling
   * super.paintImmediately(g).
   */
  public void paintImmediately(int x,int y,int w, int h) {
    RepaintManager repaintManager = null;
    boolean save = true;
    if (!isDoubleBuffered()) {
      repaintManager = RepaintManager.currentManager(this);
      save = repaintManager.isDoubleBufferingEnabled();
      repaintManager.setDoubleBufferingEnabled(false);
    }
    super.paintImmediately(x, y, w, h);
    if (repaintManager != null) {
      repaintManager.setDoubleBufferingEnabled(save);
    }
  }
  public void paint(Graphics g) {
    Dimension d = getSize();
    if (imageType == 1)
      bimg = null;
    else if (bimg == null || biw != d.width || bih != d.height) {
      bimg = createBufferedImage((Graphics2D)g,
        d.width, d.height, imageType-2);
      clearOnce = true;
      toBeInitialized = true;
    }
    if (toBeInitialized) {
      toBeInitialized = false;
      startClock();
    }
    Graphics2D g2 = createGraphics2D(d.width, d.height, bimg, g);
    render(d.width, d.height, g2);
    g2.dispose();
    if (bimg != null)  {
      g.drawImage(bimg, 0, 0, null);
      toolkit.sync();
    }
  }
  public void startClock() {
    orig = System.currentTimeMillis();
    start = orig;
    frame = 0;
  }
  private static final int REPORTFRAMES = 30;
  public static void setAlpha(Graphics2D g, float alpha) {
    g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
  }
}
static class ImageSurface extends Surface {
  private BufferedImage image;
  private double zoomX = 1, zoomY = 1;
  private Rectangle selection;
  public ImageSurface() {
    this(new RGBImage(1, 1, new int[] { 0xFFFFFF }));
  }
  public ImageSurface(RGBImage image) {
    this(image.getBufferedImage());
  }
  public ImageSurface(BufferedImage image) {
    clearSurface = false;
    this.image = image;
    /*addMouseMotionListener(new MouseAdapter() {
      public void mouseMoved(MouseEvent e) {
        getMousePosition()
      }
    });*/
  }
  public ImageSurface(RGBImage image, double zoom) {
    this(image);
    setZoom(zoom);
  }
  public void render(int w, int h, Graphics2D g) {
    g.setColor(Color.white);
    g.fillRect(0, 0, w, h);
    if (image != null)
      g.drawImage(image, 0, 0, getZoomedWidth(), getZoomedHeight(), null);
    if (selection != null) {
      // drawRect is inclusive, selection is exclusive, so... whatever, tests show it's cool.
      drawSelectionRect(g, selection, Color.green, Color.white);
    }
  }
  public void drawSelectionRect(Graphics2D g, Rectangle selection, Color green, Color white) {
    g.setColor(green);
    int top = (int) (selection.y * zoomY);
    int bottom = (int) ((selection.y+selection.height) * zoomY);
    int left = (int) (selection.x * zoomX);
    int right = (int) ((selection.x+selection.width) * zoomX);
    g.drawRect(left-1, top-1, right-left+1, bottom-top+1);
    g.setColor(white);
    g.drawRect(left - 2, top - 2, right - left + 3, bottom - top + 3);
  }
  public void setZoom(double zoom) {
    setZoom(zoom, zoom);
  }
  public void setZoom(double zoomX, double zoomY) {
    this.zoomX = zoomX;
    this.zoomY = zoomY;
    revalidate();
    repaint();
  }
  public Dimension getMinimumSize() {
    int w = getZoomedWidth();
    int h = getZoomedHeight();
    Dimension min = super.getMinimumSize();
    return new Dimension(Math.max(w, min.width), Math.max(h, min.height));
  }
  private int getZoomedHeight() {
    return (int) (image.getHeight() * zoomY);
  }
  private int getZoomedWidth() {
    return (int) (image.getWidth() * zoomX);
  }
  public void setImage(RGBImage image) {
    setImage(image.getBufferedImage());
  }
  public void setImage(BufferedImage image) {
    this.image = image;
    revalidate();
    repaint();
  }
  public BufferedImage getImage() {
    return image;
  }
  public double getZoomX() {
    return zoomX;
  }
  public double getZoomY() {
    return zoomY;
  }
  public Dimension getPreferredSize() {
    return new Dimension(getZoomedWidth(), getZoomedHeight());
  }
  /** returns a scrollpane with the scroll-mode prevent-garbage-drawing fix applied */
  public JScrollPane makeScrollPane() {
    JScrollPane scrollPane = new JScrollPane(this);
    scrollPane.getViewport().setScrollMode(JViewport.BACKINGSTORE_SCROLL_MODE);
    return scrollPane;
  }
  public void zoomToDisplaySize() {
    if (image == null) return;
    Dimension display = getDisplaySize();
    double xRatio = display.width/(double) image.getWidth();
    double yRatio = display.height/(double) image.getHeight();
    setZoom(Math.min(xRatio, yRatio));
    revalidate();
  }
  /** tricky magic to get parent scroll pane */
  private Dimension getDisplaySize() {
    Container c = getParent();
    while (c != null) {
      if (c instanceof JScrollPane)
        return c.getSize();
      c = c.getParent();
    }
    return getSize();
  }
  public void setSelection(Rectangle r) {
    selection = r;
    repaint();
  }
  public Rectangle getSelection() {
    return selection;
  }
  public RGBImage getRGBImage() {
    return new RGBImage(getImage());
  }
}
static Class> getClass(String name) {
  try {
    return Class.forName(name);
  } catch (ClassNotFoundException e) {
    return null;
  }
}
static Class getClass(Object o) {
  return o instanceof Class ? (Class) o : o.getClass();
}
static Class getClass(Object realm, String name) { try {
 
  return getClass(realm).getClassLoader().loadClass(classNameToVM(name));
} catch (Throwable __e) { throw __e instanceof RuntimeException ? (RuntimeException) __e : new RuntimeException(__e); }}
static void revalidate(Component c) {
  if (c == null) return;
  // magic combo to actually relayout and repaint
  c.revalidate();
  c.repaint();
}
static boolean inRange(int x, int n) {
  return x >= 0 && x < n;
}
static int asInt(Object o) {
  return toInt(o);
}
// get purpose 1: access a list/array (safer version of x.get(y))
static  A get(List l, int idx) {
  return idx >= 0 && idx < l(l) ? l.get(idx) : null;
}
static  A get(A[] l, int idx) {
  return idx >= 0 && idx < l(l) ? l[idx] : null;
}
// get purpose 2: access a field by reflection or a map
static Object get(Object o, String field) {
  if (o instanceof Class) return get((Class) o, field);
  
  if (o instanceof Map)
    return ((Map) o).get(field);
    
  if (o.getClass().getName().equals("main$DynamicObject"))
    return call(get_raw(o, "fieldValues"), "get", field);
    
  return get_raw(o, field);
}
static Object get_raw(Object o, String field) {
  try {
    Field f = get_findField(o.getClass(), field);
    f.setAccessible(true);
    return f.get(o);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
static Object get(Class c, String field) {
  try {
    Field f = get_findStaticField(c, field);
    f.setAccessible(true);
    return f.get(null);
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
static Field get_findStaticField(Class> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field) && (f.getModifiers() & Modifier.STATIC) != 0)
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  throw new RuntimeException("Static field '" + field + "' not found in " + c.getName());
}
static Field get_findField(Class> c, String field) {
  Class _c = c;
  do {
    for (Field f : _c.getDeclaredFields())
      if (f.getName().equals(field))
        return f;
    _c = _c.getSuperclass();
  } while (_c != null);
  throw new RuntimeException("Field '" + field + "' not found in " + c.getName());
}
static boolean equals(Object a, Object b) {
  return a == null ? b == null : a.equals(b);
}
static String classNameToVM(String name) {
  return name.replace(".", "$");
}
static int toInt(Object o) {
  if (o == null) return 0;
  if (o instanceof Number)
    return ((Number) o).intValue();
  if (o instanceof String)
    return parseInt((String) o);
  throw fail("woot not int: " + getClassName(o));
}
static String getClassName(Object o) {
  return o == null ? "null" : o.getClass().getName();
}
static int parseInt(String s) {
  return empty(s) ? 0 : Integer.parseInt(s);
}
static boolean empty(Collection c) {
  return isEmpty(c);
}
static boolean empty(String s) {
  return isEmpty(s);
}
static boolean empty(Map map) {
  return map == null || map.isEmpty();
}
static boolean empty(Object[] o) {
  return o == null || o.length == 0;
}
static boolean empty(Object o) {
  if (o instanceof Collection) return empty((Collection) o);
  if (o instanceof String) return empty((String) o);
  if (o instanceof Map) return empty((Map) o);
  if (o instanceof Object[]) return empty((Object[]) o);
  throw fail("unknown type for 'empty': " + getType(o));
}
static String getType(Object o) {
  return getClassName(o);
}
}