package org.nutz.img; import java.awt.Color; import java.awt.Graphics; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Iterator; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.metadata.IIOMetadata; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import org.nutz.lang.Files; import org.nutz.lang.Lang; import org.nutz.repo.gif.AnimatedGifEncoder; /** * 对图像操作的简化 API * * @author zozoh(zozohtnt@gmail.com) */ public class Images { /** * 对一个图像进行旋转 * * @param srcIm * 原图像文件 * @param taIm * 转换后的图像文件 * @param degree * 旋转角度, 90 为顺时针九十度, -90 为逆时针九十度 * @return 旋转后得图像对象 */ public static BufferedImage rotate(Object srcIm, File taIm, int degree) { BufferedImage im = Images.read(srcIm); BufferedImage im2 = Images.rotate(im, degree); Images.write(im2, taIm); return im2; } /** * 对一个图像进行旋转 * * @param srcPath * 原图像文件路径 * @param taPath * 转换后的图像文件路径 * @param degree * 旋转角度, 90 为顺时针九十度, -90 为逆时针九十度 * @return 旋转后得图像对象 */ public static BufferedImage rotate(String srcPath, String taPath, int degree) throws IOException { File srcIm = Files.findFile(srcPath); if (null == srcIm) throw Lang.makeThrow("Fail to find image file '%s'!", srcPath); File taIm = Files.createFileIfNoExists(taPath); return rotate(srcIm, taIm, degree); } /** * 对一个图像进行旋转 * * @param image * 图像 * @param degree * 旋转角度, 90 为顺时针九十度, -90 为逆时针九十度 * @return 旋转后得图像对象 */ public static BufferedImage rotate(BufferedImage image, int degree) { int iw = image.getWidth();// 原始图象的宽度 int ih = image.getHeight();// 原始图象的高度 int w = 0; int h = 0; int x = 0; int y = 0; degree = degree % 360; if (degree < 0) degree = 360 + degree;// 将角度转换到0-360度之间 double ang = degree * 0.0174532925;// 将角度转为弧度 /** * 确定旋转后的图象的高度和宽度 */ if (degree == 180 || degree == 0 || degree == 360) { w = iw; h = ih; } else if (degree == 90 || degree == 270) { w = ih; h = iw; } else { int d = iw + ih; w = (int) (d * Math.abs(Math.cos(ang))); h = (int) (d * Math.abs(Math.sin(ang))); } x = (w / 2) - (iw / 2);// 确定原点坐标 y = (h / 2) - (ih / 2); BufferedImage rotatedImage = new BufferedImage(w, h, image.getType()); Graphics gs = rotatedImage.getGraphics(); gs.fillRect(0, 0, w, h);// 以给定颜色绘制旋转后图片的背景 AffineTransform at = new AffineTransform(); at.rotate(ang, w / 2, h / 2);// 旋转图象 at.translate(x, y); AffineTransformOp op = new AffineTransformOp(at, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); op.filter(image, rotatedImage); image = rotatedImage; return image; } /** * 自动等比缩放一个图片,并将其保存成目标图像文件<br /> * 多余的部分,用给定背景颜色补上<br /> * 如果参数中的宽度或高度为<b>-1</b>的话,着按照指定的高度或宽度对原图等比例缩放图片,不添加背景颜色 * <p> * 图片格式支持 png | gif | jpg | bmp | wbmp * * @param srcIm * 源图像文件对象 * @param taIm * 目标图像文件对象 * @param w * 宽度 * @param h * 高度 * @param bgColor * 背景颜色 * * @return 被转换前的图像对象 * * @throws IOException * 当读写文件失败时抛出 */ public static BufferedImage zoomScale(Object srcIm, File taIm, int w, int h, Color bgColor) throws IOException { BufferedImage old = read(srcIm); BufferedImage im = Images.zoomScale(old, w, h, bgColor); write(im, taIm); return old; } /** * 自动等比缩放一个图片,并将其保存成目标图像文件<br /> * 多余的部分,用给定背景颜色补上<br /> * 如果参数中的宽度或高度为<b>-1</b>的话,着按照指定的高度或宽度对原图等比例缩放图片,不添加背景颜色 * <p> * 图片格式支持 png | gif | jpg | bmp | wbmp * * @param srcPath * 源图像路径 * @param taPath * 目标图像路径,如果不存在,则创建 * @param w * 宽度 * @param h * 高度 * @param bgColor * 背景颜色 * * @return 被转换前的图像对象 * * @throws IOException * 当读写文件失败时抛出 */ public static BufferedImage zoomScale(String srcPath, String taPath, int w, int h, Color bgColor) throws IOException { File srcIm = Files.findFile(srcPath); if (null == srcIm) throw Lang.makeThrow("Fail to find image file '%s'!", srcPath); File taIm = Files.createFileIfNoExists(taPath); return zoomScale(srcIm, taIm, w, h, bgColor); } /** * 自动等比缩放一个图片,多余的部分,用给定背景颜色补上<br /> * 如果参数中的宽度或高度为<b>-1</b>的话,着按照指定的高度或宽度对原图等比例缩放图片,不添加背景颜色 * * @param im * 图像对象 * @param w * 宽度 * @param h * 高度 * @param bgColor * 背景颜色 * * @return 被转换后的图像 */ public static BufferedImage zoomScale(BufferedImage im, int w, int h, Color bgColor) { if (w == -1 || h == -1) { return zoomScale(im, w, h); } // 检查背景颜色 bgColor = null == bgColor ? Color.black : bgColor; // 获得尺寸 int oW = im.getWidth(); int oH = im.getHeight(); float oR = (float) oW / (float) oH; float nR = (float) w / (float) h; int nW, nH, x, y; /* * 缩放 */ // 原图太宽,计算当原图与画布同高时,原图的等比宽度 if (oR > nR) { nW = w; nH = (int) (((float) w) / oR); x = 0; y = (h - nH) / 2; } // 原图太长 else if (oR < nR) { nH = h; nW = (int) (((float) h) * oR); x = (w - nW) / 2; y = 0; } // 比例相同 else { nW = w; nH = h; x = 0; y = 0; } // 创建图像 BufferedImage re = new BufferedImage(w, h, ColorSpace.TYPE_RGB); // 得到一个绘制接口 Graphics gc = re.getGraphics(); gc.setColor(bgColor); gc.fillRect(0, 0, w, h); gc.drawImage(im, x, y, nW, nH, bgColor, null); // 返回 return re; } /** * 自动等比缩放一个图片 * * @param im * 图像对象 * @param w * 宽度 * @param h * 高度 * * @return 被转换后的图像 */ public static BufferedImage zoomScale(BufferedImage im, int w, int h) { // 获得尺寸 int oW = im.getWidth(); int oH = im.getHeight(); int nW = w, nH = h; /* * 缩放 */ // 未指定图像高度,根据原图尺寸计算出高度 if (h == -1) { nH = (int) ((float) w / oW * oH); } // 未指定图像宽度,根据原图尺寸计算出宽度 else if (w == -1) { nW = (int) ((float) h / oH * oW); } // 创建图像 BufferedImage re = new BufferedImage(nW, nH, ColorSpace.TYPE_RGB); re.getGraphics().drawImage(im, 0, 0, nW, nH, null); // 返回 return re; } /** * 自动缩放剪切一个图片,令其符合给定的尺寸,并将其保存成目标图像文件 * <p> * 图片格式支持 png | gif | jpg | bmp | wbmp * * @param srcIm * 源图像文件对象 * @param taIm * 目标图像文件对象 * @param w * 宽度 * @param h * 高度 * @return 被转换前的图像对象 * * @throws IOException * 当读写文件失败时抛出 */ public static BufferedImage clipScale(Object srcIm, File taIm, int w, int h) throws IOException { BufferedImage old = read(srcIm); BufferedImage im = Images.clipScale(old, w, h); write(im, taIm); return old; } /** * 自动缩放剪切一个图片,令其符合给定的尺寸,并将其保存到目标图像路径 * <p> * 图片格式支持 png | gif | jpg | bmp | wbmp * * @param srcPath * 源图像路径 * @param taPath * 目标图像路径,如果不存在,则创建 * @param w * 宽度 * @param h * 高度 * * @return 被转换前的图像对象 * * @throws IOException * 当读写文件失败时抛出 */ public static BufferedImage clipScale(String srcPath, String taPath, int w, int h) throws IOException { File srcIm = Files.findFile(srcPath); if (null == srcIm) throw Lang.makeThrow("Fail to find image file '%s'!", srcPath); File taIm = Files.createFileIfNoExists(taPath); return clipScale(srcIm, taIm, w, h); } /** * 根据给定的起始坐标点与结束坐标点来剪切一个图片,令其符合给定的尺寸,并将其保存成目标图像文件 * <p> * 图片格式支持 png | gif | jpg | bmp | wbmp * * @param srcIm * 源图像文件对象 * @param taIm * 目标图像文件对象 * @param startPoint * 起始坐标点,其值[x, y]为相对原图片左上角的坐标 * @param endPoint * 结束坐标点,其值[x, y]为相对原图片左上角的坐标 * @return 被转换前的图像对象 * * @throws IOException * 当读写文件失败时抛出 */ public static BufferedImage clipScale(Object srcIm, File taIm, int[] startPoint, int[] endPoint) throws IOException { // 计算给定坐标后的图片的尺寸 int width = endPoint[0] - startPoint[0]; int height = endPoint[1] - startPoint[1]; BufferedImage old = read(srcIm); BufferedImage im = Images.clipScale(old.getSubimage(startPoint[0], startPoint[1], width, height), width, height); write(im, taIm); return old; } /** * 根据给定的起始坐标点与结束坐标点来剪切一个图片,令其符合给定的尺寸,并将其保存成目标图像文件 * <p> * 图片格式支持 png | gif | jpg | bmp | wbmp * * @param srcIm * 源图像文件对象 * @param taIm * 目标图像文件对象 * @param startPoint * 起始坐标点,其值[x, y]为相对原图片左上角的坐标 * @param endPoint * 结束坐标点,其值[x, y]为相对原图片左上角的坐标 * @return 被转换前的图像对象 * * @throws IOException * 当读写文件失败时抛出 */ public static BufferedImage clipScale(String srcPath, String taPath, int[] startPoint, int[] endPoint) throws IOException { File srcIm = Files.findFile(srcPath); if (null == srcIm) throw Lang.makeThrow("Fail to find image file '%s'!", srcPath); File taIm = Files.createFileIfNoExists(taPath); return clipScale(srcIm, taIm, startPoint, endPoint); } /** * 自动缩放剪切一个图片,令其符合给定的尺寸 * <p> * 如果图片太大,则将其缩小,如果图片太小,则将其放大,多余的部分被裁减 * * @param im * 图像对象 * @param w * 宽度 * @param h * 高度 * @return 被转换后的图像 */ public static BufferedImage clipScale(BufferedImage im, int w, int h) { // 获得尺寸 int oW = im.getWidth(); int oH = im.getHeight(); float oR = (float) oW / (float) oH; float nR = (float) w / (float) h; int nW, nH, x, y; /* * 裁减 */ // 原图太宽,计算当原图与画布同高时,原图的等比宽度 if (oR > nR) { nW = (h * oW) / oH; nH = h; x = (w - nW) / 2; y = 0; } // 原图太长 else if (oR < nR) { nW = w; nH = (w * oH) / oW; x = 0; y = (h - nH) / 2; } // 比例相同 else { nW = w; nH = h; x = 0; y = 0; } // 创建图像 BufferedImage re = new BufferedImage(w, h, ColorSpace.TYPE_RGB); re.getGraphics().drawImage(im, x, y, nW, nH, Color.black, null); // 返回 return re; } /** * 将一个图片文件读入内存 * * @param img * 图片文件 * @return 图片对象 */ public static BufferedImage read(Object img) { try { if (img instanceof CharSequence) { return ImageIO.read(Files.checkFile(img.toString())); } if (img instanceof File) return ImageIO.read((File) img); if (img instanceof URL) img = ((URL) img).openStream(); if (img instanceof InputStream) { File tmp = File.createTempFile("nutz_img", ".jpg"); Files.write(tmp, (InputStream) img); try { return read(tmp); } finally { tmp.delete(); } } throw Lang.makeThrow("Unkown img info!! --> " + img); } catch (IOException e) { try { InputStream in = null; if (img instanceof File) in = new FileInputStream((File) img); else if (img instanceof URL) in = ((URL) img).openStream(); else if (img instanceof InputStream) in = (InputStream) img; if (in != null) return readJpeg(in); } catch (IOException e2) { e2.fillInStackTrace(); } return null; // throw Lang.wrapThrow(e); } } /** * 将内存中一个图片写入目标文件 * * @param im * 图片对象 * @param targetFile * 目标文件,根据其后缀,来决定写入何种图片格式 */ public static void write(RenderedImage im, File targetFile) { try { ImageIO.write(im, Files.getSuffixName(targetFile), targetFile); } catch (IOException e) { throw Lang.wrapThrow(e); } } /** * 写入一个 JPG 图像 * * @param im * 图像对象 * @param targetJpg * 目标输出 JPG 图像文件 * @param quality * 质量 0.1f ~ 1.0f */ public static void writeJpeg(RenderedImage im, Object targetJpg, float quality) { try { ImageWriter writer = ImageIO.getImageWritersBySuffix("jpg").next(); ImageWriteParam param = writer.getDefaultWriteParam(); param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); param.setCompressionQuality(quality); ImageOutputStream os = ImageIO.createImageOutputStream(targetJpg); writer.setOutput(os); writer.write((IIOMetadata) null, new IIOImage(im, null, null), param); os.flush(); os.close(); } catch (IOException e) { throw Lang.wrapThrow(e); } } /** * 尝试读取JPEG文件的高级方法,可读取32位的jpeg文件 * <p/> * 来自: * http://stackoverflow.com/questions/2408613/problem-reading-jpeg-image- * using-imageio-readfile-file * * */ private static BufferedImage readJpeg(InputStream in) throws IOException { Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG"); ImageReader reader = null; while (readers.hasNext()) { reader = (ImageReader) readers.next(); if (reader.canReadRaster()) { break; } } ImageInputStream input = ImageIO.createImageInputStream(in); reader.setInput(input); // Read the image raster Raster raster = reader.readRaster(0, null); BufferedImage image = createJPEG4(raster); ByteArrayOutputStream out = new ByteArrayOutputStream(); writeJpeg(image, out, 1); out.flush(); BufferedImage img = read(new ByteArrayInputStream(out.toByteArray())); return img; } /** * Java's ImageIO can't process 4-component images and Java2D can't apply * AffineTransformOp either, so convert raster data to RGB. Technique due to * MArk Stephens. Free for any use. */ private static BufferedImage createJPEG4(Raster raster) { int w = raster.getWidth(); int h = raster.getHeight(); byte[] rgb = new byte[w * h * 3]; float[] Y = raster.getSamples(0, 0, w, h, 0, (float[]) null); float[] Cb = raster.getSamples(0, 0, w, h, 1, (float[]) null); float[] Cr = raster.getSamples(0, 0, w, h, 2, (float[]) null); float[] K = raster.getSamples(0, 0, w, h, 3, (float[]) null); for (int i = 0, imax = Y.length, base = 0; i < imax; i++, base += 3) { float k = 220 - K[i], y = 255 - Y[i], cb = 255 - Cb[i], cr = 255 - Cr[i]; double val = y + 1.402 * (cr - 128) - k; val = (val - 128) * .65f + 128; rgb[base] = val < 0.0 ? (byte) 0 : val > 255.0 ? (byte) 0xff : (byte) (val + 0.5); val = y - 0.34414 * (cb - 128) - 0.71414 * (cr - 128) - k; val = (val - 128) * .65f + 128; rgb[base + 1] = val < 0.0 ? (byte) 0 : val > 255.0 ? (byte) 0xff : (byte) (val + 0.5); val = y + 1.772 * (cb - 128) - k; val = (val - 128) * .65f + 128; rgb[base + 2] = val < 0.0 ? (byte) 0 : val > 255.0 ? (byte) 0xff : (byte) (val + 0.5); } raster = Raster.createInterleavedRaster(new DataBufferByte(rgb, rgb.length), w, h, w * 3, 3, new int[]{0, 1, 2}, null); ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); ColorModel cm = new ComponentColorModel(cs, false, true, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); return new BufferedImage(cm, (WritableRaster) raster, true, null); } /** * 根据一堆图片生成一个gif图片 * * @param targetFile * 目标输出文件 * @param frameFiles * 组成动画的文件 * @param delay * 帧间隔 * @return 是否生成成功 */ public static boolean writeGif(String targetFile, String[] frameFiles, int delay) { try { AnimatedGifEncoder e = new AnimatedGifEncoder(); e.setRepeat(0); e.start(targetFile); for (String f : frameFiles) { e.setDelay(delay); e.addFrame(ImageIO.read(new File(f))); } return e.finish(); } catch (Exception e) { return false; } } }