// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/omGraphics/OMRasterObject.java,v $ // $RCSfile: OMRasterObject.java,v $ // $Revision: 1.16 $ // $Date: 2009/01/21 01:24:41 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.omGraphics; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Toolkit; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.image.AreaAveragingScaleFilter; import java.awt.image.BufferedImage; import java.awt.image.FilteredImageSource; import java.awt.image.ImageConsumer; import java.awt.image.ImageFilter; import java.awt.image.ImageObserver; import java.awt.image.ImageProducer; import java.awt.image.MemoryImageSource; import java.awt.image.PixelGrabber; import java.awt.image.RenderedImage; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.logging.Level; import java.util.logging.Logger; import com.bbn.openmap.image.ImageHelper; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.util.DeepCopyUtil; /** * The OMRasterObject is the parent class for OMRaster and OMBitmap objects. It * manages some of the same functions that both classes require in order to * create image pixel data from bytes or integers. * * <P> * An ImageFilter may be applied to OMRasterObjects. These can be scale filters, * color filters, or maybe (?hopefully?) projection filters. These filters won't * change the original image data, and the original can be reconstructed by * resetting the filter to null, and generating the object. * <P> * * For all classes in the OMRasterObject family, a java.awt.Shape object is * created for the border of the image. This Shape object is used for distance * calculations. If the OMRasterObject is selected(), however, this Shape will * be rendered with the OMGraphic parameters that are set in the OMGraphic. */ public abstract class OMRasterObject extends OMGraphicAdapter implements OMGraphic, ImageObserver { private static final long serialVersionUID = 1L; /** * The direct colormodel, for OMRasters, means the integer values passed in * as pixels, already reflect the RGB color values each pixel should * display. */ public final static int COLORMODEL_DIRECT = 0; /** * The indexed colormodel, for OMRasters, means that the byte array passed * in for the pixels has to be resolved with a colortable in order to create * a integer array of RGB pixels. */ public final static int COLORMODEL_INDEXED = 1; /** * The ImageIcon colormodel means that the image is externally set, and we * just want to to display the image at the given location. */ public final static int COLORMODEL_IMAGEICON = 2; /** If scaling the image, use the slower, smoothing algorithm. */ public final static int SMOOTH_SCALING = 0; /** * If scaling the image, use the faster, replicating/clipping algorithm. */ public final static int FAST_SCALING = 1; /** * colorModel helps figure out what kind of updates are necessary, by * knowing what kind of image we're dealing with. For the images created * with a ImageIcon, the attribute updates that don't relate to position * will not take affect. */ protected int colorModel = COLORMODEL_DIRECT; /** * The pixels are used for the image that is drawn on the window. The pixels * are either passed in as an int[] in some constructors of the OMRaster, or * it is constructed in the OMBitmap and in OMRasters that have a * colortable. */ protected int[] pixels = null; /** * Horizontal location of the upper left corner of the image, or the x * offset from the lon for that corner, in pixels. */ protected int x = 0; /** * Vertical location of the upper left corner of the image, or the y offset * from the lat for that corner, in pixels. */ protected int y = 0; /** * The latitude of the upper left corner for the image, in decimal degrees. */ protected double lat = 0.0f; /** * The longitude of the upper left corner for the image, in decimal degrees. */ protected double lon = 0.0f; /** * The width of the image, in pixels. This always reflects the width of the * original image, even if a filter is applied to the image. */ protected int width = 0; /** * The height of the image, in pixels. This always reflects the height of * the original image, even if a filter is applied to the image. */ protected int height = 0; /** * The byte info for the image. OMBitmaps use each bit as an indication to * use the lineColor or the fillColor for each pixel (like a XBitmap). * OMRasters only use the bits when the image being created follows the * indexed colormodel. Then, the bits hold the colortable indexes that each * pixel needs to have a color substituted in later. */ protected byte[] bits = null; /** The bitmap is drawn to the graphics. */ protected transient Image bitmap = null; /** * Projected window pixel location of the upper left corner of the image. */ protected transient Point point1 = null; /** * Projected window pixel location of the lower right corner of the image. */ protected transient Point point2 = null; /** * The width of the image after scaling, if you want the image to be a * different size than the source. */ protected int filteredWidth = 0; /** * The height of the image after scaling, if you want the image to be a * different size than the source. */ protected int filteredHeight = 0; /** The image filter to use on the constructed image. */ protected ImageFilter imageFilter = null; /** * Set if the projection has had attributes change that require a * repositioning of the image, not a regeneration. */ protected boolean needToReposition = true; /** * Pixel height of the current projection. Used for efficient zoom-in * scaling. */ transient int projHeight; /** * Pixel width of the current projection. Used for efficient zoom-in * scaling. */ transient int projWidth; /** the angle by which the image is to be rotated, in radians */ protected double rotationAngle; /** * The angle, perhaps taking into account NO_ROTATE, that the image is * rotated at render-time. */ protected Double renderRotationAngle = null; public static Logger logger = Logger.getLogger("com.bbn.openmap.omGraphics.OMRasterObject"); protected transient boolean DEBUG = logger.isLoggable(Level.FINE); /** * A Constructor that sets the graphic type to raster, render type to * unknown, line type to unknown, and the declutter type to none. */ public OMRasterObject() { super(RENDERTYPE_UNKNOWN, LINETYPE_UNKNOWN, DECLUTTERTYPE_NONE); } /** * A Constructor that sets the graphic type, render type, line type and the * declutter type to the values you pass in. See OMGraphic for the * definitions of these attributes. * * @param rType render type * @param lType line type * @param dcType declutter type */ public OMRasterObject(int rType, int lType, int dcType) { super(rType, lType, dcType); } /** * The color model is set based on the constructor. This setting controls * what parameter changes are possible for different models of images. * * @param cm the colormode that describes how the colors are being set - * COLORMODEL_DIRECT, COLORMODEL_INDEXED, or COLORMODEL_IMAGEICON. */ protected void setColorModel(int cm) { colorModel = cm; } /** * Get the color model type of the image. * * @return COLORMODEL_DIRECT, COLORMODEL_INDEXED, or COLORMODEL_IMAGEICON. */ public int getColorModel() { return colorModel; } /** * Set the flag for the object that lets the render method (which draws the * object) know that the object needs to be repositioned first. */ public void setNeedToReposition(boolean value) { needToReposition = value; } /** Return the reposition status. */ public boolean getNeedToReposition() { return needToReposition; } /** * Set the angle by which the image is to rotated. * * @param angle the number of radians the image is to be rotated. Measured * clockwise from horizontal. */ public void setRotationAngle(double angle) { this.rotationAngle = angle; setNeedToRegenerate(true); } /** * Get the current rotation of the image. * * @return the image rotation. */ public double getRotationAngle() { return rotationAngle; } /** * Compute the raster objects pixels, based on the color model and the byte * values. * * @return int[] where things are OK (height*width = pixel.length), null if * there is a problem. */ protected abstract int[] computePixels(byte[] bits); /** * Called from within render(). This method should call rotate() on the * provided Graphics2D object, setting the rotation angle and the rotation * point. By default, the rotation angle is whatever is set in the * OMRasterObject, and the rotation point is the offset point plus half the * image width in the horizontal direction, and half the image in the * vertical direction. */ protected void rotate(Graphics2D g) { Double angle = renderRotationAngle; if (angle != null) { int w = width; int h = height; // Note that this rotation is based on height and width of the // image, which is not the right thing to do for scaled images. ((Graphics2D) g).rotate(angle, point1.x + w / 2, point1.y + h / 2); } } /** * Render the raster on the java.awt.Graphics * * @param graphics java.awt.Graphics to draw the image on. */ public void render(Graphics graphics) { if (getNeedToRegenerate() || getNeedToReposition() || !isVisible()) { if (DEBUG) { logger.fine("OMRasterObject.render(): need to regenerate or not visible!"); } return; } // copy the graphic, so our transform doesn't cascade to // others... Graphics g = graphics.create(); // Just a little check to find out if someone is rushing // things. If a Image isn't fully loaded, the getWidth will // return -1. This is just a courtesy notification in case // someone isn't seeing their image, and don't know why. if (colorModel == COLORMODEL_IMAGEICON && (getWidth() == -1)) { logger.fine("OMRasterObject.render: Attempting to draw a Image that is not ready! Image probably wasn't available."); } if (g instanceof Graphics2D && renderRotationAngle != null) { // rotate about our image center point rotate((Graphics2D) g); } renderImage(g, bitmap, point1); if (isSelected() || logger.isLoggable(Level.FINER)) { renderShape(g); } renderLabel(graphics); } /** * Render the image at the given pixel location. This method should be * overridden for special Image handling. * * @param g the Graphics object to render the image into. Assumes this is a * derivative of the Graphics passed into the OMGraphic, and can be * modified without worrying about passing settings on to other * OMGraphics. * @param loc the pixel location of the image. */ protected void renderImage(Graphics g, Image image, Point loc) { if (image != null) { if (DEBUG) { logger.fine("drawing " + width + "x" + height + " image at " + loc.x + ", " + loc.y); } if (g instanceof Graphics2D && image instanceof RenderedImage) { // Affine translation for placement... ((Graphics2D) g).drawRenderedImage((RenderedImage) image, new AffineTransform(1f, 0f, 0f, 1f, loc.x, loc.y)); } else { g.drawImage(image, loc.x, loc.y, this); } } else if (DEBUG) { logger.fine("ignoring null bitmap image"); } } /** * Set the rectangle, based on the location and size of the image. */ public void setShape() { // generate shape that is a boundary of the generated image. // We'll make it a GeneralPath rectangle. int w = width; int h = height; if (imageFilter != null) { w = filteredWidth; h = filteredHeight; } GeneralPath projectedShape = createBoxShape(point1.x, point1.y, w, h); double anchorX = point1.x + w / 2; double anchorY = point1.y + h / 2; setShape(adjustShapeForRotation(projectedShape, anchorX, anchorY)); } /** * Internally evaluates renderRotationAngle and if necessary, applies the * rotation to the shape. If no rotation modifications are needed, the gp is * returned as is. * * @param projectedShape The GeneralPath to rotate, if necessary. * @param anchorX the x coordinate of the rotation point. * @param anchorY the y coordinate of the rotation point. * @return A rotated path if renderRotationAngle is not null, gp otherwise. */ protected GeneralPath adjustShapeForRotation(GeneralPath projectedShape, double anchorX, double anchorY) { if (renderRotationAngle != null) { AffineTransform at = new AffineTransform(); at.rotate(rotationAngle, anchorX, anchorY); PathIterator pi = projectedShape.getPathIterator(at); GeneralPath rotPath = new GeneralPath(); rotPath.append(pi, false); // Replace shape with rotated version return rotPath; } return projectedShape; } /** * Since the image doesn't necessarily need to be regenerated when it is * merely moved, raster objects have this function, called from generate() * and when a placement attribute is changed. * * @return true if enough information is in the object for proper placement. * @param proj projection of window. */ protected boolean position(Projection proj) { if (proj == null) { logger.fine("OMRasterObject: null projection in position!"); return false; } projWidth = proj.getWidth(); projHeight = proj.getHeight(); switch (renderType) { case RENDERTYPE_LATLON: if (!proj.isPlotable(lat, lon)) { if (DEBUG) { logger.fine("OMRasterObject: point is not plotable!"); } setNeedToReposition(true);// so we don't render it! return false; } point1 = (Point) proj.forward(lat, lon, new Point()); break; case RENDERTYPE_XY: point1 = new Point(x, y); break; case RENDERTYPE_OFFSET: if (!proj.isPlotable(lat, lon)) { if (DEBUG) { logger.fine("OMRasterObject: point is not plotable!"); } setNeedToReposition(true);// so we don't render it! return false; } point1 = (Point) proj.forward(lat, lon, new Point()); point1.x += x; point1.y += y; break; case RENDERTYPE_UNKNOWN: if (DEBUG) { logger.fine("OMRasterObject.position(): ignoring unknown rendertype, wingin' it"); } if (lat == 0 && lon == 0) { if (x == 0 && y == 0) { if (DEBUG) { logger.fine("OMRasterObject.position(): Not enough info in object to place it reasonably."); } point1 = new Point(-width, -height); point2 = new Point(0, 0); return false; } else { point1 = new Point(x, y); } } else { if (!proj.isPlotable(lat, lon)) { logger.fine("OMRasterObject: point is not plotable!"); return false; } point1 = (Point) proj.forward(lat, lon, new Point()); } break; } point2 = new Point(0, 0); point2.x = point1.x + width; point2.y = point1.y + height; setNeedToReposition(false); return true; } /** * Set the image to be drawn, if the color model is COLORMODEL_IMAGEICON. * * @param ii the image icon to use. */ public void setImage(Image ii) { if (ii == null) { logger.fine("OMRasterObject.setImage(): image is null!"); return; } colorModel = COLORMODEL_IMAGEICON; bitmap = ii; // Make sure the image is ready to draw. If not, this method // will be called again by the ImageObserver method // imageUpdate. Set the height and width anyway. If they are // -1, you know the image isn't ready - another way to find // out. if (bitmap instanceof BufferedImage) { width = ((BufferedImage) bitmap).getWidth(); height = ((BufferedImage) bitmap).getHeight(); } else { width = bitmap.getWidth(this); height = bitmap.getHeight(this); } if (!(ii instanceof RenderedImage)) { Toolkit.getDefaultToolkit().prepareImage(bitmap, -1, -1, this); } } /** * Overrides OMGraphicAdapter version to handle OMRasterObject * getNeedToReposition. * * @param proj the Projection * @return true if generated, false if didn't do it (maybe a problem). * @see #generate */ public boolean regenerate(Projection proj) { boolean ret = super.regenerate(proj); // handle extra case: OMRasterObject.getNeedToReposition() if (proj != null && !ret) { ret = generate(proj); } return ret; } /** * Get the image that will be put on the window. * * @return the Image created by computePixels and generate(). */ public Image getImage() { return bitmap; } /** * Always true for images, affects distance measurements. Forces the * omGraphics package to treat the OMRasterObject as a filled shape. */ public boolean shouldRenderFill() { return true; } /** * Set the pixels for the image for direct color model images. Checks to see * of the length matches the height * width, but doesn't do anything if they * don't match. Make sure it does. * * @param values the pixel values. */ public void setPixels(int[] values) { if (values.length != (height * width)) logger.fine("OMRasterObject: new pixel[] size (" + +values.length + ") doesn't" + " match [height*width (" + height * width + ")]"); pixels = values; setNeedToRegenerate(true); } /** * Return the pixels used for the image. * * @return the integer array of ints used as integer colors for each pixel * of the image. */ public int[] getPixels() { return pixels; } /** * Change the x attribute, which matters only if the render type is * RENDERTYPE_XY or RENDERTYPE_OFFSET. * * @param value the x location in pixels. */ public void setX(int value) { if (x == value) return; x = value; setNeedToReposition(true); } /** * Returns the x attribute. * * @return the x value, pixels from left of window or image origin. */ public int getX() { return x; } /** * Change the y attribute, which matters only if the render type is * RENDERTYPE_XY or RENDERTYPE_OFFSET. * * @param value the y location in pixels */ public void setY(int value) { if (y == value) return; y = value; setNeedToReposition(true); } /** * Return the y attribute. * * @return the y value, pixels from top or image origin. */ public int getY() { return y; } /** * Return the map location of the image, after generation. * * @return Point, null if not projected yet. */ public Point getMapLocation() { return point1; } /** * Change the latitude attribute, which matters only if the render type is * RENDERTYPE_LATLON or RENDERTYPE_OFFSET. * * @param value latitude in decimal degrees. */ public void setLat(double value) { if (lat == value) return; lat = value; setNeedToReposition(true); } /** * Get the latitude. * * @return the latitude in decimal degrees. */ public double getLat() { return lat; } /** * Change the longitude attribute, which matters only if the render type is * RENDERTYPE_LATLON or RENDERTYPE_OFFSET. * * @param value the longitude in decimal degrees. */ public void setLon(double value) { if (lon == value) return; lon = value; setNeedToReposition(true); } /** * Get the longitude. * * @return longitude in decimal degrees. */ public double getLon() { return lon; } /** * Set the height of the image, in pixels. * * @param value height in pixels. */ public void setHeight(int value) { if (height == value) return; setNeedToRegenerate(true); height = value; } /** * Get the height of image. * * @return height in pixels. */ public int getHeight() { return height; } /** * Get the height of image after a filter was applied. * * @return filteredHeight in pixels. */ public int getFilteredHeight() { return filteredHeight; } /** * Set width of image. * * @param value width in pixels. */ public void setWidth(int value) { if (width == value) return; setNeedToRegenerate(true); width = value; } /** * Get width of image. * * @return width of image in pixels. */ public int getWidth() { return width; } /** * Get width of image, after a filter is applied. * * @return filteredWidth of image in pixels. */ public int getFilteredWidth() { return filteredWidth; } /** * Set the bytes used to create the pixels used to create the image. Used * for indexed color model images in OMRaster, and OMBitmaps. * * @param values byte values */ public void setBits(byte[] values) { setNeedToRegenerate(true); bits = values; } /** * Get the byte values for indexed color model images and OMBitmaps. * * @return the bytes used to create the pixels. */ public byte[] getBits() { return bits; } /** * Set a filter to be used on the constructed image. Applied at generate(). * * @param filter Image filter to apply to constructed raster. */ public void setImageFilter(ImageFilter filter) { imageFilter = filter; filteredWidth = width; filteredHeight = height; setNeedToRegenerate(true); } /** * Return the image filter used on the image. * * @return imagefilter, null if one wasn't set. */ public ImageFilter getImageFilter() { return imageFilter; } /** * Convenience function to scale the Image to the xy size. Sets the * imageFilter to a ReplicateScaleFilter or AreaAveragingScaleFilter, * depending on the algorithm type. * * @param w width to scale to, in pixels * @param h height to scale to, in pixels * @param algorithmType OMRasterObject parameter describing which scaling * algorithm to use. */ public void scaleTo(int w, int h, int algorithmType) { filteredWidth = w; filteredHeight = h; imageFilter = new TrimScaleFilter(filteredWidth, filteredHeight, algorithmType); setNeedToRegenerate(true); } /** * A method used to manipulate the image according to the parameters set by * the imageFilter in the OMRasterObject. Called from generate() if the * filteredWidth and filteredHeight differ from width and height. * * @param image the Image to filter * @return the filtered image. */ protected Image filterImage(Image image) { // Can we do a little clipping here?? If it's been projected, // maybe. // See if the frame is getting blown up, probably by at // least a certain margin, so we know that there will be time // savings as well as memory savings. java.awt.image.ImageFilter iImageFilter = imageFilter; if (iImageFilter instanceof TrimScaleFilter) { TrimScaleFilter tf = (TrimScaleFilter) iImageFilter; Image img = tf.trimExcessPixels(); if (img != null) { image = img; iImageFilter = tf.getFilterWithChanges(); // we can play around with point1, since that is where // the // image is getting laid out. If point1.x or point1.y // < 0, we // can set it to zero. Assumes that the image has // already // been positioned. if (point1.x < 0) point1.x = 0; if (point1.y < 0) point1.y = 0; if (DEBUG) { logger.fine("OMRasterObject: newly located at " + point1); } } else if (DEBUG) { logger.fine("OMRasterObject: not being trimmed due to projection"); } } if (Toolkit.getDefaultToolkit() != null && image != null && iImageFilter != null) { ImageProducer prod = new FilteredImageSource(image.getSource(), iImageFilter); return Toolkit.getDefaultToolkit().createImage(prod); } return image; } /** * From the Image Observer Interface. Called when the image bits have * arrived, and therefore calls setImage() to reset all the OMRasterObject * parameters. <br> * Don't call this method! */ public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { if ((infoflags & ImageObserver.ALLBITS) != 0) { if (colorModel == COLORMODEL_IMAGEICON) { setImage(img); } } if ((infoflags & ImageObserver.WIDTH) != 0) { this.width = width; } if ((infoflags & ImageObserver.HEIGHT) != 0) { this.height = height; } return !(this.bitmap != null && this.width > 0 && this.height > 0); } /** * This is an effort to create an scaling ImageFilter that will trim off the * unused pixels, lessoning the load on the display server. It depends on * knowing several things about the projection and the current image * parameters, which is why it's not a stand-alone filter class. */ protected class TrimScaleFilter extends AreaAveragingScaleFilter { ImageFilter actualFilter = null; int algorithmType; /** * Constructs an TrimScaleFilter that scales the pixels from its source * Image as specified by the width and height parameters. * * @param width the target width to scale the image * @param height the target height to scale the image */ public TrimScaleFilter(int width, int height) { super(width, height); algorithmType = FAST_SCALING; } /** * Constructs an AreaAveragingScaleFilter that scales the pixels from * its source Image as specified by the width and height parameters. * * @param width the target width to scale the image * @param height the target height to scale the image * @param algorithmType FAST_SCALING or SMOOTH_SCALING - FAST is much * faster! */ protected TrimScaleFilter(int width, int height, int algorithmType) { super(width, height); this.algorithmType = algorithmType; } /** * Detect if the data is being delivered with the necessary hints to * allow the averaging algorithm to do its work. If the algorithmType is * set to FAST, I manipulate the hints to force the filter to act like a * ReplicateScaleFilter. * * @see ImageConsumer#setHints */ public void setHints(int hints) { int passthrough = hints; if (algorithmType == FAST_SCALING) { // / XOR passthrough = hints ^ ImageConsumer.TOPDOWNLEFTRIGHT; } super.setHints(passthrough); } /** * The filter must change if the requested image size changes because of * clipping. Get the good filter here, after calling trimExcessPixels(). */ protected ImageFilter getFilterWithChanges() { if (actualFilter == null) { return this; } return actualFilter; } /** * Get a trimmed-down image to expand to the map, that contains all the * pixels that will be visible after expansion. Returns null if the * image should be used as is, and the filter as well. */ protected Image trimExcessPixels() { if (filteredWidth <= width && filteredHeight <= height) { if (DEBUG) { logger.fine("TrimScaleFilter.trimExcessPixels(): image not enlarged, using entire image."); } return null; } if (DEBUG) { logger.fine("TrimScaleFilter.trimExcessPixels(): clipping enlarged image."); } // Figure out the pixels of the old image being used in // the new image. Figure out the proj location of the // upper // left pixel of the new image. We want to substitute this // proj location for the projection location already // calculated. This should get overwritten later for any // projection changes. float widthScale = (float) filteredWidth / (float) width; float heightScale = (float) filteredHeight / (float) height; int startXPixelInSource = point1.x < 0 ? (int) ((-1.0 * point1.x) / widthScale) : 0; int startYPixelInSource = point1.y < 0 ? (int) ((-1 * point1.y) / heightScale) : 0; Point scaledDim = new Point((int) (point1.x + (width * widthScale)), (int) (point1.y + (height * heightScale))); int endXPixelInSource = (scaledDim.x > projWidth ? (int) ((projWidth - point1.x) / widthScale) + 1 : width); int endYPixelInSource = scaledDim.y > projHeight ? (int) ((projHeight - point1.y) / heightScale) + 1 : height; if (DEBUG) { logger.fine("TrimScaleFilter.trimExcessPixels(): image contributes " + startXPixelInSource + ", " + startYPixelInSource + " to " + endXPixelInSource + ", " + endYPixelInSource); } // Create a buffered image out of the old image, clipping // out the unused pixels. if (DEBUG) { logger.fine("TrimScaleFilter.trimExcessPixels(): " + " new dimensions of scaled image " + (int) ((endXPixelInSource - startXPixelInSource) * widthScale) + ", " + (int) ((endYPixelInSource - startYPixelInSource) * heightScale)); } // Get only the pixels you need. // Use a pixel grabber to get the right pixels. PixelGrabber pg = new PixelGrabber(bitmap, startXPixelInSource, startYPixelInSource, endXPixelInSource - startXPixelInSource, endYPixelInSource - startYPixelInSource, true); int[] pix = ImageHelper.grabPixels(pg); if (pix == null) { return null; } // Set the filter to the dimensions. Need to remember to // ask for this!!! actualFilter = new TrimScaleFilter((int) ((endXPixelInSource - startXPixelInSource) * widthScale), (int) ((endYPixelInSource - startYPixelInSource) * heightScale), algorithmType); // create the new bitmap, which holds the image that gets // drawn Toolkit tk = Toolkit.getDefaultToolkit(); Image image = tk.createImage(new MemoryImageSource(endXPixelInSource - startXPixelInSource, endYPixelInSource - startYPixelInSource, pix, 0, endXPixelInSource - startXPixelInSource)); return image; } } /** * Code derived from * http://www.dcs.shef.ac.uk/~tom/Java/Power/image_serialization.html */ private void writeObject(ObjectOutputStream objectstream) throws IOException { // write non-transient, non-static data objectstream.defaultWriteObject(); PixelGrabber grabber = new PixelGrabber(bitmap, 0, 0, -1, -1, true); if (colorModel == COLORMODEL_IMAGEICON && bitmap != null) { try { grabber.grabPixels(); } catch (InterruptedException e) { logger.fine("error grabbing pixels"); } Object pix = grabber.getPixels(); Dimension dim = new Dimension(bitmap.getWidth(this), bitmap.getHeight(this)); objectstream.writeObject(dim); objectstream.writeObject(pix); } } /** * Code derived from * http://www.dcs.shef.ac.uk/~tom/Java/Power/image_serialization.html */ private void readObject(ObjectInputStream objectstream) throws IOException, ClassNotFoundException { Toolkit toolkit = Toolkit.getDefaultToolkit(); try { // read non-transient, non-static data objectstream.defaultReadObject(); if (colorModel == COLORMODEL_IMAGEICON) { Dimension dim = (Dimension) objectstream.readObject(); Object img = objectstream.readObject(); int[] pix = (int[]) img; bitmap = toolkit.createImage(new MemoryImageSource(dim.width, dim.height, pix, 0, dim.width)); setImage(bitmap); } } catch (ClassNotFoundException ce) { System.out.println("class not found"); } } public boolean hasLineTypeChoice() { return false; } public void restore(OMGeometry source) { super.restore(source); if (source instanceof OMRasterObject) { OMRasterObject rasterO = (OMRasterObject) source; this.colorModel = rasterO.colorModel; this.pixels = DeepCopyUtil.deepCopy(rasterO.pixels); this.x = rasterO.x; this.y = rasterO.y; this.lat = rasterO.lat; this.lon = rasterO.lon; this.width = rasterO.width; this.height = rasterO.height; this.bits = DeepCopyUtil.deepCopy(rasterO.bits); this.filteredWidth = rasterO.filteredWidth; this.filteredHeight = rasterO.filteredHeight; this.rotationAngle = rasterO.rotationAngle; // OKOK, again, not a deep copy. this.imageFilter = rasterO.imageFilter; } } }