/******************************************************************************* * 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.base.viewer2d; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.Line2D; import java.awt.geom.Rectangle2D; import java.awt.image.RenderedImage; import java.util.HashMap; import java.util.Optional; import org.weasis.core.api.gui.util.ActionW; import org.weasis.core.api.gui.util.DecFormater; import org.weasis.core.api.gui.util.Filter; import org.weasis.core.api.gui.util.JMVUtils; import org.weasis.core.api.image.ImageOpNode; import org.weasis.core.api.image.OpManager; import org.weasis.core.api.image.PseudoColorOp; import org.weasis.core.api.image.RotationOp; import org.weasis.core.api.image.WindowOp; import org.weasis.core.api.image.op.ByteLut; import org.weasis.core.api.image.util.Unit; import org.weasis.core.api.media.data.ImageElement; import org.weasis.core.api.util.FontTools; import org.weasis.core.api.util.StringUtil; import org.weasis.core.ui.editor.image.PixelInfo; import org.weasis.core.ui.editor.image.ViewCanvas; import org.weasis.core.ui.model.layer.LayerAnnotation; import org.weasis.core.ui.model.layer.LayerType; import org.weasis.core.ui.model.utils.imp.DefaultGraphicLabel; import org.weasis.core.ui.model.utils.imp.DefaultUUID; /** * The Class InfoLayer. * * @author Nicolas Roduit */ public class InfoLayer extends DefaultUUID implements LayerAnnotation { private static final long serialVersionUID = 1782300490253793711L; private final HashMap<String, Boolean> displayPreferences = new HashMap<>(); private boolean visible = true; private static final Color color = Color.yellow; private final ViewCanvas view2DPane; private PixelInfo pixelInfo = null; private final Rectangle pixelInfoBound; private final Rectangle preloadingProgressBound; private int border = 10; private boolean showBottomScale = true; private String name; public InfoLayer(ViewCanvas view2DPane) { this.view2DPane = view2DPane; displayPreferences.put(ANNOTATIONS, true); displayPreferences.put(IMAGE_ORIENTATION, true); displayPreferences.put(SCALE, true); displayPreferences.put(LUT, false); displayPreferences.put(PIXEL, true); displayPreferences.put(WINDOW_LEVEL, true); displayPreferences.put(ZOOM, true); displayPreferences.put(ROTATION, false); displayPreferences.put(FRAME, true); this.pixelInfoBound = new Rectangle(); this.preloadingProgressBound = new Rectangle(); } @Override public Boolean isShowBottomScale() { return showBottomScale; } @Override public void setShowBottomScale(Boolean showBottomScale) { this.showBottomScale = showBottomScale; } @Override public Boolean getVisible() { return visible; } @Override public void setVisible(Boolean visible) { this.visible = Optional.ofNullable(visible).orElse(getType().getVisible()); } @Override public Integer getLevel() { return getType().getLevel(); } @Override public void setLevel(Integer i) { // Do nothing } @Override public Integer getBorder() { return border; } @Override public void setBorder(Integer border) { this.border = border; } @Override public LayerType getType() { return LayerType.IMAGE_ANNOTATION; } @Override public void setType(LayerType type) { // Cannot change this type } @Override public void setName(String layerName) { this.name = layerName; } @Override public String getName() { return name; } @Override public String toString() { return Optional.ofNullable(getName()).orElse(getType().getDefaultName()); } @Override public Boolean getDisplayPreferences(String item) { Boolean val = displayPreferences.get(item); return val == null ? false : val; } @Override public Boolean setDisplayPreferencesValue(String displayItem, Boolean selected) { boolean selected2 = getDisplayPreferences(displayItem); displayPreferences.put(displayItem, selected); return selected != selected2; } @Override public Rectangle getPreloadingProgressBound() { return preloadingProgressBound; } @Override public Rectangle getPixelInfoBound() { return pixelInfoBound; } @Override public void setPixelInfo(PixelInfo pixelInfo) { this.pixelInfo = pixelInfo; } @Override public PixelInfo getPixelInfo() { return pixelInfo; } @Override public void paint(Graphics2D g2) { ImageElement image = view2DPane.getImage(); if (!visible || image == null) { return; } OpManager disOp = view2DPane.getDisplayOpManager(); final Rectangle bound = view2DPane.getJComponent().getBounds(); float midx = bound.width / 2f; float midy = bound.height / 2f; g2.setPaint(color); final float fontHeight = FontTools.getAccurateFontHeight(g2); final float midfontHeight = fontHeight * FontTools.getMidFontHeightFactor(); float drawY = bound.height - border - 1.5f; // -1.5 for outline if (!image.isReadable()) { String message = Messages.getString("InfoLayer.error_msg"); //$NON-NLS-1$ float y = midy; DefaultGraphicLabel.paintColorFontOutline(g2, message, midx - g2.getFontMetrics().stringWidth(message) / 2, y, Color.RED); String[] desc = image.getMediaReader().getReaderDescription(); if (desc != null) { for (String str : desc) { if (StringUtil.hasText(str)) { y += fontHeight; DefaultGraphicLabel.paintColorFontOutline(g2, str, midx - g2.getFontMetrics().stringWidth(str) / 2, y, Color.RED); } } } } if (image.isReadable() && getDisplayPreferences(SCALE)) { drawScale(g2, bound, fontHeight); } if (image.isReadable() && getDisplayPreferences(LUT)) { drawLUT(g2, bound, midfontHeight); } if (getDisplayPreferences(PIXEL)) { StringBuilder sb = new StringBuilder(Messages.getString("InfoLayer.pix")); //$NON-NLS-1$ sb.append(StringUtil.COLON_AND_SPACE); if (pixelInfo != null) { sb.append(pixelInfo.getPixelValueText()); sb.append(" - "); //$NON-NLS-1$ sb.append(pixelInfo.getPixelPositionText()); } String str = sb.toString(); DefaultGraphicLabel.paintFontOutline(g2, str, border, drawY - 1); drawY -= fontHeight + 2; pixelInfoBound.setBounds(border - 2, (int) drawY + 3, g2.getFontMetrics().stringWidth(str) + 4, (int) fontHeight + 2); // g2.draw(pixelInfoBound); } if (getDisplayPreferences(WINDOW_LEVEL)) { StringBuilder sb = new StringBuilder(); Number window = (Number) disOp.getParamValue(WindowOp.OP_NAME, ActionW.WINDOW.cmd()); Number level = (Number) disOp.getParamValue(WindowOp.OP_NAME, ActionW.LEVEL.cmd()); if (window != null && level != null) { sb.append(ActionW.WINLEVEL.getTitle()); sb.append(StringUtil.COLON_AND_SPACE); sb.append(DecFormater.oneDecimal(window)); sb.append("/");//$NON-NLS-1$ sb.append(DecFormater.oneDecimal(level)); } DefaultGraphicLabel.paintFontOutline(g2, sb.toString(), border, drawY); drawY -= fontHeight; } if (getDisplayPreferences(ZOOM)) { DefaultGraphicLabel.paintFontOutline(g2, Messages.getString("InfoLayer.zoom") + StringUtil.COLON_AND_SPACE //$NON-NLS-1$ + DecFormater.percentTwoDecimal(view2DPane.getViewModel().getViewScale()), border, drawY); drawY -= fontHeight; } if (getDisplayPreferences(ROTATION)) { DefaultGraphicLabel.paintFontOutline(g2, Messages.getString("InfoLayer.angle") + StringUtil.COLON_AND_SPACE //$NON-NLS-1$ + disOp.getParamValue(RotationOp.OP_NAME, RotationOp.P_ROTATE) + " " //$NON-NLS-1$ + Messages.getString("InfoLayer.angle_symb"), //$NON-NLS-1$ border, drawY); drawY -= fontHeight; } if (getDisplayPreferences(FRAME)) { DefaultGraphicLabel.paintFontOutline(g2, Messages.getString("InfoLayer.frame") //$NON-NLS-1$ + StringUtil.COLON_AND_SPACE + (view2DPane.getFrameIndex() + 1) + " / " //$NON-NLS-1$ + view2DPane.getSeries() .size((Filter<ImageElement>) view2DPane.getActionValue(ActionW.FILTERED_SERIES.cmd())), border, drawY); drawY -= fontHeight; } // if (getDisplayPreferences(ANNOTATIONS)) { // MediaSeries<ImageElement> series = view2DPane.getSeries(); // // Boolean synchLink = (Boolean) view2DPane.getActionValue(ActionW.SYNCH_LINK.cmd()); // String str = synchLink != null && synchLink ? "linked" : "unlinked"; // paintFontOutline(g2, str, bound.width - g2.getFontMetrics().stringWidth(str) - border, drawY); // // } } public Rectangle2D getOutLine(Line2D l) { Rectangle2D r = l.getBounds2D(); r.setFrame(r.getX() - 1.0, r.getY() - 1.0, r.getWidth() + 2.0, r.getHeight() + 2.0); return r; } public void drawLUT(Graphics2D g2, Rectangle bound, float midfontHeight) { OpManager disOp = view2DPane.getDisplayOpManager(); ImageOpNode pseudoColorOp = disOp.getNode(PseudoColorOp.OP_NAME); ByteLut lut = null; if (pseudoColorOp != null) { lut = (ByteLut) pseudoColorOp.getParam(PseudoColorOp.P_LUT); } if (lut != null && bound.height > 350) { if (lut.getLutTable() == null) { lut = ByteLut.grayLUT; } byte[][] table = JMVUtils.getNULLtoFalse(pseudoColorOp.getParam(PseudoColorOp.P_LUT_INVERSE)) ? lut.getInvertedLutTable() : lut.getLutTable(); float length = table[0].length; float x = bound.width - 30f; float y = bound.height / 2f - length / 2f; g2.setPaint(Color.black); Rectangle2D.Float rect = new Rectangle2D.Float(x - 11f, y - 2f, 12f, 2f); g2.draw(rect); int separation = 4; float step = length / separation; for (int i = 1; i < separation; i++) { float posY = y + i * step; rect.setRect(x - 6f, posY - 1f, 7f, 2f); g2.draw(rect); } rect.setRect(x - 11f, y + length, 12f, 2f); g2.draw(rect); rect.setRect(x - 2f, y - 2f, 23f, length + 4f); g2.draw(rect); g2.setPaint(Color.white); Line2D.Float line = new Line2D.Float(x - 10f, y - 1f, x - 1f, y - 1f); g2.draw(line); Double ww = (Double) disOp.getParamValue(WindowOp.OP_NAME, ActionW.WINDOW.cmd()); Double wl = (Double) disOp.getParamValue(WindowOp.OP_NAME, ActionW.LEVEL.cmd()); if (ww != null && wl != null) { int stepWindow = (int) (ww / separation); int firstlevel = (int) (wl - stepWindow * 2.0); String str = Integer.toString(firstlevel); // $NON-NLS-1$ DefaultGraphicLabel.paintFontOutline(g2, str, x - g2.getFontMetrics().stringWidth(str) - 12f, y + midfontHeight); for (int i = 1; i < separation; i++) { float posY = y + i * step; line.setLine(x - 5f, posY, x - 1f, posY); g2.draw(line); str = Integer.toString(firstlevel + i * stepWindow); // $NON-NLS-1$ DefaultGraphicLabel.paintFontOutline(g2, str, x - g2.getFontMetrics().stringWidth(str) - 7, posY + midfontHeight); } line.setLine(x - 10f, y + length + 1f, x - 1f, y + length + 1f); g2.draw(line); str = Integer.toString(firstlevel + 4 * stepWindow); // $NON-NLS-1$ DefaultGraphicLabel.paintFontOutline(g2, str, x - g2.getFontMetrics().stringWidth(str) - 12, y + length + midfontHeight); rect.setRect(x - 1f, y - 1f, 21f, length + 2f); g2.draw(rect); } for (int k = 0; k < length; k++) { g2.setPaint(new Color(table[0][k] & 0xff, table[1][k] & 0xff, table[2][k] & 0xff)); rect.setRect(x, y + k, 19f, 1f); g2.draw(rect); } } } public void drawScale(Graphics2D g2d, Rectangle bound, float fontHeight) { ImageElement image = view2DPane.getImage(); RenderedImage source = view2DPane.getSourceImage(); if (source == null) { return; } double zoomFactor = view2DPane.getViewModel().getViewScale(); double scale = image.getPixelSize() / zoomFactor; double scaleSizex = ajustShowScale(scale, (int) Math.min(zoomFactor * source.getWidth() * image.getRescaleX(), bound.width / 2.0)); if (showBottomScale && scaleSizex > 50.0d) { Unit[] unit = { image.getPixelSpacingUnit() }; String str = ajustLengthDisplay(scaleSizex * scale, unit); g2d.setPaint(color); g2d.setStroke(new BasicStroke(1.0F)); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setPaint(Color.black); double posx = bound.width / 2.0 - scaleSizex / 2.0; double posy = bound.height - border - 1.5; // -1.5 for outline Line2D line = new Line2D.Double(posx, posy, posx + scaleSizex, posy); g2d.draw(getOutLine(line)); line.setLine(posx, posy - 15.0, posx, posy); g2d.draw(getOutLine(line)); line.setLine(posx + scaleSizex, posy - 15.0, posx + scaleSizex, posy); g2d.draw(getOutLine(line)); int divisor = str.indexOf("5") == -1 ? str.indexOf("2") == -1 ? 10 : 2 : 5; //$NON-NLS-1$ //$NON-NLS-2$ double divSquare = scaleSizex / divisor; for (int i = 1; i < divisor; i++) { line.setLine(posx + divSquare * i, posy, posx + divSquare * i, posy - 10.0); g2d.draw(getOutLine(line)); } if (divSquare > 90) { double secondSquare = divSquare / 10.0; for (int i = 0; i < divisor; i++) { for (int k = 1; k < 10; k++) { double secBar = posx + divSquare * i + secondSquare * k; line.setLine(secBar, posy, secBar, posy - 5.0); g2d.draw(getOutLine(line)); } } } g2d.setPaint(Color.white); line.setLine(posx, posy, posx + scaleSizex, posy); g2d.draw(line); line.setLine(posx, posy - 15.0, posx, posy); g2d.draw(line); line.setLine(posx + scaleSizex, posy - 15.0, posx + scaleSizex, posy); g2d.draw(line); for (int i = 0; i < divisor; i++) { line.setLine(posx + divSquare * i, posy, posx + divSquare * i, posy - 10.0); g2d.draw(line); } if (divSquare > 90) { double secondSquare = divSquare / 10.0; for (int i = 0; i < divisor; i++) { for (int k = 1; k < 10; k++) { double secBar = posx + divSquare * i + secondSquare * k; line.setLine(secBar, posy, secBar, posy - 5.0); g2d.draw(line); } } } g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_DEFAULT); String pixSizeDesc = image.getPixelSizeCalibrationDescription(); if (StringUtil.hasText(pixSizeDesc)) { DefaultGraphicLabel.paintFontOutline(g2d, pixSizeDesc, (float) (posx + scaleSizex + 5), (float) posy - fontHeight); } str += " " + unit[0].getAbbreviation(); //$NON-NLS-1$ DefaultGraphicLabel.paintFontOutline(g2d, str, (float) (posx + scaleSizex + 5), (float) posy); } double scaleSizeY = ajustShowScale(scale, (int) Math.min(zoomFactor * source.getHeight() * image.getRescaleY(), bound.height / 2.0)); if (scaleSizeY > 30.0d) { Unit[] unit = { image.getPixelSpacingUnit() }; String str = ajustLengthDisplay(scaleSizeY * scale, unit); g2d.setPaint(color); g2d.setStroke(new BasicStroke(1.0F)); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setPaint(Color.black); double posx = border + 20; double posy = bound.height / 2.0 - scaleSizeY / 2.0; Line2D line = new Line2D.Double(posx, posy, posx, posy + scaleSizeY); g2d.draw(getOutLine(line)); line.setLine(posx, posy, posx + 15, posy); g2d.draw(getOutLine(line)); line.setLine(posx, posy + scaleSizeY, posx + 15, posy + scaleSizeY); g2d.draw(getOutLine(line)); int divisor = str.indexOf("5") == -1 ? str.indexOf("2") == -1 ? 10 : 2 : 5; //$NON-NLS-1$ //$NON-NLS-2$ double divSquare = scaleSizeY / divisor; for (int i = 0; i < divisor; i++) { line.setLine(posx, posy + divSquare * i, posx + 10.0, posy + divSquare * i); g2d.draw(getOutLine(line)); } if (divSquare > 90) { double secondSquare = divSquare / 10.0; for (int i = 0; i < divisor; i++) { for (int k = 1; k < 10; k++) { double secBar = posy + divSquare * i + secondSquare * k; line.setLine(posx, secBar, posx + 5.0, secBar); g2d.draw(getOutLine(line)); } } } g2d.setPaint(Color.white); line.setLine(posx, posy, posx, posy + scaleSizeY); g2d.draw(line); line.setLine(posx, posy, posx + 15, posy); g2d.draw(line); line.setLine(posx, posy + scaleSizeY, posx + 15, posy + scaleSizeY); g2d.draw(line); for (int i = 0; i < divisor; i++) { line.setLine(posx, posy + divSquare * i, posx + 10.0, posy + divSquare * i); g2d.draw(line); } if (divSquare > 90) { double secondSquare = divSquare / 10.0; for (int i = 0; i < divisor; i++) { for (int k = 1; k < 10; k++) { double secBar = posy + divSquare * i + secondSquare * k; line.setLine(posx, secBar, posx + 5.0, secBar); g2d.draw(line); } } } g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_DEFAULT); DefaultGraphicLabel.paintFontOutline(g2d, str + " " + unit[0].getAbbreviation(), (int) posx, //$NON-NLS-1$ (int) (posy - 5)); } } private double ajustShowScale(double ratio, int maxLength) { int digits = (int) ((Math.log(maxLength * ratio) / Math.log(10)) + 1); double scaleLength = Math.pow(10, digits); double scaleSize = scaleLength / ratio; int loop = 0; while ((int) scaleSize > maxLength) { scaleLength /= findGeometricSuite(scaleLength); scaleSize = scaleLength / ratio; loop++; if (loop > 50) { return 0.0; } } return scaleSize; } public double findGeometricSuite(double length) { int shift = (int) ((Math.log(length) / Math.log(10)) + 0.1); int firstDigit = (int) (length / Math.pow(10, shift) + 0.5); if (firstDigit == 5) { return 2.5; } return 2.0; } public String ajustLengthDisplay(double scaleLength, Unit[] unit) { double ajustScaleLength = scaleLength; Unit ajustUnit = unit[0]; if (scaleLength < 1.0) { Unit down = ajustUnit; while ((down = down.getDownUnit()) != null) { double length = scaleLength * down.getConversionRatio(unit[0].getConvFactor()); if (length > 1) { ajustUnit = down; ajustScaleLength = length; break; } } } else if (scaleLength > 10.0) { Unit up = ajustUnit; while ((up = up.getUpUnit()) != null) { double length = scaleLength * up.getConversionRatio(unit[0].getConvFactor()); if (length < 1) { break; } ajustUnit = up; ajustScaleLength = length; } } // Trick to keep the value as a return parameter unit[0] = ajustUnit; if (ajustScaleLength < 1.0) { return ajustScaleLength < 0.001 ? DecFormater.scientificFormat(ajustScaleLength) : DecFormater.fourDecimal(ajustScaleLength); } return ajustScaleLength > 50000.0 ? DecFormater.scientificFormat(ajustScaleLength) : DecFormater.twoDecimal(ajustScaleLength); } @Override public LayerAnnotation getLayerCopy(ViewCanvas view2dPane) { InfoLayer layer = new InfoLayer(view2DPane); HashMap<String, Boolean> prefs = layer.displayPreferences; prefs.put(ANNOTATIONS, getDisplayPreferences(ANNOTATIONS)); prefs.put(SCALE, getDisplayPreferences(SCALE)); prefs.put(LUT, getDisplayPreferences(LUT)); prefs.put(PIXEL, getDisplayPreferences(PIXEL)); prefs.put(WINDOW_LEVEL, getDisplayPreferences(WINDOW_LEVEL)); prefs.put(ZOOM, getDisplayPreferences(ZOOM)); prefs.put(ROTATION, getDisplayPreferences(ROTATION)); return layer; } }