/******************************************************************************* * 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.model.layer.imp; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.RenderedImage; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Optional; import javax.media.jai.PlanarImage; import javax.media.jai.iterator.RandomIter; import javax.media.jai.iterator.RandomIterFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.core.api.gui.util.MathUtil; import org.weasis.core.api.image.ImageOpEvent; import org.weasis.core.api.image.OpEventListener; import org.weasis.core.api.image.OpManager; import org.weasis.core.api.image.SimpleOpManager; import org.weasis.core.api.image.ZoomOp; import org.weasis.core.api.image.measure.MeasurementsAdapter; import org.weasis.core.api.image.util.ImageLayer; import org.weasis.core.api.image.util.Unit; import org.weasis.core.api.media.data.ImageElement; import org.weasis.core.api.media.data.TagW; import org.weasis.core.ui.model.layer.Layer; import org.weasis.core.ui.model.layer.LayerType; import org.weasis.core.ui.model.utils.ImageLayerChangeListener; import org.weasis.core.ui.model.utils.imp.DefaultUUID; /** * The Class RenderedImageLayer. * * @author Nicolas Roduit */ public class RenderedImageLayer<E extends ImageElement> extends DefaultUUID implements Layer, ImageLayer<E> { private static final long serialVersionUID = -7071485066284475687L; private static final Logger LOGGER = LoggerFactory.getLogger(RenderedImageLayer.class); private final SimpleOpManager disOpManager; private final List<ImageLayerChangeListener<E>> listenerList; private final List<OpEventListener> opListeners; private OpManager preprocessing; private E sourceImage; private RandomIter readIterator; private boolean buildIterator = false; private RenderedImage displayImage; private Boolean visible = true; private boolean enableDispOperations = true; private Point offset; public RenderedImageLayer(boolean buildIterator) { this(null, buildIterator); } public RenderedImageLayer(SimpleOpManager disOpManager, boolean buildIterator) { this.disOpManager = Optional.ofNullable(disOpManager).orElseGet(SimpleOpManager::new); this.listenerList = new ArrayList<>(); this.opListeners = new ArrayList<>(); this.buildIterator = buildIterator; addEventListener(this.disOpManager); } public boolean isBuildIterator() { return buildIterator; } public void setBuildIterator(boolean buildIterator) { this.buildIterator = buildIterator; } @Override public RandomIter getReadIterator() { return readIterator; } @Override public E getSourceImage() { return sourceImage; } @Override public RenderedImage getSourceRenderedImage() { if (sourceImage != null) { return sourceImage.getImage(preprocessing); } return null; } @Override public RenderedImage getDisplayImage() { return displayImage; } public OpManager getPreprocessing() { return preprocessing; } public void setPreprocessing(OpManager preprocessing) { setImage(sourceImage, preprocessing); } @Override public SimpleOpManager getDisplayOpManager() { return disOpManager; } @Override public synchronized boolean isEnableDispOperations() { return enableDispOperations; } @Override public synchronized void setEnableDispOperations(boolean enabled) { this.enableDispOperations = enabled; if (enabled) { updateDisplayOperations(); } } @Override public void setVisible(Boolean visible) { this.visible = Optional.ofNullable(visible).orElse(getType().getVisible()); } @Override public Boolean getVisible() { return visible; } @Override public void setType(LayerType type) { // Do nothing } @Override public void setName(String graphicLayerName) { // Do nothing } @Override public String getName() { return getType().getDefaultName(); } @Override public String toString() { return getName(); } @Override public Integer getLevel() { return getType().getLevel(); } @Override public void setLevel(Integer i) { // Assume to be at the lowest level } @Override public AffineTransform getTransform() { return null; } @Override public void setTransform(AffineTransform transform) { // Does handle affine transform for image, already in operation manager } @Override public LayerType getType() { return LayerType.IMAGE; } @Override public boolean hasContent() { return getSourceImage() != null; } @Override public void setImage(E image, OpManager preprocessing) { boolean init = (image != null && !image.equals(this.sourceImage)) || (image == null && sourceImage != null); this.sourceImage = image; this.preprocessing = preprocessing; if (preprocessing != null || init) { disOpManager.setFirstNode(getSourceRenderedImage()); updateDisplayOperations(); } } public void drawImage(Graphics2D g2d) { // Get the clipping rectangle if (!visible || displayImage == null) { return; } Shape clip = g2d.getClip(); if (clip instanceof Rectangle2D) { Rectangle2D rect = new Rectangle2D.Double(displayImage.getMinX(), displayImage.getMinY(), displayImage.getWidth() - 1, displayImage.getHeight() - 1); rect = rect.createIntersection((Rectangle2D) clip); if (rect.isEmpty()) { return; } // Avoid to display one pixel outside the border line of a view. // rect.setRect(Math.ceil(rect.getX()), Math.ceil(rect.getY()), rect.getWidth() - 1, rect.getHeight() - 1); g2d.setClip(rect); } try { g2d.drawRenderedImage(displayImage, AffineTransform.getTranslateInstance(0.0, 0.0)); } catch (Exception | OutOfMemoryError e) { LOGGER.error("Draw rendered image", e);//$NON-NLS-1$ if ("java.io.IOException: closed".equals(e.getMessage())) { //$NON-NLS-1$ // Issue when the stream has been closed of a tiled image (problem that readAsRendered do not read data // immediately) if (sourceImage.isImageInCache()) { sourceImage.removeImageFromCache(); } disOpManager.setFirstNode(getSourceRenderedImage()); updateDisplayOperations(); } else if (e instanceof OutOfMemoryError) { // When outOfMemory exception or when tiles are not available anymore (file stream closed) System.gc(); try { // Let garbage collection Thread.sleep(100); } catch (InterruptedException et) { } } } g2d.setClip(clip); } public void drawImageForPrinter(Graphics2D g2d, double viewScale) { // Get the clipping rectangle if (!visible || displayImage == null) { return; } Shape clip = g2d.getClip(); if (clip instanceof Rectangle2D) { Rectangle2D rect = new Rectangle2D.Double(displayImage.getMinX(), displayImage.getMinY(), displayImage.getWidth() - 1, displayImage.getHeight() - 1); rect = rect.createIntersection((Rectangle2D) clip); if (rect.isEmpty()) { return; } g2d.setClip(rect); } double ratioX = (Double) disOpManager.getParamValue(ZoomOp.OP_NAME, ZoomOp.P_RATIO_X); double ratioY = (Double) disOpManager.getParamValue(ZoomOp.OP_NAME, ZoomOp.P_RATIO_Y); double imageResX = viewScale * sourceImage.getRescaleX(); double imageResY = viewScale * sourceImage.getRescaleY(); // Do not print lower than 72 dpi (drawRenderedImage can only decrease the size for printer not interpolate) // TODO add crop image as clip as no effect on the image imageResX = imageResX < ratioX ? ratioX : imageResX; imageResY = imageResY < ratioY ? ratioY : imageResY; disOpManager.setParamValue(ZoomOp.OP_NAME, ZoomOp.P_RATIO_X, imageResX); disOpManager.setParamValue(ZoomOp.OP_NAME, ZoomOp.P_RATIO_Y, imageResY); RenderedImage img = disOpManager.process(); disOpManager.setParamValue(ZoomOp.OP_NAME, ZoomOp.P_RATIO_X, ratioX); disOpManager.setParamValue(ZoomOp.OP_NAME, ZoomOp.P_RATIO_Y, ratioY); ratioX /= imageResX; ratioY /= imageResY; g2d.drawRenderedImage(img, AffineTransform.getScaleInstance(ratioX, ratioY)); g2d.setClip(clip); } public void dispose() { sourceImage = null; displayImage = null; listenerList.clear(); opListeners.clear(); } public void addLayerChangeListener(ImageLayerChangeListener<E> listener) { if (listener != null && !listenerList.contains(listener)) { listenerList.add(listener); } } /** * Removes a layer manager listener from this layer. */ public void removeLayerChangeListener(ImageLayerChangeListener<E> listener) { if (listener != null) { listenerList.remove(listener); } } public void fireImageChanged() { if (displayImage == null) { disOpManager.clearNodeIOCache(); } PlanarImage img = null; if (buildIterator && sourceImage != null) { img = sourceImage.getImage(preprocessing); } readIterator = (img == null) ? null : RandomIterFactory.create(img, null); fireLayerChanged(); } public void fireLayerChanged() { for (int i = 0; i < listenerList.size(); i++) { listenerList.get(i).handleLayerChanged(this); } } public synchronized void addEventListener(OpEventListener listener) { if (listener != null && !opListeners.contains(listener)) { opListeners.add(listener); } } public synchronized void removeEventListener(OpEventListener listener) { if (listener != null) { opListeners.remove(listener); } } public synchronized void fireOpEvent(final ImageOpEvent event) { Iterator<OpEventListener> i = opListeners.iterator(); while (i.hasNext()) { i.next().handleImageOpEvent(event); } } @Override public void updateDisplayOperations() { if (isEnableDispOperations()) { displayImage = disOpManager.process(); fireImageChanged(); } } @Override public MeasurementsAdapter getMeasurementAdapter(Unit displayUnit) { if (hasContent()) { return getSourceImage().getMeasurementAdapter(displayUnit, offset); } return null; } @Override public AffineTransform getShapeTransform() { E imageElement = getSourceImage(); if (imageElement != null) { double scaleX = imageElement.getRescaleX(); double scaleY = imageElement.getRescaleY(); if (MathUtil.isDifferent(scaleX, scaleY)) { return AffineTransform.getScaleInstance(1.0 / scaleX, 1.0 / scaleY); } } return null; } @Override public Object getSourceTagValue(TagW tagW) { E imageElement = getSourceImage(); if (imageElement != null) { return imageElement.getTagValue(tagW); } return null; } @Override public String getPixelValueUnit() { E imageElement = getSourceImage(); if (imageElement != null) { return imageElement.getPixelValueUnit(); } return null; } @Override public Point getOffset() { return offset; } @Override public void setOffset(Point offset) { this.offset = offset; } }