class FocalPointModifier {

	public BufferedImage apply(BufferedImage img, ImageManipulationParameters parameters) {
		FocalPoint focalPoint = parameters.getFocalPoint();
		if (focalPoint == null) {
			return img;
		}

		if (parameters.getFocalPointDebug()) {
			drawFocusPointAxis(img, focalPoint);
		}

		// Validate the focal point position
		Point imageSize = new Point(img.getWidth(), img.getHeight());
		Point absFocalPoint = parameters.getFocalPoint().convertToAbsolutePoint(imageSize);
		if (!absFocalPoint.isWithinBoundsOf(imageSize)) {
			throw error(BAD_REQUEST, "image_error_focalpoint_out_of_bounds", focalPoint.toString(), imageSize.toString());
		}

		Point targetSize = parameters.getSize();
		Float zoomFactor = parameters.getFocalPointZoom();
		BufferedImage zoomedImg = applyZoom(img, zoomFactor, focalPoint, targetSize);
		// Apply the regular focal point logic if the zoom was not applied. Otherwise the image is already cropped and handled correctly.
		if (zoomedImg == null) {
			if (targetSize == null) {
				throw error(BAD_REQUEST, "image_error_focalpoint_target_missing");
			}

			Point newSize = calculateResize(imageSize, targetSize);

			// Resize the image to the largest dimension while keeping the aspect ratio
			img = applyResize(img, newSize);

			// No need for cropping. The image has already the target dimensions
			if (imageSize.equals(targetSize)) {
				return img;
			}

			boolean alignX = calculateAlignment(imageSize, targetSize);
			Point cropStart = calculateCropStart(alignX, targetSize, newSize, focalPoint);
			if (cropStart != null) {
				img = applyCrop(img, cropStart, targetSize);
			}

		} else {
			img = zoomedImg;
		}
		img.flush();
		return img;
	}

	public  FocalPointModifier(ImageManipulatorOptions options);

}