/* * Copyright 1999-2101 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.simpleimage.util; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Transparency; import java.awt.image.BufferedImage; import java.awt.image.IndexColorModel; import java.awt.image.renderable.ParameterBlock; import javax.media.jai.BorderExtender; import javax.media.jai.Interpolation; import javax.media.jai.JAI; import javax.media.jai.PlanarImage; import javax.media.jai.RenderedImageAdapter; import org.w3c.dom.Node; import com.alibaba.simpleimage.ImageWrapper; import com.alibaba.simpleimage.SimpleImageException; import com.alibaba.simpleimage.jai.scale.LanczosScaleOp; import com.alibaba.simpleimage.render.ScaleParameter; import com.sun.media.jai.opimage.SubsampleAverageCRIF; public class ImageScaleHelper { private static ImageLog log = ImageLog.getLog(ImageScaleHelper.class); public static ImageWrapper scaleGIF(ImageWrapper imgWrapper, ScaleParameter zoom) throws SimpleImageException { Node streamMetadata = imgWrapper.getStreamMetadata(); int width = 0, height = 0; Node screenDescNode = NodeUtils.getChild(streamMetadata, "LogicalScreenDescriptor"); if (screenDescNode != null) { width = NodeUtils.getIntAttr(screenDescNode, "logicalScreenWidth"); height = NodeUtils.getIntAttr(screenDescNode, "logicalScreenHeight"); } if (width <= 0 || height <= 0) { width = imgWrapper.getAsBufferedImage().getWidth(); height = imgWrapper.getAsBufferedImage().getHeight(); } // do not need scale the image if (zoom.getMaxWidth() >= width && zoom.getMaxHeight() >= height) { return imgWrapper; } double scale = computeDoubleScale(width, height, zoom); int newWidth = (int) (width * scale); int newHeight = (int) (height * scale); NodeUtils.setAttrValue(screenDescNode, "logicalScreenWidth", newWidth); NodeUtils.setAttrValue(screenDescNode, "logicalScreenHeight", newHeight); NodeUtils.removeChild(streamMetadata, "GlobalColorTable"); for (int i = 0; i < imgWrapper.getNumOfImages(); i++) { PlanarImage img = imgWrapper.getAsPlanarImage(i); Node imgMetadata = imgWrapper.getMetadata(i); if (img.getColorModel() instanceof IndexColorModel) { throw new SimpleImageException( "Unsupported scale image with IndexColorModel, please convert to RGB color model first"); } // No more need, index color model will triger throws exception first // NodeUtils.removeChild(imgMetadata, "LocalColorTable"); Node imgDescNode = NodeUtils.getChild(imgMetadata, "ImageDescriptor"); int x = NodeUtils.getIntAttr(imgDescNode, "imageLeftPosition"); int y = NodeUtils.getIntAttr(imgDescNode, "imageTopPosition"); int newX = (int) (x * scale), newY = (int) (y * scale); newX = newX > 0 ? newX : 0; newY = newY > 0 ? newY : 0; NodeUtils.setAttrValue(imgDescNode, "imageLeftPosition", newX); NodeUtils.setAttrValue(imgDescNode, "imageTopPosition", newY); int imgWidth = NodeUtils.getIntAttr(imgDescNode, "imageWidth"); int imgHeight = NodeUtils.getIntAttr(imgDescNode, "imageHeight"); int newImgWidth = (int) (imgWidth * scale), newImgHeight = (int) (imgHeight * scale); newImgWidth = newImgWidth > 1 ? newImgWidth : 1; newImgHeight = newImgHeight > 1 ? newImgHeight : 1; NodeUtils.setAttrValue(imgDescNode, "imageWidth", newImgWidth); NodeUtils.setAttrValue(imgDescNode, "imageHeight", newImgHeight); if (zoom.getAlgorithm() == ScaleParameter.Algorithm.INTERP_BICUBIC) { img = bicubicScaleImage(img, (float) scale, Interpolation.INTERP_BICUBIC); } else if (zoom.getAlgorithm() == ScaleParameter.Algorithm.INTERP_BICUBIC_2) { img = bicubicScaleImage(img, (float) scale, Interpolation.INTERP_BICUBIC_2); } else if (zoom.getAlgorithm() == ScaleParameter.Algorithm.SUBSAMPLE_AVG) { img = subsampleavgScaleImage(img, scale); } else if (zoom.getAlgorithm() == ScaleParameter.Algorithm.LANCZOS) { img = lanczosScaleImage(img, scale); } else if (zoom.getAlgorithm() == ScaleParameter.Algorithm.AUTO) { img = autoScaleImage(img, scale); } else { throw new IllegalArgumentException("Unknow algorithm"); } imgWrapper.setImage(i, img); } return imgWrapper; } public static PlanarImage scale(PlanarImage input, ScaleParameter zoom) { int w = input.getWidth(); int h = input.getHeight(); // 如果不超过最大限制则不做任何处理 if (zoom.getMaxWidth() >= w && zoom.getMaxHeight() >= h) { return input; } if (zoom.getAlgorithm() == ScaleParameter.Algorithm.INTERP_BICUBIC) { float scale = computeFloatScale(w, h, zoom); return bicubicScaleImage(input, scale, Interpolation.INTERP_BICUBIC); } else if (zoom.getAlgorithm() == ScaleParameter.Algorithm.INTERP_BICUBIC_2) { float scale = computeFloatScale(w, h, zoom); return bicubicScaleImage(input, scale, Interpolation.INTERP_BICUBIC_2); } else if (zoom.getAlgorithm() == ScaleParameter.Algorithm.SUBSAMPLE_AVG) { double scale = computeDoubleScale(w, h, zoom); return subsampleavgScaleImage(input, scale); } else if (zoom.getAlgorithm() == ScaleParameter.Algorithm.LANCZOS) { double scale = computeDoubleScale(w, h, zoom); return lanczosScaleImage(input, scale); } else if (zoom.getAlgorithm() == ScaleParameter.Algorithm.AUTO) { double scale = computeDoubleScale(w, h, zoom); return autoScaleImage(input, scale); } else if (zoom.getAlgorithm() == ScaleParameter.Algorithm.PROGRESSIVE) { throw new UnsupportedOperationException("Deprecated method"); } else { throw new IllegalArgumentException("Unknow algorithm"); } } public static float computeFloatScale(int w, int h, ScaleParameter zoom) { int maxWidth = zoom.getMaxWidth(); int maxHeight = zoom.getMaxHeight(); float scale = 0.0f; scale = Math.min(((float) maxWidth) / w, ((float) maxHeight) / h); return scale; } public static double computeDoubleScale(int w, int h, ScaleParameter zoom) { int maxWidth = zoom.getMaxWidth(); int maxHeight = zoom.getMaxHeight(); double scale = 0.0; scale = Math.min(((double) maxWidth) / w, ((double) maxHeight) / h); return scale; } public static PlanarImage bicubicScaleImage(PlanarImage input, float scale, int alg) { RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // 必须使用该hint,否则会出现边框变黑到情况 qualityHints.put(JAI.KEY_BORDER_EXTENDER, BorderExtender.createInstance(BorderExtender.BORDER_COPY)); ParameterBlock pb = new ParameterBlock(); pb.addSource(input); pb.add(scale); pb.add(scale); pb.add(0.0F); pb.add(0.0F); pb.add(Interpolation.getInstance(alg)); return JAI.create("scale", pb, qualityHints); } public static PlanarImage subsampleavgScaleImage(PlanarImage input, double scale) { RenderingHints qualityHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); ParameterBlock pb = new ParameterBlock(); pb.addSource(input); pb.add(scale); pb.add(scale); // Because the mlib subsampleaverage has bug, use pure java version SubsampleAverageCRIF factory = new SubsampleAverageCRIF(); PlanarImage zoomOp = (PlanarImage) factory.create(pb, qualityHints); return zoomOp; } public static PlanarImage lanczosScaleImage(PlanarImage input, double scale) { LanczosScaleOp lanczosOp = new LanczosScaleOp(scale, scale); BufferedImage dest = lanczosOp.compute(input.getAsBufferedImage()); return PlanarImage.wrapRenderedImage(dest); } public static PlanarImage autoScaleImage(PlanarImage input, double scale) { if (input.getWidth() > 3000 || input.getHeight() > 3000) { return subsampleavgScaleImage(input, scale); } try { return lanczosScaleImage(input, scale); } catch (Exception e) { log.warn("LanczosScale fail : " + e.getMessage(), input); return subsampleavgScaleImage(input, scale); } } /** * 折半渐进压缩图片方法 测试后觉得效果不太理想,不鼓励使用 * * @param img * @param targetWidth * @param targetHeight * @param hint * @param progressiveBilinear * @return */ @Deprecated public static PlanarImage progressiveScaleImage(PlanarImage input, int targetWidth, int targetHeight, Object hint, boolean progressive) { BufferedImage img = input.getAsBufferedImage(); int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; BufferedImage ret = img; BufferedImage scratchImage = null; Graphics2D g2 = null; int w, h; int prevW = ret.getWidth(); int prevH = ret.getHeight(); boolean isTranslucent = img.getTransparency() != Transparency.OPAQUE; if (progressive) { // Use multi-step technique: start with original size, then // scale down in multiple passes with drawImage() // until the target size is reached w = img.getWidth(); h = img.getHeight(); } else { // Use one-step technique: scale directly from original // size to target size with a single drawImage() call w = targetWidth; h = targetHeight; } do { if (progressive && w > targetWidth) { w /= 2; if (w < targetWidth) { w = targetWidth; } } if (progressive && h > targetHeight) { h /= 2; if (h < targetHeight) { h = targetHeight; } } if (scratchImage == null || isTranslucent) { // Use a single scratch buffer for all iterations // and then copy to the final, correctly-sized image // before returning scratchImage = new BufferedImage(w, h, type); g2 = scratchImage.createGraphics(); } g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); g2.drawImage(ret, 0, 0, w, h, 0, 0, prevW, prevH, null); prevW = w; prevH = h; ret = scratchImage; } while (w != targetWidth || h != targetHeight); if (g2 != null) { g2.dispose(); } // If we used a scratch buffer that is larger than our target size, // create an image of the right size and copy the results into it if (targetWidth != ret.getWidth() || targetHeight != ret.getHeight()) { scratchImage = new BufferedImage(targetWidth, targetHeight, type); g2 = scratchImage.createGraphics(); g2.drawImage(ret, 0, 0, null); g2.dispose(); ret = scratchImage; } return new RenderedImageAdapter(ret); } }