001/******************************************************************************* 002 * This software is provided as a supplement to the authors' textbooks on digital 003 * image processing published by Springer-Verlag in various languages and editions. 004 * Permission to use and distribute this software is granted under the BSD 2-Clause 005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit https://imagingbook.com for additional details. 008 ******************************************************************************/ 009 010package imagingbook.common.ij; 011 012import ij.IJ; 013import ij.ImagePlus; 014import ij.WindowManager; 015import ij.gui.GenericDialog; 016import ij.gui.ImageCanvas; 017import ij.gui.ImageWindow; 018import ij.plugin.ScreenGrabber; 019 020import java.awt.Dimension; 021import java.awt.Rectangle; 022import java.awt.Toolkit; 023import java.lang.reflect.Field; 024import java.util.ArrayList; 025import java.util.List; 026 027/** 028 * Defines static helper methods related to ImageJ's GUI. 029 * 030 * @author WB 031 * @version 2022/09/15 032 */ 033public abstract class GuiTools { 034 035 private GuiTools() {} 036 037 public static final String DEFAULT_DIALOG_TITLE = "Choose image"; 038 039 /** 040 * Queries the user to select one of the currently open images. 041 * 042 * @param title name of the dialog window (if null, {@link #DEFAULT_DIALOG_TITLE} is used) 043 * @param exclude image to exclude from being selected (typically the current image) 044 * @return a reference to the chosen image ({@link ImagePlus}) or null, if the dialog was cancelled 045 */ 046 public static ImagePlus chooseOpenImage(String title, ImagePlus exclude) { 047 if (title == null) { 048 title = DEFAULT_DIALOG_TITLE; 049 } 050 int[] imgIdsAll = WindowManager.getIDList(); 051 if (imgIdsAll == null) { 052 IJ.error("No images are open."); 053 return null; 054 } 055 056 List<Integer> imgIdList = new ArrayList<Integer>(imgIdsAll.length); // use a Map instead? 057 List<String> imgNameList = new ArrayList<String>(imgIdsAll.length); 058 059 for (int id : imgIdsAll) { 060 ImagePlus img = WindowManager.getImage(id); 061 if (img!=null && img != exclude && img.isProcessor()) { 062 imgIdList.add(id); 063 imgNameList.add(img.getShortTitle()); 064 } 065 } 066 067 if (imgIdList.size() < 1) { 068 IJ.error("No other images found."); 069 return null; 070 } 071 072 Integer[] imgIds = imgIdList.toArray(new Integer[0]); 073 String[] imgNames = imgNameList.toArray(new String[0]); 074 GenericDialog gd = new GenericDialog(title, null); 075 gd.addChoice("Image:", imgNames, imgNames[0]); 076 gd.showDialog(); 077 if (gd.wasCanceled()) 078 return null; 079 else { 080 int idx = gd.getNextChoiceIndex(); 081 return WindowManager.getImage(imgIds[idx]); 082 } 083 } 084 085 /** 086 * Queries the user to select one of the currently open images. 087 * 088 * @param title name of the dialog window (if null, {@link #DEFAULT_DIALOG_TITLE} is used) 089 * @return a reference to the chosen image ({@link ImagePlus}) or null, if the dialog was cancelled 090 */ 091 public static ImagePlus chooseOpenImage(String title) { 092 return chooseOpenImage(title, null); 093 } 094 095 /** 096 * Queries the user to select one of the currently open images. 097 * 098 * @return a reference to the chosen image ({@link ImagePlus}) or null, if the dialog was cancelled 099 */ 100 public static ImagePlus chooseOpenImage() { 101 return chooseOpenImage(null, null); 102 } 103 104 // --------------------------------------------------------------------------------------------- 105 106 /** 107 * Modifies the view of the given {@link ImagePlus} image to the specified magnification (zoom) factor and the 108 * anchor position in the source image. The size of the image window remains unchanged. The specified anchor point 109 * is the top-left corner of the source rectangle, both coordinates must be positive. The method fails (does nothing 110 * and returns {@code null}) if the resulting source rectangle does not fit into the image. If successful, the view 111 * is modified and the resulting source rectangle is returned. Otherwise {@code null} is returned. 112 * 113 * @param im the image, which must be currently open (displayed) 114 * @param magnification the new magnification factor (1.0 = 100%) 115 * @param xa the x-coordinate of the anchor point 116 * @param ya the y-coordinate of the anchor point 117 * @return the resulting source rectangle if successful, {@code null} otherwise 118 */ 119 public static Rectangle setImageView(ImagePlus im, double magnification, int xa, int ya) { 120 ImageCanvas ic = im.getCanvas(); 121 if (ic == null) { 122 IJ.showMessage("Image has no canvas."); 123 return null; 124 } 125 126 Dimension d = ic.getPreferredSize(); 127 int dstWidth = d.width; 128 int dstHeight = d.height; 129 130 int imgWidth = im.getWidth(); 131 int imgHeight = im.getHeight(); 132 133 if (xa < 0 || ya < 0) { 134 throw new IllegalArgumentException("anchor coordinates may not be negative!"); 135 } 136 137 if (magnification <= 0.001) { 138 throw new IllegalArgumentException("magnification value must be positive!"); 139 } 140 141 // calculate size of the new source rectangle 142 int srcWidth = (int) Math.ceil(dstWidth / magnification); // check!! 143 int srcHeight = (int) Math.ceil(dstHeight / magnification); 144 145 if (xa + srcWidth > imgWidth || ya + srcHeight > imgHeight) { 146 // source rectangle does not fully fit into source image 147 return null; 148 } 149 150 Rectangle srcRect = new Rectangle(xa, ya, srcWidth, srcHeight); 151 ic.setSourceRect(srcRect); 152 im.repaintWindow(); 153 154 return srcRect; 155 } 156 157 // --------------------------------------------------------------- 158 159 private static final int DEFAULT_SCREEN_MARGIN_X = 10; // horizontal screen margin 160 private static final int DEFAULT_SCREEN_MARGIN_Y = 30; // vertical screen margin 161 162 /** 163 * Resizes the window of the given image to fit an arbitrary, user-specified magnification factor. The resulting 164 * window size is limited by the current screen size. The window size is reduced if too large but the given 165 * magnification factor remains always unchanged. <br> Adapted from 166 * https://albert.rierol.net/plugins/Zoom_Exact.java by Albert Cardona @ 2006 General Public License applies. 167 * 168 * @param im the image, which must be currently open (displayed) 169 * @param magnification the new magnification factor (1.0 = 100%) 170 * @param marginX horizontal screen margin 171 * @param marginY vertical screen margin 172 * @return true if successful, false otherwise 173 */ 174 public static boolean zoomExact(ImagePlus im, double magnification, int marginX, int marginY) { 175// TODO: Check calculation of source rectangle, final magnification may not always be exact! 176 ImageWindow win = im.getWindow(); 177 if (null == win) 178 return false; 179 ImageCanvas ic = win.getCanvas(); 180 if (null == ic) 181 return false; 182 183 if (magnification <= 0.001) { 184 return false; 185 } 186 187 // fit to screen 188 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); 189 int maxWidth = screen.width - marginX; 190 int maxHeight = screen.height - marginY; 191 double w = Math.min(magnification * im.getWidth(), maxWidth); 192 double h = Math.min(magnification * im.getHeight(), maxHeight); 193 194 Rectangle sourceRect = new Rectangle(0, 0, (int) (w / magnification), (int) (h / magnification)); 195 try { 196 // by reflection: 197 Field f_srcRect = ic.getClass().getDeclaredField("srcRect"); 198 f_srcRect.setAccessible(true); 199 f_srcRect.set(ic, sourceRect); 200 ic.setSize((int) w, (int) h); 201 ic.setMagnification(magnification); 202 win.pack(); 203 im.repaintWindow(); 204 return true; 205 } 206 catch (Exception e) { e.printStackTrace(); } 207 return false; 208 } 209 210 /** 211 * Convenience method for {@link #zoomExact(ImagePlus, double, int, int)} using default screen margins. 212 * 213 * @param im the image, which must be currently open (displayed) 214 * @param magnification the new magnification factor (1.0 = 100%) 215 * @return true if successful, false otherwise 216 */ 217 public static boolean zoomExact(ImagePlus im, double magnification) { 218 return zoomExact(im, magnification, DEFAULT_SCREEN_MARGIN_X, DEFAULT_SCREEN_MARGIN_Y); 219 } 220 221 // ----------------------------------------------------------------------- 222 223 /** 224 * Returns the current magnification (zoom) factor for the specified {@link ImagePlus} instance. Throws an exception 225 * if the image is currently not displayed. 226 * 227 * @param im the image, which must be currently open (displayed) 228 * @return the magnification factor 229 */ 230 public static double getMagnification(ImagePlus im) { 231 ImageWindow win = im.getWindow(); 232 if (win == null) { 233 throw new IllegalArgumentException("cannot get magnification for non-displayed image"); 234 } 235 return im.getWindow().getCanvas().getMagnification(); 236 } 237 238 // ----------------------------------------------------------------------- 239 240 /** 241 * Captures the specified image window and returns it as a new {@link ImagePlus} instance. Uses ImageJ's built-in 242 * {@link ScreenGrabber} plugin. 243 * 244 * @param im the image, which must be currently open (displayed) 245 * @return a new image with the grabbed contents 246 */ 247 public static ImagePlus captureImage(ImagePlus im) { 248 return new ScreenGrabber().captureImage(); 249 } 250 251}