private void makeNoDataTransparent(
        BufferedImage image, short missingDataSignal, short missingDataReplacement) {
      WritableRaster raster = null;

      if (null != image
          && (image.getType() == BufferedImage.TYPE_4BYTE_ABGR
              || image.getType() == BufferedImage.TYPE_INT_ARGB)
          && null != (raster = image.getRaster())) {
        int nodata_r = missingDataSignal & 0xff;
        int nodata_g = missingDataSignal & 0xff;
        int nodata_b = missingDataSignal & 0xff;

        int[] pixel = new int[4];
        int[] transparentPixel =
            new int[] {
              0xFF & missingDataReplacement /* red */,
              0xFF & missingDataReplacement /* green */,
              0xFF & missingDataReplacement,
              0x00 /* alpha */
            };

        int width = image.getWidth();
        int height = image.getHeight();
        for (int j = 0; j < height; j++) {
          for (int i = 0; i < width; i++) {
            // We know, by the nature of this source, that we are dealing with RGBA rasters...
            raster.getPixel(i, j, pixel);
            if (pixel[0] == nodata_r && pixel[1] == nodata_g && pixel[2] == nodata_b) {
              //                            pixel[0] = pixel[1] = pixel[2] = 0xFF &
              // missingDataReplacement;
              //                            pixel[3] = 0;   // transparent
              raster.setPixel(i, j, transparentPixel);
            } else {
              pixel[3] = 0xff; // non-transparent
              raster.setPixel(i, j, pixel);
            }
          }
        }
      }
    }
  private void writeGrayscaleImage(BufferedImage image, AVList params) throws IOException {
    int type = image.getType();

    int bitsPerSample =
        (BufferedImage.TYPE_USHORT_GRAY == type)
            ? Tiff.BitsPerSample.MONOCHROME_UINT16
            : Tiff.BitsPerSample.MONOCHROME_UINT8;

    int numBands = image.getSampleModel().getNumBands();
    // well, numBands for GrayScale images must be 1

    int bytesPerSample = numBands * bitsPerSample / Byte.SIZE;

    this.writeTiffHeader();

    // write the image data...
    int numRows = image.getHeight();
    int numCols = image.getWidth();
    int[] stripCounts = new int[numRows];
    int[] stripOffsets = new int[numRows];
    ByteBuffer dataBuff = ByteBuffer.allocateDirect(numCols * bytesPerSample);
    Raster rast = image.getRaster();

    for (int i = 0; i < numRows; i++) {
      stripOffsets[i] = (int) this.theChannel.position();
      stripCounts[i] = numCols * bytesPerSample;
      int[] rowData = rast.getPixels(0, i, image.getWidth(), 1, (int[]) null);
      dataBuff.clear();

      if (BufferedImage.TYPE_USHORT_GRAY == type) {
        for (int j = 0; j < numCols * numBands; j++) {
          this.putUnsignedShort(dataBuff, rowData[j]);
        }
      } else if (BufferedImage.TYPE_BYTE_GRAY == type) {
        for (int j = 0; j < numCols * numBands; j++) {
          this.putUnsignedByte(dataBuff, rowData[j]);
        }
      }
      dataBuff.flip();
      this.theChannel.write(dataBuff);
    }

    // write out values for the tiff tags and build up the IFD. These are supposed to be sorted; for
    // now
    // do this manually here.
    ArrayList<TiffIFDEntry> ifds = new ArrayList<TiffIFDEntry>(10);

    ifds.add(new TiffIFDEntry(Tiff.Tag.IMAGE_WIDTH, Tiff.Type.LONG, 1, numCols));
    ifds.add(new TiffIFDEntry(Tiff.Tag.IMAGE_LENGTH, Tiff.Type.LONG, 1, numRows));
    ifds.add(new TiffIFDEntry(Tiff.Tag.BITS_PER_SAMPLE, Tiff.Type.SHORT, 1, bitsPerSample));
    ifds.add(new TiffIFDEntry(Tiff.Tag.COMPRESSION, Tiff.Type.LONG, 1, Tiff.Compression.NONE));
    ifds.add(
        new TiffIFDEntry(
            Tiff.Tag.PHOTO_INTERPRETATION,
            Tiff.Type.SHORT,
            1,
            Tiff.Photometric.Grayscale_BlackIsZero));
    ifds.add(
        new TiffIFDEntry(Tiff.Tag.SAMPLE_FORMAT, Tiff.Type.SHORT, 1, Tiff.SampleFormat.UNSIGNED));

    long offset = this.theChannel.position();
    dataBuff = ByteBuffer.allocateDirect(stripOffsets.length * INTEGER_SIZEOF);
    for (int stripOffset : stripOffsets) {
      dataBuff.putInt(stripOffset);
    }
    dataBuff.flip();
    this.theChannel.write(dataBuff);
    ifds.add(new TiffIFDEntry(Tiff.Tag.STRIP_OFFSETS, Tiff.Type.LONG, stripOffsets.length, offset));

    ifds.add(new TiffIFDEntry(Tiff.Tag.SAMPLES_PER_PIXEL, Tiff.Type.SHORT, 1, numBands));
    ifds.add(new TiffIFDEntry(Tiff.Tag.ROWS_PER_STRIP, Tiff.Type.LONG, 1, 1));

    offset = this.theChannel.position();
    dataBuff.clear(); // stripOffsets and stripCounts are same length by design; can reuse the
    // ByteBuffer...

    for (int stripCount : stripCounts) {
      dataBuff.putInt(stripCount);
    }
    dataBuff.flip();
    this.theChannel.write(dataBuff);
    ifds.add(
        new TiffIFDEntry(Tiff.Tag.STRIP_BYTE_COUNTS, Tiff.Type.LONG, stripCounts.length, offset));

    this.appendGeoTiff(ifds, params);

    this.writeIFDs(ifds);
  }
  private void writeColorImage(BufferedImage image, AVList params) throws IOException {
    int numBands = image.getRaster().getNumBands();
    long offset;

    this.writeTiffHeader();

    // write the image data...
    int numRows = image.getHeight();
    int numCols = image.getWidth();
    int[] stripCounts = new int[numRows];
    int[] stripOffsets = new int[numRows];
    ByteBuffer dataBuff = ByteBuffer.allocateDirect(numCols * numBands);
    Raster rast = image.getRaster();

    for (int i = 0; i < numRows; i++) {
      stripOffsets[i] = (int) this.theChannel.position();
      stripCounts[i] = numCols * numBands;
      int[] rowData = rast.getPixels(0, i, image.getWidth(), 1, (int[]) null);
      dataBuff.clear();
      for (int j = 0; j < numCols * numBands; j++) {
        putUnsignedByte(dataBuff, rowData[j]);
      }
      dataBuff.flip();
      this.theChannel.write(dataBuff);
    }

    // write out values for the tiff tags and build up the IFD. These are supposed to be sorted; for
    // now
    // do this manually here.
    ArrayList<TiffIFDEntry> ifds = new ArrayList<TiffIFDEntry>(10);

    ifds.add(new TiffIFDEntry(Tiff.Tag.IMAGE_WIDTH, Tiff.Type.LONG, 1, numCols));
    ifds.add(new TiffIFDEntry(Tiff.Tag.IMAGE_LENGTH, Tiff.Type.LONG, 1, numRows));

    ifds.add(
        new TiffIFDEntry(
            Tiff.Tag.PLANAR_CONFIGURATION, Tiff.Type.SHORT, 1, Tiff.PlanarConfiguration.CHUNKY));
    ifds.add(new TiffIFDEntry(Tiff.Tag.SAMPLES_PER_PIXEL, Tiff.Type.SHORT, 1, numBands));
    ifds.add(new TiffIFDEntry(Tiff.Tag.COMPRESSION, Tiff.Type.LONG, 1, Tiff.Compression.NONE));
    ifds.add(
        new TiffIFDEntry(
            Tiff.Tag.PHOTO_INTERPRETATION, Tiff.Type.SHORT, 1, Tiff.Photometric.Color_RGB));

    ifds.add(new TiffIFDEntry(Tiff.Tag.ORIENTATION, Tiff.Type.SHORT, 1, Tiff.Orientation.DEFAULT));

    offset = this.theChannel.position();

    short[] bps = new short[numBands];
    for (int i = 0; i < numBands; i++) {
      bps[i] = Tiff.BitsPerSample.MONOCHROME_BYTE;
    }
    this.theChannel.write(ByteBuffer.wrap(this.getBytes(bps)));
    ifds.add(new TiffIFDEntry(Tiff.Tag.BITS_PER_SAMPLE, Tiff.Type.SHORT, numBands, offset));

    offset = this.theChannel.position();
    dataBuff = ByteBuffer.allocateDirect(stripOffsets.length * INTEGER_SIZEOF);
    for (int stripOffset : stripOffsets) {
      dataBuff.putInt(stripOffset);
    }
    dataBuff.flip();
    this.theChannel.write(dataBuff);
    ifds.add(new TiffIFDEntry(Tiff.Tag.STRIP_OFFSETS, Tiff.Type.LONG, stripOffsets.length, offset));
    ifds.add(new TiffIFDEntry(Tiff.Tag.ROWS_PER_STRIP, Tiff.Type.LONG, 1, 1));

    offset = this.theChannel.position();
    dataBuff.clear();
    // stripOffsets and stripCounts are same length by design; can reuse the ByteBuffer...
    for (int stripCount : stripCounts) {
      dataBuff.putInt(stripCount);
    }
    dataBuff.flip();
    this.theChannel.write(dataBuff);
    ifds.add(
        new TiffIFDEntry(Tiff.Tag.STRIP_BYTE_COUNTS, Tiff.Type.LONG, stripCounts.length, offset));

    this.appendGeoTiff(ifds, params);

    this.writeIFDs(ifds);
  }