use of org.csstudio.javafx.rtplot.RegionOfInterest in project org.csstudio.display.builder by kasemir.
the class ImagePlot method mouseDown.
/**
* onMousePressed
*/
private void mouseDown(final MouseEvent e) {
// Don't start mouse actions when user invokes context menu
if (!e.isPrimaryButtonDown() || (PlatformInfo.is_mac_os_x && e.isControlDown()))
return;
// -> User clicked outside of tracker. Remove it.
if (roi_tracker != null) {
removeROITracker();
// User needs to click again for that.
return;
}
// Select any tracker
final Point2D current = new Point2D(e.getX(), e.getY());
for (RegionOfInterest roi : rois) if (roi.isVisible() && roi.isInteractive()) {
final Rectangle rect = roiToScreen(roi);
if (rect.contains(current.getX(), current.getY())) {
// Check if complete ROI is visible,
// because otherwise tracker would extend beyond the
// current image viewport
final Rectangle2D image_rect = GraphicsUtils.convert(image_area);
if (image_rect.contains(rect.x, rect.y, rect.width, rect.height)) {
roi_tracker = new Tracker(image_rect);
roi_tracker.setPosition(rect.x, rect.y, rect.width, rect.height);
ChildCare.addChild(getParent(), roi_tracker);
final int index = rois.indexOf(roi);
roi_tracker.setListener((old_pos, new_pos) -> updateRoiFromScreen(index, new_pos));
return;
}
}
}
mouse_start = mouse_current = Optional.of(current);
final int clicks = e.getClickCount();
if (mouse_mode == MouseMode.NONE) {
if (crosshair) {
updateLocationInfo(e.getX(), e.getY());
requestRedraw();
}
} else if (mouse_mode == MouseMode.PAN) {
// Determine start of 'pan'
mouse_start_x_range = x_axis.getValueRange();
mouse_start_y_range = y_axis.getValueRange();
mouse_mode = MouseMode.PAN_PLOT;
} else if (mouse_mode == MouseMode.ZOOM_IN && clicks == 1) {
// Reset cursor from SIZE* to CROSS.
if (y_axis.getBounds().contains(current.getX(), current.getY())) {
mouse_mode = MouseMode.ZOOM_IN_Y;
PlotCursors.setCursor(this, mouse_mode);
} else if (image_area.contains(current.getX(), current.getY())) {
mouse_mode = MouseMode.ZOOM_IN_PLOT;
PlotCursors.setCursor(this, mouse_mode);
} else if (x_axis.getBounds().contains(current.getX(), current.getY())) {
mouse_mode = MouseMode.ZOOM_IN_X;
PlotCursors.setCursor(this, mouse_mode);
}
} else if ((mouse_mode == MouseMode.ZOOM_IN && clicks == 2) || mouse_mode == MouseMode.ZOOM_OUT)
zoomInOut(current.getX(), current.getY(), ZOOM_FACTOR);
}
use of org.csstudio.javafx.rtplot.RegionOfInterest in project org.csstudio.display.builder by kasemir.
the class ImageRepresentation method createROI.
private void createROI(final ROIWidgetProperty model_roi) {
final RegionOfInterest plot_roi = image_plot.addROI(model_roi.name().getValue(), JFXUtil.convert(model_roi.color().getValue()), model_roi.visible().getValue(), model_roi.interactive().getValue());
// Show/hide ROI as roi.visible() changes
model_roi.visible().addPropertyListener((prop, old, visible) -> {
plot_roi.setVisible(visible);
Platform.runLater(() -> image_plot.removeROITracker());
image_plot.requestUpdate();
});
// For now _not_ listening to runtime changes of roi.interactive() or roi.file() ...
// Listen to roi.x_value(), .. and update plot_roi
final WidgetPropertyListener<Double> model_roi_listener = (o, old, value) -> {
if (changing_roi)
return;
Rectangle2D region = plot_roi.getRegion();
region = new Rectangle2D(existingOrProperty(region.getMinX(), model_roi.x_value()), existingOrProperty(region.getMinY(), model_roi.y_value()), existingOrProperty(region.getWidth(), model_roi.width_value()), existingOrProperty(region.getHeight(), model_roi.height_value()));
changing_roi = true;
plot_roi.setRegion(region);
changing_roi = false;
image_plot.requestUpdate();
};
model_roi.x_value().addPropertyListener(model_roi_listener);
model_roi.y_value().addPropertyListener(model_roi_listener);
model_roi.width_value().addPropertyListener(model_roi_listener);
model_roi.height_value().addPropertyListener(model_roi_listener);
// Load image file (if there is one) on background thread
ModelThreadPool.getExecutor().execute(() -> loadROI_Image(plot_roi, model_roi));
}
use of org.csstudio.javafx.rtplot.RegionOfInterest in project org.csstudio.display.builder by kasemir.
the class ImagePlot method updateRoiFromScreen.
/**
* @param roi RegionOfInterest to move to new location
* @param screen_pos Screen position, will be converted into axes' values
*/
private void updateRoiFromScreen(final int index, final Rectangle2D screen_pos) {
final RegionOfInterest roi = rois.get(index);
// Convert screen position to axis values
final double x0 = x_axis.getValue((int) screen_pos.getMinX()), y0 = y_axis.getValue((int) screen_pos.getMinY()), x1 = x_axis.getValue((int) screen_pos.getMaxX()), y1 = y_axis.getValue((int) screen_pos.getMaxY());
final double x = Math.min(x0, x1);
final double y = Math.min(y0, y1);
final Rectangle2D region = new Rectangle2D(x, y, Math.abs(x1 - x0), Math.abs(y1 - y0));
roi.setRegion(region);
requestUpdate();
// Notify listener of ROI change
final RTImagePlotListener listener = plot_listener;
if (listener != null)
listener.changedROI(index, roi.getName(), roi.getRegion());
}
use of org.csstudio.javafx.rtplot.RegionOfInterest in project org.csstudio.display.builder by kasemir.
the class ImagePlot method addROI.
/**
* Add region of interest
* @param name
* @param color
* @param visible
* @param interactive
* @return {@link RegionOfInterest}
*/
public RegionOfInterest addROI(final String name, final javafx.scene.paint.Color color, final boolean visible, final boolean interactive) {
// Return a ROI that triggers a redraw as it's changed
final RegionOfInterest roi = new RegionOfInterest(name, color, visible, interactive, 0, 0, 10, 10) {
@Override
public void setImage(final Image image) {
super.setImage(image);
requestUpdate();
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
requestUpdate();
}
@Override
public void setRegion(Rectangle2D region) {
super.setRegion(region);
requestUpdate();
}
};
rois.add(roi);
return roi;
}
use of org.csstudio.javafx.rtplot.RegionOfInterest in project org.csstudio.display.builder by kasemir.
the class ImagePlot method updateImageBuffer.
/**
* Draw all components into image buffer
*/
@Override
protected BufferedImage updateImageBuffer() {
// Would like to use JFX WritableImage,
// but rendering problem on Linux (sandbox.ImageScaling),
// and no way to disable the color interpolation that 'smears'
// the scaled image.
// (http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8091877).
// So image is prepared in AWT and then converted to JFX
logger.log(Level.FINE, "updateImageBuffer");
final Rectangle area_copy = area;
if (area_copy.width <= 0 || area_copy.height <= 0)
return null;
final BufferUtil buffer = buffers.getBufferedImage(area_copy.width, area_copy.height);
if (buffer == null)
return null;
final BufferedImage image = buffer.getImage();
final Graphics2D gc = buffer.getGraphics();
gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gc.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
gc.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED);
gc.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
// Get safe copy of the data
// (not synchronized, i.e. width vs. data may be inconsistent,
// but at least data won't change within this method)
final int data_width = this.data_width, data_height = this.data_height;
final ListNumber numbers = this.image_data;
final boolean unsigned = this.unsigned_data;
double min = this.min, max = this.max;
final VImageType type = this.vimage_type;
final ColorMappingFunction color_mapping = this.color_mapping;
ToDoubleFunction<IteratorNumber> next_sample_func = IteratorNumber::nextDouble;
boolean isRGB = type == VImageType.TYPE_RGB1 || type == VImageType.TYPE_RGB2 || type == VImageType.TYPE_RGB3;
@SuppressWarnings("unchecked") final ToIntFunction<IteratorNumber>[] next_rgb = new ToIntFunction[3];
if (numbers != null) {
if (isRGB) {
if (numbers instanceof ArrayShort) {
if (unsigned) {
next_rgb[0] = (iter) -> getUShortForRGB(iter) << 8 & 0xFF0000;
next_rgb[1] = (iter) -> getUShortForRGB(iter) & 0xFF00;
next_rgb[2] = (iter) -> getUShortForRGB(iter) >>> 8;
} else {
next_rgb[0] = (iter) -> getShortForRGB(iter) << 8 & 0xFF0000;
next_rgb[1] = (iter) -> getShortForRGB(iter) & 0xFF00;
next_rgb[2] = (iter) -> getShortForRGB(iter) >>> 8;
}
}
if (numbers instanceof ArrayInt) {
if (unsigned) {
next_rgb[0] = (iter) -> getUIntForRGB(iter) >>> 8 & 0xFF0000;
next_rgb[1] = (iter) -> getUIntForRGB(iter) >>> 16 & 0xFF00;
next_rgb[2] = (iter) -> getUIntForRGB(iter) >>> 24;
} else {
next_rgb[0] = (iter) -> getIntForRGB(iter) >>> 8 & 0xFF0000;
next_rgb[1] = (iter) -> getIntForRGB(iter) >>> 16 & 0xFF00;
next_rgb[2] = (iter) -> getIntForRGB(iter) >>> 24;
}
} else {
if (!(numbers instanceof ArrayByte))
logger.log(Level.WARNING, "Cannot handle rgb1 image data of type " + numbers.getClass().getName());
if (unsigned) {
next_rgb[0] = (iter) -> getUByteForRGB(iter) << 16;
next_rgb[1] = (iter) -> getUByteForRGB(iter) << 8;
next_rgb[2] = (iter) -> getUByteForRGB(iter);
} else {
next_rgb[0] = (iter) -> getByteForRGB(iter) << 16;
next_rgb[1] = (iter) -> getByteForRGB(iter) << 8;
next_rgb[2] = (iter) -> getByteForRGB(iter);
}
}
} else // is not RGB
{
if (unsigned) {
if (numbers instanceof ArrayShort)
next_sample_func = ImagePlot::getUnsignedShort;
else if (numbers instanceof ArrayByte)
next_sample_func = ImagePlot::getUnsignedByte;
else if (numbers instanceof ArrayInt)
next_sample_func = ImagePlot::getUnsignedInt;
else
logger.log(Level.WARNING, "Cannot handle unsigned data of type " + numbers.getClass().getName());
}
if (autoscale) {
// Compute min..max before layout of color bar
final IteratorNumber iter = numbers.iterator();
min = Double.MAX_VALUE;
max = Double.NEGATIVE_INFINITY;
while (iter.hasNext()) {
final double sample = next_sample_func.applyAsDouble(iter);
if (sample > max)
max = sample;
if (sample < min)
min = sample;
}
logger.log(Level.FINE, "Autoscale range {0} .. {1}", new Object[] { min, max });
}
}
}
// If log, min needs to be 1
if (colorbar_axis.isLogarithmic() && min < 1.0)
min = 1;
colorbar_axis.setValueRange(min, max);
if (need_layout.getAndSet(false))
computeLayout(gc, area_copy, min, max);
// Fill with a 'background' color
gc.setColor(background);
gc.fillRect(0, 0, area_copy.width, area_copy.height);
if (numbers != null) {
// Paint the image
gc.setClip(image_area.x, image_area.y, image_area.width, image_area.height);
final Object image_or_error = !isRGB ? drawData(data_width, data_height, numbers, next_sample_func, min, max, color_mapping) : drawDataRGB(data_width, data_height, numbers, next_rgb, type);
if (image_or_error instanceof BufferedImage) {
final BufferedImage unscaled = (BufferedImage) image_or_error;
// Transform from full axis range into data range,
// using the current 'zoom' state of each axis
final LinearScreenTransform t = new LinearScreenTransform();
AxisRange<Double> zoomed = x_axis.getValueRange();
t.config(min_x, max_x, 0, data_width);
// Round down .. up to always cover the image_area
final int src_x1 = Math.max(0, (int) t.transform(zoomed.getLow()));
final int src_x2 = Math.min(data_width, (int) (t.transform(zoomed.getHigh()) + 1));
// Pixels of the image need to be aligned to their axis location,
// especially when zoomed way in and the pixels are huge.
// Turn pixel back into axis value, and then determine its destination on screen.
final int dst_x1 = x_axis.getScreenCoord(t.inverse(src_x1));
final int dst_x2 = x_axis.getScreenCoord(t.inverse(src_x2));
// For Y axis, min_y == bottom == data_height
zoomed = y_axis.getValueRange();
t.config(min_y, max_y, data_height, 0);
final int src_y1 = Math.max(0, (int) t.transform(zoomed.getHigh()));
final int src_y2 = Math.min(data_height, (int) (t.transform(zoomed.getLow()) + 1));
final int dst_y1 = y_axis.getScreenCoord(t.inverse(src_y1));
final int dst_y2 = y_axis.getScreenCoord(t.inverse(src_y2));
switch(interpolation) {
case NONE:
gc.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
break;
case INTERPOLATE:
gc.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
break;
default:
// If image is smaller than screen area, show the actual pixels
if ((src_x2 - src_x1) < image_area.width && (src_y2 - src_y1) < image_area.height)
gc.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
else
// If image is larger than screen area, use best possible interpolation
// to avoid artifacts from statistically picking some specific nearest neighbor
gc.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
}
gc.drawImage(unscaled, dst_x1, dst_y1, dst_x2, dst_y2, src_x1, src_y1, src_x2, src_y2, /* ImageObserver */
null);
} else {
gc.setColor(Color.RED);
gc.setFont(x_axis.label_font);
gc.drawString(Objects.toString(image_or_error), image_area.x + 10, image_area.y + 20);
}
gc.setClip(0, 0, area_copy.width, area_copy.height);
}
// Axes
y_axis.paint(gc, image_area);
x_axis.paint(gc, image_area);
// Color bar
if (colorbar_area != null) {
final BufferedImage bar = drawColorBar(min, max, color_mapping);
gc.drawImage(bar, colorbar_area.x, colorbar_area.y, colorbar_area.width, colorbar_area.height, null);
colorbar_axis.paint(gc, colorbar_area);
}
// ROI uses X axis font
gc.setFont(x_axis.label_font);
for (RegionOfInterest roi : rois) drawROI(gc, roi);
return image;
}
Aggregations