package ij.plugin;
import ij.*;
import ij.process.*;
import ij.gui.*;
import java.awt.*;
import ij.measure.*;
import java.awt.event.*;
public class ScaleBar implements PlugIn {
static final String[] locations = {"Upper Right", "Lower Right", "Lower Left", "Upper Left", "At Selection"};
static final int UPPER_RIGHT=0, LOWER_RIGHT=1, LOWER_LEFT=2, UPPER_LEFT=3, AT_SELECTION=4;
static final String[] colors = {"White","Black","Light Gray","Gray","Dark Gray","Red","Green","Blue","Yellow"};
static final String[] bcolors = {"None","Black","White","Dark Gray","Gray","Light Gray","Yellow","Blue","Green","Red"};
static final String[] checkboxLabels = {"Horizontal", "Vertical", "Bold Text", "Hide Text", "Serif Font", "Overlay"};
final static String SCALE_BAR = "|SB|";
private static final ScaleBarConfiguration sConfig = new ScaleBarConfiguration();
private ScaleBarConfiguration config = new ScaleBarConfiguration(sConfig);
ImagePlus imp;
int hBarWidthInPixels;
int vBarHeightInPixels;
int roiX, roiY, roiWidth, roiHeight;
boolean userRoiExists;
boolean[] checkboxStates = new boolean[6];
Rectangle hBackground = new Rectangle();
Rectangle hBar = new Rectangle();
Rectangle hText = new Rectangle();
Rectangle vBackground = new Rectangle();
Rectangle vBar = new Rectangle();
Rectangle vText = new Rectangle();
public void run(String arg) {
imp = WindowManager.getCurrentImage();
if (imp == null) {
IJ.noImage();
return;
}
imp.getProcessor().snapshot();
userRoiExists = parseCurrentROI();
boolean userOKed = askUserConfiguration(userRoiExists);
if (!userOKed) {
removeScalebar();
return;
}
if (!IJ.isMacro())
persistConfiguration();
updateScalebar(!config.labelAll);
}
void removeScalebar() {
imp.getProcessor().reset();
imp.updateAndDraw();
Overlay overlay = imp.getOverlay();
if (overlay!=null) {
overlay.remove(SCALE_BAR);
imp.draw();
}
}
boolean parseCurrentROI() {
Roi roi = imp.getRoi();
if (roi == null) return false;
Rectangle r = roi.getBounds();
roiX = r.x;
roiY = r.y;
roiWidth = r.width;
roiHeight = r.height;
return true;
}
void computeDefaultBarWidth(boolean currentROIExists) {
Calibration cal = imp.getCalibration();
ImageWindow win = imp.getWindow();
double mag = (win!=null)?win.getCanvas().getMagnification():1.0;
if (mag>1.0)
mag = 1.0;
double pixelWidth = cal.pixelWidth;
if (pixelWidth==0.0)
pixelWidth = 1.0;
double pixelHeight = cal.pixelHeight;
if (pixelHeight==0.0)
pixelHeight = 1.0;
double imageWidth = imp.getWidth()*pixelWidth;
double imageHeight = imp.getHeight()*pixelHeight;
if (currentROIExists && roiX>=0 && roiWidth>10) {
config.hBarWidth = roiWidth*pixelWidth;
}
else if (config.hBarWidth<=0.0 || config.hBarWidth>0.67*imageWidth) {
config.hBarWidth = (80.0*pixelWidth)/mag;
if (config.hBarWidth>0.67*imageWidth)
config.hBarWidth = 0.67*imageWidth;
if (config.hBarWidth>5.0)
config.hBarWidth = (int) config.hBarWidth;
}
if (currentROIExists && roiY>=0 && roiHeight>10) {
config.vBarHeight = roiHeight*pixelHeight;
}
else if (config.vBarHeight<=0.0 || config.vBarHeight>0.67*imageHeight) {
config.vBarHeight = (80.0*pixelHeight)/mag;
if (config.vBarHeight>0.67*imageHeight)
config.vBarHeight = 0.67*imageHeight;
if (config.vBarHeight>5.0)
config.vBarHeight = (int) config.vBarHeight;
}
}
boolean askUserConfiguration(boolean currentROIExists) {
if (currentROIExists) {
config.location = locations[AT_SELECTION];
}
if (IJ.isMacro())
config.updateFrom(new ScaleBarConfiguration());
if (config.hBarWidth <= 0 || config.vBarHeight <= 0 || currentROIExists) {
computeDefaultBarWidth(currentROIExists);
}
updateScalebar(true);
boolean multipleSlices = imp.getStackSize() > 1;
GenericDialog dialog = new BarDialog(getHUnit(), getVUnit(), config.hDigits, config.vDigits, multipleSlices);
DialogListener dialogListener = new BarDialogListener(multipleSlices);
dialog.addDialogListener(dialogListener);
dialog.showDialog();
return dialog.wasOKed();
}
void persistConfiguration() {
sConfig.updateFrom(config);
}
String getHUnit() {
String hUnits = imp.getCalibration().getXUnits();
if (hUnits.equals("microns"))
hUnits = IJ.micronSymbol+"m";
return hUnits;
}
String getVUnit() {
String vUnits = imp.getCalibration().getYUnits();
if (vUnits.equals("microns"))
vUnits = IJ.micronSymbol+"m";
return vUnits;
}
Overlay createScaleBarOverlay() throws MissingRoiException {
Overlay overlay = new Overlay();
Color color = getColor();
Color bcolor = getBColor();
int fontType = config.boldText?Font.BOLD:Font.PLAIN;
String face = config.serifFont?"Serif":"SanSerif";
Font font = new Font(face, fontType, config.fontSize);
ImageProcessor ip = imp.getProcessor();
ip.setFont(font);
setElementsPositions(ip);
if (bcolor != null) {
if (config.showHorizontal) {
Roi hBackgroundRoi = new Roi(hBackground.x, hBackground.y, hBackground.width, hBackground.height);
hBackgroundRoi.setFillColor(bcolor);
overlay.add(hBackgroundRoi, SCALE_BAR);
}
if (config.showVertical) {
Roi vBackgroundRoi = new Roi(vBackground.x, vBackground.y, vBackground.width, vBackground.height);
vBackgroundRoi.setFillColor(bcolor);
overlay.add(vBackgroundRoi, SCALE_BAR);
}
}
if (config.showHorizontal) {
Roi hBarRoi = new Roi(hBar.x, hBar.y, hBar.width, hBar.height);
hBarRoi.setFillColor(color);
overlay.add(hBarRoi, SCALE_BAR);
}
if (config.showVertical) {
Roi vBarRoi = new Roi(vBar.x, vBar.y, vBar.width, vBar.height);
vBarRoi.setFillColor(color);
overlay.add(vBarRoi, SCALE_BAR);
}
if (!config.hideText) {
if (config.showHorizontal) {
TextRoi hTextRoi = new TextRoi(hText.x, hText.y, getHLabel(), font);
hTextRoi.setStrokeColor(color);
overlay.add(hTextRoi, SCALE_BAR);
}
if (config.showVertical) {
TextRoi vTextRoi = new TextRoi(vText.x, vText.y + vText.height, getVLabel(), font);
vTextRoi.setStrokeColor(color);
vTextRoi.setAngle(90.0);
overlay.add(vTextRoi, SCALE_BAR);
}
}
return overlay;
}
String getHLabel() {
return IJ.d2s(config.hBarWidth, config.hDigits) + " " + getHUnit();
}
String getVLabel() {
return IJ.d2s(config.vBarHeight, config.vDigits) + " " + getVUnit();
}
int getHBoxWidthInPixels() {
updateFont();
ImageProcessor ip = imp.getProcessor();
int hLabelWidth = config.hideText ? 0 : ip.getStringWidth(getHLabel());
int hBoxWidth = Math.max(hBarWidthInPixels, hLabelWidth);
return (config.showHorizontal ? hBoxWidth : 0);
}
int getHBoxHeightInPixels() {
int hLabelHeight = config.hideText ? 0 : config.fontSize;
int hBoxHeight = config.barThicknessInPixels + (int) (hLabelHeight * 1.25);
return (config.showHorizontal ? hBoxHeight : 0);
}
int getVBoxHeightInPixels() {
updateFont();
ImageProcessor ip = imp.getProcessor();
int vLabelHeight = config.hideText ? 0 : ip.getStringWidth(getVLabel());
int vBoxHeight = Math.max(vBarHeightInPixels, vLabelHeight);
return (config.showVertical ? vBoxHeight : 0);
}
int getVBoxWidthInPixels() {
int vLabelWidth = config.hideText ? 0 : config.fontSize;
int vBoxWidth = config.barThicknessInPixels + (int) (vLabelWidth * 1.25);
return (config.showVertical ? vBoxWidth : 0);
}
int getOuterMarginSizeInPixels() {
int imageWidth = imp.getWidth();
int imageHeight = imp.getHeight();
return (imageWidth + imageHeight) / 100;
}
int getInnerMarginSizeInPixels() {
int maxWidth = Math.max(getHBoxWidthInPixels(), getVBoxHeightInPixels());
int margin = Math.max(maxWidth/20, 2);
return config.bcolor.equals("None") ? 0 : margin;
}
void updateFont() {
int fontType = config.boldText?Font.BOLD:Font.PLAIN;
String font = config.serifFont?"Serif":"SanSerif";
ImageProcessor ip = imp.getProcessor();
ip.setFont(new Font(font, fontType, config.fontSize));
ip.setAntialiasedText(true);
}
void setBackgroundBoxesPositions(ImageProcessor ip) throws MissingRoiException {
Calibration cal = imp.getCalibration();
hBarWidthInPixels = (int)Math.round(config.hBarWidth/cal.pixelWidth);
vBarHeightInPixels = (int)Math.round(config.vBarHeight/cal.pixelHeight);
boolean hTextTop = config.showVertical && (config.location.equals(locations[UPPER_LEFT]) || config.location.equals(locations[UPPER_RIGHT]));
int imageWidth = imp.getWidth();
int imageHeight = imp.getHeight();
int hBoxWidth = getHBoxWidthInPixels();
int hBoxHeight = getHBoxHeightInPixels();
int vBoxWidth = getVBoxWidthInPixels();
int vBoxHeight = getVBoxHeightInPixels();
int outerMargin = getOuterMarginSizeInPixels();
int innerMargin = getInnerMarginSizeInPixels();
hBackground.width = innerMargin + hBoxWidth + innerMargin;
hBackground.height = innerMargin + hBoxHeight + innerMargin;
vBackground.width = innerMargin + vBoxWidth + innerMargin;
vBackground.height = innerMargin + vBoxHeight + innerMargin;
if (config.location.equals(locations[UPPER_RIGHT])) {
hBackground.x = imageWidth - outerMargin - innerMargin - vBoxWidth + (config.showVertical ? config.barThicknessInPixels : 0) - hBoxWidth - innerMargin;
hBackground.y = outerMargin;
vBackground.x = imageWidth - outerMargin - innerMargin - vBoxWidth - innerMargin;
vBackground.y = outerMargin + (hTextTop ? hBoxHeight - config.barThicknessInPixels : 0);
hBackground.width += (config.showVertical ? vBoxWidth - config.barThicknessInPixels : 0);
} else if (config.location.equals(locations[LOWER_RIGHT])) {
hBackground.x = imageWidth - outerMargin - innerMargin - vBoxWidth - hBoxWidth + (config.showVertical ? config.barThicknessInPixels : 0) - innerMargin;
hBackground.y = imageHeight - outerMargin - innerMargin - hBoxHeight - innerMargin;
vBackground.x = imageWidth - outerMargin - innerMargin - vBoxWidth - innerMargin;
vBackground.y = imageHeight - outerMargin - innerMargin - hBoxHeight + (config.showHorizontal ? config.barThicknessInPixels : 0) - vBoxHeight - innerMargin;
vBackground.height += (config.showHorizontal ? hBoxHeight - config.barThicknessInPixels : 0);
} else if (config.location.equals(locations[UPPER_LEFT])) {
hBackground.x = outerMargin + (config.showVertical ? vBackground.width - 2*innerMargin - config.barThicknessInPixels : 0);
hBackground.y = outerMargin;
vBackground.x = outerMargin;
vBackground.y = outerMargin + (hTextTop ? hBoxHeight - config.barThicknessInPixels : 0);
hBackground.width += (config.showVertical ? vBoxWidth - config.barThicknessInPixels : 0);
hBackground.x -= (config.showVertical ? vBoxWidth - config.barThicknessInPixels : 0);
} else if (config.location.equals(locations[LOWER_LEFT])) {
hBackground.x = outerMargin + (config.showVertical ? vBackground.width - 2*innerMargin - config.barThicknessInPixels : 0);
hBackground.y = imageHeight - outerMargin - innerMargin - hBoxHeight - innerMargin;
vBackground.x = outerMargin;
vBackground.y = imageHeight - outerMargin - innerMargin - hBoxHeight + (config.showHorizontal ? config.barThicknessInPixels : 0) - vBoxHeight - innerMargin;
vBackground.height += (config.showHorizontal ? hBoxHeight - config.barThicknessInPixels : 0);
} else {
if (!userRoiExists)
throw new MissingRoiException();
hBackground.x = roiX;
hBackground.y = roiY;
vBackground.x = roiX;
vBackground.y = roiY;
}
}
void setElementsPositions(ImageProcessor ip) throws MissingRoiException {
setBackgroundBoxesPositions(ip);
int hBoxWidth = getHBoxWidthInPixels();
int hBoxHeight = getHBoxHeightInPixels();
int vBoxWidth = getVBoxWidthInPixels();
int vBoxHeight = getVBoxHeightInPixels();
int innerMargin = getInnerMarginSizeInPixels();
boolean right = config.location.equals(locations[LOWER_RIGHT]) || config.location.equals(locations[UPPER_RIGHT]);
boolean upper = config.location.equals(locations[UPPER_RIGHT]) || config.location.equals(locations[UPPER_LEFT]);
boolean hTextTop = config.showVertical && upper;
hBar.x = hBackground.x + innerMargin + (hBoxWidth - hBarWidthInPixels)/2 + (config.showVertical && !right && upper ? vBoxWidth - config.barThicknessInPixels : 0);
hBar.y = hBackground.y + innerMargin + (hTextTop ? hBoxHeight - config.barThicknessInPixels : 0);
hBar.width = hBarWidthInPixels;
hBar.height = config.barThicknessInPixels;
hText.height = config.hideText ? 0 : config.fontSize;
hText.width = config.hideText ? 0 : ip.getStringWidth(getHLabel());
hText.x = hBackground.x + innerMargin + (hBoxWidth - hText.width)/2 + (config.showVertical && !right && upper ? vBoxWidth - config.barThicknessInPixels : 0);
hText.y = hTextTop ? (hBackground.y + innerMargin - (int)Math.round(config.fontSize*0.25)) : (hBar.y + hBar.height);
vBar.width = config.barThicknessInPixels;
vBar.height = vBarHeightInPixels;
vBar.x = vBackground.x + (right ? innerMargin : vBackground.width - config.barThicknessInPixels - innerMargin);
vBar.y = vBackground.y + innerMargin + (vBoxHeight - vBar.height)/2;
vText.height = config.hideText ? 0 : ip.getStringWidth(getVLabel());
vText.width = config.hideText ? 0 : config.fontSize;
vText.x = right ? (vBar.x + vBar.width) : (vBar.x - vBoxWidth + config.barThicknessInPixels - (int)Math.round(config.fontSize*0.25));
vText.y = vBackground.y + innerMargin + (vBoxHeight - vText.height)/2;
}
Color getColor() {
Color c = Color.black;
if (config.color.equals(colors[0])) c = Color.white;
else if (config.color.equals(colors[2])) c = Color.lightGray;
else if (config.color.equals(colors[3])) c = Color.gray;
else if (config.color.equals(colors[4])) c = Color.darkGray;
else if (config.color.equals(colors[5])) c = Color.red;
else if (config.color.equals(colors[6])) c = Color.green;
else if (config.color.equals(colors[7])) c = Color.blue;
else if (config.color.equals(colors[8])) c = Color.yellow;
return c;
}
Color getBColor() {
if (config.bcolor==null || config.bcolor.equals(bcolors[0])) return null;
Color bc = Color.white;
if (config.bcolor.equals(bcolors[1])) bc = Color.black;
else if (config.bcolor.equals(bcolors[3])) bc = Color.darkGray;
else if (config.bcolor.equals(bcolors[4])) bc = Color.gray;
else if (config.bcolor.equals(bcolors[5])) bc = Color.lightGray;
else if (config.bcolor.equals(bcolors[6])) bc = Color.yellow;
else if (config.bcolor.equals(bcolors[7])) bc = Color.blue;
else if (config.bcolor.equals(bcolors[8])) bc = Color.green;
else if (config.bcolor.equals(bcolors[9])) bc = Color.red;
return bc;
}
void updateScalebar(boolean previewOnly) {
removeScalebar();
Overlay scaleBarOverlay;
try {
scaleBarOverlay = createScaleBarOverlay();
} catch (MissingRoiException e) {
return; }
Overlay impOverlay = imp.getOverlay();
if (impOverlay==null) {
impOverlay = new Overlay();
}
if (config.useOverlay) {
for (Roi roi : scaleBarOverlay)
impOverlay.add(roi);
imp.setOverlay(impOverlay);
} else {
if (previewOnly) {
ImageProcessor ip = imp.getProcessor();
drawOverlayOnProcessor(scaleBarOverlay, ip);
imp.updateAndDraw();
} else {
ImageStack stack = imp.getStack();
for (int i=1; i<=stack.size(); i++) {
ImageProcessor ip = stack.getProcessor(i);
drawOverlayOnProcessor(scaleBarOverlay, ip);
imp.updateAndDraw();
}
imp.setStack(stack);
}
}
}
void drawOverlayOnProcessor(Overlay overlay, ImageProcessor processor) {
if (processor.getBitDepth() == 8 || processor.getBitDepth() == 24) {
processor.drawOverlay(overlay);
return;
}
ImageProcessor ip = new ByteProcessor(imp.getWidth(), imp.getHeight());
ip.setCalibrationTable(processor.getCalibrationTable());
LUT lut = ip.getLut();
for (Roi roi : overlay) {
Color fillColor = roi.getFillColor();
if (fillColor != null) {
int i = processor.getBestIndex(fillColor);
roi.setFillColor(new Color(lut.getRed(i), lut.getGreen(i), lut.getBlue(i)));
if (roi.getFillColor().equals(Color.BLACK)) roi.setFillColor(new Color(1, 1, 1));
}
Color strokeColor = roi.getStrokeColor();
if (strokeColor != null) {
int i = processor.getBestIndex(strokeColor);
roi.setStrokeColor(new Color(lut.getRed(i), lut.getGreen(i), lut.getBlue(i)));
if (roi.getStrokeColor().equals(Color.BLACK)) roi.setStrokeColor(new Color(1, 1, 1));
}
}
ip.drawOverlay(overlay);
for (int y = 0; y < ip.getHeight(); y++)
for (int x = 0; x < ip.getWidth(); x++) {
int p = ip.get(x, y);
if (p > 0) {
p = (int)Math.round(p * ((processor.getMax() - processor.getMin()) / 255d) + (float)processor.getMin());
if (processor.getBitDepth() == 32)
p = Float.floatToIntBits(p);
processor.putPixel(x, y, p);
}
}
}
class BarDialog extends GenericDialog {
BarDialog(String hUnits, String vUnits, int hDigits, int vDigits, boolean multipleSlices) {
super("Scale Bar");
addNumericField("Width in "+hUnits+": ", config.hBarWidth, hDigits);
addNumericField("Height in "+vUnits+": ", config.vBarHeight, vDigits);
addNumericField("Thickness in pixels: ", config.barThicknessInPixels, 0);
addNumericField("Font size: ", config.fontSize, 0);
addChoice("Color: ", colors, config.color);
addChoice("Background: ", bcolors, config.bcolor);
addChoice("Location: ", locations, config.location);
checkboxStates[0] = config.showHorizontal; checkboxStates[1] = config.showVertical;
checkboxStates[2] = config.boldText; checkboxStates[3] = config.hideText;
checkboxStates[4] = config.serifFont; checkboxStates[5] = config.useOverlay;
setInsets(10, 25, 0);
addCheckboxGroup(3, 2, checkboxLabels, checkboxStates);
if (multipleSlices) {
setInsets(0, 25, 0);
addCheckbox("Label all slices", config.labelAll);
}
}
}
class BarDialogListener implements DialogListener {
boolean multipleSlices;
public BarDialogListener(boolean multipleSlices) {
super();
this.multipleSlices = multipleSlices;
}
@Override
public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
config.hBarWidth = gd.getNextNumber();
config.vBarHeight = gd.getNextNumber();
config.barThicknessInPixels = (int)gd.getNextNumber();
config.fontSize = (int)gd.getNextNumber();
config.color = gd.getNextChoice();
config.bcolor = gd.getNextChoice();
config.location = gd.getNextChoice();
config.showHorizontal = gd.getNextBoolean();
config.showVertical = gd.getNextBoolean();
config.boldText = gd.getNextBoolean();
config.hideText = gd.getNextBoolean();
config.serifFont = gd.getNextBoolean();
config.useOverlay = gd.getNextBoolean();
if (multipleSlices)
config.labelAll = gd.getNextBoolean();
if (!config.showHorizontal && !config.showVertical && IJ.isMacro()) {
config.showHorizontal = true;
config.barThicknessInPixels = (int)config.vBarHeight;
config.vBarHeight = 0.0;
}
String widthString = ((TextField) gd.getNumericFields().elementAt(0)).getText();
boolean hasDecimalPoint = false;
config.hDigits = 0;
for (int i = 0; i < widthString.length(); i++) {
if (hasDecimalPoint)
config.hDigits += 1;
if (widthString.charAt(i) == '.')
hasDecimalPoint = true;
}
String heightString = ((TextField) gd.getNumericFields().elementAt(1)).getText();
hasDecimalPoint = false;
config.vDigits = 0;
for (int i = 0; i < heightString.length(); i++) {
if (hasDecimalPoint)
config.vDigits += 1;
if (heightString.charAt(i) == '.')
hasDecimalPoint = true;
}
updateScalebar(true);
return true;
}
}
class MissingRoiException extends Exception {
MissingRoiException() {
super("Scalebar location is set to AT_SELECTION but there is no selection on the image.");
}
}
static class ScaleBarConfiguration {
private static int defaultBarHeight = 4;
boolean showHorizontal;
boolean showVertical;
double hBarWidth;
double vBarHeight;
int hDigits; int vDigits;
int barThicknessInPixels;
String location;
String color;
String bcolor;
boolean boldText;
boolean hideText;
boolean serifFont;
boolean useOverlay;
int fontSize;
boolean labelAll;
ScaleBarConfiguration() {
this.showHorizontal = true;
this.showVertical = false;
this.hBarWidth = -1;
this.vBarHeight = -1;
this.barThicknessInPixels = defaultBarHeight;
this.location = locations[LOWER_RIGHT];
this.color = colors[0];
this.bcolor = bcolors[0];
this.boldText = true;
this.hideText = false;
this.serifFont = false;
this.useOverlay = true;
this.fontSize = 14;
this.labelAll = false;
}
ScaleBarConfiguration(ScaleBarConfiguration model) {
this.updateFrom(model);
}
void updateFrom(ScaleBarConfiguration model) {
this.showHorizontal = model.showHorizontal;
this.showVertical = model.showVertical;
this.hBarWidth = model.hBarWidth;
this.vBarHeight = model.vBarHeight;
this.hDigits = model.hDigits;
this.vDigits = model.vDigits;
this.barThicknessInPixels = model.barThicknessInPixels;
this.location = locations[LOWER_RIGHT];
this.color = model.color;
this.bcolor = model.bcolor;
this.boldText = model.boldText;
this.serifFont = model.serifFont;
this.hideText = model.hideText;
this.useOverlay = model.useOverlay;
this.fontSize = model.fontSize;
this.labelAll = model.labelAll;
}
}
}