private void assembleOperation(IMOperation op, Parameters params, Dimension fullSize) {
    // region transformation
    Region region = params.getRegion();
    if (!region.isFull()) {
      if (region.isPercent()) {
        // im4java doesn't support cropping x/y by percentage (only
        // width/height), so we have to calculate them.
        int x = Math.round(region.getX() / 100.0f * fullSize.width);
        int y = Math.round(region.getY() / 100.0f * fullSize.height);
        int width = Math.round(region.getWidth());
        int height = Math.round(region.getHeight());
        op.crop(width, height, x, y, "%");
      } else {
        op.crop(
            Math.round(region.getWidth()), Math.round(region.getHeight()),
            Math.round(region.getX()), Math.round(region.getY()));
      }
    }

    // size transformation
    Size size = params.getSize();
    if (size.getScaleMode() != Size.ScaleMode.FULL) {
      if (size.getScaleMode() == Size.ScaleMode.ASPECT_FIT_WIDTH) {
        op.resize(size.getWidth());
      } else if (size.getScaleMode() == Size.ScaleMode.ASPECT_FIT_HEIGHT) {
        op.resize(null, size.getHeight());
      } else if (size.getScaleMode() == Size.ScaleMode.NON_ASPECT_FILL) {
        op.resize(size.getWidth(), size.getHeight(), "!".charAt(0));
      } else if (size.getScaleMode() == Size.ScaleMode.ASPECT_FIT_INSIDE) {
        op.resize(size.getWidth(), size.getHeight());
      } else if (size.getPercent() != null) {
        op.resize(Math.round(size.getPercent()), Math.round(size.getPercent()), "%".charAt(0));
      }
    }

    // rotation transformation
    Rotation rotation = params.getRotation();
    if (rotation.shouldMirror()) {
      op.flop();
    }
    if (rotation.getDegrees() != 0) {
      op.rotate(rotation.getDegrees().doubleValue());
    }

    // quality transformation
    Quality quality = params.getQuality();
    if (quality != Quality.COLOR && quality != Quality.DEFAULT) {
      switch (quality) {
        case GRAY:
          op.colorspace("Gray");
          break;
        case BITONAL:
          op.monochrome();
          break;
      }
    }
  }
  /**
   * @param inputPath Absolute filename pathname or "-" to use a stream
   * @param inputStream Can be null
   * @param params
   * @param sourceFormat
   * @param fullSize
   * @param outputStream Stream to write to
   * @throws ProcessorException
   */
  private void doProcess(
      String inputPath,
      InputStream inputStream,
      Parameters params,
      SourceFormat sourceFormat,
      Dimension fullSize,
      OutputStream outputStream)
      throws ProcessorException {
    final Set<OutputFormat> availableOutputFormats = getAvailableOutputFormats(sourceFormat);
    if (getAvailableOutputFormats(sourceFormat).size() < 1) {
      throw new UnsupportedSourceFormatException(sourceFormat);
    } else if (!availableOutputFormats.contains(params.getOutputFormat())) {
      throw new UnsupportedOutputFormatException();
    }

    try {
      IMOperation op = new IMOperation();
      op.addImage(inputPath);
      assembleOperation(op, params, fullSize);

      op.addImage(params.getOutputFormat().getExtension() + ":-"); // write to stdout

      ConvertCmd convert = new ConvertCmd(true);

      String binaryPath =
          Application.getConfiguration().getString("GraphicsMagickProcessor.path_to_binaries", "");
      if (binaryPath.length() > 0) {
        convert.setSearchPath(binaryPath);
      }
      if (inputStream != null) {
        convert.setInputProvider(new Pipe(inputStream, null));
      }
      convert.setOutputConsumer(new Pipe(null, outputStream));
      convert.run(op);
    } catch (InterruptedException | IM4JavaException | IOException e) {
      throw new ProcessorException(e.getMessage(), e);
    }
  }