package ij;
import ij.process.*;
import ij.gui.*;
import ij.plugin.*;
import ij.plugin.frame.*;
import ij.io.FileInfo;
import java.awt.*;
import java.awt.image.*;
public class CompositeImage extends ImagePlus {
public static final int COMPOSITE=1, COLOR=2, GRAYSCALE=3, TRANSPARENT=4;
public static final int MAX_CHANNELS = 8;
public static final Color[] colors = {Color.red, Color.green, Color.blue, Color.white, Color.cyan, Color.magenta, Color.yellow, Color.white};
int[] rgbPixels;
boolean newPixels;
MemoryImageSource imageSource;
Image awtImage;
WritableRaster rgbRaster;
SampleModel rgbSampleModel;
BufferedImage rgbImage;
ColorModel rgbCM;
ImageProcessor[] cip;
LUT[] lut;
int currentChannel = -1;
int previousChannel;
int currentSlice = 1;
int currentFrame = 1;
boolean singleChannel;
boolean[] active = new boolean[MAX_CHANNELS];
int mode = COLOR;
int bitDepth;
double[] displayRanges;
byte[][] channelLuts;
boolean customLuts;
boolean syncChannels;
public CompositeImage(ImagePlus imp) {
this(imp, COLOR);
}
public CompositeImage(ImagePlus imp, int mode) {
if (mode<COMPOSITE || mode>GRAYSCALE)
mode = COLOR;
this.mode = mode;
int channels = imp.getNChannels();
bitDepth = getBitDepth();
if (IJ.debugMode) IJ.log("CompositeImage: "+imp+" "+mode+" "+channels);
ImageStack stack2;
boolean isRGB = imp.getBitDepth()==24;
if (isRGB) {
if (imp.getImageStackSize()>1)
throw new IllegalArgumentException("RGB stacks not supported");
stack2 = getRGBStack(imp);
} else
stack2 = imp.getImageStack();
int stackSize = stack2.getSize();
if (channels==1 && isRGB)
channels = 3;
if (channels==1 && stackSize<=MAX_CHANNELS && !imp.dimensionsSet)
channels = stackSize;
if (channels<1 || (stackSize%channels)!=0)
throw new IllegalArgumentException("stacksize not multiple of channels");
if (mode==COMPOSITE && channels>MAX_CHANNELS)
this.mode = COLOR;
compositeImage = true;
int z = imp.getNSlices();
int t = imp.getNFrames();
if (channels==stackSize || channels*z*t!=stackSize)
setDimensions(channels, stackSize/channels, 1);
else
setDimensions(channels, z, t);
setStack(imp.getTitle(), stack2);
setCalibration(imp.getCalibration());
FileInfo fi = imp.getOriginalFileInfo();
if (fi!=null) {
displayRanges = fi.displayRanges;
channelLuts = fi.channelLuts;
}
setFileInfo(fi);
Object info = imp.getProperty("Info");
if (info!=null)
setProperty("Info", imp.getProperty("Info"));
setProperties(imp.getPropertiesAsArray());
if (mode==COMPOSITE) {
for (int i=0; i<MAX_CHANNELS; i++)
active[i] = true;
} else
active[0] = true;
setRoi(imp.getRoi());
setOverlay(imp.getOverlay());
if (channels!=stackSize)
setOpenAsHyperStack(true);
}
@Override
public Image getImage() {
if (img==null)
updateImage();
return img;
}
public void updateChannelAndDraw() {
if (!customLuts) singleChannel = true;
updateAndDraw();
}
public void updateAllChannelsAndDraw() {
if (mode!=COMPOSITE)
updateChannelAndDraw();
else {
syncChannels = true;
singleChannel = false;
updateAndDraw();
}
}
public ImageProcessor getChannelProcessor() {
if (cip!=null && currentChannel!=-1)
return cip[currentChannel];
else
return getProcessor();
}
synchronized void setup(int channels, ImageStack stack2) {
if (stack2!=null && stack2.getSize()>0 && (stack2.getProcessor(1) instanceof ColorProcessor)) { cip = null;
lut = null;
return;
}
setupLuts(channels);
if (mode==COMPOSITE) {
cip = new ImageProcessor[channels];
for (int i=0; i<channels; ++i) {
cip[i] = stack2.getProcessor(i+1);
cip[i].setLut(lut[i]);
}
currentSlice = currentFrame = 1;
}
}
void setupLuts(int channels) {
if (ip==null)
return;
if (lut==null || lut.length<channels) {
if (displayRanges!=null && channels!=displayRanges.length/2)
displayRanges = null;
if (displayRanges==null&&ip.getMin()==0.0&&ip.getMax()==0.0)
ip.resetMinAndMax();
lut = new LUT[channels];
LUT lut2 = channels>MAX_CHANNELS?createLutFromColor(Color.white):null;
for (int i=0; i<channels; ++i) {
if (channelLuts!=null && i<channelLuts.length) {
lut[i] = createLutFromBytes(channelLuts[i]);
customLuts = true;
} else if (i<MAX_CHANNELS)
lut[i] = createLutFromColor(colors[i]);
else
lut[i] = (LUT)lut2.clone();
if (displayRanges!=null) {
lut[i].min = displayRanges[i*2];
lut[i].max = displayRanges[i*2+1];
} else {
lut[i].min = ip.getMin();
lut[i].max = ip.getMax();
}
}
displayRanges = null;
}
}
public void resetDisplayRanges() {
int channels = getNChannels();
if (lut==null)
setupLuts(channels);
ImageStack stack2 = getImageStack();
if (lut==null || channels!=lut.length || channels>stack2.getSize() || channels>MAX_CHANNELS)
return;
for (int i=0; i<channels; ++i) {
ImageProcessor ip2 = stack2.getProcessor(i+1);
ip2.resetMinAndMax();
lut[i].min = ip2.getMin();
lut[i].max = ip2.getMax();
}
}
public void updateAndDraw() {
if (win==null) {
img = null;
return;
}
updateImage();
if (win!=null)
notifyListeners(UPDATED);
draw();
}
public synchronized void updateImage() {
int imageSize = width*height;
int nChannels = getNChannels();
int redValue, greenValue, blueValue;
int ch = getChannel();
if (ch>nChannels) ch = nChannels;
boolean newChannel = false;
if (ch-1!=currentChannel) {
previousChannel = currentChannel;
currentChannel = ch-1;
newChannel = true;
}
ImageProcessor ip = getProcessor();
if (mode!=COMPOSITE) {
if (newChannel) {
setupLuts(nChannels);
LUT cm = lut[currentChannel];
if (ip!=null && !(ip instanceof ColorProcessor)) {
if (mode==COLOR)
ip.setLut(cm);
if (!(cm.min==0.0&&cm.max==0.0))
ip.setMinAndMax(cm.min, cm.max);
}
if (!IJ.isMacro()) ContrastAdjuster.update();
for (int i=0; i<MAX_CHANNELS; i++)
active[i] = i==currentChannel?true:false;
Channels.updateChannels();
}
if (ip!=null)
img = ip.createImage();
return;
}
if (nChannels==1) {
cip = null;
rgbPixels = null;
awtImage = null;
if (ip!=null)
img = ip.createImage();
return;
}
if (cip==null||cip[0].getWidth()!=width||cip[0].getHeight()!=height||getBitDepth()!=bitDepth) {
setup(nChannels, getImageStack());
rgbPixels = null;
rgbSampleModel = null;
if (currentChannel>=nChannels) {
setSlice(1);
currentChannel = 0;
newChannel = true;
}
bitDepth = getBitDepth();
}
if (newChannel) {
getProcessor().setMinAndMax(cip[currentChannel].getMin(), cip[currentChannel].getMax());
if (!IJ.isMacro()) ContrastAdjuster.update();
}
if (getSlice()!=currentSlice || getFrame()!=currentFrame) {
currentSlice = getSlice();
currentFrame = getFrame();
int position = getStackIndex(1, currentSlice, currentFrame);
if (cip==null) return;
for (int i=0; i<nChannels; ++i)
cip[i].setPixels(getImageStack().getProcessor(position+i).getPixels());
}
if (rgbPixels == null) {
rgbPixels = new int[imageSize];
newPixels = true;
imageSource = null;
rgbRaster = null;
rgbImage = null;
}
cip[currentChannel].setMinAndMax(ip.getMin(),ip.getMax());
int projectionMode = ImageProcessor.SUM_PROJECTION;
String prop = getProp("CompositeProjection");
if (prop!=null) {
if (prop.contains("Max")||prop.contains("max"))
projectionMode = ImageProcessor.MAX_PROJECTION;
else if (prop.contains("Min")||prop.contains("min"))
projectionMode = ImageProcessor.MIN_PROJECTION;
else if (prop.contains("Invert")||prop.contains("invert"))
projectionMode = ImageProcessor.INVERT_PROJECTION;
}
long t0 = IJ.debugMode?System.nanoTime():0L;
if (singleChannel && nChannels<=3) {
switch (currentChannel) {
case 0: cip[0].updateComposite(rgbPixels, ImageProcessor.UPDATE_RED); break;
case 1: cip[1].updateComposite(rgbPixels, ImageProcessor.UPDATE_GREEN); break;
case 2: cip[2].updateComposite(rgbPixels, ImageProcessor.UPDATE_BLUE); break;
}
} else if (projectionMode==ImageProcessor.INVERT_PROJECTION){
makeInvertedComposite(active);
} else {
if (cip==null) return;
if (syncChannels) {
ImageProcessor ip2 = getProcessor();
double min=ip2.getMin(), max=ip2.getMax();
for (int i=0; i<nChannels; i++) {
cip[i].setMinAndMax(min, max);
lut[i].min = min;
lut[i].max = max;
}
syncChannels = false;
}
if (active[0])
cip[0].updateComposite(rgbPixels, ImageProcessor.SET_FIRST_CHANNEL);
else {
int fill = projectionMode==ImageProcessor.MIN_PROJECTION?0xffffff:0;
for (int i=1; i<imageSize; i++)
rgbPixels[i] = fill;
}
if (cip==null || nChannels>cip.length)
return;
for (int i=1; i<nChannels; i++)
if (active[i]) cip[i].updateComposite(rgbPixels, projectionMode);
}
if (IJ.debugMode) IJ.log(""+(System.nanoTime()-t0)/1000L);
createBufferedImage();
if (img==null && awtImage!=null)
img = awtImage;
singleChannel = false;
}
private void makeInvertedComposite(boolean[] chnActive) {
int bitDepth = getBitDepth();
int w = getWidth();
int h = getHeight();
int nChn = getNChannels();
int nChnActive = 0;
for (int c=0; c<nChn; c++){
if (chnActive[c])
nChnActive++;
}
byte[][] in8 = null;
short[][] in16 = null;
float[][] in32 = null;
switch (bitDepth) {
case 8: in8=new byte[nChn][]; break;
case 16: in16=new short[nChn][]; break;
case 32: in32=new float[nChn][]; break;
}
double[] mins = new double[nChn];
double[] maxs = new double[nChn];
double[] scale = new double[nChn];
LUT[] luts = getLuts();
for (int c=0; c<nChn; c++){
mins[c] = cip[c].getMin();
maxs[c] = cip[c].getMax();
scale[c] = (255.0 / (maxs[c] - mins[c]));
switch (bitDepth) {
case 8: in8[c] = (byte[]) cip[c].getPixels(); break;
case 16: in16[c] = (short[]) cip[c].getPixels(); break;
case 32: in32[c] = (float[]) cip[c].getPixels(); break;
}
}
int value;
int[] v = new int[nChn];
int[] r = new int[nChn]; int[] g = new int[nChn]; int[] b = new int[nChn];
int sumR, sumG, sumB;
int newR, newG, newB;
for (int idx=0; idx<w*h; idx++) {
for (int c=0; c<nChn; c++){
switch (bitDepth) {
case 8: v[c] = (int)Math.floor(((in8[c][idx]&0xff)-mins[c])*scale[c]); break;
case 16: v[c] = (int)Math.floor(((in16[c][idx]&0xffff)-mins[c])*scale[c]); break;
case 32: v[c] = (int)Math.floor((in32[c][idx]-mins[c])*scale[c]); break;
}
v[c] = Math.min(v[c], 255);
v[c] = Math.max(v[c], 0);
r[c] = luts[c].getRed(v[c]);
g[c] = luts[c].getGreen(v[c]);
b[c] = luts[c].getBlue(v[c]);
}
sumR = sumG = sumB = 0;
for (int c=0; c<nChn; c++){
if (chnActive[c]){
sumR += r[c];
sumG += g[c];
sumB += b[c];
}
}
newR = sumR - (nChnActive-1)*255;
newG = sumG - (nChnActive-1)*255;
newB = sumB - (nChnActive-1)*255;
newR = Math.max(newR, 0);
newG = Math.max(newG, 0);
newB = Math.max(newB, 0);
value = newR*256*256 + newG*256 + newB;
rgbPixels[idx] = value;
}
}
void createImage() {
if (imageSource==null) {
rgbCM = new DirectColorModel(32, 0xff0000, 0xff00, 0xff);
imageSource = new MemoryImageSource(width, height, rgbCM, rgbPixels, 0, width);
imageSource.setAnimated(true);
imageSource.setFullBufferUpdates(true);
awtImage = Toolkit.getDefaultToolkit().createImage(imageSource);
newPixels = false;
} else if (newPixels){
imageSource.newPixels(rgbPixels, rgbCM, 0, width);
newPixels = false;
} else
imageSource.newPixels();
}
void createBufferedImage() {
if (rgbSampleModel==null)
rgbSampleModel = getRGBSampleModel();
if (rgbRaster==null) {
DataBuffer dataBuffer = new DataBufferInt(rgbPixels, width*height, 0);
rgbRaster = Raster.createWritableRaster(rgbSampleModel, dataBuffer, null);
}
if (rgbImage==null)
rgbImage = new BufferedImage(rgbCM, rgbRaster, false, null);
awtImage = rgbImage;
}
SampleModel getRGBSampleModel() {
rgbCM = new DirectColorModel(24, 0xff0000, 0xff00, 0xff);
WritableRaster wr = rgbCM.createCompatibleWritableRaster(1, 1);
SampleModel sampleModel = wr.getSampleModel();
sampleModel = sampleModel.createCompatibleSampleModel(width, height);
return sampleModel;
}
ImageStack getRGBStack(ImagePlus imp) {
ImageProcessor ip = imp.getProcessor();
int w = ip.getWidth();
int h = ip.getHeight();
int size = w*h;
byte[] r = new byte[size];
byte[] g = new byte[size];
byte[] b = new byte[size];
((ColorProcessor)ip).getRGB(r, g, b);
ImageStack stack = new ImageStack(w, h);
stack.addSlice("Red", r);
stack.addSlice("Green", g);
stack.addSlice("Blue", b);
stack.setColorModel(ip.getDefaultColorModel());
return stack;
}
public LUT createLutFromColor(Color color) {
return LUT.createLutFromColor(color);
}
LUT createLutFromBytes(byte[] bytes) {
if (bytes==null || bytes.length!=768)
return createLutFromColor(Color.white);
byte[] r = new byte[256];
byte[] g = new byte[256];
byte[] b = new byte[256];
for (int i=0; i<256; i++) r[i] = bytes[i];
for (int i=0; i<256; i++) g[i] = bytes[256+i];
for (int i=0; i<256; i++) b[i] = bytes[512+i];
return new LUT(r, g, b);
}
public Color getChannelColor() {
if (lut==null || mode==GRAYSCALE)
return Color.black;
IndexColorModel cm = lut[getChannelIndex()];
if (cm==null)
return Color.black;
int index = cm.getMapSize() - 1;
int r = cm.getRed(index);
int g = cm.getGreen(index);
int b = cm.getBlue(index);
if (r<100 || g<100 || b<100)
return new Color(r, g, b);
else
return Color.black;
}
public ImageProcessor getProcessor(int channel) {
if (cip==null || channel>cip.length)
return null;
else
return cip[channel-1];
}
public boolean[] getActiveChannels() {
return active;
}
public synchronized void setMode(int mode) {
if (mode<COMPOSITE || mode>GRAYSCALE)
return;
if (mode==COMPOSITE && getNChannels()>MAX_CHANNELS)
mode = COLOR;
if (!(mode==COMPOSITE && mode==this.mode)) {
for (int i=0; i<MAX_CHANNELS; i++)
active[i] = true;
}
if (this.mode!=COMPOSITE && mode==COMPOSITE)
img = null;
this.mode = mode;
if (mode==COLOR || mode==GRAYSCALE) {
if (cip!=null) {
for (int i=0; i<cip.length; i++) {
if (cip[i]!=null) cip[i].setPixels(null);
cip[i] = null;
}
}
cip = null;
rgbPixels = null;
awtImage = null;
currentChannel = -1;
}
if (mode==GRAYSCALE || mode==TRANSPARENT)
ip.setColorModel(ip.getDefaultColorModel());
Channels.updateChannels();
}
public int getMode() {
return mode;
}
public String getModeAsString() {
switch (mode) {
case COMPOSITE: return "composite";
case COLOR: return "color";
case GRAYSCALE: return "grayscale";
}
return "";
}
public LUT getChannelLut(int channel) {
int channels = getNChannels();
if (lut==null) setupLuts(channels);
if (channel<1 || channel>lut.length)
throw new IllegalArgumentException("Channel out of range: "+channel);
return lut[channel-1];
}
public LUT getChannelLut() {
int c = getChannelIndex();
return lut[c];
}
public LUT[] getLuts() {
int channels = getNChannels();
if (lut==null)
setupLuts(channels);
LUT[] luts = new LUT[channels];
for (int i=0; i<channels; i++) {
if (i<lut.length)
luts[i] = (LUT)lut[i].clone();
else
luts[i] = (LUT)lut[0].clone();
}
return luts;
}
public void setLuts(LUT[] luts) {
int channels = getNChannels();
if (lut==null) setupLuts(channels);
if (luts==null || luts.length<channels)
throw new IllegalArgumentException("Lut array is null or too small");
for (int i=0; i<channels; i++)
setChannelLut(luts[i], i+1);
}
public synchronized void copyLuts(ImagePlus imp) {
int channels = getNChannels();
if (!imp.isComposite() || imp.getNChannels()!=channels)
return;
CompositeImage ci = (CompositeImage)imp;
LUT[] luts = ci.getLuts();
if (luts!=null && luts.length==channels) {
lut = luts;
cip = null;
}
int mode2 = ci.getMode();
setMode(mode2);
if (mode2==COMPOSITE) {
boolean[] active2 = ci.getActiveChannels();
for (int i=0; i<MAX_CHANNELS; i++)
active[i] = active2[i];
}
if (ci.hasCustomLuts())
customLuts = true;
}
int getChannelIndex() {
int channels = getNChannels();
if (lut==null) setupLuts(channels);
int index = getChannel()-1;
return index;
}
public void reset() {
int nChannels = getNChannels();
if (nChannels>MAX_CHANNELS && getMode()==COMPOSITE)
setMode(COLOR);
setup(nChannels, getImageStack());
}
public void completeReset() {
cip = null;
lut = null;
}
public void setChannelLut(LUT table) {
int c = getChannelIndex();
double min = lut[c].min;
double max = lut[c].max;
lut[c] = table;
lut[c].min = min;
lut[c].max = max;
if (mode==COMPOSITE && cip!=null && c<cip.length) {
cip[c].setColorModel(lut[c] );
imageSource = null;
newPixels = true;
img = null;
}
currentChannel = -1;
getProcessor().setLut(table);
customLuts = true;
if (!IJ.isMacro()) ContrastAdjuster.update();
}
public synchronized void setChannelLut(LUT table, int channel) {
int channels = getNChannels();
if (lut==null) setupLuts(channels);
if (channel<1 || channel>lut.length)
throw new IllegalArgumentException("Channel out of range");
lut[channel-1] = (LUT)table.clone();
if (getWindow()!=null && channel==getChannel())
getProcessor().setLut(lut[channel-1]);
if (cip!=null && cip.length>=channel && cip[channel-1]!=null)
cip[channel-1].setLut(lut[channel-1]);
else
cip = null;
customLuts = true;
}
public void setChannelColorModel(IndexColorModel cm) {
setChannelLut(new LUT(cm,0.0,0.0));
}
public void setDisplayRange(double min, double max) {
ip.setMinAndMax(min, max);
int c = getChannelIndex();
lut[c].min = min;
lut[c].max = max;
if (getWindow()==null && cip!=null && c<cip.length)
cip[c].setLut(lut[c]);
}
public double getDisplayRangeMin() {
if (lut!=null)
return lut[getChannelIndex()].min;
else
return 0.0;
}
public double getDisplayRangeMax() {
if (lut!=null)
return lut[getChannelIndex()].max;
else
return 255.0;
}
public void resetDisplayRange() {
if (getType()==GRAY16 && getDefault16bitRange()!=0) {
int defaultRange = getDefault16bitRange();
for (int i=1; i<=getNChannels(); i++) {
LUT lut = getChannelLut(i);
lut.min = 0;
lut.max = Math.pow(2,defaultRange)-1;
if (getWindow()!=null)
setChannelLut(lut, i);
}
} else {
ip.resetMinAndMax();
int c = getChannelIndex();
lut[c].min = ip.getMin();
lut[c].max = ip.getMax();
}
}
public boolean hasCustomLuts() {
return customLuts && mode!=GRAYSCALE;
}
public void close() {
super.close();
rgbPixels = null;
imageSource = null;
awtImage = null;
rgbRaster = null;
rgbSampleModel = null;
rgbImage = null;
rgbCM = null;
if (cip!=null) {
for (int i=0; i<cip.length; i++)
cip[i] = null;
cip = null;
}
if (lut!=null) {
for (int i=0; i<lut.length; i++)
lut[i] = null;
lut = null;
}
if (channelLuts!=null) {
for (int i=0; i<channelLuts.length; i++)
channelLuts[i] = null;
channelLuts = null;
}
}
public synchronized void setChannelsUpdated() {
if (cip!=null) {
for (int i=0; i<cip.length; i++) {
if (cip[i]!=null) cip[i].setPixels(null);
cip[i] = null;
}
}
cip = null;
lut = null;
img = null;
currentChannel = -1;
previousChannel = 0;
currentSlice = currentFrame = 1;
singleChannel = false;
rgbPixels = null;
awtImage = null;
channelLuts = null;
boolean[] active = new boolean[MAX_CHANNELS];
}
}