package ij.io; import ij.gui.*; import java.awt.*; import java.io.*; import java.util.*; import java.net.*; import java.awt.geom.*; /** Saves an ROI to a file or stream. RoiDecoder.java has a description of the file format. @see ij.io.RoiDecoder @see ij.plugin.RoiReader */ public class RoiEncoder { static final int HEADER_SIZE = 64; static final int HEADER2_SIZE = 64; static final int VERSION = 221; // changed to 221 in v1.45r private String path; private OutputStream f; private final int polygon=0, rect=1, oval=2, line=3, freeline=4, polyline=5, noRoi=6, freehand=7, traced=8, angle=9, point=10; private byte[] data; private String roiName; private int roiNameSize; /** Creates an RoiEncoder using the specified path. */ public RoiEncoder(String path) { this.path = path; } /** Creates an RoiEncoder using the specified OutputStream. */ public RoiEncoder(OutputStream f) { this.f = f; } /** Save the Roi to the file of stream. */ public void write(Roi roi) throws IOException { if (f!=null) { write(roi, f); } else { f = new FileOutputStream(path); write(roi, f); f.close(); } } /** Saves the specified ROI as a byte array. */ public static byte[] saveAsByteArray(Roi roi) { if (roi==null) return null; byte[] bytes = null; try { ByteArrayOutputStream out = new ByteArrayOutputStream(4096); RoiEncoder encoder = new RoiEncoder(out); encoder.write(roi); out.close(); bytes = out.toByteArray(); } catch (IOException e) { return null; } return bytes; } void write(Roi roi, OutputStream f) throws IOException { int roiType = roi.getType(); int type = rect; int options = 0; roiName = roi.getName(); if (roiName!=null) roiNameSize = roiName.length()*2; else roiNameSize = 0; switch (roiType) { case Roi.POLYGON: type=polygon; break; case Roi.FREEROI: type=freehand; break; case Roi.TRACED_ROI: type=traced; break; case Roi.OVAL: type=oval; break; case Roi.LINE: type=line; break; case Roi.POLYLINE: type=polyline; break; case Roi.FREELINE: type=freeline; break; case Roi.ANGLE: type=angle; break; case Roi.COMPOSITE: type=rect; break; // shape array size (36-39) will be >0 to indicate composite type case Roi.POINT: type=point; break; default: type = rect; break; } if (roiType==Roi.COMPOSITE) { saveShapeRoi(roi, type, f, options); return; } int n=0; int[] x=null,y=null; if (roi instanceof PolygonRoi) { Polygon p = ((PolygonRoi)roi).getNonSplineCoordinates(); n = p.npoints; x = p.xpoints; y = p.ypoints; } data = new byte[HEADER_SIZE+HEADER2_SIZE+n*4+roiNameSize]; Rectangle r = roi.getBounds(); data[0]=73; data[1]=111; data[2]=117; data[3]=116; // "Iout" putShort(RoiDecoder.VERSION_OFFSET, VERSION); data[RoiDecoder.TYPE] = (byte)type; putShort(RoiDecoder.TOP, r.y); putShort(RoiDecoder.LEFT, r.x); putShort(RoiDecoder.BOTTOM, r.y+r.height); putShort(RoiDecoder.RIGHT, r.x+r.width); putShort(RoiDecoder.N_COORDINATES, n); putInt(RoiDecoder.POSITION, roi.getPosition()); if (type==rect) { int arcSize = roi.getCornerDiameter(); if (arcSize>0) putShort(RoiDecoder.ROUNDED_RECT_ARC_SIZE, arcSize); } if (roi instanceof Line) { Line l = (Line)roi; putFloat(RoiDecoder.X1, l.x1); putFloat(RoiDecoder.Y1, l.y1); putFloat(RoiDecoder.X2, l.x2); putFloat(RoiDecoder.Y2, l.y2); if (roi instanceof Arrow) { putShort(RoiDecoder.SUBTYPE, RoiDecoder.ARROW); if (((Arrow)roi).getDoubleHeaded()) options |= RoiDecoder.DOUBLE_HEADED; if (((Arrow)roi).getOutline()) options |= RoiDecoder.OUTLINE; putShort(RoiDecoder.OPTIONS, options); putByte(RoiDecoder.ARROW_STYLE, ((Arrow)roi).getStyle()); putByte(RoiDecoder.ARROW_HEAD_SIZE, (int)((Arrow)roi).getHeadSize()); } } if (roi instanceof EllipseRoi) { putShort(RoiDecoder.SUBTYPE, RoiDecoder.ELLIPSE); double[] p = ((EllipseRoi)roi).getParams(); putFloat(RoiDecoder.X1, (float)p[0]); putFloat(RoiDecoder.Y1, (float)p[1]); putFloat(RoiDecoder.X2, (float)p[2]); putFloat(RoiDecoder.Y2, (float)p[3]); putFloat(RoiDecoder.ELLIPSE_ASPECT_RATIO, (float)p[4]); } // save stroke width, stroke color and fill color (1.43i or later) if (VERSION>=218) { saveStrokeWidthAndColor(roi); if ((roi instanceof PolygonRoi) && ((PolygonRoi)roi).isSplineFit()) { options |= RoiDecoder.SPLINE_FIT; putShort(RoiDecoder.OPTIONS, options); } } // save TextRoi if (n==0 && roi instanceof TextRoi) saveTextRoi((TextRoi)roi); else if (n==0 && roi instanceof ImageRoi) saveImageRoi((ImageRoi)roi); else putHeader2(roi, HEADER_SIZE+n*4); if (n>0) { int base1 = 64; int base2 = base1+2*n; for (int i=0; i<n; i++) { putShort(base1+i*2, x[i]); putShort(base2+i*2, y[i]); } } saveOverlayOptions(roi, options); f.write(data); } void saveStrokeWidthAndColor(Roi roi) { BasicStroke stroke = roi.getStroke(); if (stroke!=null) putShort(RoiDecoder.STROKE_WIDTH, (int)stroke.getLineWidth()); Color strokeColor = roi.getStrokeColor(); if (strokeColor!=null) putInt(RoiDecoder.STROKE_COLOR, strokeColor.getRGB()); Color fillColor = roi.getFillColor(); if (fillColor!=null) putInt(RoiDecoder.FILL_COLOR, fillColor.getRGB()); } void saveShapeRoi(Roi roi, int type, OutputStream f, int options) throws IOException { float[] shapeArray = ((ShapeRoi)roi).getShapeAsArray(); if (shapeArray==null) return; BufferedOutputStream bout = new BufferedOutputStream(f); Rectangle r = roi.getBounds(); data = new byte[HEADER_SIZE+HEADER2_SIZE+shapeArray.length*4+roiNameSize]; data[0]=73; data[1]=111; data[2]=117; data[3]=116; // "Iout" putShort(RoiDecoder.VERSION_OFFSET, VERSION); data[RoiDecoder.TYPE] = (byte)type; putShort(RoiDecoder.TOP, r.y); putShort(RoiDecoder.LEFT, r.x); putShort(RoiDecoder.BOTTOM, r.y+r.height); putShort(RoiDecoder.RIGHT, r.x+r.width); putInt(RoiDecoder.POSITION, roi.getPosition()); //putShort(16, n); putInt(36, shapeArray.length); // non-zero segment count indicate composite type if (VERSION>=218) saveStrokeWidthAndColor(roi); saveOverlayOptions(roi, options); // handle the actual data: data are stored segment-wise, i.e., // the type of the segment followed by 0-6 control point coordinates. int base = 64; for (int i=0; i<shapeArray.length; i++) { putFloat(base, shapeArray[i]); base += 4; } int hdr2Offset = HEADER_SIZE+shapeArray.length*4; //ij.IJ.log("saveShapeRoi: "+HEADER_SIZE+" "+shapeArray.length); putHeader2(roi, hdr2Offset); bout.write(data,0,data.length); bout.flush(); } void saveOverlayOptions(Roi roi, int options) { Overlay proto = roi.getPrototypeOverlay(); if (proto.getDrawLabels()) options |= RoiDecoder.OVERLAY_LABELS; if (proto.getDrawNames()) options |= RoiDecoder.OVERLAY_NAMES; if (proto.getDrawBackgrounds()) options |= RoiDecoder.OVERLAY_BACKGROUNDS; Font font = proto.getLabelFont(); if (font!=null && font.getStyle()==Font.BOLD) options |= RoiDecoder.OVERLAY_BOLD; putShort(RoiDecoder.OPTIONS, options); } void saveTextRoi(TextRoi roi) { Font font = roi.getCurrentFont(); String fontName = font.getName(); int size = font.getSize(); int style = font.getStyle(); String text = roi.getText(); int fontNameLength = fontName.length(); int textLength = text.length(); int textRoiDataLength = 16+fontNameLength*2+textLength*2; byte[] data2 = new byte[HEADER_SIZE+HEADER2_SIZE+textRoiDataLength+roiNameSize]; System.arraycopy(data, 0, data2, 0, HEADER_SIZE); data = data2; putShort(RoiDecoder.SUBTYPE, RoiDecoder.TEXT); putInt(HEADER_SIZE, size); putInt(HEADER_SIZE+4, style); putInt(HEADER_SIZE+8, fontNameLength); putInt(HEADER_SIZE+12, textLength); for (int i=0; i<fontNameLength; i++) putShort(HEADER_SIZE+16+i*2, fontName.charAt(i)); for (int i=0; i<textLength; i++) putShort(HEADER_SIZE+16+fontNameLength*2+i*2, text.charAt(i)); int hdr2Offset = HEADER_SIZE+textRoiDataLength; //ij.IJ.log("saveTextRoi: "+HEADER_SIZE+" "+textRoiDataLength+" "+fontNameLength+" "+textLength); putHeader2(roi, hdr2Offset); } void saveImageRoi(ImageRoi roi) { byte[] bytes = roi.getSerializedImage(); int imageSize = bytes.length; byte[] data2 = new byte[HEADER_SIZE+HEADER2_SIZE+imageSize+roiNameSize]; System.arraycopy(data, 0, data2, 0, HEADER_SIZE); data = data2; putShort(RoiDecoder.SUBTYPE, RoiDecoder.IMAGE); for (int i=0; i<imageSize; i++) putByte(HEADER_SIZE+i, bytes[i]&255); int hdr2Offset = HEADER_SIZE+imageSize; double opacity = roi.getOpacity(); putByte(hdr2Offset+RoiDecoder.IMAGE_OPACITY, (int)(opacity*255.0)); putInt(hdr2Offset+RoiDecoder.IMAGE_SIZE, imageSize); putHeader2(roi, hdr2Offset); } void putHeader2(Roi roi, int hdr2Offset) { //ij.IJ.log("putHeader2: "+hdr2Offset+" "+roiNameSize+" "+roiName); putInt(RoiDecoder.HEADER2_OFFSET, hdr2Offset); putInt(hdr2Offset+RoiDecoder.C_POSITION, roi.getCPosition()); putInt(hdr2Offset+RoiDecoder.Z_POSITION, roi.getZPosition()); putInt(hdr2Offset+RoiDecoder.T_POSITION, roi.getTPosition()); Overlay proto = roi.getPrototypeOverlay(); Color overlayLabelColor = proto.getLabelColor(); if (overlayLabelColor!=null) putInt(hdr2Offset+RoiDecoder.OVERLAY_LABEL_COLOR, overlayLabelColor.getRGB()); Font font = proto.getLabelFont(); if (font!=null) putShort(hdr2Offset+RoiDecoder.OVERLAY_FONT_SIZE, font.getSize()); if (roiNameSize>0) putName(roi, hdr2Offset); } void putName(Roi roi, int hdr2Offset) { int offset = hdr2Offset+HEADER2_SIZE; int nameLength = roiNameSize/2; putInt(hdr2Offset+RoiDecoder.NAME_OFFSET, offset); putInt(hdr2Offset+RoiDecoder.NAME_LENGTH, nameLength); for (int i=0; i<nameLength; i++) putShort(offset+i*2, roiName.charAt(i)); } void putByte(int base, int v) { data[base] = (byte)v; } void putShort(int base, int v) { data[base] = (byte)(v>>>8); data[base+1] = (byte)v; } void putFloat(int base, float v) { int tmp = Float.floatToIntBits(v); data[base] = (byte)(tmp>>24); data[base+1] = (byte)(tmp>>16); data[base+2] = (byte)(tmp>>8); data[base+3] = (byte)tmp; } void putInt(int base, int i) { data[base] = (byte)(i>>24); data[base+1] = (byte)(i>>16); data[base+2] = (byte)(i>>8); data[base+3] = (byte)i; } }