Package org.nutz.img

Source Code of org.nutz.img.Images

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.BufferedOutputStream;
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.Base64;
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;
        }
    }

    /**
     * 生成该图片对应的 Base64 编码的字符串
     *
     * @param targetFile
     *            图片文件
     * @return 图片对应的 Base64 编码的字符串
     */
    public static String encodeBase64(String targetFile) {
        return encodeBase64(new File(targetFile));
    }

    /**
     * 生成该图片对应的 Base64 编码的字符串
     *
     * @param targetFile
     *            图片文件
     * @return 图片对应的 Base64 编码的字符串
     */
    public static String encodeBase64(File targetFile) {
        BufferedImage image = null;

        try {
            image = ImageIO.read(targetFile);
        }
        catch (IOException e) {
            throw Lang.wrapThrow(e);
        }

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        BufferedOutputStream bos = new BufferedOutputStream(baos);
        image.flush();
        try {
            ImageIO.write(image, Files.getSuffixName(targetFile), bos);
            bos.flush();
            bos.close();
        }
        catch (IOException e) {
            throw Lang.wrapThrow(e);
        }

        byte[] bImage = baos.toByteArray();

        return Base64.encodeToString(bImage, false);
    }
}
TOP

Related Classes of org.nutz.img.Images

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.