/******************************************************************************* * 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.dicom.codec.display; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.image.DataBuffer; import java.awt.image.MultiPixelPackedSampleModel; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.util.HashMap; import java.util.Optional; import javax.media.jai.PlanarImage; import javax.media.jai.ROIShape; import javax.media.jai.TiledImage; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.Tag; import org.weasis.core.api.gui.util.ActionW; import org.weasis.core.api.gui.util.JMVUtils; import org.weasis.core.api.image.AbstractOp; import org.weasis.core.api.image.ImageOpEvent; import org.weasis.core.api.image.ImageOpEvent.OpEvent; import org.weasis.core.api.image.MergeImgOp; import org.weasis.core.api.image.op.ShutterDescriptor; import org.weasis.core.api.image.util.ImageFiler; import org.weasis.core.api.media.data.ImageElement; import org.weasis.core.api.media.data.TagW; import org.weasis.dicom.codec.DicomMediaIO; import org.weasis.dicom.codec.PRSpecialElement; import org.weasis.dicom.codec.PresentationStateReader; import org.weasis.dicom.codec.TagD; import org.weasis.dicom.codec.utils.DicomMediaUtils; import org.weasis.dicom.codec.utils.OverlayUtils; public class ShutterOp extends AbstractOp { public static final String OP_NAME = ActionW.IMAGE_SHUTTER.getTitle(); /** * Set whether the shutter is applied (Required parameter). * * Boolean value. */ public static final String P_SHOW = "show"; //$NON-NLS-1$ public static final String P_SHAPE = "shape"; //$NON-NLS-1$ public static final String P_RGB_COLOR = "rgb.color"; //$NON-NLS-1$ public static final String P_PS_VALUE = "ps.value"; //$NON-NLS-1$ public static final String P_PR_ELEMENT = "pr.element"; //$NON-NLS-1$ public static final String P_IMAGE_ELEMENT = "img.element"; //$NON-NLS-1$ public ShutterOp() { setName(OP_NAME); } public ShutterOp(ShutterOp op) { super(op); } @Override public ShutterOp copy() { return new ShutterOp(this); } @Override public void handleImageOpEvent(ImageOpEvent event) { OpEvent type = event.getEventType(); if (OpEvent.ImageChange.equals(type) || OpEvent.ResetDisplay.equals(type)) { ImageElement img = event.getImage(); // If no image, reset the shutter boolean noMedia = img == null; setParam(P_SHAPE, noMedia ? null : img.getTagValue(TagW.ShutterFinalShape)); setParam(P_PS_VALUE, noMedia ? null : img.getTagValue(TagW.ShutterPSValue)); setParam(P_RGB_COLOR, noMedia ? null : img.getTagValue(TagW.ShutterRGBColor)); setParam(P_PR_ELEMENT, null); setParam(P_IMAGE_ELEMENT, noMedia ? null : img); } else if (OpEvent.ApplyPR.equals(type)) { HashMap<String, Object> p = event.getParams(); if (p != null) { PRSpecialElement pr = Optional.ofNullable(p.get(ActionW.PR_STATE.cmd())) .filter(PRSpecialElement.class::isInstance).map(PRSpecialElement.class::cast).orElse(null); setParam(P_SHAPE, pr == null ? null : pr.getTagValue(TagW.ShutterFinalShape)); setParam(P_PS_VALUE, pr == null ? null : pr.getTagValue(TagW.ShutterPSValue)); setParam(P_RGB_COLOR, pr == null ? null : pr.getTagValue(TagW.ShutterRGBColor)); setParam(P_PR_ELEMENT, pr); setParam(P_IMAGE_ELEMENT, event.getImage()); Area shape = (Area) params.get(P_SHAPE); if (shape != null) { Rectangle area = (Rectangle) p.get(ActionW.CROP.cmd()); if (area != null) { Area trArea = new Area(shape); trArea.transform(AffineTransform.getTranslateInstance(-area.getX(), -area.getY())); setParam(P_SHAPE, trArea); } } } } } @Override public void process() throws Exception { RenderedImage source = (RenderedImage) params.get(Param.INPUT_IMG); RenderedImage result = source; Boolean shutter = (Boolean) params.get(P_SHOW); Area area = (Area) params.get(P_SHAPE); Object pr = params.get(P_PR_ELEMENT); if (shutter != null && shutter && area != null) { Byte[] color = getShutterColor(); if (isBlack(color)) { result = ShutterDescriptor.create(source, new ROIShape(area), getShutterColor(), null); } else { result = MergeImgOp.combineTwoImages(source, ImageFiler.getEmptyImage(color, source.getWidth(), source.getHeight()), getAsImage(area, source)); } } // Potentially override the shutter in the original dicom if (shutter && params.get(P_PS_VALUE) != null && (pr instanceof PRSpecialElement)) { DicomMediaIO prReader = ((PRSpecialElement) pr).getMediaReader(); RenderedImage imgOverlay = null; ImageElement image = (ImageElement) params.get(P_IMAGE_ELEMENT); boolean overlays = JMVUtils.getNULLtoFalse(prReader.getTagValue(TagW.HasOverlay)); if (overlays && image != null && image.getKey() instanceof Integer) { int frame = (Integer) image.getKey(); Integer height = TagD.getTagValue(image, Tag.Rows, Integer.class); Integer width = TagD.getTagValue(image, Tag.Columns, Integer.class); if (height != null && width != null) { Byte[] color = getShutterColor(); Attributes attributes = ((PRSpecialElement) pr).getMediaReader().getDicomObject(); Integer shuttOverlayGroup = DicomMediaUtils.getIntegerFromDicomElement(attributes, Tag.ShutterOverlayGroup, null); if (shuttOverlayGroup != null) { PlanarImage alpha = PlanarImage.wrapRenderedImage( OverlayUtils.getShutterOverlay(attributes, frame, width, height, shuttOverlayGroup)); if (color.length == 1) { int transperency = color[0]; imgOverlay = MergeImgOp.combineTwoImages(result, alpha, transperency); } else { imgOverlay = MergeImgOp.combineTwoImages(result, ImageFiler.getEmptyImage(color, width, height), alpha); } } } } result = imgOverlay == null ? result : imgOverlay; } params.put(Param.OUTPUT_IMG, result); } private Byte[] getShutterColor() { Color color = (Color) params.get(P_RGB_COLOR); if (color == null) { /* * A single gray unsigned value used to replace those parts of the image occluded by the shutter, when * rendered on a monochrome display. The units are specified in P-Values, from a minimum of 0000H (black) up * to a maximum of FFFFH (white). */ Integer val = (Integer) params.get(P_PS_VALUE); return val == null ? new Byte[] { 0 } : new Byte[] { (byte) (val >> 8) }; } else { return new Byte[] { (byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue() }; } } private static boolean isBlack(Byte[] color) { for (Byte i : color) { if (i != 0) { return false; } } return true; } private static PlanarImage getAsImage(Area shape, RenderedImage source) { SampleModel sm = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, source.getWidth(), source.getHeight(), 1); TiledImage ti = new TiledImage(source.getMinX(), source.getMinY(), source.getWidth(), source.getHeight(), source.getTileGridXOffset(), source.getTileGridYOffset(), sm, PlanarImage.createColorModel(sm)); Graphics2D g2d = ti.createGraphics(); // Write the Shape into the TiledImageGraphics. g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.fill(shape); g2d.dispose(); return ti; } }