// **********************************************************************
//
// <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;
}
}
}