Thumbnails图片处理库代码分析

Thumbnails git
https://github.com/coobird/thumbnailator

Thumbnails API文档
https://coobird.github.io/thumbnailator/javadoc/0.4.13/

分析代码 Thumbnails.of(bufferedImage).scale(scale).asBufferedImage();

探究压缩系数scale与实际图片压缩比例之间的关系。

以下为目前最新版本0.4.19代码,2022-12-31日发布。

1.Thumbnails.of(BufferedImage)

Thumbnails.of(BufferedImage)方法返回Builder。
其作用是创建其内部类Builder,图片流相关处理都在Builder中进行。


Thumbnails图片处理库代码分析_第1张图片

image.png
Thumbnails图片处理库代码分析_第2张图片

2.Builder.scale()方法

设置压缩系数,更新配置状态
Thumbnails图片处理库代码分析_第3张图片
Build.scale(double scale)方法调用scale(width, height)将scale值赋值给scaleWidth和scaleHeight,并更新配置状态。
由此可见,压缩时长宽都会乘以压缩系数进行相应比例压缩,所以原始图片像素scalescale=目标图片像素。
Thumbnails图片处理库代码分析_第4张图片

3.asBufferedImage()方法

asBufferedImage()方法主要分两部分:
1.checkReadiness()检查配置。
2.Thumbnailator.createThumbnail()。核心功能压缩图片在第二部分。

Thumbnails图片处理库代码分析_第5张图片

1、checkReadiness()方法,检查配置是否已设置。

Thumbnails图片处理库代码分析_第6张图片

2、Thumbnailator.createThumbnail()。

这部分代码分为以下部分:
1)makeParam(),将参数组装ThumbnailParameter对象,主要是scaleWidth和scaleHeight。
2)创建SourceSinkThumbnailTask对象。
3)createThumbnail(),选择压缩器,压缩图片核心代码。

Thumbnailator.createThumbnail(
   new SourceSinkThumbnailTask(makeParam(), source, destination)
);

1)makeParam()

将参数组装ThumbnailParameter对象,主要是scaleWidth和scaleHeight。组装缩放器工厂类ResizerFactory。

/**
 * 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
        );
    }
}

prepareResizerFactory()

如果未设置ScalingMode,默认使用DefaultResizerFactory。
Thumbnails图片处理库代码分析_第7张图片

ScalingMode

压缩模式,分为3种;BILINEAR、BICUBIC、PROGRESSIVE_BILINEAR。
前2个压缩算法resize()方法完全一致,属于双线性压缩。
默认压缩算法当压缩比例小于一半时,会采用PROGRESSIVE_BILINEAR。

public enum ScalingMode {
   /**
    * A hint to use bilinear interpolation when resizing images.
    */
   BILINEAR,
   
   /**
    * A hint to use bicubic interpolation when resizing images.
    */
   BICUBIC,
   
   /**
    * A hint to use progressing bilinear interpolation when resizing images.
    * 

* For details on this technique, refer to the documentation of the * {@link ProgressiveBilinearResizer} class. */ PROGRESSIVE_BILINEAR, ; }

DefaultResizerFactory

默认压缩工厂,会根据目前图片文件大小与目标文件大小比例选择对应压缩器。

public class DefaultResizerFactory implements ResizerFactory {
    private static final DefaultResizerFactory INSTANCE = new DefaultResizerFactory();

    /**
    * This class is not intended to be instantiated via the constructor.
    */
    private DefaultResizerFactory() {}

    /**
    * Returns an instance of this class.
    * 
    * @return    An instance of this class.
    */
    public static ResizerFactory getInstance() {
        return INSTANCE;
    }

    public Resizer getResizer() {
        return Resizers.PROGRESSIVE;
    }

    public Resizer getResizer(Dimension originalSize, Dimension thumbnailSize) {
        int origWidth = originalSize.width;
        int origHeight = originalSize.height;
        int thumbWidth = thumbnailSize.width;
        int thumbHeight = thumbnailSize.height;

        if (thumbWidth < origWidth && thumbHeight < origHeight) {
            if (thumbWidth < (origWidth / 2) && thumbHeight < (origHeight / 2)) {
                // 当目标长宽压缩不到原长度一半时,使用PROGRESSIVE算法
                return Resizers.PROGRESSIVE;
            } else {
                // 当目标长宽压缩后在小于原长度,并且大于原长度一半时,使用BILINEAR算法
                return Resizers.BILINEAR;
            }
        }
        else if (thumbWidth > origWidth && thumbHeight > origHeight) {
            // 当图片放大时,使用BICUBIC算法
            return Resizers.BICUBIC;
        } else if (thumbWidth == origWidth && thumbHeight == origHeight) {
            return Resizers.NULL;
        } else {
            return getResizer();
        }
    }
}

2)创建SourceSinkThumbnailTask对象。

SourceSinkThumbnailTask主要用来传递参数配置,源文件流,目标文件流等信息。
new SourceSinkThumbnailTask(makeParam(), source, destination)

