/******************************************************************************* * Copyright (c) 2016 Weasis Team and others. * 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 * * Contributors: * Nicolas Roduit - initial API and implementation *******************************************************************************/ package org.weasis.core.ui.editor.image; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Stroke; import java.awt.event.InputEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Rectangle2D; import java.awt.geom.RectangularShape; import java.awt.image.RenderedImage; import java.util.HashMap; import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import javax.swing.UIManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.core.api.gui.util.ActionState; import org.weasis.core.api.gui.util.ActionW; import org.weasis.core.api.gui.util.JMVUtils; import org.weasis.core.api.gui.util.MouseActionAdapter; import org.weasis.core.api.gui.util.SliderChangeListener; import org.weasis.core.api.gui.util.ToggleButtonListener; import org.weasis.core.api.image.FlipOp; import org.weasis.core.api.image.ImageOpEvent; import org.weasis.core.api.image.ImageOpNode; import org.weasis.core.api.image.ImageOpNode.Param; import org.weasis.core.api.image.OpManager; import org.weasis.core.api.image.RotationOp; import org.weasis.core.api.image.SimpleOpManager; import org.weasis.core.api.image.ZoomOp; import org.weasis.core.api.image.util.ImageLayer; import org.weasis.core.api.media.data.ImageElement; import org.weasis.core.ui.editor.image.SynchData.Mode; import org.weasis.core.ui.editor.image.dockable.MeasureTool; import org.weasis.core.ui.model.layer.imp.RenderedImageLayer; import org.weasis.core.ui.model.utils.ImageLayerChangeListener; import org.weasis.core.ui.pref.ZoomSetting; /** * The Class ZoomWin. * * @author Nicolas Roduit */ public class ZoomWin<E extends ImageElement> extends GraphicsPane implements ImageLayerChangeListener<E> { private static final long serialVersionUID = 3542710545706544620L; private static final Logger LOGGER = LoggerFactory.getLogger(ZoomWin.class); public enum SyncType { NONE, PARENT_IMAGE, PARENT_PARAMETERS }; private final DefaultView2d<E> view2d; private RectangularShape shape; private int borderOffset = 2; private Color lineColor; private Color backgroundColor; private Stroke stroke; public static final String SYNCH_CMD = "synchronize"; //$NON-NLS-1$ public static final String FREEZE_CMD = "freeze"; //$NON-NLS-1$ private PopUpMenuOnZoom popup = null; private final RenderedImageLayer<E> imageLayer; private final MouseHandler mouseHandler; private SimpleOpManager freezeOperations; private final HashMap<String, Object> freezeActionsInView = new HashMap<>(); public ZoomWin(DefaultView2d<E> view2d) { super(null); this.view2d = view2d; this.setOpaque(false); ImageViewerEventManager<E> manager = view2d.getEventManager(); this.imageLayer = new RenderedImageLayer<>(false); SimpleOpManager operations = imageLayer.getDisplayOpManager(); operations.addImageOperationAction(new ZoomOp()); operations.addImageOperationAction(new RotationOp()); operations.addImageOperationAction(new FlipOp()); ActionState zoomAction = manager.getAction(ActionW.LENSZOOM); if (zoomAction instanceof SliderChangeListener) { actionsInView.put(ActionW.ZOOM.cmd(), ((SliderChangeListener) zoomAction).getRealValue()); } this.popup = new PopUpMenuOnZoom(this); this.popup.setInvoker(this); this.setCursor(DefaultView2d.MOVE_CURSOR); ZoomSetting z = manager.getZoomSetting(); OpManager disOp = getDisplayOpManager(); disOp.setParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE, view2d.getDisplayOpManager().getParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE)); disOp.setParamValue(FlipOp.OP_NAME, FlipOp.P_FLIP, view2d.getDisplayOpManager().getParamValue(FlipOp.OP_NAME, FlipOp.P_FLIP)); actionsInView.put(ZoomOp.P_INTERPOLATION, z.getInterpolation()); disOp.setParamValue(ZoomOp.OP_NAME, ZoomOp.P_INTERPOLATION, z.getInterpolation()); actionsInView.put(SYNCH_CMD, z.isLensSynchronize()); actionsInView.put(ActionW.DRAWINGS.cmd(), z.isLensShowDrawings()); actionsInView.put(FREEZE_CMD, SyncType.NONE); Color bckColor = UIManager.getColor("Panel.background"); //$NON-NLS-1$ this.setLensDecoration(z.getLensLineWidth(), z.getLensLineColor(), bckColor, z.isLensRound()); this.setSize(z.getLensWidth(), z.getLensHeight()); this.setLocation(-1, -1); this.imageLayer.addLayerChangeListener(this); this.mouseHandler = new MouseHandler(); } public void setActionInView(String action, Object value) { Optional.ofNullable(action).ifPresent(a -> actionsInView.put(a, value)); } private void refreshZoomWin() { Point loc = getLocation(); if (loc.x == -1 && loc.y == -1) { centerZoomWin(); return; } Rectangle rect = view2d.getBounds(); rect.x = 0; rect.y = 0; Rectangle2D r = rect.createIntersection(getBounds()); if (r.getWidth() < 25.0 || r.getHeight() < 25.0) { centerZoomWin(); } } public void updateImage() { view2d.graphicManager.addGraphicChangeHandler(graphicsChangeHandler); imageLayer.setImage(view2d.getImage(), (OpManager) view2d.getActionValue(ActionW.PREPROCESSING.cmd())); getViewModel().setModelArea(view2d.getViewModel().getModelArea()); SyncType type = (SyncType) actionsInView.get(ZoomWin.FREEZE_CMD); if (SyncType.PARENT_PARAMETERS.equals(type)) { freezeOperations.setFirstNode(imageLayer.getSourceRenderedImage()); freezeOperations.handleImageOpEvent( new ImageOpEvent(ImageOpEvent.OpEvent.ImageChange, view2d.getSeries(), view2d.getImage(), null)); freezeOperations.process(); } } public void showLens(boolean val) { if (val) { updateImage(); refreshZoomWin(); updateZoom(); enableMouseListener(); setVisible(true); } else { setVisible(false); view2d.graphicManager.removeGraphicChangeHandler(graphicsChangeHandler); disableMouseAndKeyListener(); } } public void centerZoomWin() { int magPosx = (view2d.getWidth() / 2) - (getWidth() / 2); int magPosy = (view2d.getHeight() / 2) - (getHeight() / 2); setLocation(magPosx, magPosy); } public void hideZoom() { if (Objects.equals(view2d.getEventManager().getSelectedViewPane(), view2d)) { ActionState lens = view2d.getEventManager().getAction(ActionW.LENS); if (lens instanceof ToggleButtonListener) { ((ToggleButtonListener) lens).setSelected(false); } } } @Override public void paintComponent(Graphics g) { if (g instanceof Graphics2D) { draw((Graphics2D) g); } } protected void draw(Graphics2D g2d) { Stroke oldStroke = g2d.getStroke(); Paint oldColor = g2d.getPaint(); Shape oldClip = g2d.getClip(); g2d.clip(shape); g2d.setBackground(backgroundColor); drawBackground(g2d); double viewScale = getViewModel().getViewScale(); double offsetX = getViewModel().getModelOffsetX() * viewScale; double offsetY = getViewModel().getModelOffsetY() * viewScale; // Paint the visible area g2d.translate(-offsetX, -offsetY); // Set font size according to the view size g2d.setFont(MeasureTool.viewSetting.getFont()); imageLayer.drawImage(g2d); drawLayers(g2d, affineTransform, inverseTransform); g2d.translate(offsetX, offsetY); g2d.setClip(oldClip); g2d.setStroke(stroke); g2d.setPaint(lineColor); Rectangle bound = getBounds(); g2d.drawRect(bound.width - 12 - borderOffset, bound.height - 12 - borderOffset, 12, 12); g2d.draw(shape); g2d.setPaint(oldColor); g2d.setStroke(oldStroke); } public void drawLayers(Graphics2D g2d, AffineTransform transform, AffineTransform inverseTransform) { if ((Boolean) actionsInView.get(ActionW.DRAWINGS.cmd())) { view2d.getGraphicManager().draw(g2d, transform, inverseTransform, new Rectangle2D.Double(modelToViewLength(getViewModel().getModelOffsetX()), modelToViewLength(getViewModel().getModelOffsetY()), getWidth(), getHeight())); } } private void drawBackground(Graphics2D g2d) { g2d.clearRect(0, 0, getWidth(), getHeight()); } protected void updateAffineTransform() { Rectangle2D modelArea = getViewModel().getModelArea(); double viewScale = getViewModel().getViewScale(); affineTransform.setToScale(viewScale, viewScale); OpManager dispOp = getDisplayOpManager(); Boolean flip = JMVUtils.getNULLtoFalse(dispOp.getParamValue(FlipOp.OP_NAME, FlipOp.P_FLIP)); Integer rotationAngle = (Integer) dispOp.getParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE); if (rotationAngle != null && rotationAngle > 0) { if (flip != null && flip) { rotationAngle = 360 - rotationAngle; } affineTransform.rotate(rotationAngle * Math.PI / 180.0, modelArea.getWidth() / 2.0, modelArea.getHeight() / 2.0); } if (flip != null && flip) { // Using only one allows to enable or disable flip with the rotation action // case FlipMode.TOP_BOTTOM: // at = new AffineTransform(new double[] {1.0,0.0,0.0,-1.0}); // at.translate(0.0, -imageHt); // break; // case FlipMode.LEFT_RIGHT : // at = new AffineTransform(new double[] {-1.0,0.0,0.0,1.0}); // at.translate(-imageWid, 0.0); // break; // case FlipMode.TOP_BOTTOM_LEFT_RIGHT: // at = new AffineTransform(new double[] {-1.0,0.0,0.0,-1.0}); // at.translate(-imageWid, -imageHt); affineTransform.scale(-1.0, 1.0); affineTransform.translate(-getViewModel().getModelArea().getWidth(), 0.0); } Point offset = view2d.getImageLayer().getOffset(); if (offset != null) { affineTransform.translate(-offset.getX(), -offset.getY()); } try { inverseTransform.setTransform(affineTransform.createInverse()); } catch (NoninvertibleTransformException e) { LOGGER.error(e.getMessage(), e); } } public void setLensDecoration(int lineWidth, Color lineColor, Color backgroundColor, boolean roundShape) { this.borderOffset = lineWidth / 2 + 1; this.stroke = new BasicStroke(lineWidth); this.lineColor = lineColor; this.backgroundColor = backgroundColor; this.setBackground(backgroundColor); upateShape(roundShape); } @Override public void setSize(int width, int height) { shape.setFrame(borderOffset, borderOffset, width - borderOffset, height - borderOffset); super.setSize(width + borderOffset, height + borderOffset); } private void upateShape(boolean round) { if (round) { shape = new java.awt.geom.Ellipse2D.Double(getX(), getY(), getWidth(), getHeight()); } else { shape = new java.awt.geom.Rectangle2D.Double(getX(), getY(), getWidth(), getHeight()); } } public double getCenterX() { return view2d.viewToModelX(getX() + (getWidth() - 1) * 0.5); } public double getCenterY() { return view2d.viewToModelY(getY() + (getHeight() - 1) * 0.5); } @Override public void zoom(Double viewScale) { ImageOpNode node = imageLayer.getDisplayOpManager().getNode(ZoomOp.OP_NAME); E img = imageLayer.getSourceImage(); if (img != null && node != null) { node.setParam(Param.INPUT_IMG, getSourceImage()); node.setParam(ZoomOp.P_RATIO_X, viewScale * img.getRescaleX()); node.setParam(ZoomOp.P_RATIO_Y, viewScale * img.getRescaleY()); actionsInView.put(ActionW.ZOOM.cmd(), viewScale); super.zoom(getCenterX(), getCenterY(), Math.abs(viewScale)); imageLayer.updateDisplayOperations(); updateAffineTransform(); } } public void updateZoom() { double zoomFactor = (Boolean) actionsInView.get(SYNCH_CMD) ? view2d.getViewModel().getViewScale() : (Double) actionsInView.get(ActionW.ZOOM.cmd()); zoom(zoomFactor); } protected RenderedImage getSourceImage() { SyncType type = (SyncType) actionsInView.get(ZoomWin.FREEZE_CMD); if (SyncType.PARENT_PARAMETERS.equals(type) || SyncType.PARENT_IMAGE.equals(type)) { return freezeOperations.getLastNodeOutputImage(); } // return the image before the zoom operation from the parent view ImageOpNode node = view2d.getImageLayer().getDisplayOpManager().getNode(ZoomOp.OP_NAME); if (node != null) { return (RenderedImage) node.getParam(Param.INPUT_IMG); } return view2d.getImageLayer().getDisplayOpManager().getLastNodeOutputImage(); } public void setFreezeImage(SyncType type) { actionsInView.put(ZoomWin.FREEZE_CMD, type); if (Objects.isNull(type) || SyncType.NONE.equals(type)) { freezeActionsInView.clear(); freezeOperations = null; actionsInView.put(ZoomWin.FREEZE_CMD, SyncType.NONE); } else { freezeParentParameters(); } imageLayer.updateDisplayOperations(); updateZoom(); } void freezeParentParameters() { SimpleOpManager pManager = view2d.getImageLayer().getDisplayOpManager(); freezeActionsInView.clear(); view2d.copyActionWState(freezeActionsInView); freezeOperations = new SimpleOpManager(); for (ImageOpNode op : pManager.getOperations()) { if (ZoomOp.OP_NAME.equals(op.getParam(Param.NAME))) { break; } ImageOpNode operation = op.copy(); freezeOperations.addImageOperationAction(operation); } freezeOperations.setFirstNode(imageLayer.getSourceRenderedImage()); freezeOperations.process(); } class MouseHandler extends MouseAdapter { private Point pickPoint = null; private int pickWidth; private int pickHeight; private int cursor; @Override public void mousePressed(MouseEvent e) { ImageViewerPlugin<E> pane = view2d.getEventManager().getSelectedView2dContainer(); if (pane == null) { return; } if (pane.isContainingView(view2d)) { pane.setSelectedImagePane(view2d); } if (e.isPopupTrigger()) { popup.enableMenuItem(); popup.show(e.getComponent(), e.getX(), e.getY()); } pickPoint = e.getPoint(); pickWidth = getWidth(); pickHeight = getHeight(); cursor = getCursor(e); } @Override public void mouseReleased(MouseEvent mouseevent) { pickPoint = null; if (mouseevent.isPopupTrigger()) { popup.enableMenuItem(); popup.show(mouseevent.getComponent(), mouseevent.getX(), mouseevent.getY()); } else if (mouseevent.getClickCount() == 2) { ImageViewerEventManager<E> manager = view2d.getEventManager(); ActionState zoomAction = manager.getAction(ActionW.LENSZOOM); if (zoomAction instanceof SliderChangeListener) { ((SliderChangeListener) zoomAction).setRealValue(view2d.getViewModel().getViewScale()); } } } @Override public void mouseDragged(MouseEvent e) { int mods = e.getModifiers(); if (pickPoint != null && (mods & InputEvent.BUTTON1_MASK) != 0) { Point p = e.getPoint(); int dx = p.x - pickPoint.x; int dy = p.y - pickPoint.y; switch (cursor) { case Cursor.SE_RESIZE_CURSOR: int nw = pickWidth + dx; int nh = pickHeight + dy; nw = nw < 50 ? 50 : nw > 500 ? 500 : nw; nh = nh < 50 ? 50 : nh > 500 ? 500 : nh; setSize(nw, nh); zoom(getCenterX(), getCenterY(), getViewModel().getViewScale()); break; default: setLocation(getX() + dx, getY() + dy); zoom(getCenterX(), getCenterY(), getViewModel().getViewScale()); } setCursor(Cursor.getPredefinedCursor(cursor)); } } @Override public void mouseMoved(MouseEvent me) { setCursor(Cursor.getPredefinedCursor(getCursor(me))); } @Override public void mouseExited(MouseEvent mouseEvent) { setCursor(Cursor.getDefaultCursor()); } public int getCursor(MouseEvent me) { Component c = me.getComponent(); int w = c.getWidth(); int h = c.getHeight(); Rectangle rect = new Rectangle(w - 12 - borderOffset, h - 12 - borderOffset, 12, 12); if (rect.contains(me.getPoint())) { return Cursor.SE_RESIZE_CURSOR; } return Cursor.MOVE_CURSOR; } } public void disableMouseAndKeyListener() { this.removeMouseListener(mouseHandler); this.removeMouseMotionListener(mouseHandler); this.removeMouseWheelListener((MouseActionAdapter) view2d.getEventManager().getAction(ActionW.LENSZOOM)); } public void enableMouseListener() { disableMouseAndKeyListener(); this.addMouseListener(mouseHandler); this.addMouseMotionListener(mouseHandler); this.addMouseWheelListener((MouseActionAdapter) view2d.getEventManager().getAction(ActionW.LENSZOOM)); } public ViewCanvas<E> getView2d() { return view2d; } @Override public void handleLayerChanged(ImageLayer layer) { repaint(); } public void setCommandFromParentView(String command, Object value) { if (ActionW.SYNCH.cmd().equals(command) && value instanceof SynchEvent) { if (!(value instanceof SynchCineEvent)) { SynchData synchData = (SynchData) view2d.getActionValue(ActionW.SYNCH_LINK.cmd()); if (synchData != null && Mode.NONE.equals(synchData.getMode())) { return; } for (Entry<String, Object> entry : ((SynchEvent) value).getEvents().entrySet()) { String cmd = entry.getKey(); if (synchData != null && !synchData.isActionEnable(cmd)) { continue; } applyCommandFromParentView(cmd, entry.getValue()); } } } else { applyCommandFromParentView(command, value); } } protected void applyCommandFromParentView(String command, Object value) { OpManager dispOp = getDisplayOpManager(); if (command.equals(ActionW.ROTATION.cmd())) { if (dispOp.setParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE, view2d.getDisplayOpManager().getParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE))) { refreshZoomWin(); } } else if (command.equals(ActionW.FLIP.cmd())) { if (dispOp.setParamValue(FlipOp.OP_NAME, FlipOp.P_FLIP, view2d.getDisplayOpManager().getParamValue(FlipOp.OP_NAME, FlipOp.P_FLIP))) { refreshZoomWin(); } } else if (command.equals(ActionW.PROGRESSION.cmd())) { updateImage(); refreshZoomWin(); } } public OpManager getDisplayOpManager() { return imageLayer.getDisplayOpManager(); } }