/******************************************************************************* * 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.acquire.dockable.components.actions.rectify; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.RenderedImage; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.acquire.dockable.components.AcquireActionButtonsPanel; import org.weasis.acquire.dockable.components.actions.AbstractAcquireAction; import org.weasis.acquire.dockable.components.actions.AcquireActionPanel; import org.weasis.acquire.explorer.AcquireImageInfo; import org.weasis.acquire.graphics.CropRectangleGraphic; import org.weasis.core.api.gui.util.GeomUtil; import org.weasis.core.api.gui.util.JMVUtils; import org.weasis.core.api.image.FlipOp; import org.weasis.core.api.image.OpManager; import org.weasis.core.api.image.RotationOp; import org.weasis.core.api.media.data.ImageElement; import org.weasis.core.api.media.data.TagW; import org.weasis.core.ui.editor.image.Panner; import org.weasis.core.ui.editor.image.ViewCanvas; import org.weasis.core.ui.model.AbstractGraphicModel; import org.weasis.core.ui.model.graphic.imp.area.RectangleGraphic; import org.weasis.core.ui.model.layer.imp.RenderedImageLayer; import org.weasis.core.ui.model.utils.exceptions.InvalidShapeException; /** * * @author Yannick LARVOR * @version 2.5.0 * @since 2.5.0 - 2016-04-08 - ylar - Creation * */ public class RectifyAction extends AbstractAcquireAction { private static final Logger LOGGER = LoggerFactory.getLogger(RectifyAction.class); private static final CropRectangleGraphic CROP_RECTANGLE_GRAPHIC = new CropRectangleGraphic(); private RectangleGraphic currentCropArea; public RectifyAction(AcquireActionButtonsPanel panel) { super(panel); } protected void updateCropGraphic() { AcquireImageInfo imageInfo = getImageInfo(); ViewCanvas<ImageElement> view = getView(); RenderedImage img = view.getSourceImage(); if (img != null) { Rectangle2D modelArea = view.getViewModel().getModelArea(); Rectangle2D area = Optional.ofNullable((Rectangle2D) imageInfo.getNextValues().getCropZone()).orElse(modelArea); try { if (currentCropArea == null) { currentCropArea = CROP_RECTANGLE_GRAPHIC.copy().buildGraphic(area); } else { currentCropArea.buildGraphic(area); } if (!view.getGraphicManager().getModels().contains(currentCropArea)) { AbstractGraphicModel.addGraphicToModel(view, currentCropArea); } currentCropArea.setSelected(true); GeomUtil.growRectangle(modelArea, 15); double viewportWidth = view.getJComponent().getWidth() - 1.0; double viewportHeight = view.getJComponent().getHeight() - 1.0; view.zoom(Math.min(viewportWidth / modelArea.getWidth(), viewportHeight / modelArea.getHeight())); } catch (InvalidShapeException e) { LOGGER.error("Build crop graphic", e); //$NON-NLS-1$ } } } private static Rectangle2D adaptToValidateCropArea(ViewCanvas<ImageElement> view, Rectangle2D area) { AffineTransform transform = AffineTransform.getScaleInstance(1.0, 1.0); buildAffineTransform(transform, view.getDisplayOpManager(), view.getViewModel().getModelArea(), view.getImageLayer().getOffset()); Point2D pMin = new Point2D.Double(area.getMinX(), area.getMinY()); Point2D pMax = new Point2D.Double(area.getMaxX(), area.getMaxY()); transform.transform(pMin, pMin); transform.transform(pMax, pMax); Rectangle2D rect = new Rectangle2D.Double(); rect.setFrameFromDiagonal(pMin, pMax); return rect; } static Rectangle adaptToinitCropArea(Rectangle2D area) { if (area == null) { return null; } ViewCanvas<ImageElement> view = getView(); AffineTransform transform = AffineTransform.getScaleInstance(1.0, 1.0); buildAffineTransform(transform, view.getDisplayOpManager(), view.getViewModel().getModelArea(), null); Point2D pMin = new Point2D.Double(area.getMinX(), area.getMinY()); Point2D pMax = new Point2D.Double(area.getMaxX(), area.getMaxY()); try { transform = transform.createInverse(); transform.transform(pMin, pMin); transform.transform(pMax, pMax); } catch (NoninvertibleTransformException e) { LOGGER.error("Create inverse transformation", e); //$NON-NLS-1$ } Rectangle2D rect = new Rectangle2D.Double(); rect.setFrameFromDiagonal(pMin, pMax); return rect.getBounds(); } private static void buildAffineTransform(AffineTransform transform, OpManager dispOp, Rectangle2D modelArea, Point offset) { 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) { rotationAngle = (rotationAngle + 720) % 360; if (flip != null && flip) { rotationAngle = 360 - rotationAngle; } transform.rotate(Math.toRadians(rotationAngle), 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); transform.scale(-1.0, 1.0); transform.translate(-modelArea.getWidth(), 0.0); } if (offset != null) { // TODO not consistent with image coordinates after crop transform.translate(-offset.getX(), -offset.getY()); } } @Override public boolean cancel() { boolean doCancel = super.cancel(); updateCropGraphic(); return doCancel; } @Override public void validate(AcquireImageInfo imageInfo, ViewCanvas<ImageElement> view) { imageInfo.removeLayer(view); this.centralPanel.restoreLastAction(); if (view.getImageLayer() instanceof RenderedImageLayer && currentCropArea != null) { imageInfo.getCurrentValues().setCropZone(null); // Force dirty value, rotation is always apply in post // process imageInfo.getNextValues() .setCropZone(adaptToValidateCropArea(view, currentCropArea.getShape().getBounds()).getBounds()); view.getDisplayOpManager().setParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE, 0); view.getDisplayOpManager().setParamValue(FlipOp.OP_NAME, FlipOp.P_FLIP, false); imageInfo.applyPostProcess(view); view.getImage().setTag(TagW.ThumbnailPath, null); Panner<?> panner = view.getPanner(); if (panner != null) { panner.updateImage(); } } } @Override public boolean reset() { boolean doReset = super.reset(); updateCropGraphic(); return doReset; } public RectangleGraphic getCurrentCropArea() { return currentCropArea; } public void updateCropDisplay() { Optional.ofNullable(currentCropArea).map(CropRectangleGraphic.class::cast).ifPresent(c -> { c.updateCropDisplay(getImageInfo()); updateCropGraphic(); }); } @Override public AcquireActionPanel newCentralPanel() { return new RectifyPanel(this); } }