package com.profcon.tini.stikclik; import java.io.DataOutputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.DataInputStream; /** Convert a camera bitmap into a TIFF image. This class converts * the raw rasters from the camera into a TIFF image. Understanding * it propbably requires a fairly detailed knowledge of both. * See http://www.chipcenter.com/circuitcellar/august00/c0800bl1.htm * for the camera's format. */ public class TiffBuilder { /** Convert from rggb into tiff. */ private byte[] rggb, tiff; /** tiff[at] is the next byte of tiff to write. */ private int at; private ByteArrayOutputStream baos = new ByteArrayOutputStream(); private DataOutputStream dos = new DataOutputStream(baos); private void flush() { byte[] buf = baos.toByteArray(); baos.reset(); System.arraycopy(buf, 0, tiff, at, buf.length); at += buf.length; } /** swapShort(0x1234)==0x3412. */ private short swapShort(short s) { return (short) (((s>>>8)&0xFF)+(s<<8)); } /** Translate the raw rggb from the camera into TIFF format. */ public synchronized void translate(byte[] rggb, byte[] tiff) throws Exception { this.rggb = rggb; this.tiff = tiff; at = 0; writeHeader(); writePixels(); } // Some TIFF magic numbers private static final short TIFF_BYTE = 1; private static final short TIFF_SHORT = 3; private static final short TIFF_LONG = 4; private void writeHeader() throws Exception { // Write TIFF ID bytes and image file directory offset (follows immediately). dos.write(new byte[] { 0x4d, 0x4d }); dos.writeShort(0x2a); dos.writeInt(8); // 9 tagged entries in the first and only IFD. dos.writeShort(9); // Write the 9 tagged entries // Tag 1: SubfileType dos.writeShort(0xff); // Tag dos.writeShort(TIFF_SHORT); // data type dos.writeInt(1); // data len dos.writeShort(1); // data offset or value dos.writeShort(0); // padding // Tag 2: ImageWidth dos.writeShort(0x100); // Tag dos.writeShort(TIFF_LONG); // data type dos.writeInt(1); // data len dos.writeInt(TIFF_WIDTH); // data offset or value // Tag 3: ImageLength dos.writeShort(0x101); // Tag dos.writeShort(TIFF_LONG); // data type dos.writeInt(1); // data len dos.writeInt(TIFF_HEIGHT); // data offset or value // Tag 4: BitsPerSample dos.writeShort(0x102); // Tag dos.writeShort(TIFF_SHORT); // data type dos.writeInt(1); // data len dos.writeShort(8); // data offset or value dos.writeShort(0); // padding // Tag 5: PhotometricInterpretation dos.writeShort(0x106); // Tag dos.writeShort(TIFF_SHORT); // data type dos.writeInt(1); // data len dos.writeShort(2); // data offset or value dos.writeShort(0); // padding // Tag 6: StripOffset dos.writeShort(0x111); // Tag dos.writeShort(TIFF_LONG); // data type dos.writeInt(1); // data len dos.writeInt(TIFF_HEADER_LEN);// data offset or value // Tag 7: SamplesPerPixel dos.writeShort(0x115); // Tag dos.writeShort(TIFF_SHORT); // data type dos.writeInt(1); // data len dos.writeShort(3); // data offset or value dos.writeShort(0); // padding // Tag 8: StripByteCount dos.writeShort(0x117); // Tag dos.writeShort(TIFF_LONG); // data type dos.writeInt(1); // data len dos.writeInt(TIFF_HEIGHT*TIFF_WIDTH*3); // data offset or value // Tag 9: PlanarConfiguration dos.writeShort(0x11c); // Tag dos.writeShort(TIFF_SHORT); // data type dos.writeInt(1); // data len dos.writeShort(1); // data offset or value dos.writeShort(0); // padding // Mark the end of the IFD dos.write(new byte[] { 0, 0, 0, 0 }); flush(); // The pixel data follows } private void writePixels() throws Exception { flush(); int pixelsAt = at; // starting pos for TIFF pixel data Diagnostics.startTimer("rgb transform"); monochrome_transform(); //balanceTiff(pixelsAt); Diagnostics.endTimer("rgb transform"); flush(); } /* The G1-RR-BB-G2 values are Bayer pixels. The X values are TIFF pixels. Note that the TIFF image is smaller by one pixel in each dimension. This picture does not show how the Bayer pixels are physically layed out. G1 RR G1 RR G1 RR X X X X X BB G2 BB G2 BB G2 X X X X X G1 RR G1 RR G1 RR X X X X X BB G2 BB G2 BB G2 X X X X X G1 RR G1 RR G1 RR */ // Read from camera: A series of 2-row blocks consisting of: // // (1) One row of red pixels values (0-255) // (2) One row of green1 pixels values (0-255) // (3) One row of green2 pixels values (0-255) // (4) One row of blue pixels values (0-255) /** A property of the TIFF format */ private final static int TIFF_HEADER_LEN = 122; // Room for 9 tags private static final int RGGB_WIDTH = CameraController.RGGB_WIDTH, RGGB_HEIGHT = CameraController.RGGB_HEIGHT-1; public static final int TIFF_WIDTH = RGGB_WIDTH, TIFF_HEIGHT = RGGB_HEIGHT, TIFF_SIZE = (TIFF_HEIGHT)*(TIFF_WIDTH)*3 + TIFF_HEADER_LEN; private static final int WIDTHBY2 = CameraController.RGGB_WIDTH/2, RGGB_BLOCK = WIDTHBY2*4; /** Transform a raw Bayer bitmap from the camera into raw, * unfiltered TIFF rows. Don't mess with this method unless * you have a detailed understanding of both formats. */ private void orig_transform() { byte[] result = tiff, rggb = this.rggb; int at = this.at; for( int block=0; block 255) n=255; return (byte)n; } /////////////////////////////////////////////////////////////////////// private void balanceTiff(int startAt) { byte[] tiff = this.tiff; final int TIFF_PIXELS = TIFF_HEIGHT*TIFF_WIDTH; int avgRed=0, avgGreen=0, avgBlue=0; int at = startAt; for(int row=0; row