*
* A {@link ThumbnailTask} which holds an {@link ImageSource} from which the
* image is read or retrieved, and an {@link ImageSink} to which the thumbnail
* is stored or written.
* <p>
* This class will take care of handing off information from the
* {@link ImageSource} to the {@link ImageSink}. For example, the output format
* that should be used by the {@link ImageSink} will be handed off if the
* {@link ThumbnailParameter#ORIGINAL_FORMAT} parameter is set.
* 
* @author coobird
*
* @param <S> 		The source class from which the source image is retrieved
* 					or read.
* @param <D> 		The destination class to which the thumbnail is stored
* 					or written.
*/
public class SourceSinkThumbnailTask<S, D> extends ThumbnailTask<S, D> {
    /**
	 * The source from which the image is retrieved or read.
	 */
    private final ImageSource<S> source;

    /**
	 * The destination to which the thumbnail is stored or written.
	 */
    private final ImageSink<D> destination;

    /**
	 * Creates a {@link ThumbnailTask} in which an image is retrived from the
	 * specified {@link ImageSource} and written to the specified
	 * {@link ImageSink}, using the parameters provided in the specified
	 * {@link ThumbnailParameter}.
	 * 
	 * @param param				The parameters to use to create the thumbnail.
	 * @param source			The source from which the image is retrieved
	 * 							or read from.
	 * @param destination		The destination to which the thumbnail is
	 * 							stored or written to.
	 * @throws NullPointerException		If either the parameter,
	 * 									{@link ImageSource} or {@link ImageSink}
	 * 									is {@code null}.
	 */
    public SourceSinkThumbnailTask(ThumbnailParameter param, ImageSource<S> source, ImageSink<D> destination) {
        super(param);
        if (source == null) {
            throw new NullPointerException("ImageSource cannot be null.");
        }
        if (destination == null) {
            throw new NullPointerException("ImageSink cannot be null.");
        }

        source.setThumbnailParameter(param);
        this.source = source;

        destination.setThumbnailParameter(param);
        this.destination = destination;
    }

3)Thumbnailator.createThumbnail()
/**
	 * Creates a thumbnail from parameters specified in a {@link ThumbnailTask}.
	 * 
	 * @param task				A {@link ThumbnailTask} to execute.
	 * @throws IOException		Thrown when a problem occurs when creating a
	 * 							thumbnail.
	 */
	public static void createThumbnail(ThumbnailTask<?, ?> task) throws IOException {
		ThumbnailParameter param = task.getParam();
		
		// Obtain the original image.
		BufferedImage sourceImage = task.read();

		// Decide the image type of the destination image.
		int imageType = param.getType();
		/*
		 * If the imageType indicates that the image type of the original image
		 * should be used in the thumbnail, then obtain the image type of the
		 * original.
		 * 
		 * If the original type is a custom type, then the default image type
		 * will be used.
		 */
		if (param.useOriginalImageType()) {
			int imageTypeToUse = sourceImage.getType();
			
			if (imageTypeToUse == BufferedImage.TYPE_CUSTOM) {
				imageType = ThumbnailParameter.DEFAULT_IMAGE_TYPE;
			} else {
				imageType = sourceImage.getType();
			}
		}

		// Check for presence of marker indicating to swap the width and height.
		boolean isSwapDimensions = hasSwapDimensionsFilter(param.getImageFilters());

		BufferedImage destinationImage;

        // 目前通过改变scale进行图片缩放,size不改变,这里会执行else if逻辑分支
		if (param.getSize() != null) {
			// Get the dimensions of the original and thumbnail images.
			Dimension size = param.getSize();
			int destinationWidth = !isSwapDimensions ? size.width : size.height;
			int destinationHeight = !isSwapDimensions ? size.height : size.width;
			
			// Create the thumbnail.
			destinationImage =
				new FixedSizeThumbnailMaker()
					.size(destinationWidth, destinationHeight)
					.keepAspectRatio(param.isKeepAspectRatio())
					.fitWithinDimensions(param.fitWithinDimenions())
					.imageType(imageType)
					.resizerFactory(param.getResizerFactory())
					.make(sourceImage);

		} else if (!Double.isNaN(param.getWidthScalingFactor())) {
			// 设置了压缩系数会执行当前逻辑分支
            // Create the thumbnail.
			double widthScalingFactor = !isSwapDimensions ?
					param.getWidthScalingFactor() : param.getHeightScalingFactor();
			double heightScalingFactor = !isSwapDimensions ?
					param.getHeightScalingFactor() : param.getWidthScalingFactor();
        	
			destinationImage =
				new ScaledThumbnailMaker()
                    // 这里的scale方法只是传递了系数,没有其他操作
					.scale(widthScalingFactor, heightScalingFactor)
					.imageType(imageType)
                    // 从参数配置中获取缩放器工厂类
					.resizerFactory(param.getResizerFactory())
                    // 重点-调用makeThumbnail()图片缩放及输出文件流
					.make(sourceImage);

		} else {
			throw new IllegalStateException("Parameters to make thumbnail" +
					" does not have scaling factor nor thumbnail size specified.");
		}
		
		// Perform the image filters
		for (ImageFilter filter : param.getImageFilters()) {
			destinationImage = filter.apply(destinationImage);
		}
		
		// Write the thumbnail image to the destination.
		task.write(destinationImage);
		
		sourceImage.flush();
		destinationImage.flush();
	}

makeThumbnail()

通过缩放器Resizer对图片进行缩放。

/**
 * Makes a thumbnail of the specified dimensions, from the specified
 * source image.
 * 
 * @param img     The source image.
 * @param width       The target width of the thumbnail.
 * @param height   The target height of the thumbnail.
 * @return       The thumbnail image.
 * @throws IllegalStateException      If the {@code ThumbnailMaker} is
 *                                not ready to create thumbnails.
 * @throws IllegalArgumentException       If the width and/or height is less
 *                                than or equal to zero.
 */
protected BufferedImage makeThumbnail(BufferedImage img, int width, int height) {
   if (!ready.isReady()) {
      throw new IllegalStateException(ThumbnailMaker.NOT_READY_FOR_MAKE);
   }
   
   if (width <= 0) {
      throw new IllegalArgumentException(
            "Width must be greater than zero."
      );
   }
   if (height <= 0) {
      throw new IllegalArgumentException(
            "Height must be greater than zero."
      );
   }

   BufferedImage thumbnailImage =
      new BufferedImageBuilder(width, height, imageType).build();
   
   Dimension imgSize = new Dimension(img.getWidth(), img.getHeight());
   Dimension thumbnailSize = new Dimension(width, height);
   // 获取缩放器
   Resizer resizer = resizerFactory.getResizer(imgSize, thumbnailSize);
   // 对图片进行缩放
   resizer.resize(img, thumbnailImage);
   
   return thumbnailImage;
}

ProgressiveBilinearResizer

Resizer核心功能是使用Graphics2D重绘图片。

public void resize(BufferedImage srcImage, BufferedImage destImage)
			throws NullPointerException {
		super.performChecks(srcImage, destImage);
		
		int currentWidth = srcImage.getWidth();
		int currentHeight = srcImage.getHeight();
		
		final int targetWidth = destImage.getWidth();
		final int targetHeight = destImage.getHeight();
		
		// If multi-step downscaling is not required, perform one-step.
		if ((targetWidth * 2 >= currentWidth) && (targetHeight * 2 >= currentHeight)) {
			Graphics2D g = createGraphics(destImage);
			g.drawImage(srcImage, 0, 0, targetWidth, targetHeight, null);
			g.dispose();
			return;
		}
		
		// Temporary image used for in-place resizing of image.
		BufferedImage tempImage = new BufferedImageBuilder(
				currentWidth,
				currentHeight,
				destImage.getType()
		).build();
		
		Graphics2D g = createGraphics(tempImage);
		g.setComposite(AlphaComposite.Src);
		
		/*
		 * Determine the size of the first resize step should be.
		 * 1) Beginning from the target size
		 * 2) Increase each dimension by 2
		 * 3) Until reaching the original size
		 */
		int startWidth = targetWidth;
		int startHeight = targetHeight;
		
		while (startWidth < currentWidth && startHeight < currentHeight) {
			startWidth *= 2;
			startHeight *= 2;
		}
		
		currentWidth = startWidth / 2;
		currentHeight = startHeight / 2;

		// Perform first resize step.
		g.drawImage(srcImage, 0, 0, currentWidth, currentHeight, null);
		
		// Perform an in-place progressive bilinear resize.
		while (	(currentWidth >= targetWidth * 2) && (currentHeight >= targetHeight * 2) ) {
			currentWidth /= 2;
			currentHeight /= 2;
			
			if (currentWidth < targetWidth) {
				currentWidth = targetWidth;
			}
			if (currentHeight < targetHeight) {
				currentHeight = targetHeight;
			}
			
			g.drawImage(
					tempImage,
					0, 0, currentWidth, currentHeight,
					0, 0, currentWidth * 2, currentHeight * 2,
					null
			);
		}
		
		g.dispose();
		
		// Draw the resized image onto the destination image.
		Graphics2D destg = createGraphics(destImage);
		destg.drawImage(tempImage, 0, 0, targetWidth, targetHeight, 0, 0, currentWidth, currentHeight, null);
		destg.dispose();
	}

总结

1.Thumbnail通过scale系数压缩,实际是通过控制长宽乘以系数达到图片大小的变化,压缩时会抛弃一些像素,所以最终压缩不一定与系数一致(实际测试发现相差很远)。
由于长宽都会乘以压缩系数,所以倒推scale时,要用destSize/originSize然后开平方。
2.Thumbnail有3种压缩算法,分别是BILINEAR,BICUBIC,PROGRESSIVE_BILINEAR。
前2个压缩算法resize()方法完全一致,属于双线性压缩。
默认压缩算法当压缩比例小于一半时,会采用PROGRESSIVE_BILINEAR。

你可能感兴趣的:(Java,图像处理,java)