/** Set source */
  private void setDefaults(RenderedImage imgsrc) {
    // override the params in the super class
    setSuperProperties();

    tilingMode = MODE_EXPLICIT;

    if (imgsrc != null) {
      this.imgsrc = imgsrc;
      tileGridXOffset = imgsrc.getTileGridXOffset();
      tileGridYOffset = imgsrc.getTileGridYOffset();
      tileWidth = imgsrc.getTileWidth();
      tileHeight = imgsrc.getTileHeight();
      tilingSet = true;

      numTiles = imgsrc.getNumXTiles() * imgsrc.getNumYTiles();
      numComponents = imgsrc.getSampleModel().getNumBands();
    }
    setDefaults();
  }
  private void writeRows(
      RenderedImage image,
      LZWCompressor compressor,
      int sx,
      int sdx,
      int sy,
      int sdy,
      int sw,
      int dy,
      int ddy,
      int dw,
      int dh,
      int numRowsWritten,
      int progressReportRowPeriod)
      throws IOException {
    if (DEBUG) System.out.println("Writing unoptimized");

    int[] sbuf = new int[sw];
    byte[] dbuf = new byte[dw];

    Raster raster =
        image.getNumXTiles() == 1 && image.getNumYTiles() == 1
            ? image.getTile(0, 0)
            : image.getData();
    for (int y = dy; y < dh; y += ddy) {
      if (numRowsWritten % progressReportRowPeriod == 0) {
        if (abortRequested()) {
          processWriteAborted();
          return;
        }
        processImageProgress((numRowsWritten * 100.0F) / dh);
      }

      raster.getSamples(sx, sy, sw, 1, 0, sbuf);
      for (int i = 0, j = 0; i < dw; i++, j += sdx) {
        dbuf[i] = (byte) sbuf[j];
      }
      compressor.compress(dbuf, 0, dw);
      numRowsWritten++;
      sy += sdy;
    }
  }
  /**
   * Returns a contiguous <code>Raster</code> of data over the specified <code>Rectangle</code>. If
   * the region is a sub-region of a single tile, then a child of that tile will be returned. If the
   * region overlaps more than one tile and has 8 bits per sample, then a pixel interleaved Raster
   * having band offsets 0,1,... will be returned. Otherwise the Raster returned by <code>
   * im.copyData(null)</code> will be returned.
   */
  private static final Raster getContiguousData(RenderedImage im, Rectangle region) {
    if (im == null) {
      throw new IllegalArgumentException("im == null");
    } else if (region == null) {
      throw new IllegalArgumentException("region == null");
    }

    Raster raster;
    if (im.getNumXTiles() == 1 && im.getNumYTiles() == 1) {
      // Image is not tiled so just get a reference to the tile.
      raster = im.getTile(im.getMinTileX(), im.getMinTileY());

      // Ensure result has requested coverage.
      Rectangle bounds = raster.getBounds();
      if (!bounds.equals(region)) {
        raster =
            raster.createChild(
                region.x, region.y, region.width, region.height, region.x, region.y, null);
      }
    } else {
      // Image is tiled.

      // Create an interleaved raster for copying for 8-bit case.
      // This ensures that for RGB data the band offsets are {0,1,2}.
      SampleModel sampleModel = im.getSampleModel();
      WritableRaster target =
          sampleModel.getSampleSize(0) == 8
              ? Raster.createInterleavedRaster(
                  DataBuffer.TYPE_BYTE,
                  im.getWidth(),
                  im.getHeight(),
                  sampleModel.getNumBands(),
                  new Point(im.getMinX(), im.getMinY()))
              : null;

      // Copy the data.
      raster = im.copyData(target);
    }

    return raster;
  }
  public void encode(RenderedImage im) throws IOException {
    // Get the SampleModel.
    SampleModel sm = im.getSampleModel();

    // Check the data type, band count, and sample size.
    int dataType = sm.getTransferType();
    if (dataType == DataBuffer.TYPE_FLOAT || dataType == DataBuffer.TYPE_DOUBLE) {
      throw new IllegalArgumentException(JaiI18N.getString("WBMPImageEncoder0"));
    } else if (sm.getNumBands() != 1) {
      throw new IllegalArgumentException(JaiI18N.getString("WBMPImageEncoder1"));
    } else if (sm.getSampleSize(0) != 1) {
      throw new IllegalArgumentException(JaiI18N.getString("WBMPImageEncoder2"));
    }

    // Save image dimensions.
    int width = im.getWidth();
    int height = im.getHeight();

    // Write WBMP header.
    output.write(0); // TypeField
    output.write(0); // FixHeaderField
    output.write(intToMultiByte(width)); // width
    output.write(intToMultiByte(height)); // height

    Raster tile = null;

    // If the data are not formatted nominally then reformat.
    if (sm.getDataType() != DataBuffer.TYPE_BYTE
        || !(sm instanceof MultiPixelPackedSampleModel)
        || ((MultiPixelPackedSampleModel) sm).getDataBitOffset() != 0) {
      MultiPixelPackedSampleModel mppsm =
          new MultiPixelPackedSampleModel(
              DataBuffer.TYPE_BYTE, width, height, 1, (width + 7) / 8, 0);
      WritableRaster raster =
          Raster.createWritableRaster(mppsm, new Point(im.getMinX(), im.getMinY()));
      raster.setRect(im.getData());
      tile = raster;
    } else if (im.getNumXTiles() == 1 && im.getNumYTiles() == 1) {
      tile = im.getTile(im.getMinTileX(), im.getMinTileY());
    } else {
      tile = im.getData();
    }

    // Check whether the image is white-is-zero.
    boolean isWhiteZero = false;
    if (im.getColorModel() instanceof IndexColorModel) {
      IndexColorModel icm = (IndexColorModel) im.getColorModel();
      isWhiteZero =
          (icm.getRed(0) + icm.getGreen(0) + icm.getBlue(0))
              > (icm.getRed(1) + icm.getGreen(1) + icm.getBlue(1));
    }

    // Get the line stride, bytes per row, and data array.
    int lineStride = ((MultiPixelPackedSampleModel) sm).getScanlineStride();
    int bytesPerRow = (width + 7) / 8;
    byte[] bdata = ((DataBufferByte) tile.getDataBuffer()).getData();

    // Write the data.
    if (!isWhiteZero && lineStride == bytesPerRow) {
      // Write the entire image.
      output.write(bdata, 0, height * bytesPerRow);
    } else {
      // Write the image row-by-row.
      int offset = 0;
      if (!isWhiteZero) {
        // Black-is-zero
        for (int row = 0; row < height; row++) {
          output.write(bdata, offset, bytesPerRow);
          offset += lineStride;
        }
      } else {
        // White-is-zero: need to invert data.
        byte[] inverted = new byte[bytesPerRow];
        for (int row = 0; row < height; row++) {
          for (int col = 0; col < bytesPerRow; col++) {
            inverted[col] = (byte) (~(bdata[col + offset]));
          }
          output.write(inverted, 0, bytesPerRow);
          offset += lineStride;
        }
      }
    }
  }
  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 - tile.getSampleModelTranslateX(),
                sourceYOffset - tile.getSampleModelTranslateY(),
                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 - tile.getSampleModelTranslateX(),
                sourceYOffset - tile.getSampleModelTranslateY(),
                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();
  }
  private void testType(RenderedImage src, boolean nodataUsed, boolean roiUsed) {
    // Optional No Data Range used
    Range noData;
    // Source image data type
    int dataType = src.getSampleModel().getDataType();
    // If no Data are present, the No Data Range associated is used
    if (nodataUsed) {

      switch (dataType) {
        case DataBuffer.TYPE_BYTE:
          noData = noDataByte;
          break;
        case DataBuffer.TYPE_USHORT:
          noData = noDataUShort;
          break;
        case DataBuffer.TYPE_SHORT:
          noData = noDataShort;
          break;
        case DataBuffer.TYPE_INT:
          noData = noDataInt;
          break;
        case DataBuffer.TYPE_FLOAT:
          noData = noDataFloat;
          break;
        case DataBuffer.TYPE_DOUBLE:
          noData = noDataDouble;
          break;
        default:
          throw new IllegalArgumentException("Wrong data type");
      }
    } else {
      noData = null;
    }

    ROI roi;

    if (roiUsed) {
      roi = roiObject;
    } else {
      roi = null;
    }

    // BandCombined result
    RenderedOp combined =
        BandCombineDescriptor.create(src, matrix, roi, noData, destinationNoData, null);

    int tileWidth = combined.getTileWidth();
    int tileHeight = combined.getTileHeight();
    int minTileX = combined.getMinTileX();
    int minTileY = combined.getMinTileY();
    int numXTiles = combined.getNumXTiles();
    int numYTiles = combined.getNumYTiles();
    int maxTileX = minTileX + numXTiles;
    int maxTileY = minTileY + numYTiles;
    // Ensure same size
    assertEquals(combined.getWidth(), src.getWidth());
    assertEquals(combined.getHeight(), src.getHeight());
    assertEquals(combined.getMinX(), src.getMinX());
    assertEquals(combined.getMinY(), src.getMinY());
    assertEquals(minTileX, src.getMinTileX());
    assertEquals(minTileY, src.getMinTileY());
    assertEquals(numXTiles, src.getNumXTiles());
    assertEquals(numYTiles, src.getNumYTiles());
    assertEquals(tileWidth, src.getTileWidth());
    assertEquals(tileHeight, src.getTileHeight());

    int srcBands = src.getSampleModel().getNumBands();
    int dstBands = combined.getNumBands();

    // Ensure a correct band size
    assertEquals(dstBands, matrix.length);

    // Check on all the pixels if they have been calculate correctly
    for (int tileX = minTileX; tileX < maxTileX; tileX++) {
      for (int tileY = minTileY; tileY < maxTileY; tileY++) {
        Raster tile = combined.getTile(tileX, tileY);
        Raster srcTile = src.getTile(tileX, tileY);

        int minX = tile.getMinX();
        int minY = tile.getMinY();
        int maxX = minX + tileWidth - 1;
        int maxY = minY + tileHeight - 1;

        for (int x = minX; x <= maxX; x++) {
          for (int y = minY; y <= maxY; y++) {

            boolean isValidRoi = !roiUsed || (roiUsed && roiObject.contains(x, y));

            if (isValidRoi) {
              for (int b = 0; b < dstBands; b++) {
                // Getting the result
                double result = tile.getSampleDouble(x, y, b);

                // Calculating the expected result from sources
                boolean valid = false;
                double calculated = 0;

                for (int i = 0; i < srcBands; i++) {
                  double sample = srcTile.getSampleDouble(x, y, i);
                  boolean isValidData =
                      !nodataUsed || (nodataUsed && !noDataDouble.contains(sample));
                  valid |= isValidData;
                  if (isValidData) {
                    switch (dataType) {
                      case DataBuffer.TYPE_BYTE:
                        calculated += ((int) sample & 0xFF) * matrix[b][i];
                        break;
                      case DataBuffer.TYPE_USHORT:
                        calculated += ((int) sample & 0xFFFF) * matrix[b][i];
                        break;
                      case DataBuffer.TYPE_SHORT:
                      case DataBuffer.TYPE_INT:
                      case DataBuffer.TYPE_FLOAT:
                      case DataBuffer.TYPE_DOUBLE:
                        calculated += sample * matrix[b][i];
                        break;
                      default:
                        break;
                    }
                  }
                }

                if (valid) {
                  calculated += matrix[b][srcBands];
                  switch (dataType) {
                    case DataBuffer.TYPE_BYTE:
                      calculated = ImageUtil.clampRoundByte(calculated);
                      result = ImageUtil.clampRoundByte(result);
                      break;
                    case DataBuffer.TYPE_USHORT:
                      calculated = ImageUtil.clampRoundUShort(calculated);
                      result = ImageUtil.clampRoundUShort(result);
                      break;
                    case DataBuffer.TYPE_SHORT:
                      calculated = ImageUtil.clampRoundShort(calculated);
                      result = ImageUtil.clampRoundShort(result);
                      break;
                    case DataBuffer.TYPE_INT:
                      calculated = ImageUtil.clampRoundInt(calculated);
                      result = ImageUtil.clampRoundInt(result);
                      break;
                    case DataBuffer.TYPE_FLOAT:
                      calculated = (float) calculated;
                      calculated = (float) result;
                      break;
                    case DataBuffer.TYPE_DOUBLE:
                      break;
                    default:
                      break;
                  }
                  assertEquals(result, calculated, TOLERANCE);
                } else {
                  assertEquals(result, destNoData, TOLERANCE);
                }
              }
            } else {
              for (int b = 0; b < dstBands; b++) {
                assertEquals(tile.getSampleDouble(x, y, b), destNoData, TOLERANCE);
              }
            }
          }
        }
      }
    }

    // Disposal of the output image
    combined.dispose();
  }