/*******************************************************************************
* Copyright (c) 2010 Oak Ridge National Laboratory.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
******************************************************************************/
package org.csstudio.swt.widgets.figures;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.csstudio.swt.widgets.datadefinition.ByteArrayWrapper;
import org.csstudio.swt.widgets.datadefinition.ColorMap;
import org.csstudio.swt.widgets.datadefinition.ColorMap.PredefinedColorMap;
import org.csstudio.swt.widgets.datadefinition.DoubleArrayWrapper;
import org.csstudio.swt.widgets.datadefinition.FloatArrayWrapper;
import org.csstudio.swt.widgets.datadefinition.IPrimaryArrayWrapper;
import org.csstudio.swt.widgets.datadefinition.IntArrayWrapper;
import org.csstudio.swt.widgets.datadefinition.LongArrayWrapper;
import org.csstudio.swt.widgets.datadefinition.ShortArrayWrapper;
import org.csstudio.swt.widgets.figureparts.ColorMapRamp;
import org.csstudio.swt.widgets.figureparts.ROIFigure;
import org.csstudio.swt.widgets.introspection.DefaultWidgetIntrospector;
import org.csstudio.swt.widgets.introspection.Introspectable;
import org.csstudio.swt.xygraph.figures.Axis;
import org.csstudio.swt.xygraph.linearscale.Range;
import org.csstudio.swt.xygraph.util.GraphicsUtil;
import org.csstudio.swt.xygraph.util.SWTConstants;
import org.csstudio.swt.xygraph.util.XYGraphMediaFactory;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Cursors;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.FigureListener;
import org.eclipse.draw2d.FigureUtilities;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.Polyline;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;
/**An intensity graph figure.
* @author Xihui Chen
*
*/
public class IntensityGraphFigure extends Figure implements Introspectable {
/**
* Color depth of the image data in RGB1 mode, since SWT only support 8 bit color depth,
* it has to convert all data to [0,255].
* @author Xihui Chen
*
*/
public enum ColorDepth {
BIT8("8 bit"), //No need to convert
BIT16("16 bit"), //Convert by >>8
BIT24("24 bit"), //Convert by >>16
BIT30("30 bit"), //Convert by >>22
SCALE("Scaled to [Max, Min]"), //Convert by (x-min)/(max-min)*255
LOWER8BIT("Use only lower 8 bits"); //Convert by &0xFF
private ColorDepth(String description) {
this.description = description;
}
private String description;
@Override
public String toString() {
return description;
}
public static String[] stringValues() {
String[] sv = new String[values().length];
int i = 0;
for (ColorDepth p : values())
sv[i++] = p.toString();
return sv;
}
}
private static final int MAX_ARRAY_SIZE = 10000000;
/**
* ROI listener which will be notified whenever ROI moved.
* @author Xihui
*
*/
public interface IROIListener{
/**Called whenever ROI updated.
* @param xIndex x index of ROI start.
* @param yIndex y index of ROI start
* @param width width of ROI
* @param height height of ROI
*/
public void roiUpdated(int xIndex, int yIndex, int width, int height);
}
/**Provides info to be displayed on ROI label.
* @author Xihui Chen
*
*/
public interface IROIInfoProvider{
/**Return the information to be displayed on ROI label.
* It will called whenever the ROI is repainted.
* @param xIndex x index of ROI start.
* @param yIndex y index of ROI start
* @param width width of ROI
* @param height height of ROI
*/
public String getROIInfo(int xIndex, int yIndex, int width, int height);
}
class SinglePixelProfileCrossHair extends Figure {
/**
* Center coordinates
*/
private int crossX, crossY;
/**
* Data index of cross center on cropped data array.
*/
private Point crossDataIndex;
private boolean inDefaultPosition = true;
private Polyline hLine, vLine;
private Figure crossPoint;
public SinglePixelProfileCrossHair() {
hLine = new Polyline();
hLine.setCursor(Cursors.SIZENS);
hLine.setForegroundColor(ColorConstants.yellow);
hLine.setTolerance(3);
hLine.addMouseMotionListener(new MouseMotionListener.Stub(){
@Override
public void mouseDragged(MouseEvent me) {
setCrossPosition(crossX, me.y, true);
me.consume();
}
});
hLine.addMouseListener(new MouseListener.Stub(){
@Override
public void mousePressed(MouseEvent me) {
me.consume();
}
});
vLine = new Polyline();
vLine.setCursor(Cursors.SIZEWE);
vLine.setForegroundColor(ColorConstants.yellow);
vLine.setTolerance(3);
vLine.addMouseListener(new MouseListener.Stub(){
@Override
public void mousePressed(MouseEvent me) {
me.consume();
}
});
vLine.addMouseMotionListener(new MouseMotionListener.Stub(){
@Override
public void mouseDragged(MouseEvent me) {
setCrossPosition(me.x, crossY, true);
me.consume();
}
});
crossPoint = new Figure();
crossPoint.setCursor(Cursors.SIZEALL);
crossPoint.addMouseListener(new MouseListener.Stub(){
@Override
public void mousePressed(MouseEvent me) {
me.consume();
}
});
crossPoint.addMouseMotionListener(new MouseMotionListener.Stub(){
@Override
public void mouseDragged(MouseEvent me) {
setCrossPosition(me.x, me.y, true);
me.consume();
}
});
add(hLine);
add(vLine);
add(crossPoint);
addFigureListener(new FigureListener() {
@Override
public void figureMoved(IFigure source) {
if(crossDataIndex != null){
Point p = graphArea.getGeoLocation(crossDataIndex.x, crossDataIndex.y);
setCrossPosition(p.x, p.y, false);
}
}
});
addCroppedDataSizeListener(new ICroppedDataSizeListener() {
@Override
public void croppedDataSizeChanged(int croppedDataWidth,
int croppedDataHeight) {
crossDataIndex = graphArea.getDataLocation(crossX, crossY);
}
});
}
@Override
public boolean containsPoint(int x, int y) {
return hLine.containsPoint(x, y) || vLine.containsPoint(x, y)
|| crossPoint.containsPoint(x, y);
}
@Override
protected void layout() {
Rectangle bounds = getBounds();
//First time when it was created.
if(inDefaultPosition){
setCrossPosition(bounds.x + bounds.width/2, bounds.y + bounds.height/2, true);
}else{
Point p = graphArea.getGeoLocation(crossDataIndex.x, crossDataIndex.y);
setCrossPosition(p.x, p.y, false);
}
}
public void setCrossHairColor(Color crossHairColor) {
hLine.setForegroundColor(crossHairColor);
vLine.setForegroundColor(crossHairColor);
}
/**set Cross Position
* @param x
* @param y
*/
public void setCrossPosition(int x, int y, boolean updatedCrossDataIndex){
Rectangle bounds = getBounds();
if(x < bounds.x)
crossX = bounds.x;
else if(x>=bounds.x + bounds.width)
crossX = bounds.x + bounds.width-1;
else
crossX = x;
if(y < bounds.y)
crossY = bounds.y;
else if(y>=bounds.y + bounds.height)
crossY = bounds.y + bounds.height-1;
else
crossY = y;
inDefaultPosition = false;
if(updatedCrossDataIndex){
crossDataIndex = graphArea.getDataLocation(crossX, crossY);
if(croppedDataArray != null)
fireProfileDataChanged(croppedDataArray, croppedDataWidth, croppedDataHeight);
}
hLine.setPoints(new PointList(new int[]{bounds.x,crossY, bounds.width+bounds.x, crossY}));
vLine.setPoints(new PointList(new int[]{crossX, bounds.y, crossX, bounds.y + bounds.height}));
crossPoint.setBounds(new Rectangle(crossX-5, crossY-5, 10,10));
}
}
public class GraphArea extends Figure{
private final static int CURSOR_SIZE = 14;
private SinglePixelProfileCrossHair crossHair;
public GraphArea() {
if(runMode){
setCursor(null);
GraphAreaZoomer zoomer = new GraphAreaZoomer();
addMouseMotionListener(zoomer);
addMouseListener(zoomer);
}
setSinglePixelProfiling(isSingleLineProfiling());
}
protected void setSinglePixelProfiling(boolean isSinglePixelProfiling) {
if(!runMode)
return;
if(isSingleLineProfiling()){
if(crossHair == null)
crossHair = new SinglePixelProfileCrossHair();
add(crossHair);
}else if(crossHair != null && crossHair.getParent()==this)
remove(crossHair);
dataDirty = true;
repaint();
}
@Override
protected void layout() {
Rectangle clientArea = getClientArea();
if(runMode && isSingleLineProfiling()){
crossHair.setBounds(clientArea);
}
for(ROIFigure roiFigure : roiMap.values()){
roiFigure.setBounds(clientArea);
}
}
private synchronized IPrimaryArrayWrapper cropDataArray(int left, int right, int top, int bottom){
if((left != 0 || right != 0 || top != 0 || bottom != 0) &&
(dataWidth - left - right) * (dataHeight - top-bottom) >0){
int i=0;
if((dataWidth - left - right) * (dataHeight - top - bottom) > MAX_ARRAY_SIZE)
return dataArray;
double[] result = null;
if (inRGBMode) {
result = new double[(dataWidth - left - right)
* (dataHeight - top - bottom)*3];
for (int y = top; y < (dataHeight - bottom); y++) {
for (int x = left; x < (dataWidth - right); x++) {
int p=y * dataWidth*3 + x*3;
result[i] = dataArray.get(p);
result[i+1]=dataArray.get(p+1);
result[i+2]=dataArray.get(p+2);
i+=3;
}
}
} else {
result = new double[(dataWidth - left - right)
* (dataHeight - top - bottom)];
for (int y = top; y < (dataHeight - bottom); y++) {
for (int x = left; x < (dataWidth - right); x++) {
result[i++] = dataArray.get(y * dataWidth + x);
}
}
}
return new DoubleArrayWrapper(result);
}else
return dataArray;
}
/**Get data index location on cropped data array from geometry location.
* @param x x much be inside graph area.
* @param y y much be inside graph area
* @return
*/
public PrecisionPoint getDataLocation(double x, double y){
Rectangle clientArea = getClientArea();
double hIndex = croppedDataWidth * (x - clientArea.x)/(double)clientArea.width;
double vIndex = croppedDataHeight * (y - clientArea.y)/(double)clientArea.height;
return new PrecisionPoint(hIndex, vIndex);
}
/**Get geometry location from data index location on cropped data array.
* @param xIndex x index location on cropped data array
* @param yIndex y index location on cropped data array
* @return
*/
public PrecisionPoint getGeoLocation(double xIndex, double yIndex){
Rectangle clientArea = getClientArea();
if(croppedDataHeight == 0 || croppedDataWidth ==0)
return new PrecisionPoint(clientArea.x, clientArea.y);
double x = (xIndex*clientArea.width)/(double)croppedDataWidth + clientArea.x;
double y = (yIndex*clientArea.height)/(double)croppedDataHeight + clientArea.y;
return new PrecisionPoint(x, y);
}
@Override
protected synchronized void paintClientArea(Graphics graphics) {
if(dataArray == null)
return;
Rectangle clientArea = getClientArea();
//draw image if data is dirty or bufferedImage has not been created yet
if(dataDirty || bufferedImage == null){
dataDirty = false;
if(bufferedImage != null){
bufferedImage.dispose();
bufferedImage = null;
}
if(clientArea.width <0 || clientArea.height <0)
return;
if(dataWidth == 0 || dataHeight == 0 || (!isInRGBMode() && dataArray.getSize() < dataWidth * dataHeight)
|| (isInRGBMode() && dataArray.getSize() < 3*dataWidth * dataHeight)){
graphics.drawRectangle(new Rectangle(
clientArea.x - (yAxis.isVisible()? 1:0),
clientArea.y,
clientArea.width-(yAxis.isVisible()? 0:1), clientArea.height - (xAxis.isVisible()? 0:1)));
if(dataArray.getSize() ==0)
graphics.drawText("No data.", clientArea.getLocation());
else if(!isInRGBMode() && dataArray.getSize() < dataWidth * dataHeight)
graphics.drawText("Size of input data is less than dataWidth*dataHeight!",
clientArea.getLocation());
else if(isInRGBMode() && dataArray.getSize() < 3*dataWidth * dataHeight)
graphics.drawText("Size of input data is less than 3*dataWidth*dataHeight!" +
"\nPlease make sure the data is in RGB mode.",
clientArea.getLocation());
return;
}
if(dataWidth - cropLeft - cropRight < 0 || dataHeight - cropTop - cropBottom < 0)
return;
croppedDataArray = cropDataArray(cropLeft, cropRight, cropTop, cropBottom);
fireProfileDataChanged(croppedDataArray, croppedDataWidth, croppedDataHeight);
// for(ROIFigure roiFigure : roiMap.values()){
// roiFigure.fireROIUpdated();
// }
boolean shrink= false;
if(clientArea.width*clientArea.height < croppedDataHeight * croppedDataWidth){
shrink = true;
}
if(shrink){
if(bufferedImageData == null || bufferedImageData.width != clientArea.width
|| bufferedImageData.height !=clientArea.height){
bufferedImageData = new ImageData(clientArea.width, clientArea.height, 24, colorMap.getPalette());
}
}else if(bufferedImageData == null || bufferedImageData.width != croppedDataWidth
|| bufferedImageData.height !=croppedDataHeight)
bufferedImageData = new ImageData(croppedDataWidth, croppedDataHeight, 24, colorMap.getPalette());
ImageData imageData = null;
if(inRGBMode)
try {
imageData = drawRGBImage(croppedDataArray,
croppedDataWidth, croppedDataHeight,
max, min, bufferedImageData, shrink);
} catch (IllegalArgumentException e) {
graphics.drawText("Drawing Exception: RGB value is not between 0 and 255." +
"\nPlease check if the data or color depth is correct.",
clientArea.getLocation());
}
else
imageData = colorMap.drawImage(croppedDataArray,
croppedDataWidth, croppedDataHeight,
max, min, bufferedImageData, shrink);
if(imageData == null)
return;
bufferedImage = new Image(Display.getCurrent(), imageData);
}
graphics.drawImage(bufferedImage, new Rectangle(bufferedImage.getBounds()), clientArea);
if(armed && end != null && start != null){
graphics.setLineStyle(SWTConstants.LINE_DOT);
graphics.setLineWidth(1);
graphics.setForegroundColor(BLACK_COLOR);
graphics.drawRectangle(start.x, start.y, end.x - start.x, end.y - start.y);
}
// System.out.println((System.nanoTime() - startTime)/1000000);
// startTime = System.nanoTime();
super.paintClientArea(graphics);
}
private synchronized void updateTextCursor(MouseEvent me) {
if(SWT.getPlatform().startsWith("rap")) //$NON-NLS-1$
return;
if(croppedDataArray == null)
return;
if(getCursor() != null)
getCursor().dispose();
double xCoordinate = xAxis.getPositionValue(me.x, false);
double yCoordinate = yAxis.getPositionValue(me.y, false);
Point dataLocation = getDataLocation(me.x, me.y);
if(dataLocation == null)
return;
if((dataLocation.y)*croppedDataWidth + dataLocation.x >= croppedDataArray.getSize())
return;
double valueUnderMouse;
if(inRGBMode){
int index = (dataLocation.y) * croppedDataWidth * 3
+ dataLocation.x * 3;
valueUnderMouse = (croppedDataArray.get(index)
+ croppedDataArray.get(index + 1) + croppedDataArray
.get(index + 2)) / 3;
}
else
valueUnderMouse = croppedDataArray.get((dataLocation.y)*croppedDataWidth + dataLocation.x);
String text = "(" + xAxis.format(xCoordinate) + ", " + yAxis.format(yCoordinate) + ", "+
yAxis.format(valueUnderMouse) + ")";
text = text + getPixelInfo(dataLocation.x + cropLeft, dataLocation.y + cropTop,
xCoordinate, yCoordinate, valueUnderMouse);
Dimension size = FigureUtilities.getTextExtents(
text, Display.getDefault().getSystemFont());
Image image = new Image(Display.getDefault(),
size.width + CURSOR_SIZE, size.height + CURSOR_SIZE);
GC gc = GraphicsUtil.createGC(image);
//gc.setAlpha(0);
gc.setBackground(TRANSPARENT_COLOR);
gc.fillRectangle(image.getBounds());
gc.setForeground(BLACK_COLOR);
gc.drawLine(0, CURSOR_SIZE/2, CURSOR_SIZE, CURSOR_SIZE/2);
gc.drawLine(CURSOR_SIZE/2, 0, CURSOR_SIZE/2, CURSOR_SIZE);
gc.setBackground(WHITE_COLOR);
gc.fillRectangle(CURSOR_SIZE, CURSOR_SIZE,
image.getBounds().width-CURSOR_SIZE,
image.getBounds().height-CURSOR_SIZE);
gc.drawText(text, CURSOR_SIZE, CURSOR_SIZE, true);
ImageData imageData = image.getImageData();
imageData.transparentPixel = imageData.palette.getPixel(TRANSPARENT_COLOR.getRGB());
setCursor(GraphicsUtil.createCursor(Display.getCurrent(),
imageData, CURSOR_SIZE/2 ,CURSOR_SIZE/2));
gc.dispose();
image.dispose();
}
}
class GraphAreaZoomer extends MouseMotionListener.Stub implements MouseListener{
@Override
public void mouseDoubleClicked(MouseEvent me) {
if(me.button !=1)
return;
if(xAxisRange !=null)
xAxis.setRange(xAxisRange);
if(yAxisRange != null)
yAxis.setRange(yAxisRange);
if(originalCrop != null){
setCropLeft(originalCrop.x);
setCropTop(originalCrop.y);
setCropRight(originalCrop.width);
setCropBottom(originalCrop.height);
graphArea.repaint();
}
}
@Override
public void mouseDragged(MouseEvent me) {
if(!armed)
return;
if(graphArea.getClientArea().contains(me.getLocation())){
graphArea.updateTextCursor(me);
end = me.getLocation();
graphArea.repaint();
}
}
@Override
public void mouseMoved(MouseEvent me) {
graphArea.updateTextCursor(me);
}
@Override
public void mousePressed(MouseEvent me) {
requestFocus();
// Only react to 'main' mouse button
if (me.button != 1)
return;
armed = true;
//get start position
start = me.getLocation();
end = null;
me.consume();
}
@Override
public void mouseReleased(MouseEvent me) {
if(!armed || end == null || start == null)
return;
zoom();
armed = false;
end = null;
start = null;
}
}
public interface ICroppedDataSizeListener {
void croppedDataSizeChanged(int croppedDataWidth, int croppedDataHeight);
}
public interface IProfileDataChangeLisenter{
/**Called whenever profile data changed. This is called in a non-UI thread.
* @param xProfileData Profile data on x Axis.
* @param yProfileData Profile data on y Axis.
* @param xAxisRange x Axis range.
* @param yAxisRange y Axis range.
*/
void profileDataChanged(double[] xProfileData, double[] yProfileData,
Range xAxisRange, Range yAxisRange);
}
public interface IPixelInfoProvider{
/**Get related information on this pixel, which will be displayed below the cursor.
* @param xIndex x index of the pixel
* @param yIndex y index of the pixel
* @param xCoordinate x axis coordinate of the pixel
* @param yCoordinate y axis coordinate of the pixel
* @param pixelValue value of the pixel
* @return the information about this pixel.
*/
public String getPixelInfo(int xIndex, int yIndex,
double xCoordinate, double yCoordinate, double pixelValue);
}
private int dataWidth, dataHeight;
private int cropLeft, cropRight, cropTop, cropBottom;
// private double[] dataArray;
private IPrimaryArrayWrapper dataArray;
private IPrimaryArrayWrapper croppedDataArray;
private int croppedDataWidth, croppedDataHeight;
private double max, min;
private ColorMapRamp colorMapRamp;
private GraphArea graphArea;
private ColorMap colorMap;
private final Axis xAxis;
private final Axis yAxis;
private Range xAxisRange = null;
private Range yAxisRange = null;
private Rectangle originalCrop = null;
private final static int GAP = 3;
private org.eclipse.draw2d.geometry.Point start;
private org.eclipse.draw2d.geometry.Point end;
private boolean armed;
private boolean dataDirty; //true if the image need to be redrawn
private ImageData bufferedImageData;
private Image bufferedImage; //the buffered image
private List<IProfileDataChangeLisenter> profileListeners;
private List<IPixelInfoProvider> pixelInfoProviders;
private List<ICroppedDataSizeListener> croppedDataSizeListeners;
private boolean runMode;
private Map<String, ROIFigure> roiMap;
// private long startTime = System.nanoTime();
private final static Color WHITE_COLOR = XYGraphMediaFactory.getInstance().getColor(
XYGraphMediaFactory.COLOR_WHITE);
private final static Color BLACK_COLOR = XYGraphMediaFactory.getInstance().getColor(
XYGraphMediaFactory.COLOR_BLACK);
private final static Color TRANSPARENT_COLOR = XYGraphMediaFactory.getInstance().getColor(
new RGB(123,0,23));
private boolean inRGBMode = false;
private ColorDepth colorDepth = ColorDepth.BIT8;
private PaletteData palette = new PaletteData(0xff, 0xff00, 0xff0000);
private Boolean savedShowRamp;
private boolean isSingleLineProfiling = false;
private Color roiColor = ColorConstants.cyan;
public IntensityGraphFigure() {
this(true);
}
public IntensityGraphFigure(boolean runMode) {
this.runMode = runMode;
dataArray = new DoubleArrayWrapper(new double[0]);
max = 255;
min = 0;
dataWidth = 0;
dataHeight = 0;
profileListeners = new ArrayList<IProfileDataChangeLisenter>();
colorMap = new ColorMap(PredefinedColorMap.GrayScale, true, true);
colorMapRamp = new ColorMapRamp();
colorMapRamp.setMax(max);
colorMapRamp.setMin(min);
colorMapRamp.setColorMap(colorMap);
graphArea = new GraphArea();
xAxis = new Axis("X", false);
yAxis = new Axis("Y", true);
add(colorMapRamp);
add(graphArea);
add(xAxis);
add(yAxis);
roiMap = new HashMap<String, ROIFigure>(2);
setFocusTraversable(true);
setRequestFocusEnabled(true);
}
public void addProfileDataListener(IProfileDataChangeLisenter listener){
if(listener != null)
profileListeners.add(listener);
}
public void addPixelInfoProvider(IPixelInfoProvider pixelInfoProvider){
if(pixelInfoProvider != null){
if(pixelInfoProviders == null)
pixelInfoProviders = new ArrayList<IPixelInfoProvider>();
pixelInfoProviders.add(pixelInfoProvider);
}
}
public void addCroppedDataSizeListener(ICroppedDataSizeListener listener){
if(croppedDataSizeListeners == null)
croppedDataSizeListeners = new ArrayList<ICroppedDataSizeListener>();
croppedDataSizeListeners.add(listener);
}
/** Add a new ROI to the graph.
* @param name name of the ROI. It must be unique for this graph.
* @param color color of the ROI.
* @param roiListener listener on ROI updates. Can be null.
* @param roiInfoProvider provides information for the ROI. Can be null.
*/
public void addROI(String name, IROIListener roiListener, IROIInfoProvider roiInfoProvider){
ROIFigure roiFigure = new ROIFigure(this, name, roiColor, roiListener, roiInfoProvider);
roiMap.put(name, roiFigure);
graphArea.add(roiFigure);
}
public void removeROI(String name){
if(roiMap.containsKey(name)){
ROIFigure roiFigure = roiMap.get(name);
roiMap.remove(name);
graphArea.remove(roiFigure);
}
}
public void setROIVisible(String name, boolean visible){
if(roiMap.containsKey(name)){
roiMap.get(name).setVisible(visible);
}
}
private double[] calculateXProfileData(IPrimaryArrayWrapper data, int dw, int dh){
double[] output = new double[dw];
if(isSingleLineProfiling()){
Point dataloc = graphArea.getDataLocation(graphArea.crossHair.crossX,
graphArea.crossHair.crossY);
for(int i=0; i<dw; i++){
if(inRGBMode){
int index = dataloc.y*dw*3 + i*3;
output[i] = (data.get(index) + data.get(index + 1) + data
.get(index + 2)) / 3;
}else
output[i] = data.get(dataloc.y*dw + i);
}
}else {
for (int i = 0; i < dw; i++) {
for (int j = 0; j < dh; j++)
if (inRGBMode) {
int index = j * dw * 3 + i * 3;
output[i] += (data.get(index) + data.get(index + 1) + data
.get(index + 2)) / 3;
} else
output[i] += data.get(j * dw + i);
output[i] /= dh;
}
}
return output;
}
private double[] calculateYProfileData(IPrimaryArrayWrapper data, int dw,
int dh) {
double[] output = new double[dh];
if (isSingleLineProfiling()) {
Point dataloc = graphArea.getDataLocation(graphArea.crossHair.crossX,
graphArea.crossHair.crossY);
for (int i = 0; i < dh; i++) {
if (inRGBMode) {
int index = dataloc.x *3 + i*dw* 3;
output[i] = (data.get(index) + data.get(index + 1) + data
.get(index + 2)) / 3;
} else
output[i] = data.get(dataloc.x + i*dw);
}
} else {
for (int i = 0; i < dh; i++) {
for (int j = 0; j < dw; j++)
if (inRGBMode) {
int index = i * dw * 3 + j * 3;
output[i] += (data.get(index) + data.get(index + 1) + data
.get(index + 2)) / 3;
} else
output[i] += data.get(i * dw + j);
output[i] /= dw;
}
}
return output;
}
public void dispose(){
if(bufferedImage != null){
bufferedImage.dispose();
bufferedImage = null;
}
}
/**Calculate the image data from source RGB data array [RGBRGBRGB...].
* @param dataArray the source data in RGB mode.
* @param dataWidth number of columns of dataArray; This will be the width of image data.
* @param dataHeight number of rows of dataArray; This will be the height of image data.
* @param max the upper limit of the data in dataArray
* @param min the lower limit of the data in dataArray
* @param imageData the imageData to be filled. null if a new instance should be created.
* @param shrink true if area size of image data is smaller than dataWidth*dataHeight. If this is true, it will use
* the nearest neighbor iamge scaling algorithm as described at http://tech-algorithm.com/articles/nearest-neighbor-image-scaling/.
* @return the image data. null if dataWidth or dataHeight is less than 1 or larger than the data array.
*/
private ImageData drawRGBImage(IPrimaryArrayWrapper dataArray,
int dataWidth, int dataHeight, double max, double min,
ImageData imageData, boolean shrink) {
if (dataWidth < 1 || dataHeight < 1
|| dataWidth * dataHeight * 3 > dataArray.getSize()
|| dataWidth * dataHeight < 0)
return null;
if (imageData == null)
imageData = new ImageData(dataWidth, dataHeight, 24, palette);
if (shrink) {
int height = imageData.height;
int width = imageData.width;
// EDIT: added +1 to account for an early rounding problem
int x_ratio = (int) ((dataWidth << 16) / width) + 1;
int y_ratio = (int) ((dataHeight << 16) / height) + 1;
// int x_ratio = (int)((w1<<16)/w2) ;
// int y_ratio = (int)((h1<<16)/h2) ;
int x2, y2;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
x2 = ((j * x_ratio) >> 16);
y2 = ((i * y_ratio) >> 16);
int index = y2 * dataWidth * 3 + x2 * 3;
int pixel = calcRGBPixel(dataArray, max, min, index);
imageData.setPixel(j, i, pixel);
}
}
} else {
for (int y = 0; y < dataHeight; y++) {
for (int x = 0; x < dataWidth; x++) {
// the index of the value in the color table array
int index = y * dataWidth * 3 + x * 3;
int pixel = calcRGBPixel(dataArray, max, min, index);
imageData.setPixel(x, y, pixel);
}
}
}
return imageData;
}
/**
* @param dataArray
* @param max
* @param min
* @param index
* @return
*/
protected int calcRGBPixel(IPrimaryArrayWrapper dataArray, double max,
double min, int index) {
int r = (int) dataArray.get(index);
int g = (int) dataArray.get(index + 1);
int b = (int) dataArray.get(index + 2);
switch (colorDepth) {
case BIT16:
r = r >> 8;
g = g >> 8;
b = b >> 8;
break;
case BIT24:
r = r >> 16;
g = g >> 16;
b = b >> 16;
break;
case BIT30:
r = r >> 22;
g = g >> 22;
b = b >> 22;
break;
case LOWER8BIT:
r = r & 0xFF;
b = b & 0xFF;
g = g & 0xFF;
break;
case SCALE:
r = (int) ((dataArray.get(index) - min) / (max - min) * 255);
g = (int) ((dataArray.get(index + 1) - min) / (max - min) * 255);
b = (int) ((dataArray.get(index + 2) - min) / (max - min) * 255);
break;
case BIT8:
default:
break;
}
// if(r>255) r=255; else if(r<0) r=0;
// if(g>255) g=255; else if(g<0) g=0;
// if(b>255) b=255; else if(b<0) b=0;
int pixel = palette.getPixel(new RGB(r, g, b));
return pixel;
}
private synchronized void fireProfileDataChanged(final IPrimaryArrayWrapper data,
final int dw, final int dh) {
if (profileListeners.size() <= 0)
return;
double[] xProfileData = calculateXProfileData(data, dw, dh);
double[] yProfileData = calculateYProfileData(data, dw, dh);
for (IProfileDataChangeLisenter lisenter : profileListeners)
lisenter.profileDataChanged(xProfileData, yProfileData,
xAxis.getRange(), yAxis.getRange());
}
/**
* @return the colorMap
*/
public ColorMap getColorMap() {
return colorMap;
}
/**
* @return the cropBottom
*/
public int getCropBottom() {
return cropBottom;
}
/**
* @return the cropLeft
*/
public int getCropLeft() {
return cropLeft;
}
/**
* @return the cropRigth
*/
public int getCropRight() {
return cropRight;
}
/**
* @return the cropTop
*/
public int getCropTop() {
return cropTop;
}
public double[] getDataArray() {
double[] data = new double[dataArray.getSize()];
for(int i=0; i<dataArray.getSize(); i++){
data[i]=dataArray.get(i);
}
return data;
}
/**
* @return the dataHeight
*/
public int getDataHeight() {
return dataHeight;
}
/**
* @return the dataWidth
*/
public int getDataWidth() {
return dataWidth;
}
public GraphArea getGraphArea() {
return graphArea;
}
/**
* @return the two dimension insets (cropped_width, cropped_height) of graph area
*/
public Dimension getGraphAreaInsets() {
int width = getInsets().getWidth();
int height = getInsets().getHeight();
if(colorMapRamp.isVisible())
width += (colorMapRamp.getPreferredSize(
getClientArea().width, getClientArea().height).width + GAP);
if(yAxis.isVisible()){
width += yAxis.getPreferredSize(getClientArea().width, getClientArea().height).width;
height += yAxis.getMargin();
if(!xAxis.isVisible())
height += yAxis.getMargin();
}
if(xAxis.isVisible()){
height += xAxis.getPreferredSize(getClientArea().width, getClientArea().height).height;
if(!colorMapRamp.isVisible())
width += xAxis.getMargin();
if(!yAxis.isVisible())
width += xAxis.getMargin();
}
return new Dimension(width, height);
}
/**
* @return the max
*/
public double getMax() {
return max;
}
/**
* @return the min
*/
public double getMin() {
return min;
}
/**
* @return the xAxis
*/
public final Axis getXAxis() {
return xAxis;
}
/**
* @return the yAxis
*/
public final Axis getYAxis() {
return yAxis;
}
/**
* @return true if the input data is in RGB mode. For example, the input data is a 1D array of
* [RGBRGBRGBRGB...]
*/
public boolean isInRGBMode() {
return inRGBMode;
}
/**
* @return the runMode
*/
public boolean isRunMode() {
return runMode;
}
public boolean isShowRamp(){
return colorMapRamp.isVisible();
}
@Override
protected void layout() {
Rectangle clientArea = getClientArea().getCopy();
Rectangle yAxisBounds = null, xAxisBounds = null, rampBounds;
if(yAxis.isVisible()){
Dimension yAxisSize = yAxis.getPreferredSize(clientArea.width, clientArea.height);
yAxisBounds = new Rectangle(clientArea.x, clientArea.y,
yAxisSize.width, yAxisSize.height); // the height is not correct for now
clientArea.x += yAxisSize.width;
clientArea.y += yAxis.getMargin();
clientArea.height -= xAxis.isVisible()? yAxis.getMargin() : 2*yAxis.getMargin()-1;
clientArea.width -= yAxisSize.width;
}
if(xAxis.isVisible()){
Dimension xAxisSize = xAxis.getPreferredSize(clientArea.width, clientArea.height);
xAxisBounds = new Rectangle((yAxis.isVisible() ? yAxisBounds.x + yAxisBounds.width - xAxis.getMargin()-1 : clientArea.x),
clientArea.y + clientArea.height - xAxisSize.height,
xAxisSize.width, xAxisSize.height); // the width is not correct for now
clientArea.height -= xAxisSize.height;
//re-adjust yAxis height
if(yAxis.isVisible()){
yAxisBounds.height -= (xAxisSize.height - yAxis.getMargin());
}else{
clientArea.x +=xAxis.getMargin();
clientArea.width -= xAxis.getMargin()-1;
}
}
if(colorMapRamp.isVisible()){
Dimension rampSize = colorMapRamp.getPreferredSize(clientArea.width, clientArea.height);
rampBounds = new Rectangle(clientArea.x + clientArea.width - rampSize.width, clientArea.y,
rampSize.width, clientArea.height);
colorMapRamp.setBounds(rampBounds);
clientArea.width -= (rampSize.width + GAP);
//re-adjust xAxis width
if(xAxis.isVisible())
if(yAxis.isVisible())
xAxisBounds.width -=(rampSize.width + GAP - 2*xAxis.getMargin());
else
xAxisBounds.width -= (rampSize.width + GAP - xAxis.getMargin());
}else{
//re-adjust xAxis width
if(xAxis.isVisible()){
if(yAxis.isVisible())
xAxisBounds.width += xAxis.getMargin();
clientArea.width -= xAxis.getMargin();
}
}
if(yAxis.isVisible()){
yAxis.setBounds(yAxisBounds);
}
if(xAxis.isVisible()){
xAxis.setBounds(xAxisBounds);
}
graphArea.setBounds(clientArea);
super.layout();
}
/**
* @param colorMap the colorMap to set
*/
public final void setColorMap(ColorMap colorMap) {
if(colorMap == null)
return;
this.colorMap = colorMap;
colorMapRamp.setColorMap(colorMap);
dataDirty = true;
repaint();
}
/**
* @param cropBottom the cropBottom to set
*/
public final void setCropBottom(int cropBottom) {
if(cropBottom < 0 || cropBottom + cropTop > dataHeight)
throw new IllegalArgumentException();
if(this.cropBottom == cropBottom)
return;
this.cropBottom = cropBottom;
dataDirty = true;
updateCroppedDataSize();
repaint();
}
/**
* @param cropLeft the cropLeft to set
*/
public final void setCropLeft(int cropLeft) {
if(cropLeft <0 || cropLeft + cropRight > dataWidth)
throw new IllegalArgumentException();
if(this.cropLeft == cropLeft)
return;
this.cropLeft = cropLeft;
dataDirty = true;
updateCroppedDataSize();
repaint();
}
/**
* @param cropRight the cropRigth to set
*/
public final void setCropRight(int cropRight) {
if(cropRight < 0 || cropRight + cropLeft > dataWidth)
throw new IllegalArgumentException();
if(this.cropRight == cropRight)
return;
this.cropRight = cropRight;
dataDirty = true;
updateCroppedDataSize();
repaint();
}
/**
* @param cropTop the cropTop to set
*/
public final void setCropTop(int cropTop) {
if(cropTop < 0 || cropTop + cropBottom > dataHeight)
throw new IllegalArgumentException();
if(this.cropTop == cropTop)
return;
this.cropTop = cropTop;
dataDirty = true;
updateCroppedDataSize();
repaint();
}
/**Set the double[] data array for the intensity graph. It must be called in UI thread.
* Warning: for big image for example 1024*768, it may takes several milliseconds (10-50ms)
* to paint the image. If this is called too fast that exceeds the painting capability,
* it may cause memory leaking.
* @param data the dataArray to set
*
*/
public final void setDataArray(double[] data) {
if(dataArray instanceof DoubleArrayWrapper){
((DoubleArrayWrapper)dataArray).setData(data);
}else
dataArray = new DoubleArrayWrapper(data);
setDataArray(dataArray);
}
/**Set the short[] data array for the intensity graph. It must be called in UI thread.
* Warning: for big image for example 1024*768, it may takes several milliseconds (10-50ms)
* to paint the image. If this is called too fast that exceeds the painting capability,
* it may cause memory leaking.
* @param data the dataArray to set
*
*/
public final void setDataArray(short[] data) {
if(dataArray instanceof ShortArrayWrapper){
((ShortArrayWrapper)dataArray).setData(data);
}else
dataArray = new ShortArrayWrapper(data);
setDataArray(dataArray);
}
/**Set the byte[] data array for the intensity graph. It must be called in UI thread.
* Warning: for big image for example 1024*768, it may takes several milliseconds (10-50ms)
* to paint the image. If this is called too fast that exceeds the painting capability,
* it may cause memory leaking.
* @param data the dataArray to set
*
*/
public final void setDataArray(byte[] data) {
if(dataArray instanceof ByteArrayWrapper){
((ByteArrayWrapper)dataArray).setData(data);
}else
dataArray = new ByteArrayWrapper(data);
setDataArray(dataArray);
}
/**Set the int[] data array for the intensity graph. It must be called in UI thread.
* Warning: for big image for example 1024*768, it may takes several milliseconds (10-50ms)
* to paint the image. If this is called too fast that exceeds the painting capability,
* it may cause memory leaking.
* @param data the dataArray to set
*
*/
public final void setDataArray(int[] data) {
if(dataArray instanceof IntArrayWrapper){
((IntArrayWrapper)dataArray).setData(data);
}else
dataArray = new IntArrayWrapper(data);
setDataArray(dataArray);
}
/**Set the long[] data array for the intensity graph. It must be called in UI thread.
* Warning: for big image for example 1024*768, it may takes several milliseconds (10-50ms)
* to paint the image. If this is called too fast that exceeds the painting capability,
* it may cause memory leaking.
* @param data the dataArray to set
*
*/
public final void setDataArray(long[] data) {
if(dataArray instanceof LongArrayWrapper){
((LongArrayWrapper)dataArray).setData(data);
}else
dataArray = new LongArrayWrapper(data);
setDataArray(dataArray);
}
/**Set the float[] data array for the intensity graph. It must be called in UI thread.
* Warning: for big image for example 1024*768, it may takes several milliseconds (10-50ms)
* to paint the image. If this is called too fast that exceeds the painting capability,
* it may cause memory leaking.
* @param data the dataArray to set
*
*/
public final void setDataArray(float[] data) {
if(dataArray instanceof FloatArrayWrapper){
((FloatArrayWrapper)dataArray).setData(data);
}else
dataArray = new FloatArrayWrapper(data);
setDataArray(dataArray);
}
/**Set the data array wrapper for the intensity graph. It must be called in UI thread.
* Warning: for big image for example 1024*768, it may takes several milliseconds (10-50ms)
* to paint the image. If this is called too fast that exceeds the painting capability,
* it may cause memory leaking.
* @param data the dataArray to set
*
*/
public synchronized final void setDataArray(IPrimaryArrayWrapper dataWrapper){
dataArray = dataWrapper;
croppedDataArray = null;
dataDirty = true;
graphArea.repaint();
}
/**
* @param dataHeight the dataHeight to set
*/
public final void setDataHeight(int dataHeight) {
if(dataHeight <0|| dataWidth * dataHeight > MAX_ARRAY_SIZE || dataWidth * dataHeight < 0)
throw new IllegalArgumentException();
if(this.dataHeight == dataHeight)
return;
this.dataHeight = dataHeight;
updateCroppedDataSize();
dataDirty = true;
repaint();
}
/**
* @param dataWidth the dataWidth to set
*/
public final void setDataWidth(int dataWidth) {
if(dataWidth < 0 || dataWidth * dataHeight > MAX_ARRAY_SIZE || dataWidth * dataHeight < 0)
throw new IllegalArgumentException();
if(this.dataWidth == dataWidth)
return;
this.dataWidth = dataWidth;
updateCroppedDataSize();
dataDirty = true;
repaint();
}
/**Set if the input data is in RGB mode. For example, the input data is a 1D array of
* [RGBRGBRGBRGB...]. If it is true, the color of the pixel will come from the
* data directly and the color map will be ignored.
*
* @param inRGBMode true if the input data in RGB mode.
*/
public synchronized void setInRGBMode(boolean inRGBMode) {
if(isInRGBMode() == inRGBMode)
return;
if(!isInRGBMode()){
if(savedShowRamp == null)
savedShowRamp = isShowRamp();
colorMapRamp.setVisible(false);
}else if(savedShowRamp != null)
colorMapRamp.setVisible(savedShowRamp);
this.inRGBMode = inRGBMode;
dataDirty = true;
repaint();
}
/**
* @param max the max to set
*/
public final void setMax(double max) {
if(this.max == max)
return;
this.max = max;
colorMapRamp.setMax(max);
dataDirty = true;
repaint();
}
@Override
public void setFont(Font f) {
super.setFont(f);
colorMapRamp.setFont(f);
}
/**
* @param min the min to set
*/
public final void setMin(double min) {
if(this.min == min)
return;
this.min = min;
colorMapRamp.setMin(min);
dataDirty = true;
repaint();
}
/**Set color of ROI figures.
* @param roiColor
*/
public void setROIColor(Color roiColor) {
this.roiColor = roiColor;
for(ROIFigure f : roiMap.values())
f.setROIColor(roiColor);
}
public Color getRoiColor() {
return roiColor;
}
public void setROIDataBounds(String name, int xIndex, int yIndex, int width, int height){
if(roiMap.containsKey(name))
roiMap.get(name).setROIDataBounds(xIndex, yIndex, width, height);
else
throw new IllegalArgumentException(name + " is not an existing ROI");
}
/**
* @param runMode the runMode to set
*/
public void setRunMode(boolean runMode) {
this.runMode = runMode;
}
public void setShowRamp(boolean show){
if(isShowRamp() == show)
return;
if(!isInRGBMode()){
colorMapRamp.setVisible(show);
}
savedShowRamp = show;
dataDirty = true;
revalidate();
}
private void zoom(){
double t1, t2;
if(xAxisRange == null || yAxisRange == null){
xAxisRange = xAxis.getRange();
yAxisRange = yAxis.getRange();
}
if(originalCrop == null){
originalCrop = new Rectangle(cropLeft, cropTop, cropRight, cropBottom);
}
Point leftTop = graphArea.getDataLocation(
Math.min(start.x, end.x), Math.min(start.y, end.y));
Point rightBottom = graphArea.getDataLocation(
Math.max(start.x, end.x), Math.max(start.y, end.y));
if(leftTop == null || rightBottom == null || leftTop.equals(rightBottom))
return;
int toBeCropLeft = cropLeft + leftTop.x;
int toBeCropTop = cropTop + leftTop.y;
int toBeCropRight = cropRight + croppedDataWidth - rightBottom.x;
int toBeCropBottom = cropBottom + croppedDataHeight - rightBottom.y;
if(toBeCropLeft + toBeCropRight >= dataWidth ||
toBeCropBottom + toBeCropTop >=dataHeight)
return;
setCropLeft(toBeCropLeft);
setCropTop(toBeCropTop);
setCropRight(toBeCropRight);
setCropBottom(toBeCropBottom);
graphArea.repaint();
t1 = xAxis.getPositionValue(start.x, false);
t2 = xAxis.getPositionValue(end.x, false);
xAxis.setRange(t1, t2);
t1 = yAxis.getPositionValue(start.y, false);
t2 = yAxis.getPositionValue(end.y, false);
yAxis.setRange(t1, t2);
}
@Override
public BeanInfo getBeanInfo() throws IntrospectionException {
return new DefaultWidgetIntrospector().getBeanInfo(this.getClass());
}
/**
* @return the colorDepth
*/
public ColorDepth getColorDepth() {
return colorDepth;
}
/**Set Color depth of the image.
* @param colorDepth the colorDepth to set
*/
public void setColorDepth(ColorDepth colorDepth) {
this.colorDepth = colorDepth;
dataDirty = true;
repaint();
}
/**If it is profiling on single pixel.
* @return the isSinglePixelProfiling
*/
public boolean isSingleLineProfiling() {
return isSingleLineProfiling;
}
/**Profile on single pixel.
* @param isSingleLineProfiling the isSinglePixelProfiling to set
*/
public void setSingleLineProfiling(boolean isSingleLineProfiling) {
if(isSingleLineProfiling() == isSingleLineProfiling)
return;
this.isSingleLineProfiling = isSingleLineProfiling;
graphArea.setSinglePixelProfiling(isSingleLineProfiling);
}
public String getPixelInfo(int xIndex, int yIndex,
double xCoordinate, double yCoordinate, double pixelValue){
String result = "";
if(pixelInfoProviders == null)
return result;
for(IPixelInfoProvider p: pixelInfoProviders){
result += " " + p.getPixelInfo(xIndex, yIndex, xCoordinate, yCoordinate, pixelValue);
}
return result;
}
/**
*
*/
protected void updateCroppedDataSize() {
croppedDataWidth = dataWidth - cropLeft - cropRight;
croppedDataHeight = dataHeight - cropTop - cropBottom;
if(croppedDataSizeListeners != null){
for(ICroppedDataSizeListener listener : croppedDataSizeListeners){
listener.croppedDataSizeChanged(croppedDataWidth, croppedDataHeight);
}
}
}
}