From be2675886104ea3540a4cd04b07a5997d93a1fc9 Mon Sep 17 00:00:00 2001
From: Amandine Tournay <amandine.tournay@pasteur.fr>
Date: Tue, 9 Mar 2021 19:07:46 +0100
Subject: [PATCH] Added project

---
 .gitignore                                    |   6 +
 pom.xml                                       |  92 ++++++++++
 .../java/plugins/tprovoost/scale/Scale.java   |  56 ++++++
 .../plugins/tprovoost/scale/ScalePainter.java | 159 ++++++++++++++++++
 .../tprovoost/scale/ScalePainterOld.java      | 105 ++++++++++++
 src/main/resources/Scale.png                  | Bin 0 -> 758 bytes
 src/main/resources/Scale.xml                  |  78 +++++++++
 src/main/resources/Scale_icon.png             | Bin 0 -> 5132 bytes
 8 files changed, 496 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 pom.xml
 create mode 100644 src/main/java/plugins/tprovoost/scale/Scale.java
 create mode 100644 src/main/java/plugins/tprovoost/scale/ScalePainter.java
 create mode 100644 src/main/java/plugins/tprovoost/scale/ScalePainterOld.java
 create mode 100644 src/main/resources/Scale.png
 create mode 100644 src/main/resources/Scale.xml
 create mode 100644 src/main/resources/Scale_icon.png

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3d47f98
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+.idea/
+target/
+.settings/
+*.iml
+.project
+.classpath
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..e61cd16
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <!-- Inherited Icy Parent POM -->
+    <parent>
+        <groupId>org.bioimageanalysis.icy</groupId>
+        <artifactId>parent-pom-plugin</artifactId>
+        <version>1.0.3</version>
+    </parent>
+
+    <!-- Project Information -->
+    <artifactId>scale-bar</artifactId>
+    <version>3.1.6</version>
+
+    <packaging>jar</packaging>
+
+    <name>Scale Bar</name>
+    <description>
+        Displays a scale bar overlay on the sequence.
+        Warning: this plugin needs correct sequence metadata to be effective. Otherwise it will display wrong values.
+    </description>
+    <url>http://icy.bioimageanalysis.org/plugin/scale-bar/</url>
+    <inceptionYear>2020</inceptionYear>
+
+    <organization>
+        <name>Institut Pasteur</name>
+        <url>https://pasteur.fr</url>
+    </organization>
+
+    <licenses>
+        <license>
+            <name>GNU GPLv3</name>
+            <url>https://www.gnu.org/licenses/gpl-3.0.en.html</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+
+    <developers>
+        <developer>
+            <id>sdallongeville</id>
+            <name>Stéphane Dallongeville</name>
+            <url>https://research.pasteur.fr/fr/member/stephane-dallongeville/</url>
+            <roles>
+                <role>founder</role>
+                <role>lead</role>
+                <role>architect</role>
+                <role>developer</role>
+                <role>debugger</role>
+                <role>tester</role>
+                <role>maintainer</role>
+                <role>support</role>
+            </roles>
+        </developer>
+    </developers>
+
+    <!-- Project properties -->
+    <properties>
+
+    </properties>
+
+    <!-- Project build configuration -->
+    <build>
+
+    </build>
+
+    <!-- List of project's dependencies -->
+    <dependencies>
+        <!-- The core of Icy -->
+        <dependency>
+            <groupId>org.bioimageanalysis.icy</groupId>
+            <artifactId>icy-kernel</artifactId>
+        </dependency>
+
+        <!-- The EzPlug library, simplifies writing UI for Icy plugins. -->
+        <dependency>
+            <groupId>org.bioimageanalysis.icy</groupId>
+            <artifactId>ezplug</artifactId>
+        </dependency>
+    </dependencies>
+
+    <!-- Icy Maven repository (to find parent POM) -->
+    <repositories>
+        <repository>
+            <id>icy</id>
+            <name>Icy's Nexus</name>
+            <url>https://icy-nexus.pasteur.fr/repository/Icy/</url>
+        </repository>
+    </repositories>
+</project>
\ No newline at end of file
diff --git a/src/main/java/plugins/tprovoost/scale/Scale.java b/src/main/java/plugins/tprovoost/scale/Scale.java
new file mode 100644
index 0000000..af5f315
--- /dev/null
+++ b/src/main/java/plugins/tprovoost/scale/Scale.java
@@ -0,0 +1,56 @@
+package plugins.tprovoost.scale;
+
+import icy.gui.main.GlobalSequenceListener;
+import icy.main.Icy;
+import icy.plugin.abstract_.Plugin;
+import icy.plugin.interface_.PluginDaemon;
+import icy.sequence.Sequence;
+
+public class Scale extends Plugin implements PluginDaemon
+{
+    ScalePainter sp;
+    GlobalSequenceListener globalSequenceListener;
+
+    @Override
+    public void run()
+    {
+        // nothing to do here
+    }
+
+    @Override
+    public void stop()
+    {
+        for (Sequence sequence : Icy.getMainInterface().getSequences())
+            sequence.removeOverlay(sp);
+
+        Icy.getMainInterface().removeGlobalSequenceListener(globalSequenceListener);
+    }
+
+    @Override
+    public void init()
+    {
+        // create painter
+        sp = new ScalePainter();
+        globalSequenceListener = new GlobalSequenceListener()
+        {
+            @Override
+            public void sequenceOpened(Sequence sequence)
+            {
+                sequence.addOverlay(sp);
+            }
+
+            @Override
+            public void sequenceClosed(Sequence sequence)
+            {
+                sequence.removeOverlay(sp);
+            }
+        };
+
+        // add the Scale Bar overlay on opened sequences
+        for (Sequence sequence : Icy.getMainInterface().getSequences())
+            sequence.addOverlay(sp);
+
+        // listen new opened sequence
+        Icy.getMainInterface().addGlobalSequenceListener(globalSequenceListener);
+    }
+}
diff --git a/src/main/java/plugins/tprovoost/scale/ScalePainter.java b/src/main/java/plugins/tprovoost/scale/ScalePainter.java
new file mode 100644
index 0000000..af4f9ce
--- /dev/null
+++ b/src/main/java/plugins/tprovoost/scale/ScalePainter.java
@@ -0,0 +1,159 @@
+package plugins.tprovoost.scale;
+
+import icy.canvas.IcyCanvas;
+import icy.canvas.IcyCanvas2D;
+import icy.math.MathUtil;
+import icy.math.UnitUtil;
+import icy.math.UnitUtil.UnitPrefix;
+import icy.painter.Overlay;
+import icy.sequence.Sequence;
+import icy.util.GraphicsUtil;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.Stroke;
+
+public class ScalePainter extends Overlay
+{
+    /**
+     * Possible rounded scale factors : 1 --> 900
+     */
+    final static double[] scaleRoundedFactors = new double[] {1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d, 9d, 10d, 20d, 30d, 40d,
+            50d, 60d, 70d, 80d, 90d, 100d, 200d, 300d, 400d, 500d, 600d, 700d, 800d, 900d};
+
+    public ScalePainter()
+    {
+        super("Scale Bar");
+    }
+
+    @Override
+    public void paint(Graphics2D g2, Sequence sequence, IcyCanvas canvas)
+    {
+        if (g2 == null)
+            return;
+        if (!(canvas instanceof IcyCanvas2D))
+            return;
+
+        final IcyCanvas2D c2d = (IcyCanvas2D) canvas;
+        final Graphics2D g = (Graphics2D) g2.create();
+
+        double scaleFactor = c2d.getScaleX();
+        g.transform(c2d.getInverseTransform());
+
+        BasicStroke[] stroke = new BasicStroke[4];
+        stroke[0] = new BasicStroke(2f);
+        stroke[1] = new BasicStroke(3f);
+        stroke[2] = new BasicStroke(4f);
+        stroke[3] = new BasicStroke(5f);
+        int w = canvas.getCanvasSizeX();
+        int h = canvas.getCanvasSizeY();
+        double pxSize = sequence.getPixelSizeX();
+        int space = 20; // spacing from borders
+
+        int sizeW = w / 8;
+        // get the value with the best display possible
+        double valueReal = sizeW * pxSize / scaleFactor / 10;
+        UnitPrefix bestUnit = UnitUtil.getBestUnit(valueReal, UnitPrefix.MICRO, 1);
+        double valueRealBestUnit = UnitUtil.getValueInUnit(valueReal * 10, UnitPrefix.MICRO, bestUnit);
+
+        double closestScale = MathUtil.closest(valueRealBestUnit, scaleRoundedFactors);
+
+        // closest scale in px
+        int closestScPx = (int) (UnitUtil.getValueInUnit(closestScale, bestUnit, UnitPrefix.MICRO) / pxSize * scaleFactor);
+        // int closestScPx = closestScale;
+
+        g.setColor(Color.BLACK);
+        g.setStroke(stroke[3]);
+        // g.drawLine(w - space - closestScPx, h - space, w - space, h - space);
+        g.drawLine(space, h - space, space + closestScPx, h - space);
+
+        g.setColor(Color.WHITE);
+        g.setStroke(stroke[2]);
+        g.drawLine(space, h - space, space + closestScPx, h - space);
+
+        int fontSize = 14;
+        Font font = new Font("Arial", Font.PLAIN, fontSize);
+        g.setFont(font);
+        String pixelString = "" + closestScale + " " + bestUnit + "m";
+        int txW = g.getFontMetrics().charsWidth(pixelString.toCharArray(), 0, pixelString.length());
+        int txH = g.getFontMetrics().getHeight();
+
+        g.setColor(Color.black);
+        // GraphicsUtil.drawString(g, pixelString, w - space - closestScPx / 2 -
+        // txW / 2, h - space - txH, true);
+        GraphicsUtil.drawString(g, pixelString, space + closestScPx / 2 - txW / 2, h - space - txH, true);
+
+        g.dispose();
+    }
+
+    double convertScale(IcyCanvas canvas, double value)
+    {
+        return canvas.canvasToImageLogDeltaX((int) value);
+    }
+
+    public class CompositeStroke implements Stroke
+    {
+        private Stroke stroke1, stroke2;
+
+        public CompositeStroke(Stroke stroke1, Stroke stroke2)
+        {
+            this.stroke1 = stroke1;
+            this.stroke2 = stroke2;
+        }
+
+        @Override
+        public Shape createStrokedShape(Shape shape)
+        {
+            return stroke2.createStrokedShape(stroke1.createStrokedShape(shape));
+        }
+    }
+
+//    private int findBestScale(IcyCanvas2D canvas, Sequence s)
+//    {
+//        int w = s.getWidth();
+//        int minSize = w / 8;
+//        int maxSize = w / 4;
+//        double pxSize = s.getPixelSizeX();
+//        double tab[] = new double[maxSize - minSize];
+//        double scaleFactor = canvas.getScaleX();
+//
+//        // generate all values
+//        for (int i = minSize; i < maxSize; ++i)
+//        {
+//
+//            // get the value with the best display possible
+//            double valueReal = i * pxSize / scaleFactor;
+//            UnitPrefix bestUnit = UnitUtil.getBestUnit(valueReal, UnitPrefix.MICRO, 1);
+//            double bestValue = UnitUtil.getValueInUnit(valueReal, UnitPrefix.MICRO, bestUnit);
+//            bestValue = ((int) (bestValue * 100)) / 100d;
+//
+//            tab[i - minSize] = bestValue;
+//        }
+//
+//        // return the first value with a round value
+//        for (int i = 0; i < maxSize - minSize; ++i)
+//        {
+//            double bestValue = tab[i];
+//            if (bestValue == (int) bestValue)
+//            {
+//                return i + minSize;
+//            }
+//        }
+//
+//        // otherwise, return first value with only one decimal
+//        for (int i = 0; i < maxSize - minSize; ++i)
+//        {
+//            double bestValue = tab[i];
+//            if (bestValue == ((int) (bestValue * 10)) / 10d)
+//            {
+//                return i + minSize;
+//            }
+//        }
+//
+//        // otherwise, return w / 4
+//        return w / 4;
+//    }
+}
diff --git a/src/main/java/plugins/tprovoost/scale/ScalePainterOld.java b/src/main/java/plugins/tprovoost/scale/ScalePainterOld.java
new file mode 100644
index 0000000..caf0c37
--- /dev/null
+++ b/src/main/java/plugins/tprovoost/scale/ScalePainterOld.java
@@ -0,0 +1,105 @@
+package plugins.tprovoost.scale;
+
+import icy.canvas.IcyCanvas;
+import icy.canvas.IcyCanvas2D;
+import icy.math.UnitUtil;
+import icy.math.UnitUtil.UnitPrefix;
+import icy.painter.Overlay;
+import icy.roi.ROI2D;
+import icy.sequence.Sequence;
+import icy.util.GraphicsUtil;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.Stroke;
+
+public class ScalePainterOld extends Overlay
+{
+	public ScalePainterOld()
+	{
+		super("Scale Bar");
+	}
+
+	@Override
+	public void paint(Graphics2D g2, Sequence sequence, IcyCanvas canvas)
+	{
+		if (g2 == null)
+			return;
+		Graphics2D g = (Graphics2D) g2.create();
+		// g.transform(((IcyCanvas2D) canvas).getInverseTransform());
+		int w = sequence.getWidth();
+		int h = sequence.getHeight();
+		int sizeW = w / 10;
+		double pxSize = sequence.getPixelSizeX();
+		int spaceFBords = 20; // spacing from borders
+
+		BasicStroke[] stroke = new BasicStroke[4];
+		stroke[0] = new BasicStroke((float) canvas.canvasToImageLogDeltaX(2));
+		stroke[1] = new BasicStroke((float) canvas.canvasToImageLogDeltaX(3));
+		stroke[2] = new BasicStroke((float) canvas.canvasToImageLogDeltaX(4));
+		stroke[3] = new BasicStroke((float) canvas.canvasToImageLogDeltaX(5));
+
+		// get the value with the best display possible
+		double valueReal = sizeW * pxSize / 10;
+		UnitPrefix bestUnit = UnitUtil.getBestUnit(valueReal, UnitPrefix.MICRO, 1);
+		double bestValue = UnitUtil.getValueInUnit(valueReal * 10, UnitPrefix.MICRO, bestUnit);
+
+		// get the best scale possible
+		int closestScale = 0;
+		if (bestValue < 10)
+		{
+			closestScale = (int) bestValue;
+		} else
+			closestScale = (int) (bestValue / 10) * 10;
+
+		// closest scale in px
+		int closestScPx = (int) (UnitUtil.getValueInUnit(closestScale, bestUnit, UnitPrefix.MICRO) / pxSize);
+
+		g.setColor(Color.BLACK);
+		g.setStroke(stroke[3]);
+		g.drawLine(w - spaceFBords - closestScPx, h - spaceFBords, w - spaceFBords, h - spaceFBords);
+
+		g.setColor(Color.WHITE);
+		g.setStroke(stroke[2]);
+		g.drawLine(w - spaceFBords - closestScPx, h - spaceFBords, w - spaceFBords, h - spaceFBords);
+
+		int fontSize = (int) canvas.canvasToImageLogDeltaX(14);
+		Font font = new Font("Arial", Font.PLAIN, fontSize);
+		g.setFont(font);
+		String pixelString = "" + closestScale + " " + bestUnit + "m";
+		int txW = g.getFontMetrics().charsWidth(pixelString.toCharArray(), 0, pixelString.length());
+		int txH = g.getFontMetrics().getHeight();
+
+		// g.fillRect(w - spaceFBords - closestScPx / 2 - txW / 2, h -
+		// spaceFBords - txH, txW, txH);
+
+		g.setColor(Color.black);
+		GraphicsUtil.drawString(g, pixelString, w - spaceFBords - closestScPx / 2 - txW / 2, h - spaceFBords - txH, true);
+
+		g.dispose();
+	}
+
+	double convertScale(IcyCanvas canvas, double value)
+	{
+		return canvas.canvasToImageLogDeltaX((int) value);
+	}
+
+	public class CompositeStroke implements Stroke
+	{
+		private Stroke stroke1, stroke2;
+
+		public CompositeStroke(Stroke stroke1, Stroke stroke2)
+		{
+			this.stroke1 = stroke1;
+			this.stroke2 = stroke2;
+		}
+
+		public Shape createStrokedShape(Shape shape)
+		{
+			return stroke2.createStrokedShape(stroke1.createStrokedShape(shape));
+		}
+	}
+}
diff --git a/src/main/resources/Scale.png b/src/main/resources/Scale.png
new file mode 100644
index 0000000000000000000000000000000000000000..2227c16fad9f21113d628a11d5b912a4f42f1431
GIT binary patch
literal 758
zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911L)MWvCLm<VGm>l8*76lSpbYCt2Qc`IU
zF{m&|6BIBqFfuZ{QkNHJU|?$Yba4!+nDh3=-Ylg=5w--!rijkH8wH%YL>zPW@H+-a
zF5#YJRm-(;E62{B30kdlmuwQ%&Oe}~py|YM(DqG(_UfOJC*S2vdal`a*nqF*!@m1x
zW}JlCGpOkgdfoM^Z(9$k37)-u`}X0(hv&|no3+(yrq853ea9c~-MjZmO3VU=8ehS)
z9KQ-IdLBM{v?y$~YGv;B+kflq+w!(omy|34O0Slzt*h(PwV$ay_0;3X$voxd<?QV2
zuQTK3C-2y^M@NL~Xwt`5ud)mzrg$yg@$l@~UZxN8qk6yA*!7n$OgZ=b^Xbp4Kfh+R
zu(hqNs_J@i{q@(15|_nFe?3<Q#i{3Pk$N4szPz?}uiVF{$q&ym-MN2X|9QrtSFD+O
zLUEhUd@QlDwYI*!<MsakoF7iHu3RS+HzB3OZ1&tab9BDH*|2}Ve7=j}k!5o6CttIA
z>=Sr2Bap#veaEu`iyRXvet!Pdg7$WHebav&Jzw8=>+QEH#&hS-CwH{FxiS9OqcrPj
z(a-J1i6&B^T2ph(q;LPPu!+-mSbn)Pzu6*9q~Uz_#;Lk>Grl<`PJYWZ;q+6HhV==%
znQ|0C0m^k?`!a?P-m9<vny|xs_Sr4dnd<!*e_SgoFP|;1_-^tyU<yWn2l>Alj|ttd
TJ9X;fH;|~OtDnm{r-UW|Jxl<8

literal 0
HcmV?d00001

diff --git a/src/main/resources/Scale.xml b/src/main/resources/Scale.xml
new file mode 100644
index 0000000..9a16e0b
--- /dev/null
+++ b/src/main/resources/Scale.xml
@@ -0,0 +1,78 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<root>
+<url><![CDATA[http://icy.bioimageanalysis.org/repository/getXMLPluginFile.php?pluginId=179&beta=0]]></url><name>Scale Bar</name><version>2.0.0.0</version><required_kernel_version>1.3.6.0</required_kernel_version><kernel_ver>1.3.6.0</kernel_ver><jar_url><![CDATA[http://icy.bioimageanalysis.org/repository/getJarFile.php?pluginId=179&beta=0]]></jar_url><icon_url><![CDATA[http://bioimageanalysis.org/icy/image.php?idAttach=1229]]></icon_url><image_url><![CDATA[http://bioimageanalysis.org/icy/image.php?idAttach=2020]]></image_url><description><![CDATA[Displays a scale bar overlay on the sequence. Be advised, this plugin needs correct Metadata on the sequence to be effective or it will display wrong values.]]></description><classname><![CDATA[plugins.tprovoost.scale.Scale]]></classname><author><![CDATA[tprovoost - Thomas Provoost]]></author><changelog><![CDATA[-
+Version 2.0.0.0
+Date 2013-07-03 12:15:38
+
+* Bottom left now. This will make it avoid being invisible because of the zoom factor is displayed at the same time
+* the scale bar now fully works
+
+-
+Version 1.6.0.0
+Date 2013-07-03 12:02:48
+
+* Uses orders of magnitude instead of precise values.
+* size of scale bar never changes now: will always be : size of the canvas / 8.
+
+-
+Version 1.5.0.0
+Date 2013-06-26 16:52:26
+
+* Now only updates text while zooming, and change the size and value after the zoom
+
+-
+Version 1.4.0.0
+Date 2013-06-26 11:20:13
+
+* fixed bar location issue
+
+-
+Version 1.3.1.0
+Date 2013-06-17 15:18:23
+
+* removed debug information
+
+-
+Version 1.3.0.0
+Date 2013-06-17 15:17:46
+
+* now scales with images zoom
+
+-
+Version 1.2.0.0
+Date 2013-06-17 14:15:50
+
+* removed white rectangle
+* now shadow on text
+
+-
+Version 1.1.0.0
+Date 2013-01-11 16:30:30
+
+Updated to 1.3.0.0
+
+-
+Version 1.0.0.2
+Date 2012-10-22 17:10:43
+
+* corrected 3D issue.
+
+-
+Version 1.0.0.1
+Date 2012-07-03 09:23:11
+
+Corrected issue with compilation.
+
+-
+Version 1.0.0.0
+Date 2012-07-02 21:17:10
+
+Updated to Icy last version.
+
+-
+Version 0.0.1.0
+Date 2012-06-20 14:33:49
+
+
+
+]]></changelog><web><![CDATA[http://icy.bioimageanalysis.org/plugin/Scale_Bar]]></web><dependencies></dependencies></root>
\ No newline at end of file
diff --git a/src/main/resources/Scale_icon.png b/src/main/resources/Scale_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..46567ed34a2c5de77c00a72963814c1866260f07
GIT binary patch
literal 5132
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

literal 0
HcmV?d00001

-- 
GitLab