private boolean needToCreateIndex(RenderedImage image) {

    SampleModel sampleModel = image.getSampleModel();
    ColorModel colorModel = image.getColorModel();

    return sampleModel.getNumBands() != 1
        || sampleModel.getSampleSize()[0] > 8
        || colorModel.getComponentSize()[0] > 8;
  }
  /** Create a color table from the image ColorModel and SampleModel. */
  private static byte[] createColorTable(ColorModel colorModel, SampleModel sampleModel) {
    byte[] colorTable;
    if (colorModel instanceof IndexColorModel) {
      IndexColorModel icm = (IndexColorModel) colorModel;
      int mapSize = icm.getMapSize();

      /**
       * The GIF image format assumes that size of image palette is power of two. We will use
       * closest larger power of two as size of color table.
       */
      int ctSize = getGifPaletteSize(mapSize);

      byte[] reds = new byte[ctSize];
      byte[] greens = new byte[ctSize];
      byte[] blues = new byte[ctSize];
      icm.getReds(reds);
      icm.getGreens(greens);
      icm.getBlues(blues);

      /**
       * fill tail of color component arrays by replica of first color in order to avoid appearance
       * of extra colors in the color table
       */
      for (int i = mapSize; i < ctSize; i++) {
        reds[i] = reds[0];
        greens[i] = greens[0];
        blues[i] = blues[0];
      }

      colorTable = new byte[3 * ctSize];
      int idx = 0;
      for (int i = 0; i < ctSize; i++) {
        colorTable[idx++] = reds[i];
        colorTable[idx++] = greens[i];
        colorTable[idx++] = blues[i];
      }
    } else if (sampleModel.getNumBands() == 1) {
      // create gray-scaled color table for single-banded images
      int numBits = sampleModel.getSampleSize()[0];
      if (numBits > 8) {
        numBits = 8;
      }
      int colorTableLength = 3 * (1 << numBits);
      colorTable = new byte[colorTableLength];
      for (int i = 0; i < colorTableLength; i++) {
        colorTable[i] = (byte) (i / 3);
      }
    } else {
      // We do not have enough information here
      // to create well-fit color table for RGB image.
      colorTable = null;
    }

    return colorTable;
  }
  public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
    GIFWritableImageMetadata imageMetadata = new GIFWritableImageMetadata();

    // Image dimensions

    SampleModel sampleModel = imageType.getSampleModel();

    Rectangle sourceBounds = new Rectangle(sampleModel.getWidth(), sampleModel.getHeight());
    Dimension destSize = new Dimension();
    computeRegions(sourceBounds, destSize, param);

    imageMetadata.imageWidth = destSize.width;
    imageMetadata.imageHeight = destSize.height;

    // Interlacing

    if (param != null
        && param.canWriteProgressive()
        && param.getProgressiveMode() == ImageWriteParam.MODE_DISABLED) {
      imageMetadata.interlaceFlag = false;
    } else {
      imageMetadata.interlaceFlag = true;
    }

    // Local color table

    ColorModel colorModel = imageType.getColorModel();

    imageMetadata.localColorTable = createColorTable(colorModel, sampleModel);

    // Transparency

    if (colorModel instanceof IndexColorModel) {
      int transparentIndex = ((IndexColorModel) colorModel).getTransparentPixel();
      if (transparentIndex != -1) {
        imageMetadata.transparentColorFlag = true;
        imageMetadata.transparentColorIndex = transparentIndex;
      }
    }

    return imageMetadata;
  }
  private void writeRasterData(
      RenderedImage image,
      Rectangle sourceBounds,
      Dimension destSize,
      ImageWriteParam param,
      boolean interlaceFlag)
      throws IOException {

    int sourceXOffset = sourceBounds.x;
    int sourceYOffset = sourceBounds.y;
    int sourceWidth = sourceBounds.width;
    int sourceHeight = sourceBounds.height;

    int destWidth = destSize.width;
    int destHeight = destSize.height;

    int periodX;
    int periodY;
    if (param == null) {
      periodX = 1;
      periodY = 1;
    } else {
      periodX = param.getSourceXSubsampling();
      periodY = param.getSourceYSubsampling();
    }

    SampleModel sampleModel = image.getSampleModel();
    int bitsPerPixel = sampleModel.getSampleSize()[0];

    int initCodeSize = bitsPerPixel;
    if (initCodeSize == 1) {
      initCodeSize++;
    }
    stream.write(initCodeSize);

    LZWCompressor compressor = new LZWCompressor(stream, initCodeSize, false);

    boolean isOptimizedCase =
        periodX == 1
            && periodY == 1
            && sampleModel instanceof ComponentSampleModel
            && image.getNumXTiles() == 1
            && image.getNumYTiles() == 1
            && image.getTile(0, 0).getDataBuffer() instanceof DataBufferByte;

    int numRowsWritten = 0;

    int progressReportRowPeriod = Math.max(destHeight / 20, 1);

    processImageStarted(imageIndex);

    if (interlaceFlag) {
      if (DEBUG) System.out.println("Writing interlaced");

      if (isOptimizedCase) {
        Raster tile = image.getTile(0, 0);
        byte[] data = ((DataBufferByte) tile.getDataBuffer()).getData();
        ComponentSampleModel csm = (ComponentSampleModel) tile.getSampleModel();
        int offset = csm.getOffset(sourceXOffset, sourceYOffset, 0);
        int lineStride = csm.getScanlineStride();

        writeRowsOpt(
            data,
            offset,
            lineStride,
            compressor,
            0,
            8,
            destWidth,
            destHeight,
            numRowsWritten,
            progressReportRowPeriod);

        if (abortRequested()) {
          return;
        }

        numRowsWritten += destHeight / 8;

        writeRowsOpt(
            data,
            offset,
            lineStride,
            compressor,
            4,
            8,
            destWidth,
            destHeight,
            numRowsWritten,
            progressReportRowPeriod);

        if (abortRequested()) {
          return;
        }

        numRowsWritten += (destHeight - 4) / 8;

        writeRowsOpt(
            data,
            offset,
            lineStride,
            compressor,
            2,
            4,
            destWidth,
            destHeight,
            numRowsWritten,
            progressReportRowPeriod);

        if (abortRequested()) {
          return;
        }

        numRowsWritten += (destHeight - 2) / 4;

        writeRowsOpt(
            data,
            offset,
            lineStride,
            compressor,
            1,
            2,
            destWidth,
            destHeight,
            numRowsWritten,
            progressReportRowPeriod);
      } else {
        writeRows(
            image,
            compressor,
            sourceXOffset,
            periodX,
            sourceYOffset,
            8 * periodY,
            sourceWidth,
            0,
            8,
            destWidth,
            destHeight,
            numRowsWritten,
            progressReportRowPeriod);

        if (abortRequested()) {
          return;
        }

        numRowsWritten += destHeight / 8;

        writeRows(
            image,
            compressor,
            sourceXOffset,
            periodX,
            sourceYOffset + 4 * periodY,
            8 * periodY,
            sourceWidth,
            4,
            8,
            destWidth,
            destHeight,
            numRowsWritten,
            progressReportRowPeriod);

        if (abortRequested()) {
          return;
        }

        numRowsWritten += (destHeight - 4) / 8;

        writeRows(
            image,
            compressor,
            sourceXOffset,
            periodX,
            sourceYOffset + 2 * periodY,
            4 * periodY,
            sourceWidth,
            2,
            4,
            destWidth,
            destHeight,
            numRowsWritten,
            progressReportRowPeriod);

        if (abortRequested()) {
          return;
        }

        numRowsWritten += (destHeight - 2) / 4;

        writeRows(
            image,
            compressor,
            sourceXOffset,
            periodX,
            sourceYOffset + periodY,
            2 * periodY,
            sourceWidth,
            1,
            2,
            destWidth,
            destHeight,
            numRowsWritten,
            progressReportRowPeriod);
      }
    } else {
      if (DEBUG) System.out.println("Writing non-interlaced");

      if (isOptimizedCase) {
        Raster tile = image.getTile(0, 0);
        byte[] data = ((DataBufferByte) tile.getDataBuffer()).getData();
        ComponentSampleModel csm = (ComponentSampleModel) tile.getSampleModel();
        int offset = csm.getOffset(sourceXOffset, sourceYOffset, 0);
        int lineStride = csm.getScanlineStride();

        writeRowsOpt(
            data,
            offset,
            lineStride,
            compressor,
            0,
            1,
            destWidth,
            destHeight,
            numRowsWritten,
            progressReportRowPeriod);
      } else {
        writeRows(
            image,
            compressor,
            sourceXOffset,
            periodX,
            sourceYOffset,
            periodY,
            sourceWidth,
            0,
            1,
            destWidth,
            destHeight,
            numRowsWritten,
            progressReportRowPeriod);
      }
    }

    if (abortRequested()) {
      return;
    }

    processImageProgress(100.0F);

    compressor.flush();

    stream.write(0x00);

    processImageComplete();
  }
  /**
   * Writes any extension blocks, the Image Descriptor, and the image data
   *
   * @param iioimage The image and image metadata.
   * @param param The write parameters.
   * @param globalColorTable The Global Color Table.
   * @param sourceBounds The source region.
   * @param destSize The destination dimensions.
   */
  private void writeImage(
      RenderedImage image,
      GIFWritableImageMetadata imageMetadata,
      ImageWriteParam param,
      byte[] globalColorTable,
      Rectangle sourceBounds,
      Dimension destSize)
      throws IOException {
    ColorModel colorModel = image.getColorModel();
    SampleModel sampleModel = image.getSampleModel();

    boolean writeGraphicsControlExtension;
    if (imageMetadata == null) {
      // Create default metadata.
      imageMetadata =
          (GIFWritableImageMetadata) getDefaultImageMetadata(new ImageTypeSpecifier(image), param);

      // Set GraphicControlExtension flag only if there is
      // transparency.
      writeGraphicsControlExtension = imageMetadata.transparentColorFlag;
    } else {
      // Check for GraphicControlExtension element.
      NodeList list = null;
      try {
        IIOMetadataNode root = (IIOMetadataNode) imageMetadata.getAsTree(IMAGE_METADATA_NAME);
        list = root.getElementsByTagName("GraphicControlExtension");
      } catch (IllegalArgumentException iae) {
        // Should never happen.
      }

      // Set GraphicControlExtension flag if element present.
      writeGraphicsControlExtension = list != null && list.getLength() > 0;

      // If progressive mode is not MODE_COPY_FROM_METADATA, ensure
      // the interlacing is set per the ImageWriteParam mode setting.
      if (param != null && param.canWriteProgressive()) {
        if (param.getProgressiveMode() == ImageWriteParam.MODE_DISABLED) {
          imageMetadata.interlaceFlag = false;
        } else if (param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT) {
          imageMetadata.interlaceFlag = true;
        }
      }
    }

    // Unset local color table if equal to global color table.
    if (Arrays.equals(globalColorTable, imageMetadata.localColorTable)) {
      imageMetadata.localColorTable = null;
    }

    // Override dimensions
    imageMetadata.imageWidth = destSize.width;
    imageMetadata.imageHeight = destSize.height;

    // Write Graphics Control Extension.
    if (writeGraphicsControlExtension) {
      writeGraphicControlExtension(imageMetadata);
    }

    // Write extension blocks.
    writePlainTextExtension(imageMetadata);
    writeApplicationExtension(imageMetadata);
    writeCommentExtension(imageMetadata);

    // Write Image Descriptor
    int bitsPerPixel =
        getNumBits(
            imageMetadata.localColorTable == null
                ? (globalColorTable == null
                    ? sampleModel.getSampleSize(0)
                    : globalColorTable.length / 3)
                : imageMetadata.localColorTable.length / 3);
    writeImageDescriptor(imageMetadata, bitsPerPixel);

    // Write image data
    writeRasterData(image, sourceBounds, destSize, param, imageMetadata.interlaceFlag);
  }
  /**
   * Writes any extension blocks, the Image Descriptor, the image data, and optionally the header
   * (Signature and Logical Screen Descriptor) and trailer (Block Terminator).
   *
   * @param writeHeader Whether to write the header.
   * @param writeTrailer Whether to write the trailer.
   * @param sm The stream metadata or <code>null</code> if <code>writeHeader</code> is <code>false
   *     </code>.
   * @param iioimage The image and image metadata.
   * @param p The write parameters.
   * @throws IllegalArgumentException if the number of bands is not 1.
   * @throws IllegalArgumentException if the number of bits per sample is greater than 8.
   * @throws IllegalArgumentException if the color component size is greater than 8.
   * @throws IllegalArgumentException if <code>writeHeader</code> is <code>true</code> and <code>sm
   *     </code> is <code>null</code>.
   * @throws IllegalArgumentException if <code>writeHeader</code> is <code>false</code> and a
   *     sequence is not being written.
   */
  private void write(
      boolean writeHeader,
      boolean writeTrailer,
      IIOMetadata sm,
      IIOImage iioimage,
      ImageWriteParam p)
      throws IOException {
    clearAbortRequest();

    RenderedImage image = iioimage.getRenderedImage();

    // Check for ability to encode image.
    if (needToCreateIndex(image)) {
      image = PaletteBuilder.createIndexedImage(image);
      iioimage.setRenderedImage(image);
    }

    ColorModel colorModel = image.getColorModel();
    SampleModel sampleModel = image.getSampleModel();

    // Determine source region and destination dimensions.
    Rectangle sourceBounds =
        new Rectangle(image.getMinX(), image.getMinY(), image.getWidth(), image.getHeight());
    Dimension destSize = new Dimension();
    computeRegions(sourceBounds, destSize, p);

    // Convert any provided image metadata.
    GIFWritableImageMetadata imageMetadata = null;
    if (iioimage.getMetadata() != null) {
      imageMetadata = new GIFWritableImageMetadata();
      convertMetadata(IMAGE_METADATA_NAME, iioimage.getMetadata(), imageMetadata);
      // Converted rgb image can use palette different from global.
      // In order to avoid color artefacts we want to be sure we use
      // appropriate palette. For this we initialize local color table
      // from current color and sample models.
      // At this point we can guarantee that local color table can be
      // build because image was already converted to indexed or
      // gray-scale representations
      if (imageMetadata.localColorTable == null) {
        imageMetadata.localColorTable = createColorTable(colorModel, sampleModel);

        // in case of indexed image we should take care of
        // transparent pixels
        if (colorModel instanceof IndexColorModel) {
          IndexColorModel icm = (IndexColorModel) colorModel;
          int index = icm.getTransparentPixel();
          imageMetadata.transparentColorFlag = (index != -1);
          if (imageMetadata.transparentColorFlag) {
            imageMetadata.transparentColorIndex = index;
          }
          /* NB: transparentColorFlag might have not beed reset for
             greyscale images but explicitly reseting it here
             is potentially not right thing to do until we have way
             to find whether current value was explicitly set by
             the user.
          */
        }
      }
    }

    // Global color table values.
    byte[] globalColorTable = null;

    // Write the header (Signature+Logical Screen Descriptor+
    // Global Color Table).
    if (writeHeader) {
      if (sm == null) {
        throw new IllegalArgumentException("Cannot write null header!");
      }

      GIFWritableStreamMetadata streamMetadata = (GIFWritableStreamMetadata) sm;

      // Set the version if not set.
      if (streamMetadata.version == null) {
        streamMetadata.version = "89a";
      }

      // Set the Logical Screen Desriptor if not set.
      if (streamMetadata.logicalScreenWidth == GIFMetadata.UNDEFINED_INTEGER_VALUE) {
        streamMetadata.logicalScreenWidth = destSize.width;
      }

      if (streamMetadata.logicalScreenHeight == GIFMetadata.UNDEFINED_INTEGER_VALUE) {
        streamMetadata.logicalScreenHeight = destSize.height;
      }

      if (streamMetadata.colorResolution == GIFMetadata.UNDEFINED_INTEGER_VALUE) {
        streamMetadata.colorResolution =
            colorModel != null ? colorModel.getComponentSize()[0] : sampleModel.getSampleSize()[0];
      }

      // Set the Global Color Table if not set, i.e., if not
      // provided in the stream metadata.
      if (streamMetadata.globalColorTable == null) {
        if (isWritingSequence && imageMetadata != null && imageMetadata.localColorTable != null) {
          // Writing a sequence and a local color table was
          // provided in the metadata of the first image: use it.
          streamMetadata.globalColorTable = imageMetadata.localColorTable;
        } else if (imageMetadata == null || imageMetadata.localColorTable == null) {
          // Create a color table.
          streamMetadata.globalColorTable = createColorTable(colorModel, sampleModel);
        }
      }

      // Set the Global Color Table. At this point it should be
      // A) the global color table provided in stream metadata, if any;
      // B) the local color table of the image metadata, if any, if
      //    writing a sequence;
      // C) a table created on the basis of the first image ColorModel
      //    and SampleModel if no local color table is available; or
      // D) null if none of the foregoing conditions obtain (which
      //    should only be if a sequence is not being written and
      //    a local color table is provided in image metadata).
      globalColorTable = streamMetadata.globalColorTable;

      // Write the header.
      int bitsPerPixel;
      if (globalColorTable != null) {
        bitsPerPixel = getNumBits(globalColorTable.length / 3);
      } else if (imageMetadata != null && imageMetadata.localColorTable != null) {
        bitsPerPixel = getNumBits(imageMetadata.localColorTable.length / 3);
      } else {
        bitsPerPixel = sampleModel.getSampleSize(0);
      }
      writeHeader(streamMetadata, bitsPerPixel);
    } else if (isWritingSequence) {
      globalColorTable = theStreamMetadata.globalColorTable;
    } else {
      throw new IllegalArgumentException("Must write header for single image!");
    }

    // Write extension blocks, Image Descriptor, and image data.
    writeImage(
        iioimage.getRenderedImage(), imageMetadata, p, globalColorTable, sourceBounds, destSize);

    // Write the trailer.
    if (writeTrailer) {
      writeTrailer();
    }
  }