All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.foreach.imageserver.math.ImageServerConversionUtils Maven / Gradle / Ivy

The newest version!
package com.foreach.imageserver.math;

import com.foreach.imageserver.dto.CropDto;
import com.foreach.imageserver.dto.DimensionsDto;
import com.foreach.imageserver.dto.ImageResolutionDto;
import lombok.NonNull;
import org.springframework.util.Assert;

/**
 * Utility methods to calculate/translate DTO instances.
 *
 * @author Arne Vandamme
 */
public class ImageServerConversionUtils
{
	private ImageServerConversionUtils() {
	}

	public static AspectRatio calculateAspectRatio( ImageResolutionDto resolutionDto ) {
		return calculateAspectRatio( resolutionDto.getDimensions() );
	}

	public static AspectRatio calculateAspectRatio( DimensionsDto dimensions ) {
		return new AspectRatio( dimensions.getWidth(), dimensions.getHeight() );
	}

	/**
	 * This will extend an existing crop to match a given aspect ratio.
	 * The center point of the crop will be kept, so extending will happen equally on all sides involved.
	 * This can return a crop with negative x and y coordinates.
	 * Use {@link #moveToFit(CropDto, DimensionsDto)} if you also want the crop to be moved about to fit it
	 * in the visible coordinates box.
	 * 

* Crop parameters like source and box will be kept. * * @param original crop coordinates * @param aspectRatio the crop should match * @return modified crop * @see #moveToFit(CropDto, DimensionsDto) */ public static CropDto extendCrop( CropDto original, AspectRatio aspectRatio ) { CropDto cropDto = new CropDto( original ); DimensionsDto cropDimensions = cropDto.getDimensions(); cropDimensions = normalize( cropDimensions, aspectRatio ); cropDto.setX( cropDto.getX() - ( cropDimensions.getWidth() - cropDto.getWidth() ) / 2 ); cropDto.setY( cropDto.getY() - ( cropDimensions.getHeight() - cropDto.getHeight() ) / 2 ); cropDto.setWidth( cropDimensions.getWidth() ); cropDto.setHeight( cropDimensions.getHeight() ); return cropDto; } /** * This will shrink an existing crop to match a given aspect ratio. * The center point of the crop will be kept. Assuming that the original crop had valid coordinates, * this will result in a valid crop. *

* Crop parameters like source and box will be kept. * * @param original crop coordinates * @param aspectRatio the crop should match * @return modified crop */ public static CropDto shrinkCrop( CropDto original, AspectRatio aspectRatio ) { CropDto cropDto = new CropDto( original ); DimensionsDto originalDimensions = cropDto.getDimensions(); DimensionsDto cropDimensions = normalize( originalDimensions, aspectRatio ); cropDimensions = scaleToFitIn( cropDimensions, originalDimensions ); cropDto.setX( cropDto.getX() + ( cropDto.getWidth() - cropDimensions.getWidth() ) / 2 ); cropDto.setY( cropDto.getY() + ( cropDto.getHeight() - cropDimensions.getHeight() ) / 2 ); cropDto.setWidth( cropDimensions.getWidth() ); cropDto.setHeight( cropDimensions.getHeight() ); return cropDto; } /** * Will shift a crop to fit entirely inside the box. Usually used after {@link #extendCrop(CropDto, AspectRatio)} * to verify that the resulting extension does not surpass the box boundaries. This counters the center point * based extending of a crop. *

* This function will throw an exception if it is not possible to fit the crop inside the box. * * @param original crop coordinates * @param box the crop should fit in * @return modified crop * @throws IllegalArgumentException if the crop dimensions are impossible to fit in the box */ public static CropDto moveToFit( CropDto original, DimensionsDto box ) { CropDto cropDto = new CropDto( original ); if ( !fitsIn( cropDto.getDimensions(), box ) ) { throw new IllegalArgumentException( "Crop dimensions are larger than the box dimensions - impossible to fit" ); } if ( cropDto.getX() < 0 ) { cropDto.setX( 0 ); } if ( cropDto.getY() < 0 ) { cropDto.setY( 0 ); } if ( cropDto.getX() + cropDto.getWidth() > box.getWidth() ) { cropDto.setX( box.getWidth() - cropDto.getWidth() ); } if ( cropDto.getY() + cropDto.getHeight() > box.getHeight() ) { cropDto.setY( box.getHeight() - cropDto.getHeight() ); } return cropDto; } /** * Will calculate the unknown dimensions for the original according to the boundaries specified. * Any unknown dimensions will be scaled according to the aspect ratio of the boundaries. * * @param original dimensions * @param boundaries for the new dimensions * @return normalized dimensions */ public static DimensionsDto normalize( DimensionsDto original, DimensionsDto boundaries ) { DimensionsDto normalized = new DimensionsDto( original.getWidth(), original.getHeight() ); AspectRatio originalAspectRatio = calculateAspectRatio( boundaries ); if ( original.getWidth() == 0 && original.getHeight() == 0 ) { normalized.setWidth( boundaries.getWidth() ); normalized.setHeight( boundaries.getHeight() ); } else if ( original.getHeight() == 0 ) { normalized.setHeight( originalAspectRatio.calculateHeightForWidth( original.getWidth() ) ); } else if ( original.getWidth() == 0 ) { normalized.setWidth( originalAspectRatio.calculateWidthForHeight( original.getHeight() ) ); } return normalized; } /** * Will verify and modify the dimensions so that they match the aspect ratio. * The resulting dimensions will never be smaller than the original one and always * one of the original dimensions will be kept. * * @param original dimensions * @param aspectRatio to use when normalizing the dimensions * @return normalized dimensions */ public static DimensionsDto normalize( DimensionsDto original, AspectRatio aspectRatio ) { DimensionsDto normalized = new DimensionsDto( original.getWidth(), original.getHeight() ); if ( !calculateAspectRatio( normalized ).equals( aspectRatio ) ) { int newWidth = aspectRatio.calculateWidthForHeight( normalized.getHeight() ); if ( newWidth < normalized.getWidth() ) { normalized.setHeight( aspectRatio.calculateHeightForWidth( normalized.getWidth() ) ); } else { normalized.setWidth( newWidth ); } } return normalized; } /** * Will translate a crop to fit inside the box specified. * This requires the source dimensions of the crop to be set. * * @param original crop * @param box to use for translating the crop coordinates * @return normalized crop */ public static CropDto normalize( CropDto original, DimensionsDto box ) { if ( original.getSource() == null || original.getSource().equals( new DimensionsDto() ) ) { throw new IllegalArgumentException( "Normalizing a crop requires a valid source to be set." ); } CropDto translated = new CropDto( original ); // Translate the crop coordinates to fit with the new box DimensionsDto newSource = scaleToFitIn( original.getSource(), box ); translateCropToNewSource( translated, newSource ); translated.setSource( newSource ); translated.setBox( box ); // Ensure the resulting crop is valid if ( translated.getWidth() == 0 || translated.getHeight() == 0 ) { throw new RuntimeException( "Could not process crop: resulted in an illegal width or height of 0" ); } return translated; } private static void translateCropToNewSource( CropDto crop, DimensionsDto newDimensions ) { int leftX = crop.getX(); int leftY = crop.getY(); int rightX = leftX + crop.getWidth(); int rightY = leftY + crop.getHeight(); DimensionsDto source = crop.getSource(); leftX = snap( leftX, source.getWidth() ); leftY = snap( leftY, source.getHeight() ); rightX = snap( rightX, source.getWidth() ); rightY = snap( rightY, source.getHeight() ); if ( !newDimensions.equals( source ) ) { double modX = (double) newDimensions.getWidth() / source.getWidth(); double modY = (double) newDimensions.getHeight() / source.getHeight(); crop.setX( Double.valueOf( leftX * modX ).intValue() ); crop.setY( Double.valueOf( leftY * modY ).intValue() ); crop.setWidth( Double.valueOf( ( rightX - leftX ) * modX ).intValue() ); crop.setHeight( Double.valueOf( ( rightY - leftY ) * modY ).intValue() ); } else { crop.setX( leftX ); crop.setY( leftY ); crop.setWidth( rightX - leftX ); crop.setHeight( rightY - leftY ); } } private static int snap( int pos, int max ) { if ( pos < 0 ) { return 0; } else if ( pos > max ) { return max; } return pos; } /** * Will downscale the dimensions to fit in the boundaries if they are larger. * * @param original dimensions * @param box that the translated dimensions should fit in * @return translated dimensions */ public static DimensionsDto scaleToFitIn( DimensionsDto original, DimensionsDto box ) { DimensionsDto normalized = normalize( original, box ); DimensionsDto scaled = new DimensionsDto( normalized.getWidth(), normalized.getHeight() ); AspectRatio aspectRatio = calculateAspectRatio( normalized ); if ( !fitsIn( normalized, box ) ) { if ( aspectRatio.isLargerOnWidth() ) { scaled.setWidth( box.getWidth() ); scaled.setHeight( aspectRatio.calculateHeightForWidth( box.getWidth() ) ); } else { scaled.setHeight( box.getHeight() ); scaled.setWidth( aspectRatio.calculateWidthForHeight( box.getHeight() ) ); } if ( !fitsIn( scaled, box ) ) { // Reverse the side as scaling basis, we made the wrong decision if ( aspectRatio.isLargerOnWidth() ) { scaled.setHeight( box.getHeight() ); scaled.setWidth( aspectRatio.calculateWidthForHeight( box.getHeight() ) ); } else { scaled.setWidth( box.getWidth() ); scaled.setHeight( aspectRatio.calculateHeightForWidth( box.getWidth() ) ); } } } return scaled; } /** * Checks if the area represented by dimensions fits inside the area defined by the box (the original * area should be the same surface or smaller). * * @param original dimensions to check * @param boundaries that the dimensions should fit in * @return true if dimensions fit */ public static boolean fitsIn( DimensionsDto original, DimensionsDto boundaries ) { return original.getWidth() <= boundaries.getWidth() && original.getHeight() <= boundaries.getHeight(); } /** * Checks if a crop is entirely positioned within a box. The crop dimensions should be smaller or equal * to the box dimensions, and the left top coordinates should not be negative. * * @param crop to check * @param box that should contain the crop * @return true if crop fits */ public static boolean isWithinBox( CropDto crop, DimensionsDto box ) { return crop.getX() >= 0 && crop.getY() >= 0 && fitsIn( crop.getDimensions(), box ) && ( crop.getX() + crop.getWidth() ) <= box.getWidth() && ( crop.getY() + crop.getHeight() ) <= box.getHeight(); } public static int calculateDistance( ImageResolutionDto from, ImageResolutionDto to ) { return calculateDistance( from.getDimensions(), to.getDimensions() ); } /** * Calculates the distance between two dimensions. If the dimensions are exactly the same, the distance is 0. * If the first dimensions fits entirely in the second, the distance will be positive. The larger the second * dimension, the greater the distance number. If the first dimension is larger than the second dimension, the * distance number will be negative. Distances closer to 0 indicate how much smaller the scond dimension is. *

* A dimension is considered smaller as soon as a single side is smaller. Calculating the distance is * impossible if any of the arguments have an unknown dimension. *

* The actual distance number is the delta between the two surfaces. If the surface is the same (but dimensions * are reversed), then the distance will be -1. * * @param from dimension * @param to dimension * @return distance number */ public static int calculateDistance( @NonNull DimensionsDto from, @NonNull DimensionsDto to ) { Assert.isTrue( from.getWidth() > 0 && from.getHeight() > 0 && to.getWidth() > 0 && to.getHeight() > 0, "Width and Height must be greater than 0" ); if ( from.equals( to ) ) { return 0; } if ( fitsIn( from, to ) ) { return Math.abs( ( to.getWidth() * to.getHeight() ) - ( from.getWidth() * from.getHeight() ) ); } int distance = -Math.abs( ( to.getWidth() * to.getHeight() ) - ( from.getWidth() * from.getHeight() ) ); return distance == 0 ? -1 : distance; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy