package ij.plugin;
import ij.*;
import ij.gui.*;
import ij.process.*;
import ij.measure.*;
import ij.util.Tools;
import ij.plugin.frame.Recorder;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class Scaler implements PlugIn, TextListener, FocusListener {
private ImagePlus imp;
private static String xstr = "0.5";
private static String ystr = "0.5";
private String zstr = "1.0";
private static int newWidth, newHeight;
private int newDepth;
private boolean doZScaling;
private static boolean averageWhenDownsizing = true;
private static boolean newWindow = true;
private static int staticInterpolationMethod = ImageProcessor.BILINEAR;
private int interpolationMethod = staticInterpolationMethod;
private String[] methods = ImageProcessor.getInterpolationMethods();
private static boolean fillWithBackground;
private static boolean processStack = true;
private double xscale, yscale, zscale;
private String title = "Untitled";
private Vector fields;
private double bgValue;
private boolean constainAspectRatio = true;
private TextField xField, yField, zField, widthField, heightField, depthField;
private Rectangle r;
private Object fieldWithFocus;
private int oldDepth;
public void run(String arg) {
imp = IJ.getImage();
Roi roi = imp.getRoi();
ImageProcessor ip = imp.getProcessor();
if (roi!=null && !roi.isArea())
ip.resetRoi();
if (!showDialog(ip))
return;
doZScaling = newDepth>0 && newDepth!=oldDepth;
if (doZScaling) {
newWindow = true;
processStack = true;
}
if ((ip.getWidth()>1 && ip.getHeight()>1) || newWindow)
ip.setInterpolationMethod(interpolationMethod);
else
ip.setInterpolationMethod(ImageProcessor.NONE);
ip.setBackgroundValue(bgValue);
imp.startTiming();
try {
if (newWindow && imp.getStackSize()>1 && processStack) {
ImagePlus imp2 = createNewStack(imp, ip, newWidth, newHeight, newDepth);
if (imp2!=null) {
imp2.show();
imp2.changes = true;
}
} else
scale(imp);
}
catch(OutOfMemoryError o) {
IJ.outOfMemory("Scale");
}
IJ.showProgress(1.0);
record(imp, newWidth, newHeight, newDepth, interpolationMethod);
}
public static ImagePlus resize(ImagePlus imp, int dstWidth, int dstHeight, int dstDepth, String options) {
if (options==null)
options = "";
Scaler scaler = new Scaler();
if (options.contains("none"))
scaler.interpolationMethod = ImageProcessor.NONE;
if (options.contains("bicubic"))
scaler.interpolationMethod = ImageProcessor.BICUBIC;
if (scaler.xscale==0) {
scaler.xscale = (double)dstWidth/imp.getWidth();
scaler.yscale = (double)dstHeight/imp.getHeight();
scaler.zscale = (double)dstDepth/imp.getStackSize();
}
boolean processStack = imp.getStackSize()>1 && !options.contains("slice");
Roi roi = imp.getRoi();
ImageProcessor ip = imp.getProcessor();
if (roi!=null && !roi.isArea())
ip.resetRoi();
scaler.doZScaling = dstDepth!=1;
if (scaler.doZScaling)
scaler.processStack = true;
return scaler.createNewStack(imp, ip, dstWidth, dstHeight, dstDepth);
}
private ImagePlus createNewStack(ImagePlus imp, ImageProcessor ip, int newWidth, int newHeight, int newDepth) {
int nSlices = imp.getStackSize();
int w=imp.getWidth(), h=imp.getHeight();
ImagePlus imp2 = imp.createImagePlus();
Rectangle r = ip.getRoi();
boolean crop = r.width!=imp.getWidth() || r.height!=imp.getHeight();
ImageStack stack1 = imp.getStack();
ImageStack stack2 = new ImageStack(newWidth, newHeight);
boolean virtualStack = stack1.isVirtual();
double min = imp.getDisplayRangeMin();
double max = imp.getDisplayRangeMax();
ImageProcessor ip1, ip2;
int method = interpolationMethod;
if (w==1 || h==1)
method = ImageProcessor.NONE;
for (int i=1; i<=nSlices; i++) {
IJ.showStatus("Scale: " + i + "/" + nSlices);
ip1 = stack1.getProcessor(i);
String label = stack1.getSliceLabel(i);
if (crop) {
ip1.setRoi(r);
ip1 = ip1.crop();
}
ip1.setInterpolationMethod(method);
ip2 = ip1.resize(newWidth, newHeight, averageWhenDownsizing);
if (ip2!=null)
stack2.addSlice(label, ip2);
IJ.showProgress(i, nSlices);
}
imp2.setStack(title, stack2);
if (virtualStack)
imp2.setDisplayRange(min, max);
Calibration cal = imp2.getCalibration();
if (cal.scaled()) {
cal.pixelWidth *= 1.0/xscale;
cal.pixelHeight *= 1.0/yscale;
}
cal.xOrigin *= xscale;
cal.yOrigin *= yscale;
Overlay overlay = imp.getOverlay();
if (overlay!=null && !imp.getHideOverlay() && !doZScaling) {
overlay = overlay.duplicate();
Rectangle roi = imp.getProcessor().getRoi();
if (roi!=null)
overlay = overlay.crop(ip.getRoi());
imp2.setOverlay(overlay.scale(xscale, yscale));
}
IJ.showProgress(1.0);
int[] dim = imp.getDimensions();
imp2.setDimensions(dim[2], dim[3], dim[4]);
if (imp.isComposite()) {
imp2 = new CompositeImage(imp2, ((CompositeImage)imp).getMode());
((CompositeImage)imp2).copyLuts(imp);
}
if (imp.isHyperStack())
imp2.setOpenAsHyperStack(true);
if (doZScaling) {
double oldSize = imp2.getStackSize();
Resizer resizer = new Resizer();
resizer.setAverageWhenDownsizing(averageWhenDownsizing);
imp2 = resizer.zScale(imp2, newDepth, interpolationMethod);
cal = imp2.getCalibration();
cal.zOrigin *= imp2.getStackSize()/oldSize;
}
return imp2;
}
private void scale(ImagePlus imp) {
ImageProcessor ip = imp.getProcessor();
if (newWindow) {
Rectangle r = ip.getRoi();
ImagePlus imp2 = imp.createImagePlus();
imp2.setProcessor(title, ip.resize(newWidth, newHeight, averageWhenDownsizing));
Calibration cal = imp2.getCalibration();
if (cal.scaled()) {
cal.pixelWidth *= 1.0/xscale;
cal.pixelHeight *= 1.0/yscale;
}
cal.xOrigin *= xscale;
cal.yOrigin *= yscale;
Overlay overlay = imp.getOverlay();
if (overlay!=null && !imp.getHideOverlay()) {
overlay = overlay.duplicate();
int slice = imp.getCurrentSlice();
overlay.crop(slice,slice);
Rectangle roi = imp.getProcessor().getRoi();
if (roi!=null)
overlay = overlay.crop(ip.getRoi());
overlay = overlay.scale(xscale, yscale);
imp2.setOverlay(overlay);
}
imp2.show();
imp.trimProcessor();
imp2.trimProcessor();
imp2.changes = true;
} else {
if (processStack && imp.getStackSize()>1) {
Undo.reset();
StackProcessor sp = new StackProcessor(imp.getStack(), ip);
sp.scale(xscale, yscale, bgValue);
} else {
ip.snapshot();
Undo.setup(Undo.FILTER, imp);
ip.setSnapshotCopyMode(true);
ip.scale(xscale, yscale);
ip.setSnapshotCopyMode(false);
}
imp.deleteRoi();
imp.updateAndDraw();
imp.changes = true;
}
}
public static void record(ImagePlus imp, int w2, int h2, int d2, int method) {
if (!Recorder.scriptMode())
return;
String options = "";
if (method==ImageProcessor.NONE)
options = "none";
else if (method==ImageProcessor.BICUBIC)
options = "bicubic";
else
options = "bilinear";
Recorder.recordCall("imp = imp.resize("+w2+", "+h2+(d2>0&&d2!=imp.getStackSize()?", "+d2:"")+", \""+options+"\");");
}
boolean showDialog(ImageProcessor ip) {
String options = Macro.getOptions();
boolean isMacro = options!=null;
if (isMacro) {
if (options.contains(" interpolate"))
options = options.replaceAll(" interpolate", " interpolation=Bilinear");
else if (!options.contains(" interpolation="))
options = options+" interpolation=None";
if (options.contains("width=")&&options.contains(" height=")) {
xstr = "-";
ystr = "-";
if (options.contains(" depth="))
zstr = "-";
else
zstr = "1.0";
}
Macro.setOptions(options);
interpolationMethod = ImageProcessor.BILINEAR;
}
int bitDepth = imp.getBitDepth();
int stackSize = imp.getStackSize();
boolean isStack = stackSize>1;
oldDepth = stackSize;
if (isStack && !isMacro) {
xstr = "1.0";
ystr = "1.0";
zstr = "1.0";
}
r = ip.getRoi();
int width = newWidth;
if (width==0) width = r.width;
int height = (int)Math.round(((double)width*r.height/r.width));
xscale = Tools.parseDouble(xstr, 0.0);
yscale = Tools.parseDouble(ystr, 0.0);
zscale = 1.0;
if (xscale!=0.0 && yscale!=0.0) {
width = (int)Math.round(r.width*xscale);
height = (int)Math.round(r.height*yscale);
} else {
xstr = "-";
ystr = "-";
}
GenericDialog gd = new GenericDialog("Scale");
gd.addStringField("X Scale:", xstr);
gd.addStringField("Y Scale:", ystr);
if (isStack)
gd.addStringField("Z Scale:", zstr);
gd.setInsets(5, 0, 5);
gd.addStringField("Width (pixels):", ""+width);
gd.addStringField("Height (pixels):", ""+height);
if (isStack) {
String label = "Depth (images):";
if (imp.isHyperStack()) {
int slices = imp.getNSlices();
int frames = imp.getNFrames();
if (slices==1&&frames>1) {
label = "Depth (frames):";
oldDepth = frames;
} else {
label = "Depth (slices):";
oldDepth = slices;
}
}
gd.addStringField(label, ""+oldDepth);
}
fields = gd.getStringFields();
if (fields!=null) {
for (int i=0; i<fields.size(); i++) {
((TextField)fields.elementAt(i)).addTextListener(this);
((TextField)fields.elementAt(i)).addFocusListener(this);
}
xField = (TextField)fields.elementAt(0);
yField = (TextField)fields.elementAt(1);
if (isStack) {
zField = (TextField)fields.elementAt(2);
widthField = (TextField)fields.elementAt(3);
heightField = (TextField)fields.elementAt(4);
depthField = (TextField)fields.elementAt(5);
} else {
widthField = (TextField)fields.elementAt(2);
heightField = (TextField)fields.elementAt(3);
}
}
fieldWithFocus = xField;
gd.addChoice("Interpolation:", methods, methods[interpolationMethod]);
if (bitDepth==8 || bitDepth==24)
gd.addCheckbox("Fill with background color", fillWithBackground);
gd.addCheckbox("Average when downsizing", averageWhenDownsizing);
boolean hyperstack = imp.isHyperStack() || imp.isComposite();
if (isStack && !hyperstack)
gd.addCheckbox("Process entire stack", processStack);
gd.addCheckbox("Create new window", newWindow);
title = WindowManager.getUniqueName(imp.getTitle());
gd.setInsets(10, 0, 0);
gd.addStringField("Title:", title, 12);
gd.showDialog();
if (gd.wasCanceled())
return false;
xstr = gd.getNextString();
ystr = gd.getNextString();
xscale = Tools.parseDouble(xstr, 0.0);
yscale = Tools.parseDouble(ystr, 0.0);
if (isStack) {
zstr = gd.getNextString();
zscale = Tools.parseDouble(zstr, 0.0);
}
String wstr = gd.getNextString();
newWidth = (int)Math.round(Tools.parseDouble(wstr, 0));
newHeight = (int)Math.round(Tools.parseDouble(gd.getNextString(), 0));
if (newHeight!=0 && (wstr.equals("-") || wstr.equals("0")))
newWidth = (int)Math.round(newHeight * (double)r.width/r.height);
else if (newWidth!=0 && newHeight==0)
newHeight= (int)Math.round(newWidth * (double)r.height/r.width);
else if (newHeight!=0 && newWidth==0)
newWidth = (int)Math.round(newHeight * (double)r.width/r.height);
if (newWidth==0 || newHeight==0) {
IJ.error("Scaler", "Width or height is 0");
return false;
}
if (xscale>0.0 && yscale>0.0) {
newWidth = (int)Math.round(r.width*xscale);
newHeight = (int)Math.round(r.height*yscale);
}
if (isStack) {
newDepth = (int)Math.round(Tools.parseDouble(gd.getNextString(), 0));
if (zscale>0.0) {
int nSlices = stackSize;
if (imp.isHyperStack()) {
int slices = imp.getNSlices();
int frames = imp.getNFrames();
if (slices==1&&frames>1)
nSlices = frames;
else
nSlices = slices;
}
newDepth = (int)Math.round(nSlices*zscale);
}
}
interpolationMethod = gd.getNextChoiceIndex();
if (bitDepth==8 || bitDepth==24)
fillWithBackground = gd.getNextBoolean();
averageWhenDownsizing = gd.getNextBoolean();
if (isStack && !hyperstack)
processStack = gd.getNextBoolean();
if (hyperstack)
processStack = true;
newWindow = gd.getNextBoolean();
if (xscale==0.0) {
xscale = (double)newWidth/r.width;
yscale = (double)newHeight/r.height;
}
gd.setSmartRecording(true);
title = gd.getNextString();
if (fillWithBackground) {
Color bgc = Toolbar.getBackgroundColor();
if (bitDepth==8)
bgValue = ip.getBestIndex(bgc);
else if (bitDepth==24)
bgValue = bgc.getRGB();
} else
bgValue = 0.0;
if (!isMacro)
staticInterpolationMethod = interpolationMethod;
return true;
}
public void textValueChanged(TextEvent e) {
if (xField==null || yField==null)
return;
Object source = e.getSource();
double newXScale = xscale;
double newYScale = yscale;
double newZScale = zscale;
if (source==xField && fieldWithFocus==xField) {
String newXText = xField.getText();
newXScale = Tools.parseDouble(newXText,0);
if (newXScale==0) return;
if (newXScale!=xscale) {
int newWidth = (int)Math.round(newXScale*r.width);
widthField.setText(""+newWidth);
if (constainAspectRatio) {
yField.setText(newXText);
int newHeight = (int)Math.round(newXScale*r.height);
heightField.setText(""+newHeight);
}
}
} else if (source==yField && fieldWithFocus==yField) {
String newYText = yField.getText();
newYScale = Tools.parseDouble(newYText,0);
if (newYScale==0) return;
if (newYScale!=yscale) {
int newHeight = (int)Math.round(newYScale*r.height);
heightField.setText(""+newHeight);
}
} else if (source==zField && fieldWithFocus==zField) {
String newZText = zField.getText();
newZScale = Tools.parseDouble(newZText,0);
if (newZScale==0) return;
if (newZScale!=zscale) {
int nSlices = imp.getStackSize();
if (imp.isHyperStack()) {
int slices = imp.getNSlices();
int frames = imp.getNFrames();
if (slices==1&&frames>1)
nSlices = frames;
else
nSlices = slices;
}
int newDepth= (int)Math.round(newZScale*nSlices);
depthField.setText(""+newDepth);
}
} else if (source==widthField && fieldWithFocus==widthField) {
int newWidth = (int)Math.round(Tools.parseDouble(widthField.getText(), 0.0));
if (newWidth!=0) {
int newHeight = (int)Math.round(newWidth*(double)r.height/r.width);
heightField.setText(""+newHeight);
xField.setText("-");
yField.setText("-");
newXScale = 0.0;
newYScale = 0.0;
}
} else if (source==depthField && fieldWithFocus==depthField) {
int newDepth = (int)Math.round(Tools.parseDouble(depthField.getText(), 0.0));
if (newDepth!=0) {
zField.setText("-");
newZScale = 0.0;
}
}
xscale = newXScale;
yscale = newYScale;
zscale = newZScale;
}
public void focusGained(FocusEvent e) {
fieldWithFocus = e.getSource();
if (fieldWithFocus==widthField)
constainAspectRatio = true;
else if (fieldWithFocus==yField)
constainAspectRatio = false;
}
public void focusLost(FocusEvent e) {}
}