package net.coobird.thumbnailator; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.imageio.ImageIO; import net.coobird.thumbnailator.filters.Canvas; import net.coobird.thumbnailator.filters.ImageFilter; import net.coobird.thumbnailator.filters.Pipeline; import net.coobird.thumbnailator.filters.Rotation; import net.coobird.thumbnailator.filters.Watermark; import net.coobird.thumbnailator.geometry.AbsoluteSize; import net.coobird.thumbnailator.geometry.Coordinate; import net.coobird.thumbnailator.geometry.Position; import net.coobird.thumbnailator.geometry.Positions; import net.coobird.thumbnailator.geometry.Region; import net.coobird.thumbnailator.geometry.Size; import net.coobird.thumbnailator.name.Rename; import net.coobird.thumbnailator.resizers.BicubicResizer; import net.coobird.thumbnailator.resizers.BilinearResizer; import net.coobird.thumbnailator.resizers.DefaultResizerFactory; import net.coobird.thumbnailator.resizers.FixedResizerFactory; import net.coobird.thumbnailator.resizers.ProgressiveBilinearResizer; import net.coobird.thumbnailator.resizers.Resizer; import net.coobird.thumbnailator.resizers.ResizerFactory; import net.coobird.thumbnailator.resizers.configurations.AlphaInterpolation; import net.coobird.thumbnailator.resizers.configurations.Antialiasing; import net.coobird.thumbnailator.resizers.configurations.Dithering; import net.coobird.thumbnailator.resizers.configurations.Rendering; import net.coobird.thumbnailator.resizers.configurations.ScalingMode; import net.coobird.thumbnailator.tasks.SourceSinkThumbnailTask; import net.coobird.thumbnailator.tasks.io.BufferedImageSink; import net.coobird.thumbnailator.tasks.io.BufferedImageSource; import net.coobird.thumbnailator.tasks.io.FileImageSink; import net.coobird.thumbnailator.tasks.io.FileImageSource; import net.coobird.thumbnailator.tasks.io.ImageSource; import net.coobird.thumbnailator.tasks.io.InputStreamImageSource; import net.coobird.thumbnailator.tasks.io.OutputStreamImageSink; import net.coobird.thumbnailator.tasks.io.URLImageSource; import net.coobird.thumbnailator.util.ThumbnailatorUtils; /** * Provides a fluent interface to create thumbnails. *

* This is the main entry point for creating thumbnails with Thumbnailator. *

* By using the Thumbnailator's fluent interface, it is possible to write * thumbnail generation code which resembles written English. *

*
Usage:
*
* The following example code demonstrates how to use the fluent interface * to create a thumbnail from multiple files from a directory, resizing them to * a maximum of 200 pixels by 200 pixels while preserving the aspect ratio of * the original, then saving the resulting thumbnails as JPEG images with file * names having {@code thumbnail.} appended to the beginning of the file name. *

*

Thumbnails.of(directory.listFiles())
    .size(200, 200)
    .outputFormat("jpeg")
    .asFiles(Rename.PREFIX_DOT_THUMBNAIL);

// English: "Make thumbnails of files in the directory, with a size of 200x200,
             with output format of JPEG, and save them as files while renaming
             the files to be prefixed with a 'thumbnail.'."
 * 
*
*
* For more examples, please visit the * Thumbnailator project page. *

*

Important Implementation Notes

* Upon calling one of the {@code Thumbnails.of(...)} methods, in the * current implementation, an instance of an inner class of this class is * returned. In most cases, the returned instance should not be used by * storing it in a local variable, as changes in the internal implementation * could break code in the future. *

* As a rule of thumb, always method chain from the {@code Thumbnails.of} * all the way until the output method (e.g. {@code toFile}, {@code asBufferedImage}, * etc.) is called without breaking them down into single statements. * See the "Usage" section above for the intended use of the Thumbnailator's * fluent interface. *

*
Unintended Use:
*
*
// Unintended use - not recommended!
Builder<File> instance = Thumbnails.of("path/to/image");
instance.size(200, 200);
instance.asFiles("path/to/thumbnail");
 * 
*
*
* * @author coobird * */ public final class Thumbnails { /** * This class is not intended to be instantiated. */ private Thumbnails() {} /** * Performs validation on the specified dimensions. *

* If any of the dimensions are less than or equal to 0, an * {@code IllegalArgumentException} is thrown with an message specifying the * reason for the exception. *

* This method is used to perform a check on the output dimensions of a * thumbnail for the {@link Thumbnails#createThumbnail} methods. * * @param width The width to validate. * @param height The height to validate. */ private static void validateDimensions(int width, int height) { if (width <= 0 && height <= 0) { throw new IllegalArgumentException( "Destination image dimensions must not be less than " + "0 pixels." ); } else if (width <= 0 || height <= 0) { String dimension = width == 0 ? "width" : "height"; throw new IllegalArgumentException( "Destination image " + dimension + " must not be " + "less than or equal to 0 pixels." ); } } private static void checkForNull(Object o, String message) { if (o == null) { throw new NullPointerException(message); } } private static void checkForEmpty(Object[] o, String message) { if (o.length == 0) { throw new IllegalArgumentException(message); } } private static void checkForEmpty(Iterable o, String message) { if (!o.iterator().hasNext()) { throw new IllegalArgumentException(message); } } /** * Indicate to make thumbnails for images with the specified filenames. * * @param files File names of image files for which thumbnails * are to be produced for. * @return Reference to a builder object which is used to * specify the parameters for creating the thumbnail. * @throws NullPointerException If the argument is {@code null}. * @throws IllegalArgumentException If the argument is an empty array. */ public static Builder of(String... files) { checkForNull(files, "Cannot specify null for input files."); checkForEmpty(files, "Cannot specify an empty array for input files."); return Builder.ofStrings(Arrays.asList(files)); } /** * Indicate to make thumbnails from the specified {@link File}s. * * @param files {@link File} objects of image files for which thumbnails * are to be produced for. * @return Reference to a builder object which is used to * specify the parameters for creating the thumbnail. * @throws NullPointerException If the argument is {@code null}. * @throws IllegalArgumentException If the argument is an empty array. */ public static Builder of(File... files) { checkForNull(files, "Cannot specify null for input files."); checkForEmpty(files, "Cannot specify an empty array for input files."); return Builder.ofFiles(Arrays.asList(files)); } /** * Indicate to make thumbnails from the specified {@link URL}s. * * @param urls {@link URL} objects of image files for which thumbnails * are to be produced for. * @return Reference to a builder object which is used to * specify the parameters for creating the thumbnail. * @throws NullPointerException If the argument is {@code null}. * @throws IllegalArgumentException If the argument is an empty array. */ public static Builder of(URL... urls) { checkForNull(urls, "Cannot specify null for input URLs."); checkForEmpty(urls, "Cannot specify an empty array for input URLs."); return Builder.ofUrls(Arrays.asList(urls)); } /** * Indicate to make thumbnails from the specified {@link InputStream}s. * * @param inputStreams {@link InputStream}s which provide the images * for which thumbnails are to be produced for. * @return Reference to a builder object which is used to * specify the parameters for creating the thumbnail. * @throws NullPointerException If the argument is {@code null}. * @throws IllegalArgumentException If the argument is an empty array. */ public static Builder of(InputStream... inputStreams) { checkForNull(inputStreams, "Cannot specify null for InputStreams."); checkForEmpty(inputStreams, "Cannot specify an empty array for InputStreams."); return Builder.ofInputStreams(Arrays.asList(inputStreams)); } /** * Indicate to make thumbnails from the specified {@link BufferedImage}s. * * @param images {@link BufferedImage}s for which thumbnails * are to be produced for. * @return Reference to a builder object which is used to * specify the parameters for creating the thumbnail. * @throws NullPointerException If the argument is {@code null}. * @throws IllegalArgumentException If the argument is an empty array. */ public static Builder of(BufferedImage... images) { checkForNull(images, "Cannot specify null for images."); checkForEmpty(images, "Cannot specify an empty array for images."); return Builder.ofBufferedImages(Arrays.asList(images)); } /** * Indicate to make thumbnails for images with the specified filenames. * * @param files File names of image files for which thumbnails * are to be produced for. * @return Reference to a builder object which is used to * specify the parameters for creating the thumbnail. * @throws NullPointerException If the argument is {@code null}. * @throws IllegalArgumentException If the argument is an empty collection. * @since 0.3.1 */ public static Builder fromFilenames(Iterable files) { checkForNull(files, "Cannot specify null for input files."); checkForEmpty(files, "Cannot specify an empty collection for input files."); return Builder.ofStrings(files); } /** * Indicate to make thumbnails from the specified {@link File}s. * * @param files {@link File} objects of image files for which thumbnails * are to be produced for. * @return Reference to a builder object which is used to * specify the parameters for creating the thumbnail. * @throws NullPointerException If the argument is {@code null}. * @throws IllegalArgumentException If the argument is an empty collection. * @since 0.3.1 */ public static Builder fromFiles(Iterable files) { checkForNull(files, "Cannot specify null for input files."); checkForEmpty(files, "Cannot specify an empty collection for input files."); return Builder.ofFiles(files); } /** * Indicate to make thumbnails for images with the specified {@link URL}s. * * @param urls URLs of the images for which thumbnails * are to be produced. * @return Reference to a builder object which is used to * specify the parameters for creating the thumbnail. * @throws NullPointerException If the argument is {@code null}. * @throws IllegalArgumentException If the argument is an empty collection. * @since 0.3.1 */ public static Builder fromURLs(Iterable urls) { checkForNull(urls, "Cannot specify null for input URLs."); checkForEmpty(urls, "Cannot specify an empty collection for input URLs."); return Builder.ofUrls(urls); } /** * Indicate to make thumbnails for images obtained from the specified * {@link InputStream}s. * * @param inputStreams {@link InputStream}s which provide images for * which thumbnails are to be produced. * @return Reference to a builder object which is used to * specify the parameters for creating the thumbnail. * @throws NullPointerException If the argument is {@code null}. * @throws IllegalArgumentException If the argument is an empty collection. * @since 0.3.1 */ public static Builder fromInputStreams(Iterable inputStreams) { checkForNull(inputStreams, "Cannot specify null for InputStreams."); checkForEmpty(inputStreams, "Cannot specify an empty collection for InputStreams."); return Builder.ofInputStreams(inputStreams); } /** * Indicate to make thumbnails from the specified {@link BufferedImage}s. * * @param images {@link BufferedImage}s for which thumbnails * are to be produced for. * @return Reference to a builder object which is used to * specify the parameters for creating the thumbnail. * @throws NullPointerException If the argument is {@code null}. * @throws IllegalArgumentException If the argument is an empty collection. * @since 0.3.1 */ public static Builder fromImages(Iterable images) { checkForNull(images, "Cannot specify null for images."); checkForEmpty(images, "Cannot specify an empty collection for images."); return Builder.ofBufferedImages(images); } /** * The builder interface for Thumbnailator to set up the thumbnail * generation task. *

* Thumbnailator is intended to be used by calling one of the * {@code Thumbnails.of(...)} methods, then chaining methods such as * {@link #size(int, int)} and {@link #outputQuality(double)} to set up * the thumbnail generation parameters. (See "Intended Use" below.) * The end result should be code that resembles English. *

* In most cases, holding an instance of this class in a local variable, * such as seen in the "Unintended Use" example below, is more verbose * and less future-proof, as changes to this class (which is just an * inner class of the {@link Thumbnails} class) can lead to broken code * when attempting to use future releases of Thumbnailator. *

*

*
Intended Use:
*
*
// Intended use - recommended!
Thumbnails.of("path/to/image")
    .size(200, 200)
    .asFile("path/to/thumbnail");

// English: "Make a thumbnail of 'path/to/image' with a size of 200x200,
             and save it as a file to 'path/to/thumbnail'."
	 * 
*
*
Unintended Use:
*
*
// Unintended use - not recommended!
Builder<File> instance = Thumbnails.of("path/to/image");
instance.size(200, 200);
instance.asFiles("path/to/thumbnail");
	 * 
*
*
*

* An instance of this class provides the fluent interface in the form of * method chaining. Through the fluent interface, the parameters used for * the thumbnail creation, such as {@link #size(int, int)} and * {@link #outputQuality(double)} can be set up. Finally, to execute the * thumbnail creation, one of the output methods whose names start with * {@code to} (e.g. {@link #toFiles(Rename)}) or {@code as} * (e.g. {@link #asBufferedImages()}) is called. *

* An instance of this class is obtained by calling one of: *

* * @author coobird * */ public static class Builder { private final Iterable> sources; private Builder(Iterable> sources) { this.sources = sources; statusMap.put(Properties.OUTPUT_FORMAT, Status.OPTIONAL); } private static final class StringImageSourceIterator implements Iterable> { private final Iterable filenames; private StringImageSourceIterator(Iterable filenames) { this.filenames = filenames; } public Iterator> iterator() { return new Iterator>() { Iterator iter = filenames.iterator(); public boolean hasNext() { return iter.hasNext(); } public ImageSource next() { return new FileImageSource(iter.next()); } public void remove() { throw new UnsupportedOperationException(); } }; } } private static final class FileImageSourceIterator implements Iterable> { private final Iterable files; private FileImageSourceIterator(Iterable files) { this.files = files; } public Iterator> iterator() { return new Iterator>() { Iterator iter = files.iterator(); public boolean hasNext() { return iter.hasNext(); } public ImageSource next() { return new FileImageSource(iter.next()); } public void remove() { throw new UnsupportedOperationException(); } }; } } private static final class URLImageSourceIterator implements Iterable> { private final Iterable urls; private URLImageSourceIterator(Iterable urls) { this.urls = urls; } public Iterator> iterator() { return new Iterator>() { Iterator iter = urls.iterator(); public boolean hasNext() { return iter.hasNext(); } public ImageSource next() { return new URLImageSource(iter.next()); } public void remove() { throw new UnsupportedOperationException(); } }; } } private static final class InputStreamImageSourceIterator implements Iterable> { private final Iterable inputStreams; private InputStreamImageSourceIterator(Iterable inputStreams) { this.inputStreams = inputStreams; } public Iterator> iterator() { return new Iterator>() { Iterator iter = inputStreams.iterator(); public boolean hasNext() { return iter.hasNext(); } public ImageSource next() { return new InputStreamImageSource(iter.next()); } public void remove() { throw new UnsupportedOperationException(); } }; } } private static final class BufferedImageImageSourceIterator implements Iterable> { private final Iterable image; private BufferedImageImageSourceIterator(Iterable images) { this.image = images; } public Iterator> iterator() { return new Iterator>() { Iterator iter = image.iterator(); public boolean hasNext() { return iter.hasNext(); } public ImageSource next() { return new BufferedImageSource(iter.next()); } public void remove() { throw new UnsupportedOperationException(); } }; } } private static Builder ofStrings(Iterable filenames) { Iterable> iter = new StringImageSourceIterator(filenames); return new Builder(iter); } private static Builder ofFiles(Iterable files) { Iterable> iter = new FileImageSourceIterator(files); return new Builder(iter); } private static Builder ofUrls(Iterable urls) { Iterable> iter = new URLImageSourceIterator(urls); return new Builder(iter); } private static Builder ofInputStreams(Iterable inputStreams) { Iterable> iter = new InputStreamImageSourceIterator(inputStreams); return new Builder(iter); } private static Builder ofBufferedImages(Iterable images) { Iterable> iter = new BufferedImageImageSourceIterator(images); return new Builder(iter); } private final class BufferedImageIterable implements Iterable { public Iterator iterator() { return new Iterator() { Iterator> sourceIter = sources.iterator(); public boolean hasNext() { return sourceIter.hasNext(); } public BufferedImage next() { ImageSource source = sourceIter.next(); BufferedImageSink destination = new BufferedImageSink(); try { Thumbnailator.createThumbnail( new SourceSinkThumbnailTask(makeParam(), source, destination) ); } catch (IOException e) { return null; } return destination.getSink(); } public void remove() { throw new UnsupportedOperationException( "Cannot remove elements from this iterator." ); } }; } } /** * Status of each property. * * @author coobird * */ private static enum Status { OPTIONAL, READY, NOT_READY, ALREADY_SET, CANNOT_SET, } /** * Interface used by {@link Properties}. * * @author coobird * */ private static interface Property { public String getName(); } /** * Enum of properties which can be set by this builder. * * @author coobird * */ private static enum Properties implements Property { SIZE("size"), WIDTH("width"), HEIGHT("height"), SCALE("scale"), IMAGE_TYPE("imageType"), SCALING_MODE("scalingMode"), ALPHA_INTERPOLATION("alphaInterpolation"), ANTIALIASING("antialiasing"), DITHERING("dithering"), RENDERING("rendering"), KEEP_ASPECT_RATIO("keepAspectRatio"), OUTPUT_FORMAT("outputFormat"), OUTPUT_FORMAT_TYPE("outputFormatType"), OUTPUT_QUALITY("outputQuality"), RESIZER("resizer"), SOURCE_REGION("sourceRegion"), RESIZER_FACTORY("resizerFactory"), ALLOW_OVERWRITE("allowOverwrite"), CROP("crop"), USE_EXIF_ORIENTATION("useExifOrientation"), ; private final String name; private Properties(String name) { this.name = name; } public String getName() { return name; } } /** * Map to keep track of whether a property has been properly set or not. */ private final Map statusMap = new HashMap(); /** * Populates the property map. */ { statusMap.put(Properties.SIZE, Status.NOT_READY); statusMap.put(Properties.WIDTH, Status.OPTIONAL); statusMap.put(Properties.HEIGHT, Status.OPTIONAL); statusMap.put(Properties.SCALE, Status.NOT_READY); statusMap.put(Properties.SOURCE_REGION, Status.OPTIONAL); statusMap.put(Properties.IMAGE_TYPE, Status.OPTIONAL); statusMap.put(Properties.SCALING_MODE, Status.OPTIONAL); statusMap.put(Properties.ALPHA_INTERPOLATION, Status.OPTIONAL); statusMap.put(Properties.ANTIALIASING, Status.OPTIONAL); statusMap.put(Properties.DITHERING, Status.OPTIONAL); statusMap.put(Properties.RENDERING, Status.OPTIONAL); statusMap.put(Properties.KEEP_ASPECT_RATIO, Status.OPTIONAL); statusMap.put(Properties.OUTPUT_FORMAT, Status.OPTIONAL); statusMap.put(Properties.OUTPUT_FORMAT_TYPE, Status.OPTIONAL); statusMap.put(Properties.OUTPUT_QUALITY, Status.OPTIONAL); statusMap.put(Properties.RESIZER, Status.OPTIONAL); statusMap.put(Properties.RESIZER_FACTORY, Status.OPTIONAL); statusMap.put(Properties.ALLOW_OVERWRITE, Status.OPTIONAL); statusMap.put(Properties.CROP, Status.OPTIONAL); statusMap.put(Properties.USE_EXIF_ORIENTATION, Status.OPTIONAL); } /** * Updates the property status map. * * @param property The property to update. * @param newStatus The new status. */ private void updateStatus(Properties property, Status newStatus) { if (statusMap.get(property) == Status.ALREADY_SET) { throw new IllegalStateException( property.getName() + " is already set."); } /* * The `newStatus != Status.CANNOT_SET` condition will allow the * status to be set to CANNOT_SET to be set multiple times. */ if (newStatus != Status.CANNOT_SET && statusMap.get(property) == Status.CANNOT_SET) { throw new IllegalStateException( property.getName() + " cannot be set."); } statusMap.put(property, newStatus); } /** * An constant used to indicate that the imageType has not been * specified. When this constant is encountered, one should use the * {@link ThumbnailParameter#DEFAULT_IMAGE_TYPE} as the value for * imageType. */ private static int IMAGE_TYPE_UNSPECIFIED = -1; private static final int DIMENSION_NOT_SPECIFIED = -1; /* * Defines the fields for the builder interface, and assigns the * default values. */ private int width = DIMENSION_NOT_SPECIFIED; private int height = DIMENSION_NOT_SPECIFIED; private double scaleWidth = Double.NaN; private double scaleHeight = Double.NaN; private Region sourceRegion; private int imageType = IMAGE_TYPE_UNSPECIFIED; private boolean keepAspectRatio = true; private String outputFormat = ThumbnailParameter.DETERMINE_FORMAT; private String outputFormatType = ThumbnailParameter.DEFAULT_FORMAT_TYPE; private float outputQuality = ThumbnailParameter.DEFAULT_QUALITY; private ScalingMode scalingMode = ScalingMode.PROGRESSIVE_BILINEAR; private AlphaInterpolation alphaInterpolation = AlphaInterpolation.DEFAULT; private Dithering dithering = Dithering.DEFAULT; private Antialiasing antialiasing = Antialiasing.DEFAULT; private Rendering rendering = Rendering.DEFAULT; private ResizerFactory resizerFactory = DefaultResizerFactory.getInstance(); private boolean allowOverwrite = true; private boolean fitWithinDimenions = true; private boolean useExifOrientation = true; /** * This field should be set to the {@link Position} to be used for * cropping if cropping is enabled. If cropping is disabled, then * this field should be left {@code null}. */ private Position croppingPosition = null; /** * The {@link ImageFilter}s that should be applied when creating the * thumbnail. */ private Pipeline filterPipeline = new Pipeline(); /** * Sets the size of the thumbnail. *

* For example, to create thumbnails which should fit within a * bounding rectangle of 640 x 480, the following code can be used: *


Thumbnails.of(image)
    .size(640, 480)
    .toFile(thumbnail);
		 * 
*

* In the above code, the thumbnail will preserve the aspect ratio * of the original image. If the thumbnail should be forced to the * specified size, the {@link #forceSize(int, int)} method can * be used instead of this method. *

* Once this method is called, calling the {@link #scale(double)} method * will result in an {@link IllegalStateException}. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param width The width of the thumbnail. * @param height The height of the thumbnail. * @return Reference to this object. */ public Builder size(int width, int height) { updateStatus(Properties.SIZE, Status.ALREADY_SET); updateStatus(Properties.SCALE, Status.CANNOT_SET); validateDimensions(width, height); this.width = width; this.height = height; return this; } /** * Sets the width of the thumbnail. *

* The thumbnail will have the dimensions constrained by the specified * width, and the aspect ratio of the original image will be preserved * by the thumbnail. *

* Once this method is called, calling the {@link #size(int, int)} or * the {@link #scale(double)} method will result in an * {@link IllegalStateException}. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param width The width of the thumbnail. * @return Reference to this object. * @since 0.3.5 */ public Builder width(int width) { if (statusMap.get(Properties.SIZE) != Status.CANNOT_SET) { updateStatus(Properties.SIZE, Status.CANNOT_SET); } if (statusMap.get(Properties.SCALE) != Status.CANNOT_SET) { updateStatus(Properties.SCALE, Status.CANNOT_SET); } updateStatus(Properties.WIDTH, Status.ALREADY_SET); validateDimensions(width, Integer.MAX_VALUE); this.width = width; return this; } /** * Sets the height of the thumbnail. *

* The thumbnail will have the dimensions constrained by the specified * height, and the aspect ratio of the original image will be preserved * by the thumbnail. *

* Once this method is called, calling the {@link #size(int, int)} or * the {@link #scale(double)} method will result in an * {@link IllegalStateException}. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param height The height of the thumbnail. * @return Reference to this object. * @since 0.3.5 */ public Builder height(int height) { if (statusMap.get(Properties.SIZE) != Status.CANNOT_SET) { updateStatus(Properties.SIZE, Status.CANNOT_SET); } if (statusMap.get(Properties.SCALE) != Status.CANNOT_SET) { updateStatus(Properties.SCALE, Status.CANNOT_SET); } updateStatus(Properties.HEIGHT, Status.ALREADY_SET); validateDimensions(Integer.MAX_VALUE, height); this.height = height; return this; } /** * Sets the size of the thumbnail. *

* The thumbnails will be forced to the specified size, therefore, * the aspect ratio of the original image will not be preserved in * the thumbnails. Calling this method will be equivalent to calling * the {@link #size(int, int)} method in conjunction with the * {@link #keepAspectRatio(boolean)} method with the value {@code false}. *

* Once this method is called, calling the {@link #scale(double)} method * will result in an {@link IllegalStateException}. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param width The width of the thumbnail. * @param height The height of the thumbnail. * @return Reference to this object. * @since 0.3.2 */ public Builder forceSize(int width, int height) { updateStatus(Properties.SIZE, Status.ALREADY_SET); updateStatus(Properties.KEEP_ASPECT_RATIO, Status.ALREADY_SET); updateStatus(Properties.SCALE, Status.CANNOT_SET); validateDimensions(width, height); this.width = width; this.height = height; this.keepAspectRatio = false; return this; } /** * Sets the scaling factor of the thumbnail. *

* For example, to create thumbnails which are 50% the size of the * original, the following code can be used: *


Thumbnails.of(image)
    .scale(0.5)
    .toFile(thumbnail);
		 * 
*

* Once this method is called, calling the {@link #size(int, int)} * method, or the {@link #scale(double, double)} method, or the * {@link #keepAspectRatio(boolean)} method will result in an * {@link IllegalStateException}. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param scale The scaling factor to use when creating a * thumbnail. *

* The value must be a {@code double} which is * greater than {@code 0.0}, and not * {@link Double#POSITIVE_INFINITY}. * @return Reference to this object. */ public Builder scale(double scale) { return scale(scale, scale); } /** * Sets the scaling factor for the width and height of the thumbnail. *

* If the scaling factor for the width and height are not equal, then * the thumbnail will not preserve the aspect ratio of the original * image. *

* For example, to create thumbnails which are 50% the width of the * original, while 75% the height of the original, the following code * can be used: *


Thumbnails.of(image)
    .scale(0.5, 0.75)
    .toFile(thumbnail);
		 * 
*

* Once this method is called, calling the {@link #size(int, int)} * method, or the {@link #scale(double)} method, or the * {@link #keepAspectRatio(boolean)} method will result in an * {@link IllegalStateException}. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param scaleWidth The scaling factor to use for the width when * creating a thumbnail. *

* The value must be a {@code double} which is * greater than {@code 0.0}, and not * {@link Double#POSITIVE_INFINITY}. * @param scaleHeight The scaling factor to use for the height when * creating a thumbnail. *

* The value must be a {@code double} which is * greater than {@code 0.0}, and not * {@link Double#POSITIVE_INFINITY}. * @return Reference to this object. * @since 0.3.10 */ public Builder scale(double scaleWidth, double scaleHeight) { updateStatus(Properties.SCALE, Status.ALREADY_SET); updateStatus(Properties.SIZE, Status.CANNOT_SET); updateStatus(Properties.KEEP_ASPECT_RATIO, Status.CANNOT_SET); if (scaleWidth <= 0.0 || scaleHeight <= 0.0) { throw new IllegalArgumentException( "The scaling factor is equal to or less than 0." ); } if (Double.isNaN(scaleWidth) || Double.isNaN(scaleHeight)) { throw new IllegalArgumentException( "The scaling factor is not a number." ); } if (Double.isInfinite(scaleWidth) || Double.isInfinite(scaleHeight)) { throw new IllegalArgumentException( "The scaling factor cannot be infinity." ); } this.scaleWidth = scaleWidth; this.scaleHeight = scaleHeight; return this; } /** * Specifies the source region from which the thumbnail is to be * created from. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param sourceRegion Source region to use when creating a thumbnail. *

* @return Reference to this object. * @throws NullPointerException If the source region object is * {@code null}. * @since 0.3.4 */ public Builder sourceRegion(Region sourceRegion) { if (sourceRegion == null) { throw new NullPointerException("Region cannot be null."); } updateStatus(Properties.SOURCE_REGION, Status.ALREADY_SET); this.sourceRegion = sourceRegion; return this; } /** * Specifies the source region from which the thumbnail is to be * created from. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param position Position of the source region. * @param size Size of the source region. * @return Reference to this object. * @throws NullPointerException If the position and/or size is * {@code null}. * @since 0.3.4 */ public Builder sourceRegion(Position position, Size size) { if (position == null) { throw new NullPointerException("Position cannot be null."); } if (size == null) { throw new NullPointerException("Size cannot be null."); } return sourceRegion(new Region(position, size)); } /** * Specifies the source region from which the thumbnail is to be * created from. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param x The horizontal-compoennt of the top left-hand * corner of the source region. * @param y The vertical-compoennt of the top left-hand * corner of the source region. * @param width Width of the source region. * @param height Height of the source region. * @return Reference to this object. * @throws IllegalArgumentException If the width and/or height is * less than or equal to {@code 0}. * @since 0.3.4 */ public Builder sourceRegion(int x, int y, int width, int height) { if (width <= 0 || height <= 0) { throw new IllegalArgumentException( "Width and height must be greater than 0." ); } return sourceRegion( new Coordinate(x, y), new AbsoluteSize(width, height) ); } /** * Specifies the source region from which the thumbnail is to be * created from. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param position Position of the source region. * @param width Width of the source region. * @param height Height of the source region. * @return Reference to this object. * @throws NullPointerException If the position and/or size is * {@code null}. * @throws IllegalArgumentException If the width and/or height is * less than or equal to {@code 0}. * @since 0.3.4 */ public Builder sourceRegion(Position position, int width, int height) { if (position == null) { throw new NullPointerException("Position cannot be null."); } if (width <= 0 || height <= 0) { throw new IllegalArgumentException( "Width and height must be greater than 0." ); } return sourceRegion( position, new AbsoluteSize(width, height) ); } /** * Specifies the source region from which the thumbnail is to be * created from. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param region A rectangular region which specifies the source * region to use when creating the thumbnail. * @throws NullPointerException If the region is {@code null}. * @since 0.3.4 */ public Builder sourceRegion(Rectangle region) { if (region == null) { throw new NullPointerException("Region cannot be null."); } return sourceRegion( new Coordinate(region.x, region.y), new AbsoluteSize(region.getSize()) ); } /** * Crops the thumbnail to the size specified when calling the * {@link #size(int, int)} method, positioned by the given * {@link Position} object. *

* Calling this method will guarantee that the size of the thumbnail * will be exactly the dimensions specified in the * {@link #size(int, int)} method. *

* Internally, the resizing is performed in two steps. * First, the thumbnail will be sized so that one of the dimensions will * be sized exactly to the dimension specified in the {@code size} * method, while allowing the other dimension to overhang the specified * dimension. Then, the thumbnail will be cropped to the dimensions * specified in the {@code size} method, positioned using the speficied * {@link Position} object. *

* Once this method is called, calling the {@link #scale(double)} method * will result in an {@link IllegalStateException}. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param position The position to which the thumbnail should be * cropped to. For example, if * {@link Positions#CENTER} is specified, the * resulting thumbnail will be made by cropping to * the center of the image. * @throws NullPointerException If the position is {@code null}. * @since 0.4.0 */ public Builder crop(Position position) { checkForNull(position, "Position cannot be null."); updateStatus(Properties.CROP, Status.ALREADY_SET); updateStatus(Properties.SCALE, Status.CANNOT_SET); croppingPosition = position; fitWithinDimenions = false; return this; } /** * Specifies whether or not to overwrite files which already exist if * they have been specified as destination files. *

* This method will change the output behavior of the following methods: *

    *
  • {@link #toFile(File)}
  • *
  • {@link #toFile(String)}
  • *
  • {@link #toFiles(Iterable)}
  • *
  • {@link #toFiles(Rename)}
  • *
  • {@link #asFiles(Iterable)}
  • *
  • {@link #asFiles(Rename)}
  • *
* The behavior of methods which are not listed above will not be * affected by calling this method. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param allowOverwrite If {@code true} then existing files will be * overwritten if specified as a destination. * If {@code false}, then the existing files * will not be altered. For specific behavior, * please refer to the specific output methods * listed above. * * @since 0.3.7 */ public Builder allowOverwrite(boolean allowOverwrite) { updateStatus(Properties.ALLOW_OVERWRITE, Status.ALREADY_SET); this.allowOverwrite = allowOverwrite; return this; } /** * Sets the image type of the thumbnail. *

* Calling this method to set this parameter is optional. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param type The image type of the thumbnail. * @return Reference to this object. */ public Builder imageType(int type) { updateStatus(Properties.IMAGE_TYPE, Status.ALREADY_SET); imageType = type; return this; } /** * Sets the resizing scaling mode to use when creating the thumbnail. *

* Calling this method to set this parameter is optional. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param config The scaling mode to use. * @return Reference to this object. */ public Builder scalingMode(ScalingMode config) { checkForNull(config, "Scaling mode is null."); updateStatus(Properties.SCALING_MODE, Status.ALREADY_SET); updateStatus(Properties.RESIZER, Status.CANNOT_SET); updateStatus(Properties.RESIZER_FACTORY, Status.CANNOT_SET); scalingMode = config; return this; } /** * Sets the resizing operation to use when creating the thumbnail. *

* Calling this method to set this parameter is optional. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. *

* This method cannot be called in conjunction with the * {@link #resizerFactory(ResizerFactory)} method. * * @param resizer The scaling operation to use. * @return Reference to this object. */ public Builder resizer(Resizer resizer) { checkForNull(resizer, "Resizer is null."); updateStatus(Properties.RESIZER, Status.ALREADY_SET); updateStatus(Properties.RESIZER_FACTORY, Status.CANNOT_SET); updateStatus(Properties.SCALING_MODE, Status.CANNOT_SET); this.resizerFactory = new FixedResizerFactory(resizer); return this; } /** * Sets the {@link ResizerFactory} object to use to decide what kind of * resizing operation is to be used when creating the thumbnail. *

* Calling this method to set this parameter is optional. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. *

* This method cannot be called in conjunction with the * {@link #resizer(Resizer)} method. * * @param resizerFactory The scaling operation to use. * @return Reference to this object. * @since 0.4.0 */ public Builder resizerFactory(ResizerFactory resizerFactory) { checkForNull(resizerFactory, "ResizerFactory is null."); updateStatus(Properties.RESIZER_FACTORY, Status.ALREADY_SET); updateStatus(Properties.RESIZER, Status.CANNOT_SET); // disable the methods which set parameters for the Resizer updateStatus(Properties.SCALING_MODE, Status.CANNOT_SET); updateStatus(Properties.ALPHA_INTERPOLATION, Status.CANNOT_SET); updateStatus(Properties.DITHERING, Status.CANNOT_SET); updateStatus(Properties.ANTIALIASING, Status.CANNOT_SET); updateStatus(Properties.RENDERING, Status.CANNOT_SET); this.resizerFactory = resizerFactory; return this; } /** * Sets the alpha interpolation mode when performing the resizing * operation to generate the thumbnail. *

* Calling this method to set this parameter is optional. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. *

* This method cannot be called in conjunction with the * {@link #resizerFactory(ResizerFactory)} method. * * @param config The alpha interpolation mode. * @return Reference to this object. */ public Builder alphaInterpolation(AlphaInterpolation config) { checkForNull(config, "Alpha interpolation is null."); updateStatus(Properties.RESIZER_FACTORY, Status.CANNOT_SET); updateStatus(Properties.ALPHA_INTERPOLATION, Status.ALREADY_SET); alphaInterpolation = config; return this; } /** * Sets the dithering mode when performing the resizing * operation to generate the thumbnail. *

* Calling this method to set this parameter is optional. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. *

* This method cannot be called in conjunction with the * {@link #resizerFactory(ResizerFactory)} method. * * @param config The dithering mode. * @return Reference to this object. */ public Builder dithering(Dithering config) { checkForNull(config, "Dithering is null."); updateStatus(Properties.RESIZER_FACTORY, Status.CANNOT_SET); updateStatus(Properties.DITHERING, Status.ALREADY_SET); dithering = config; return this; } /** * Sets the antialiasing mode when performing the resizing * operation to generate the thumbnail. *

* Calling this method to set this parameter is optional. *

* Calling this method multiple times will result in an * {@link IllegalStateException}. *

* This method cannot be called in conjunction with the * {@link #resizerFactory(ResizerFactory)} method. * * @param config The antialiasing mode. * @return Reference to this object. */ public Builder antialiasing(Antialiasing config) { checkForNull(config, "Antialiasing is null."); updateStatus(Properties.RESIZER_FACTORY, Status.CANNOT_SET); updateStatus(Properties.ANTIALIASING, Status.ALREADY_SET); antialiasing = config; return this; } /** * Sets the rendering mode when performing the resizing * operation to generate the thumbnail. *

* Calling this method to set this parameter is optional. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. *

* This method cannot be called in conjunction with the * {@link #resizerFactory(ResizerFactory)} method. * * @param config The rendering mode. * @return Reference to this object. */ public Builder rendering(Rendering config) { checkForNull(config, "Rendering is null."); updateStatus(Properties.RESIZER_FACTORY, Status.CANNOT_SET); updateStatus(Properties.RENDERING, Status.ALREADY_SET); rendering = config; return this; } /** * Sets whether or not to keep the aspect ratio of the original image * for the thumbnail. *

* Calling this method without first calling the {@link #size(int, int)} * method will result in an {@link IllegalStateException} to be thrown. *

* If this method is not called when, by default the aspect ratio of * the original image is preserved for the thumbnail. *

* Calling this method after calling the {@link #scale(double)} method * or the {@link #scale(double, double)} method will result in a * {@link IllegalStateException}. * * @param keep {@code true} if the thumbnail is to maintain * the aspect ratio of the original image, * {@code false} otherwise. * @return Reference to this object. * * @throws IllegalStateException If *

    *
  1. the {@link #size(int, int)} has * not yet been called to specify the * size of the thumbnail, or
  2. *
  3. the {@link #scale(double)} * method has been called, or
  4. *
  5. the * {@link #scale(double, double)} * method has been called, or
  6. *
  7. the {@link #width(int)} and/or * {@link #height(int)} has been called * and not preserving the aspect ratio * is desired.
  8. *
*/ public Builder keepAspectRatio(boolean keep) { if (statusMap.get(Properties.SCALE) == Status.ALREADY_SET) { throw new IllegalStateException("Cannot specify whether to " + "keep the aspect ratio if the scaling factor has " + "already been specified."); } if (statusMap.get(Properties.SIZE) == Status.NOT_READY) { throw new IllegalStateException("Cannot specify whether to " + "keep the aspect ratio unless the size parameter has " + "already been specified."); } if ((statusMap.get(Properties.WIDTH) == Status.ALREADY_SET || statusMap.get(Properties.HEIGHT) == Status.ALREADY_SET) && !keep ) { throw new IllegalStateException("The aspect ratio must be " + "preserved when the width and/or height parameter " + "has already been specified."); } updateStatus(Properties.KEEP_ASPECT_RATIO, Status.ALREADY_SET); keepAspectRatio = keep; return this; } /** * Sets the output quality of the compression algorithm used to * compress the thumbnail when it is written to an external destination * such as a file or output stream. *

* The value is a {@code float} between {@code 0.0f} and {@code 1.0f} * where {@code 0.0f} indicates the minimum quality and {@code 1.0f} * indicates the maximum quality settings should be used for by the * compression codec. *

* Calling this method to set this parameter is optional. *

* Calling this method in conjunction with {@link #asBufferedImage()} * or {@link #asBufferedImages()} will not result in any changes to the * final result. *

* Calling this method multiple times, or the * {@link #outputQuality(double)} in conjunction with this method will * result in an {@link IllegalStateException} to be thrown. * * @param quality The compression quality to use when writing * the thumbnail. * @return Reference to this object. * @throws IllegalArgumentException If the argument is less than * {@code 0.0f} or is greater than * {@code 1.0f}. */ public Builder outputQuality(float quality) { if (quality < 0.0f || quality > 1.0f) { throw new IllegalArgumentException( "The quality setting must be in the range 0.0f and " + "1.0f, inclusive." ); } updateStatus(Properties.OUTPUT_QUALITY, Status.ALREADY_SET); outputQuality = quality; return this; } /** * Sets the output quality of the compression algorithm used to * compress the thumbnail when it is written to an external destination * such as a file or output stream. *

* The value is a {@code double} between {@code 0.0d} and {@code 1.0d} * where {@code 0.0d} indicates the minimum quality and {@code 1.0d} * indicates the maximum quality settings should be used for by the * compression codec. *

* This method is a convenience method for {@link #outputQuality(float)} * where the {@code double} argument type is accepted instead of a * {@code float}. *

* Calling this method to set this parameter is optional. *

* Calling this method in conjunction with {@link #asBufferedImage()} * or {@link #asBufferedImages()} will not result in any changes to the * final result. *

* Calling this method multiple times, or the * {@link #outputQuality(float)} in conjunction with this method will * result in an {@link IllegalStateException} to be thrown. * * @param quality The compression quality to use when writing * the thumbnail. * @return Reference to this object. * @throws IllegalArgumentException If the argument is less than * {@code 0.0d} or is greater than * {@code 1.0d}. */ public Builder outputQuality(double quality) { if (quality < 0.0d || quality > 1.0d) { throw new IllegalArgumentException( "The quality setting must be in the range 0.0d and " + "1.0d, inclusive." ); } updateStatus(Properties.OUTPUT_QUALITY, Status.ALREADY_SET); outputQuality = (float)quality; if (outputQuality < 0.0f) { outputQuality = 0.0f; } else if (outputQuality > 1.0f) { outputQuality = 1.0f; } return this; } /** * Sets the compression format to use when writing the thumbnail. *

* For example, to set the output format to JPEG, the following code * can be used: *


Thumbnails.of(image)
    .size(640, 480)
    .outputFormat("JPEG")
    .toFile(thumbnail);
		 * 
* or, alternatively: *

Thumbnails.of(image)
    .size(640, 480)
    .outputFormat("jpg")
    .toFile(thumbnail);
		 * 
*

* Currently, whether or not the compression format string is valid * dependents on whether the Java Image I/O API recognizes the string * as a format that it supports for output. (Valid format names can * be obtained by calling the {@link ImageIO#getWriterFormatNames()} * method.) *

* Calling this method to set this parameter is optional. *

* Calling this method in conjunction with {@link #asBufferedImage()} * or {@link #asBufferedImages()} will not result in any changes to the * final result. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param format The compression format to use when writing * the thumbnail. * @return Reference to this object. * @throws IllegalArgumentException If an unsupported format is * specified. */ public Builder outputFormat(String format) { if (!ThumbnailatorUtils.isSupportedOutputFormat(format)) { throw new IllegalArgumentException( "Specified format is not supported: " + format ); } updateStatus(Properties.OUTPUT_FORMAT, Status.ALREADY_SET); outputFormat = format; return this; } /** * Sets the compression format to use the same format as the original * image. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @return Reference to this object. * @since 0.4.0 */ public Builder useOriginalFormat() { updateStatus(Properties.OUTPUT_FORMAT, Status.ALREADY_SET); outputFormat = ThumbnailParameter.ORIGINAL_FORMAT; return this; } /** * Sets whether or not to use the Exif metadata when orienting the * thumbnail. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @param useExifOrientation {@code true} if the Exif metadata * should be used to determine the * orientation of the thumbnail, * {@code false} otherwise. * @return Reference to this object. * @since 0.4.3 */ public Builder useExifOrientation(boolean useExifOrientation) { updateStatus(Properties.USE_EXIF_ORIENTATION, Status.ALREADY_SET); this.useExifOrientation = useExifOrientation; return this; } /** * Indicates that the output format should be determined from the * available information when writing the thumbnail image. *

* For example, calling this method will cause the output format to be * determined from the file extension if thumbnails are written to * files. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. * * @return Reference to this object. * @since 0.4.0 */ public Builder determineOutputFormat() { updateStatus(Properties.OUTPUT_FORMAT, Status.ALREADY_SET); outputFormat = ThumbnailParameter.DETERMINE_FORMAT; return this; } private boolean isOutputFormatNotSet() { return outputFormat == null || ThumbnailParameter.DETERMINE_FORMAT.equals(outputFormat); } /** * Sets the compression format type of the thumbnail to write. *

* If the default type for the compression codec should be used, a * value of {@link ThumbnailParameter#DEFAULT_FORMAT_TYPE} should be * used. *

* Calling this method to set this parameter is optional. *

* Calling this method multiple times will result in an * {@link IllegalStateException} to be thrown. *

* Furthermore, if this method is called, then calling the * {@link #outputFormat} method is disabled, in order to prevent * cases where the output format type does not exist in the format * specified for the {@code outputFormat} method. * * @param formatType The compression format type * @return Reference to this object. * @throws IllegalArgumentException If an unsupported format type is * specified for the current output * format type. Or, if the output * format has not been specified before * this method was called. */ public Builder outputFormatType(String formatType) { /* * If the output format is the original format, and the format type * is being specified, it's going to be likely that the specified * type will not be present in all the formats, so we'll disallow * it. (e.g. setting type to "JPEG", and if the original formats * were JPEG and PNG, then we'd have a problem. */ if (formatType != ThumbnailParameter.DEFAULT_FORMAT_TYPE && isOutputFormatNotSet()) { throw new IllegalArgumentException( "Cannot set the format type if a specific output " + "format has not been specified." ); } if (!ThumbnailatorUtils.isSupportedOutputFormatType(outputFormat, formatType)) { throw new IllegalArgumentException( "Specified format type (" + formatType + ") is not " + " supported for the format: " + outputFormat ); } /* * If the output format type is set, then we'd better make the * output format unchangeable, or else we'd risk having a type * that is not part of the output format. */ updateStatus(Properties.OUTPUT_FORMAT_TYPE, Status.ALREADY_SET); if (!statusMap.containsKey(Properties.OUTPUT_FORMAT)) { updateStatus(Properties.OUTPUT_FORMAT, Status.CANNOT_SET); } outputFormatType = formatType; return this; } /** * Sets the watermark to apply on the thumbnail. *

* This method can be called multiple times to apply multiple * watermarks. *

* If multiple watermarks are to be applied, the watermarks will be * applied in the order that this method is called. *

* Calling this method to set this parameter is optional. * * @param w The watermark to apply to the thumbnail. * @return Reference to this object. */ public Builder watermark(Watermark w) { if (w == null) { throw new NullPointerException("Watermark is null."); } filterPipeline.add(w); return this; } /** * Sets the image of the watermark to apply on the thumbnail. *

* This method is a convenience method for the * {@link #watermark(Position, BufferedImage, float)} method, where * the opacity is 50%, and the position is set to center of the * thumbnail: *

*

watermark(Positions.CENTER, image, 0.5f);
		 * 
* This method can be called multiple times to apply multiple * watermarks. *

* If multiple watermarks are to be applied, the watermarks will be * applied in the order that this method is called. *

* Calling this method to set this parameter is optional. * * @param image The image of the watermark. * @return Reference to this object. */ public Builder watermark(BufferedImage image) { return watermark(Positions.CENTER, image, 0.5f); } /** * Sets the image and opacity of the watermark to apply on * the thumbnail. *

* This method is a convenience method for the * {@link #watermark(Position, BufferedImage, float)} method, where * the opacity is 50%: *

*

watermark(Positions.CENTER, image, opacity);
		 * 
* This method can be called multiple times to apply multiple * watermarks. *

* If multiple watermarks are to be applied, the watermarks will be * applied in the order that this method is called. *

* Calling this method to set this parameter is optional. * * @param image The image of the watermark. * @param opacity The opacity of the watermark. *

* The value should be between {@code 0.0f} and * {@code 1.0f}, where {@code 0.0f} is completely * transparent, and {@code 1.0f} is completely * opaque. * @return Reference to this object. */ public Builder watermark(BufferedImage image, float opacity) { return watermark(Positions.CENTER, image, opacity); } /** * Sets the image and opacity and position of the watermark to apply on * the thumbnail. *

* This method can be called multiple times to apply multiple * watermarks. *

* If multiple watermarks are to be applied, the watermarks will be * applied in the order that this method is called. *

* Calling this method to set this parameter is optional. * * @param position The position of the watermark. * @param image The image of the watermark. * @param opacity The opacity of the watermark. *

* The value should be between {@code 0.0f} and * {@code 1.0f}, where {@code 0.0f} is completely * transparent, and {@code 1.0f} is completely * opaque. * @return Reference to this object. */ public Builder watermark(Position position, BufferedImage image, float opacity) { filterPipeline.add(new Watermark(position, image, opacity)); return this; } /* * rotation */ /** * Sets the amount of rotation to apply to the thumbnail. *

* The thumbnail will be rotated clockwise by the angle specified. *

* This method can be called multiple times to apply multiple * rotations. *

* If multiple rotations are to be applied, the rotations will be * applied in the order that this method is called. *

* Calling this method to set this parameter is optional. * * @param angle Angle in degrees. * @return Reference to this object. */ public Builder rotate(double angle) { filterPipeline.add(Rotation.newRotator(angle)); return this; } /* * other filters */ /** * Adds a {@link ImageFilter} to apply to the thumbnail. *

* This method can be called multiple times to apply multiple * filters. *

* If multiple filters are to be applied, the filters will be * applied in the order that this method is called. *

* Calling this method to set this parameter is optional. * * @param filter An image filter to apply to the thumbnail. * @return Reference to this object. */ public Builder addFilter(ImageFilter filter) { if (filter == null) { throw new NullPointerException("Filter is null."); } filterPipeline.add(filter); return this; } /** * Adds multiple {@link ImageFilter}s to apply to the thumbnail. *

* This method can be called multiple times to apply multiple * filters. *

* If multiple filters are to be applied, the filters will be * applied in the order that this method is called. *

* Calling this method to set this parameter is optional. * * @param filters A list of filters to apply to the thumbnail. * @return Reference to this object. */ public Builder addFilters(List filters) { if (filters == null) { throw new NullPointerException("Filters is null."); } filterPipeline.addAll(filters); return this; } /** * Checks whether the builder is ready to create thumbnails. * * @throws IllegalStateException If the builder is not ready to * create thumbnails, due to some * parameters not being set. */ private void checkReadiness() { for (Map.Entry s : statusMap.entrySet()) { if (s.getValue() == Status.NOT_READY) { throw new IllegalStateException(s.getKey().getName() + " is not set."); } } } /** * Returns a {@link Resizer} which is suitable for the current * builder state. * * @param mode The scaling mode to use to create thumbnails. * @return The {@link Resizer} which is suitable for the * specified scaling mode and builder state. */ private Resizer makeResizer(ScalingMode mode) { Map hints = new HashMap(); hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, alphaInterpolation.getValue()); hints.put(RenderingHints.KEY_DITHERING, dithering.getValue()); hints.put(RenderingHints.KEY_ANTIALIASING, antialiasing.getValue()); hints.put(RenderingHints.KEY_RENDERING, rendering.getValue()); if (mode == ScalingMode.BILINEAR) { return new BilinearResizer(hints); } else if (mode == ScalingMode.BICUBIC) { return new BicubicResizer(hints); } else if (mode == ScalingMode.PROGRESSIVE_BILINEAR) { return new ProgressiveBilinearResizer(hints); } else { return new ProgressiveBilinearResizer(hints); } } private void prepareResizerFactory() { /* * If the scalingMode has been set, then use scalingMode to obtain * a resizer, else, use the resizer field. */ if (statusMap.get(Properties.SCALING_MODE) == Status.ALREADY_SET) { this.resizerFactory = new FixedResizerFactory(makeResizer(scalingMode)); } } /** * Returns a {@link ThumbnailParameter} from the current builder state. * * @return A {@link ThumbnailParameter} from the current * builder state. */ private ThumbnailParameter makeParam() { prepareResizerFactory(); int imageTypeToUse = imageType; if (imageType == IMAGE_TYPE_UNSPECIFIED) { imageTypeToUse = ThumbnailParameter.ORIGINAL_IMAGE_TYPE; } /* * croppingPosition being non-null means that a crop should * take place. */ if (croppingPosition != null) { filterPipeline.addFirst(new Canvas(width, height, croppingPosition)); } if (Double.isNaN(scaleWidth)) { // If the dimensions were specified, do the following. // Check that at least one dimension is specified. // If it's not, it's a bug. if ( width == DIMENSION_NOT_SPECIFIED && height == DIMENSION_NOT_SPECIFIED ) { throw new IllegalStateException( "The width or height must be specified. If this " + "exception is thrown, it is due to a bug in the " + "Thumbnailator library." ); } // Set the unspecified dimension to a default value. if (width == DIMENSION_NOT_SPECIFIED) { width = Integer.MAX_VALUE; } if (height == DIMENSION_NOT_SPECIFIED) { height = Integer.MAX_VALUE; } return new ThumbnailParameter( new Dimension(width, height), sourceRegion, keepAspectRatio, outputFormat, outputFormatType, outputQuality, imageTypeToUse, filterPipeline.getFilters(), resizerFactory, fitWithinDimenions, useExifOrientation ); } else { // If the scaling factor was specified return new ThumbnailParameter( scaleWidth, scaleHeight, sourceRegion, keepAspectRatio, outputFormat, outputFormatType, outputQuality, imageTypeToUse, filterPipeline.getFilters(), resizerFactory, fitWithinDimenions, useExifOrientation ); } } /** * Create the thumbnails and return as a {@link Iterable} of * {@link BufferedImage}s. *

* For situations where multiple thumbnails are being generated, this * method is preferred over the {@link #asBufferedImages()} method, * as (1) the processing does not have to complete before the method * returns and (2) the thumbnails can be retrieved one at a time, * potentially reducing the number of thumbnails which need to be * retained in the heap memory, potentially reducing the chance of * {@link OutOfMemoryError}s from occurring. *

* If an {@link IOException} occurs during the processing of the * thumbnail, the {@link Iterable} will return a {@code null} for that * element. * * @return An {@link Iterable} which will provide an * {@link Iterator} which returns thumbnails as * {@link BufferedImage}s. */ public Iterable iterableBufferedImages() { checkReadiness(); /* * TODO To get the precise error information, there would have to * be an event notification mechanism. */ return new BufferedImageIterable(); } /** * Create the thumbnails and return as a {@link List} of * {@link BufferedImage}s. *

*

Note about performance

* If there are many thumbnails generated at once, it is possible that * the Java virtual machine's heap space will run out and an * {@link OutOfMemoryError} could result. *

* If many thumbnails are being processed at once, then using the * {@link #iterableBufferedImages()} method would be preferable. * * @return A list of thumbnails. * @throws IOException If an problem occurred during * the reading of the original * images. */ public List asBufferedImages() throws IOException { checkReadiness(); List thumbnails = new ArrayList(); // Create thumbnails for (ImageSource source : sources) { BufferedImageSink destination = new BufferedImageSink(); Thumbnailator.createThumbnail( new SourceSinkThumbnailTask(makeParam(), source, destination) ); thumbnails.add(destination.getSink()); } return thumbnails; } /** * Creates a thumbnail and returns it as a {@link BufferedImage}. *

* To call this method, the thumbnail must have been created from a * single source. * * @return A thumbnail as a {@link BufferedImage}. * @throws IOException If an problem occurred during * the reading of the original * image. * @throws IllegalArgumentException If multiple original images are * specified. */ public BufferedImage asBufferedImage() throws IOException { checkReadiness(); Iterator> iter = sources.iterator(); ImageSource source = iter.next(); if (iter.hasNext()) { throw new IllegalArgumentException("Cannot create one thumbnail from multiple original images."); } BufferedImageSink destination = new BufferedImageSink(); Thumbnailator.createThumbnail( new SourceSinkThumbnailTask(makeParam(), source, destination) ); return destination.getSink(); } /** * Creates the thumbnails and stores them to the files, and returns * a {@link List} of {@link File}s to the thumbnails. *

* When the destination file exists, and overwriting files has been * disabled by calling the {@link #allowOverwrite(boolean)} method * with {@code false}, then the thumbnail with the destination file * already existing will not be written and the corresponding * {@code File} object will not be included in the {@code List} returned * by this method. *

* The file names for the thumbnails are obtained from the given * {@link Iterable}. * * @param iterable An {@link Iterable} which returns an * {@link Iterator} which returns file names * which should be assigned to each thumbnail. * @return A list of {@link File}s of the thumbnails * which were created. * @throws IOException If a problem occurs while reading the * original images or writing the thumbnails * to files. * @since 0.3.7 */ public List asFiles(Iterable iterable) throws IOException { checkReadiness(); if (iterable == null) { throw new NullPointerException("File name iterable is null."); } List destinationFiles = new ArrayList(); Iterator filenameIter = iterable.iterator(); for (ImageSource source : sources) { if (!filenameIter.hasNext()) { throw new IndexOutOfBoundsException( "Not enough file names provided by iterator." ); } ThumbnailParameter param = makeParam(); FileImageSink destination = new FileImageSink(filenameIter.next(), allowOverwrite); try { Thumbnailator.createThumbnail( new SourceSinkThumbnailTask(param, source, destination) ); destinationFiles.add(destination.getSink()); } catch (IllegalArgumentException e) { /* * Handle the IllegalArgumentException which is thrown when * the destination file already exists by not adding the * current file to the destinationFiles list. */ } } return destinationFiles; } /** * Creates the thumbnails and stores them to the files. *

* When the destination file exists, and overwriting files has been * disabled by calling the {@link #allowOverwrite(boolean)} method * with {@code false}, then the thumbnail with the destination file * already existing will not be written. *

* The file names for the thumbnails are obtained from the given * {@link Iterable}. * * @param iterable An {@link Iterable} which returns an * {@link Iterator} which returns file names * which should be assigned to each thumbnail. * @throws IOException If a problem occurs while reading the * original images or writing the thumbnails * to files. * @since 0.3.7 */ public void toFiles(Iterable iterable) throws IOException { asFiles(iterable); } /** * Creates thumbnails and stores them to files using the * {@link Rename} function to determine the filenames. The thubnail * files are returned as a {@link List}. *

* When the destination file exists, and overwriting files has been * disabled by calling the {@link #allowOverwrite(boolean)} method * with {@code false}, then the thumbnail with the destination file * already existing will not be written and the corresponding * {@code File} object will not be included in the {@code List} returned * by this method. *

* To call this method, the thumbnails must have been creates from * files by calling the {@link Thumbnails#of(File...)} method. * * @param rename The rename function which is used to * determine the filenames of the thumbnail * files to write. * @return A list of {@link File}s of the thumbnails * which were created. * @throws IOException If a problem occurs while reading the * original images or writing the thumbnails * to files. * @throws IllegalStateException If the original images are not * from files. * @since 0.3.7 */ public List asFiles(Rename rename) throws IOException { return asFiles(null, rename); } /** * Creates thumbnails and stores them to files in the directory * specified by the given {@link File} object, and using the * {@link Rename} function to determine the filenames. The thubnail * files are returned as a {@link List}. *

* When the destination file exists, and overwriting files has been * disabled by calling the {@link #allowOverwrite(boolean)} method * with {@code false}, then the thumbnail with the destination file * already existing will not be written and the corresponding * {@code File} object will not be included in the {@code List} returned * by this method. *

* Extra caution should be taken when using this method, as there are * no protections in place to prevent file name collisions resulting * from creating thumbnails from files in separate directories but * having the same name. In such a case, the behavior will be depend * on the behavior of the {@link #allowOverwrite(boolean)} as * described in the previous paragraph. *

* To call this method, the thumbnails must have been creates from * files by calling the {@link Thumbnails#of(File...)} method. * * @param destinationDir The destination directory to which the * thumbnails should be written to. * @param rename The rename function which is used to * determine the filenames of the thumbnail * files to write. * @return A list of {@link File}s of the thumbnails * which were created. * @throws IOException If a problem occurs while reading the * original images or writing the thumbnails * to files. * @throws IllegalStateException If the original images are not * from files. * @throws IllegalArgumentException If the destination directory * is not a directory. * @since 0.4.7 */ public List asFiles(File destinationDir, Rename rename) throws IOException { checkReadiness(); if (rename == null) { throw new NullPointerException("Rename is null."); } if (destinationDir != null && !destinationDir.isDirectory()) { throw new IllegalArgumentException("Given destination is not a directory."); } List destinationFiles = new ArrayList(); for (ImageSource source : sources) { if (!(source instanceof FileImageSource)) { throw new IllegalStateException("Cannot create thumbnails to files if original images are not from files."); } ThumbnailParameter param = makeParam(); File f = ((FileImageSource)source).getSource(); File actualDestDir = destinationDir == null ? f.getParentFile() : destinationDir; File destinationFile = new File(actualDestDir, rename.apply(f.getName(), param)); FileImageSink destination = new FileImageSink(destinationFile, allowOverwrite); try { Thumbnailator.createThumbnail( new SourceSinkThumbnailTask(param, source, destination) ); destinationFiles.add(destination.getSink()); } catch (IllegalArgumentException e) { /* * Handle the IllegalArgumentException which is thrown when * the destination file already exists by not adding the * current file to the destinationFiles list. */ } } return destinationFiles; } /** * Creates thumbnails and stores them to files using the * {@link Rename} function to determine the filenames. *

* When the destination file exists, and overwriting files has been * disabled by calling the {@link #allowOverwrite(boolean)} method * with {@code false}, then the thumbnail with the destination file * already existing will not be written. *

* To call this method, the thumbnails must have been creates from * files by calling the {@link Thumbnails#of(File...)} method. * * @param rename The rename function which is used to * determine the filenames of the thumbnail * files to write. * @throws IOException If a problem occurs while reading the * original images or writing the thumbnails * to files. * thumbnails to files. * @throws IllegalStateException If the original images are not * from files. * @since 0.3.7 */ public void toFiles(Rename rename) throws IOException { toFiles(null, rename); } /** * Creates thumbnails and stores them to files in the directory * specified by the given {@link File} object, and using the * {@link Rename} function to determine the filenames. *

* When the destination file exists, and overwriting files has been * disabled by calling the {@link #allowOverwrite(boolean)} method * with {@code false}, then the thumbnail with the destination file * already existing will not be written. *

* Extra caution should be taken when using this method, as there are * no protections in place to prevent file name collisions resulting * from creating thumbnails from files in separate directories but * having the same name. In such a case, the behavior will be depend * on the behavior of the {@link #allowOverwrite(boolean)} as * described in the previous paragraph. *

* To call this method, the thumbnails must have been creates from * files by calling the {@link Thumbnails#of(File...)} method. * * @param destinationDir The destination directory to which the * thumbnails should be written to. * @param rename The rename function which is used to * determine the filenames of the thumbnail * files to write. * @throws IOException If a problem occurs while reading the * original images or writing the thumbnails * to files. * thumbnails to files. * @throws IllegalStateException If the original images are not * from files. * @throws IllegalArgumentException If the destination directory * is not a directory. * @since 0.4.7 */ public void toFiles(File destinationDir, Rename rename) throws IOException { asFiles(destinationDir, rename); } /** * Create a thumbnail and writes it to a {@link File}. *

* When the destination file exists, and overwriting files has been * disabled by calling the {@link #allowOverwrite(boolean)} method * with {@code false}, then an {@link IllegalArgumentException} will * be thrown. *

* To call this method, the thumbnail must have been created from a * single source. * * @param outFile The file to which the thumbnail is to be * written to. * @throws IOException If a problem occurs while reading the * original images or writing the thumbnails * to files. * @throws IllegalArgumentException If multiple original image files * are specified, or if the * destination file exists, and * overwriting files is disabled. */ public void toFile(File outFile) throws IOException { checkReadiness(); Iterator> iter = sources.iterator(); ImageSource source = iter.next(); if (iter.hasNext()) { throw new IllegalArgumentException("Cannot output multiple thumbnails to one file."); } FileImageSink destination = new FileImageSink(outFile, allowOverwrite); Thumbnailator.createThumbnail( new SourceSinkThumbnailTask(makeParam(), source, destination) ); } /** * Create a thumbnail and writes it to a {@link File}. *

* When the destination file exists, and overwriting files has been * disabled by calling the {@link #allowOverwrite(boolean)} method * with {@code false}, then an {@link IllegalArgumentException} will * be thrown. *

* To call this method, the thumbnail must have been created from a * single source. * * @param outFilepath The file to which the thumbnail is to be * written to. * @throws IOException If a problem occurs while reading the * original images or writing the thumbnails * to files. * @throws IllegalArgumentException If multiple original image files * are specified, or if the * destination file exists, and * overwriting files is disabled. */ public void toFile(String outFilepath) throws IOException { checkReadiness(); Iterator> iter = sources.iterator(); ImageSource source = iter.next(); if (iter.hasNext()) { throw new IllegalArgumentException("Cannot output multiple thumbnails to one file."); } FileImageSink destination = new FileImageSink(outFilepath, allowOverwrite); Thumbnailator.createThumbnail( new SourceSinkThumbnailTask(makeParam(), source, destination) ); } /** * Create a thumbnail and writes it to a {@link OutputStream}. *

* To call this method, the thumbnail must have been created from a * single source. * * @param os The output stream to which the thumbnail * is to be written to. * @throws IOException If a problem occurs while reading the * original images or writing the thumbnails. * @throws IllegalArgumentException If multiple original image files * are specified. * @throws IllegalStateException If the output format has not * been specified through the * {@link #outputFormat(String)} * method. */ public void toOutputStream(OutputStream os) throws IOException { checkReadiness(); Iterator> iter = sources.iterator(); ImageSource source = iter.next(); if (iter.hasNext()) { throw new IllegalArgumentException("Cannot output multiple thumbnails to a single OutputStream."); } /* * if the image is from a BufferedImage, then we require that the * output format be set. (or else, we can't tell what format to * output as!) */ if (source instanceof BufferedImageSource) { if (isOutputFormatNotSet()) { throw new IllegalStateException( "Output format not specified." ); } } OutputStreamImageSink destination = new OutputStreamImageSink(os); Thumbnailator.createThumbnail( new SourceSinkThumbnailTask(makeParam(), source, destination) ); } /** * Creates the thumbnails and writes them to {@link OutputStream}s * provided by the {@link Iterable}. * * @param iterable An {@link Iterable} which returns an * {@link Iterator} which returns the * output stream which should be assigned to * each thumbnail. * @throws IOException If a problem occurs while reading the * original images or writing the thumbnails. * @throws IllegalStateException If the output format has not * been specified through the * {@link #outputFormat(String)} * method. */ public void toOutputStreams(Iterable iterable) throws IOException { checkReadiness(); if (iterable == null) { throw new NullPointerException("OutputStream iterable is null."); } Iterator osIter = iterable.iterator(); for (ImageSource source : sources) { /* * if the image is from a BufferedImage, then we require that the * output format be set. (or else, we can't tell what format to * output as!) */ if (source instanceof BufferedImageSource) { if (isOutputFormatNotSet()) { throw new IllegalStateException( "Output format not specified." ); } } if (!osIter.hasNext()) { throw new IndexOutOfBoundsException( "Not enough file names provided by iterator." ); } OutputStreamImageSink destination = new OutputStreamImageSink(osIter.next()); Thumbnailator.createThumbnail( new SourceSinkThumbnailTask(makeParam(), source, destination) ); } } } }