1 package ij.plugin.frame;
3 import java.awt.event.*;
4 import java.awt.image.*;
16 implements Runnable, ActionListener, AdjustmentListener, ItemListener
19 static final int AUTO_THRESHOLD = 5000;
20 static final String[] channelLabels = {
"Red",
"Green",
"Blue",
"Cyan",
"Magenta",
"Yellow",
"RGB"};
21 static final int[] channelConstants = {4, 2, 1, 3, 5, 6, 7};
23 ContrastPlot plot =
new ContrastPlot();
25 private static Frame instance;
27 int minSliderValue=-1, maxSliderValue=-1, brightnessValue=-1, contrastValue=-1;
28 int sliderRange = 256;
29 boolean doAutoAdjust,doReset,doSet,doApplyLut,doThreshold,doUpdate;
32 Button autoB, resetB, setB, applyB, threshB, updateB;
35 Object previousSnapshot;
38 double previousMin, previousMax;
39 double defaultMin, defaultMax;
40 int contrast, brightness;
42 Scrollbar minSlider, maxSlider, contrastSlider, brightnessSlider;
45 GridBagLayout gridbag;
48 boolean windowLevel, balance;
60 super(
"Brightness & Contrast");
71 public void run(String arg)
73 SliderPane pane = null;
74 windowLevel = arg.equals(
"wl");
75 balance = arg.equals(
"balance");
83 if (instance != null) {
90 gridbag =
new GridBagLayout();
91 c =
new GridBagConstraints();
92 getContentPane().setLayout(gridbag);
98 c.fill = GridBagConstraints.BOTH;
99 c.anchor = GridBagConstraints.CENTER;
100 c.insets =
new Insets(4, 4, 4, 4);
101 gridbag.setConstraints(plot, c);
102 getContentPane().add(plot);
106 minSlider =
new Scrollbar(Scrollbar.HORIZONTAL, sliderRange/2, 1, 0, sliderRange);
107 pane =
new SliderPane(minSlider,
"Minimum: ");
109 gridbag.setConstraints(pane, c);
110 getContentPane().add(pane);
111 minSlider.addAdjustmentListener(
this);
112 minSlider.setUnitIncrement(1);
117 maxSlider =
new Scrollbar(Scrollbar.HORIZONTAL, sliderRange/2, 1, 0, sliderRange);
118 pane =
new SliderPane(maxSlider,
"Maximum: ");
120 gridbag.setConstraints(pane, c);
121 getContentPane().add(pane);
122 maxSlider.addAdjustmentListener(
this);
123 maxSlider.setUnitIncrement(1);
127 brightnessSlider =
new Scrollbar(Scrollbar.HORIZONTAL, sliderRange/2, 1, 0, sliderRange);
128 pane =
new SliderPane(brightnessSlider, windowLevel ?
"Level: " :
"Brightness: ");
130 gridbag.setConstraints(pane, c);
131 getContentPane().add(pane);
132 brightnessSlider.addAdjustmentListener(
this);
133 brightnessSlider.setUnitIncrement(1);
137 contrastSlider =
new Scrollbar(Scrollbar.HORIZONTAL, sliderRange/2, 1, 0, sliderRange);
138 pane =
new SliderPane(contrastSlider, windowLevel ?
"Window: " :
"Contrast: ");
140 gridbag.setConstraints(pane, c);
141 getContentPane().add(pane);
142 contrastSlider.addAdjustmentListener(
this);
143 contrastSlider.setUnitIncrement(1);
149 choice =
new Choice();
150 for (
int i=0; i<channelLabels.length; i++) {
151 choice.addItem(channelLabels[i]);
153 gridbag.setConstraints(choice, c);
154 choice.addItemListener(
this);
155 getContentPane().add(choice);
160 panel =
new JPanel();
161 panel.setLayout(
new GridLayout(0,2, 0, 0));
162 autoB =
new TrimmedButton(
"Auto",trim);
163 autoB.addActionListener(
this);
164 autoB.addKeyListener(ij);
166 resetB =
new TrimmedButton(
"Reset",trim);
167 resetB.addActionListener(
this);
168 resetB.addKeyListener(ij);
170 setB =
new TrimmedButton(
"Set",trim);
171 setB.addActionListener(
this);
172 setB.addKeyListener(ij);
174 applyB =
new TrimmedButton(
"Apply",trim);
175 applyB.addActionListener(
this);
176 applyB.addKeyListener(ij);
178 if (!windowLevel && !balance) {
179 threshB =
new TrimmedButton(
"Thresh",trim);
180 threshB.addActionListener(
this);
181 threshB.addKeyListener(ij);
183 updateB =
new TrimmedButton(
"Update",trim);
184 updateB.addActionListener(
this);
185 updateB.addKeyListener(ij);
189 gridbag.setConstraints(panel, c);
190 getContentPane().add(panel);
195 thread =
new Thread(
this,
"ContrastAdjuster");
216 updateLabels(imp, ip);
231 if (e.getSource()==minSlider)
232 minSliderValue = minSlider.getValue();
233 else if (e.getSource()==maxSlider)
234 maxSliderValue = maxSlider.getValue();
235 else if (e.getSource()==contrastSlider)
236 contrastValue = contrastSlider.getValue();
238 brightnessValue = brightnessSlider.getValue();
251 Button b = (Button)e.getSource();
281 boolean snapshotChanged = RGBImage && previousSnapshot!=null && ((
ColorProcessor)ip).getSnapshotPixels()!=previousSnapshot;
282 if (imp.
getID()!=previousImageID || snapshotChanged || type!=previousType)
283 setupNewImage(imp, ip);
284 previousImageID = imp.
getID();
297 void setupNewImage(
ImagePlus imp, ImageProcessor ip)
305 previousSnapshot = null;
306 double min2 = ip.getMin();
307 double max2 = ip.getMax();
309 {min2=0.0; max2=255.0;}
310 if ((ip instanceof ShortProcessor) || (ip instanceof FloatProcessor)) {
312 defaultMin = ip.getMin();
313 defaultMax = ip.getMax();
318 setMinAndMax(ip, min2, max2);
322 IJ.log(
"min: " + min);
323 IJ.log(
"max: " + max);
324 IJ.log(
"defaultMin: " + defaultMin);
325 IJ.log(
"defaultMax: " + defaultMax);
327 plot.defaultMin = defaultMin;
328 plot.defaultMax = defaultMax;
330 updateScrollBars(null);
344 void setMinAndMax(ImageProcessor ip,
double min,
double max)
346 if (channels!=7 && ip instanceof ColorProcessor)
347 ((ColorProcessor)ip).setMinAndMax(min, max, channels);
349 ip.setMinAndMax(min, max);
369 void updateLabels(ImagePlus imp, ImageProcessor ip) {
402 void updateScrollBars(Scrollbar sb)
404 if (sb==null || sb!=contrastSlider) {
405 double mid = sliderRange/2;
406 double c = ((defaultMax-defaultMin)/(max-min))*mid;
408 c = sliderRange - ((max-min)/(defaultMax-defaultMin))*mid;
410 if (contrastSlider!=null)
411 contrastSlider.setValue(contrast);
413 if (sb==null || sb!=brightnessSlider) {
414 double level = min + (max-min)/2.0;
415 double normalizedLevel = 1.0 - (level - defaultMin)/(defaultMax-defaultMin);
416 brightness = (int)(normalizedLevel*sliderRange);
417 brightnessSlider.setValue(brightness);
419 if (minSlider!=null && (sb==null || sb!=minSlider))
420 minSlider.setValue(scaleDown(min));
421 if (maxSlider!=null && (sb==null || sb!=maxSlider))
422 maxSlider.setValue(scaleDown(max));
433 int scaleDown(
double v)
435 if (v<defaultMin) v = defaultMin;
436 if (v>defaultMax) v = defaultMax;
437 return (
int)((v-defaultMin)*255.0/(defaultMax-defaultMin));
448 void doMasking(ImagePlus imp, ImageProcessor ip)
450 ImageProcessor mask = imp.getMask();
462 void adjustMin(ImagePlus imp, ImageProcessor ip,
double minvalue)
465 min = defaultMin + minvalue*(defaultMax-defaultMin)/255.0;
470 setMinAndMax(ip, min, max);
473 if (RGBImage) doMasking(imp, ip);
474 updateScrollBars(minSlider);
485 void adjustMax(ImagePlus imp, ImageProcessor ip,
double maxvalue)
488 max = defaultMin + maxvalue*(defaultMax-defaultMin)/255.0;
493 setMinAndMax(ip, min, max);
496 if (RGBImage) doMasking(imp, ip);
497 updateScrollBars(maxSlider);
508 void adjustBrightness(ImagePlus imp, ImageProcessor ip,
double bvalue)
510 double center = defaultMin + (defaultMax-defaultMin)*((sliderRange-bvalue)/sliderRange);
511 double width = max-min;
512 min = center - width/2.0;
513 max = center + width/2.0;
514 setMinAndMax(ip, min, max);
517 if (RGBImage) doMasking(imp, ip);
518 updateScrollBars(brightnessSlider);
529 void adjustContrast(ImagePlus imp, ImageProcessor ip,
int cvalue)
532 double center = min + (max-min)/2.0;
533 double range = defaultMax-defaultMin;
534 double mid = sliderRange/2;
538 slope = mid/(sliderRange-cvalue);
540 min = center-(0.5*range)/slope;
541 max = center+(0.5*range)/slope;
543 setMinAndMax(ip, min, max);
544 if (RGBImage) doMasking(imp, ip);
545 updateScrollBars(contrastSlider);
556 void reset(ImagePlus imp, ImageProcessor ip)
560 if ((ip instanceof ShortProcessor) || (ip instanceof FloatProcessor)) {
562 defaultMin = ip.getMin();
563 defaultMax = ip.getMax();
564 plot.defaultMin = defaultMin;
565 plot.defaultMax = defaultMax;
569 setMinAndMax(ip, min, max);
570 updateScrollBars(null);
574 Recorder.record(
"resetMinAndMax");
585 void update(ImagePlus imp, ImageProcessor ip)
587 if (previousMin==0.0 && previousMax==0.0 || imp.getType()!=previousType)
592 setMinAndMax(ip, min, max);
593 updateScrollBars(null);
606 void plotHistogram(ImagePlus imp)
608 ImageStatistics stats;
609 if (balance && (channels==4 || channels==2 || channels==1) && imp.getType()==ImagePlus.COLOR_RGB) {
610 int w = imp.getWidth();
611 int h = imp.getHeight();
612 byte[] r =
new byte[w*h];
613 byte[] g =
new byte[w*h];
614 byte[] b =
new byte[w*h];
615 ((ColorProcessor)imp.getProcessor()).getRGB(r,g,b);
619 else if (channels==2)
621 else if (channels==1)
623 ImageProcessor ip =
new ByteProcessor(w, h, pixels, null);
624 stats = ImageStatistics.getStatistics(ip, 0, imp.getCalibration());
626 stats = imp.getStatistics();
627 plot.setHistogram(stats);
638 void apply(ImagePlus imp, ImageProcessor ip) {
643 if (imp.getType()==ImagePlus.COLOR_RGB) {
650 if (imp.getType()!=ImagePlus.GRAY8) {
652 IJ.showStatus(
"Apply requires an 8-bit grayscale image");
656 int[] table =
new int[256];
657 int min = (int)ip.getMin();
658 int max = (int)ip.getMax();
659 for (
int i=0; i<256; i++) {
665 table[i] = (int)(((
double)(i-min)/(max-min))*255);
667 ip.setRoi(imp.getRoi());
668 if (ip.getMask()!=null) ip.snapshot();
669 ip.applyTable(table);
670 ip.reset(ip.getMask());
684 void threshold(ImagePlus imp, ImageProcessor ip)
686 int threshold = (int)((defaultMax-defaultMin)/2.0);
689 setMinAndMax(ip, min, max);
691 updateScrollBars(null);
702 void setThreshold(ImageProcessor ip)
704 if (!(ip instanceof ByteProcessor))
706 if (((ByteProcessor)ip).isInvertedLut())
707 ip.setThreshold(max, 255, ImageProcessor.NO_LUT_UPDATE);
709 ip.setThreshold(0, max, ImageProcessor.NO_LUT_UPDATE);
720 void autoAdjust(ImagePlus imp, ImageProcessor ip)
724 Calibration cal = imp.getCalibration();
725 imp.setCalibration(null);
726 ImageStatistics stats = imp.getStatistics();
727 imp.setCalibration(cal);
728 int[] histogram = stats.histogram;
729 if (autoThreshold<10)
730 autoThreshold = AUTO_THRESHOLD;
733 int threshold = stats.pixelCount/autoThreshold;
735 boolean found =
false;
738 found = histogram[i] > threshold;
739 }
while (!found && i<255);
744 found = histogram[i] > threshold;
745 }
while (!found && i>0);
749 min = stats.histMin+hmin*stats.binSize;
750 max = stats.histMin+hmax*stats.binSize;
752 {min=stats.min; max=stats.max;}
753 setMinAndMax(ip, min, max);
758 updateScrollBars(null);
759 Roi roi = imp.getRoi();
761 ImageProcessor mask = roi.getMask();
775 void setMinAndMax(ImagePlus imp, ImageProcessor ip)
779 Calibration cal = imp.getCalibration();
780 int digits = (ip instanceof FloatProcessor)||cal.calibrated()?2:0;
781 double minValue = cal.getCValue(min);
782 double maxValue = cal.getCValue(max);
783 GenericDialog gd =
new GenericDialog(
"Set Min and Max");
784 gd.addNumericField(
"Minimum Displayed Value: ", minValue, digits);
785 gd.addNumericField(
"Maximum Displayed Value: ", maxValue, digits);
787 if (gd.wasCanceled())
789 minValue = gd.getNextNumber();
790 maxValue = gd.getNextNumber();
791 minValue = cal.getRawValue(minValue);
792 maxValue = cal.getRawValue(maxValue);
793 if (maxValue>=minValue) {
796 setMinAndMax(ip, min, max);
797 updateScrollBars(null);
798 if (RGBImage) doMasking(imp, ip);
800 Recorder.record(
"setMinAndMax", (
int)min, (
int)max);
812 void setWindowLevel(ImagePlus imp, ImageProcessor ip)
816 Calibration cal = imp.getCalibration();
817 int digits = (ip instanceof FloatProcessor)||cal.calibrated()?2:0;
818 double minValue = cal.getCValue(min);
819 double maxValue = cal.getCValue(max);
821 double windowValue = maxValue - minValue;
822 double levelValue = minValue + windowValue/2.0;
823 GenericDialog gd =
new GenericDialog(
"Set W&L");
824 gd.addNumericField(
"Window Center (Level): ", levelValue, digits);
825 gd.addNumericField(
"Window Width: ", windowValue, digits);
827 if (gd.wasCanceled())
829 levelValue = gd.getNextNumber();
830 windowValue = gd.getNextNumber();
831 minValue = levelValue-(windowValue/2.0);
832 maxValue = levelValue+(windowValue/2.0);
833 minValue = cal.getRawValue(minValue);
834 maxValue = cal.getRawValue(maxValue);
835 if (maxValue>=minValue) {
838 setMinAndMax(ip, minValue, maxValue);
839 updateScrollBars(null);
840 if (RGBImage) doMasking(imp, ip);
842 Recorder.record(
"setMinAndMax", (
int)min, (
int)max);
848 static final int RESET=0, AUTO=1, SET=2, APPLY=3, THRESHOLD=4, MIN=5, MAX=6,
849 BRIGHTNESS=7, CONTRAST=8, UPDATE=9;
863 catch(InterruptedException e) {}
882 int minvalue = minSliderValue;
883 int maxvalue = maxSliderValue;
884 int bvalue = brightnessValue;
885 int cvalue = contrastValue;
886 if (doReset) action = RESET;
887 else if (doAutoAdjust) action = AUTO;
888 else if (doSet) action = SET;
889 else if (doApplyLut) action = APPLY;
890 else if (doThreshold) action = THRESHOLD;
891 else if (doUpdate) action = UPDATE;
892 else if (minSliderValue>=0) action = MIN;
893 else if (maxSliderValue>=0) action = MAX;
894 else if (brightnessValue>=0) action = BRIGHTNESS;
895 else if (contrastValue>=0) action = CONTRAST;
897 minSliderValue = maxSliderValue = brightnessValue = contrastValue = -1;
898 doReset = doAutoAdjust = doSet = doApplyLut = doThreshold = doUpdate =
false;
899 imp = ij.getImagePlus();
909 if (RGBImage && !imp.
lock())
913 case RESET: reset(imp, ip);
break;
914 case AUTO: autoAdjust(imp, ip);
break;
915 case SET:
if (windowLevel) setWindowLevel(imp, ip);
else setMinAndMax(imp, ip);
break;
916 case APPLY: apply(imp, ip);
break;
917 case THRESHOLD: threshold(imp, ip);
break;
918 case UPDATE: update(imp, ip);
break;
919 case MIN: adjustMin(imp, ip, minvalue);
break;
920 case MAX: adjustMax(imp, ip, maxvalue);
break;
921 case BRIGHTNESS: adjustBrightness(imp, ip, bvalue);
break;
922 case CONTRAST: adjustContrast(imp, ip, cvalue);
break;
925 updateLabels(imp, ip);
983 channels = channelConstants[choice.getSelectedIndex()];
1007 class ContrastPlot
extends Canvas implements MouseListener
1010 static final int WIDTH = 200, HEIGHT=64;
1011 double defaultMin = 0;
1012 double defaultMax = 255;
1020 public ContrastPlot() {
1021 addMouseListener(
this);
1022 setSize(WIDTH+1, HEIGHT+1);
1027 public Dimension getPreferredSize() {
1028 return new Dimension(WIDTH+1, HEIGHT+1);
1031 void setHistogram(ImageStatistics stats) {
1032 long startTime = System.currentTimeMillis();
1033 histogram = stats.histogram;
1034 if (histogram.length!=256)
1035 {histogram=null;
return;}
1036 for (
int i=0; i<128; i++)
1037 histogram[i] = (histogram[2*i]+histogram[2*i+1])/2;
1040 for (
int i=0; i<128; i++) {
1041 if (histogram[i]>maxCount) {
1042 maxCount = histogram[i];
1047 for (
int i=0; i<128; i++) {
1048 if ((histogram[i]>maxCount2) && (i!=mode))
1049 maxCount2 = histogram[i];
1051 hmax = stats.maxCount;
1052 if ((hmax>(maxCount2*2)) && (maxCount2!=0)) {
1053 hmax = (int)(maxCount2*1.5);
1054 histogram[mode] = hmax;
1060 public void update(Graphics g) {
1064 public void paint(Graphics g) {
1066 double scale = (double)WIDTH/(defaultMax-defaultMin);
1069 slope = HEIGHT/(max-min);
1070 if (min>=defaultMin) {
1071 x1 = (int)(scale*(min-defaultMin));
1076 y1 = HEIGHT-(int)((defaultMin-min)*slope);
1080 if (max<=defaultMax) {
1081 x2 = (int)(scale*(max-defaultMin));
1086 y2 = HEIGHT-(int)((defaultMax-min)*slope);
1090 if (histogram!=null) {
1091 if (os==null && hmax!=0) {
1092 os = createImage(WIDTH,HEIGHT);
1093 osg = os.getGraphics();
1094 osg.setColor(Color.white);
1095 osg.fillRect(0, 0, WIDTH, HEIGHT);
1096 osg.setColor(Color.gray);
1097 for (
int i = 0; i < WIDTH; i++)
1098 osg.drawLine(i, HEIGHT, i, HEIGHT - ((
int)(HEIGHT * histogram[i])/hmax));
1101 g.drawImage(os, 0, 0,
this);
1103 g.setColor(Color.white);
1104 g.fillRect(0, 0, WIDTH, HEIGHT);
1106 g.setColor(Color.black);
1107 g.drawLine(x1, y1, x2, y2);
1108 g.drawLine(x2, HEIGHT-5, x2, HEIGHT);
1109 g.drawRect(0, 0, WIDTH, HEIGHT);
1112 public void mousePressed(MouseEvent e) {}
1113 public void mouseReleased(MouseEvent e) {}
1114 public void mouseExited(MouseEvent e) {}
1115 public void mouseClicked(MouseEvent e) {}
1116 public void mouseEntered(MouseEvent e) {}
1121 class TrimmedLabel
extends JLabel {
1122 int trim = IJ.isMacOSX() && IJ.isJava14()?0:6;
1124 public TrimmedLabel(String title) {
1128 public Dimension getMinimumSize() {
1129 return new Dimension(super.getMinimumSize().width, super.getMinimumSize().height-trim);
1132 public Dimension getPreferredSize() {
1133 return getMinimumSize();
1138 class SliderPane
extends JPanel
1140 SliderPane(Scrollbar slider, String labelText)
1142 setLayout(
new GridLayout(1, 2));
1143 add(
new JLabel(labelText));