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);
  }
  public void write(BufferedImage image, AVList params) throws IOException {
    if (image == null) {
      String msg = Logging.getMessage("nullValue.ImageSource");
      Logging.logger().severe(msg);
      throw new IllegalArgumentException(msg);
    }

    if (0 == image.getWidth() || 0 == image.getHeight()) {
      String msg =
          Logging.getMessage("generic.InvalidImageSize", image.getWidth(), image.getHeight());
      Logging.logger().severe(msg);
      throw new IllegalArgumentException(msg);
    }

    if (null == params || 0 == params.getValues().size()) {
      String reason = Logging.getMessage("nullValue.AVListIsNull");
      Logging.logger().finest(Logging.getMessage("GeotiffWriter.GeoKeysMissing", reason));
      params = new AVListImpl();
    } else {
      this.validateParameters(params, image.getWidth(), image.getHeight());
    }

    // how we proceed in part depends upon the image type...
    int type = image.getType();

    // handle CUSTOM type which comes from our GeoTiffreader (for now)
    if (BufferedImage.TYPE_CUSTOM == type) {
      int numColorComponents = 0, numComponents = 0, pixelSize = 0, dataType = 0, csType = 0;
      boolean hasAlpha = false;

      if (null != image.getColorModel()) {
        ColorModel cm = image.getColorModel();

        numColorComponents = cm.getNumColorComponents();
        numComponents = cm.getNumComponents();
        pixelSize = cm.getPixelSize();
        hasAlpha = cm.hasAlpha();

        ColorSpace cs = cm.getColorSpace();
        if (null != cs) csType = cs.getType();
      }

      if (null != image.getSampleModel()) {
        SampleModel sm = image.getSampleModel();
        dataType = sm.getDataType();
      }

      if (dataType == DataBuffer.TYPE_FLOAT && pixelSize == Float.SIZE && numComponents == 1) {
        type = BufferedImage_TYPE_ELEVATION_FLOAT32;
      } else if (dataType == DataBuffer.TYPE_SHORT
          && pixelSize == Short.SIZE
          && numComponents == 1) {
        type = BufferedImage_TYPE_ELEVATION_SHORT16;
      } else if (ColorSpace.CS_GRAY == csType && pixelSize == Byte.SIZE) {
        type = BufferedImage.TYPE_BYTE_GRAY;
      } else if (dataType == DataBuffer.TYPE_USHORT
          && ColorSpace.CS_GRAY == csType
          && pixelSize == Short.SIZE) {
        type = BufferedImage.TYPE_USHORT_GRAY;
      } else if (ColorSpace.TYPE_RGB == csType
          && pixelSize == 3 * Byte.SIZE
          && numColorComponents == 3) {
        type = BufferedImage.TYPE_3BYTE_BGR;
      } else if (ColorSpace.TYPE_RGB == csType
          && hasAlpha
          && pixelSize == 4 * Byte.SIZE
          && numComponents == 4) {
        type = BufferedImage.TYPE_4BYTE_ABGR;
      }
    }

    switch (type) {
      case BufferedImage.TYPE_3BYTE_BGR:
      case BufferedImage.TYPE_4BYTE_ABGR:
      case BufferedImage.TYPE_4BYTE_ABGR_PRE:
      case BufferedImage.TYPE_INT_RGB:
      case BufferedImage.TYPE_INT_BGR:
      case BufferedImage.TYPE_INT_ARGB:
      case BufferedImage.TYPE_INT_ARGB_PRE:
        {
          this.writeColorImage(image, params);
        }
        break;

      case BufferedImage.TYPE_USHORT_GRAY:
      case BufferedImage.TYPE_BYTE_GRAY:
        {
          this.writeGrayscaleImage(image, params);
        }
        break;

      case BufferedImage_TYPE_ELEVATION_SHORT16:
      case BufferedImage_TYPE_ELEVATION_FLOAT32:
        {
          String msg = Logging.getMessage("GeotiffWriter.FeatureNotImplementedd", type);
          Logging.logger().severe(msg);
          throw new IllegalArgumentException(msg);
        }
        //            break;

      case BufferedImage.TYPE_CUSTOM:
      default:
        {
          ColorModel cm = image.getColorModel();
          SampleModel sm = image.getSampleModel();

          StringBuffer sb =
              new StringBuffer(Logging.getMessage("GeotiffWriter.UnsupportedType", type));

          sb.append("\n");
          sb.append("NumBands=").append(sm.getNumBands()).append("\n");
          sb.append("NumDataElements=").append(sm.getNumDataElements()).append("\n");
          sb.append("NumColorComponents=").append(cm.getNumColorComponents()).append("\n");
          sb.append("NumComponents=").append(cm.getNumComponents()).append("\n");
          sb.append("PixelSize=").append(cm.getPixelSize()).append("\n");
          sb.append("hasAlpha=").append(cm.hasAlpha());

          String msg = sb.toString();
          Logging.logger().severe(msg);
          throw new IllegalArgumentException(msg);
        }
    }
  }