首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >用Java制作GIF

用Java制作GIF
EN

Code Review用户
提问于 2015-12-15 03:40:46
回答 2查看 5.8K关注 0票数 11

我编写了一个Java类来根据图像列表制作GIF动画。整个项目可以找到这里

我是GitHub的新手,所以如果你能对我的项目结构进行评论,我会非常高兴的。

代码语言:javascript
运行
复制
package shine.htetaung.giffer;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;

import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream;

/*
 * Giffer is a simple java class to make my life easier in creating gif images.
 * 
 * Usage :
 * There are two methods for creating gif images
 * To generate from files, just pass the array of filename Strings to this method
 * Giffer.generateFromFiles(String[] filenames, String output, int delay, boolean loop)
 * 
 * Or as an alternative you can use this method which accepts an array of BufferedImage
 * Giffer.generateFromBI(BufferedImage[] images, String output, int delay, boolean loop)
 * 
 * output is the name of the output file
 * delay is time between frames, accepts hundredth of a time. Yeah it's weird, blame Oracle
 * loop is the boolean for whether you want to make the image loopable.
 */

public abstract class Giffer {

    // Generate gif from an array of filenames
    // Make the gif loopable if loop is true
    // Set the delay for each frame according to the delay (ms)
    // Use the name given in String output for output file
    public static void generateFromFiles(String[] filenames, String output, int delay, boolean loop)
        throws IIOException, IOException
    {
        int length = filenames.length;
        BufferedImage[] img_list = new BufferedImage[length];

        for (int i = 0; i < length; i++)
        {
            BufferedImage img = ImageIO.read(new File(filenames[i]));
            img_list[i] = img;
        }

        generateFromBI(img_list, output, delay, loop);
    }

    // Generate gif from BufferedImage array
    // Make the gif loopable if loop is true
    // Set the delay for each frame according to the delay, 100 = 1s
    // Use the name given in String output for output file
    public static void generateFromBI(BufferedImage[] images, String output, int delay, boolean loop)
            throws IIOException, IOException
    {
        ImageWriter gifWriter = getWriter();
        ImageOutputStream ios = getImageOutputStream(output);
        IIOMetadata metadata = getMetadata(gifWriter, delay, loop);

        gifWriter.setOutput(ios);
        gifWriter.prepareWriteSequence(null);
        for (BufferedImage img: images)
        {
            IIOImage temp = new IIOImage(img, null, metadata);
            gifWriter.writeToSequence(temp, null);
        }
        gifWriter.endWriteSequence();
    }

    // Retrieve gif writer
    private static ImageWriter getWriter() throws IIOException
    {
        Iterator<ImageWriter> itr = ImageIO.getImageWritersByFormatName("gif");
        if(itr.hasNext())
            return itr.next();

        throw new IIOException("GIF writer doesn't exist on this JVM!");
    }

    // Retrieve output stream from the given file name
    private static ImageOutputStream getImageOutputStream(String output) throws IOException
    {
        File outfile = new File(output);
        return ImageIO.createImageOutputStream(outfile);
    }

    // Prepare metadata from the user input, add the delays and make it loopable
    // based on the method parameters
    private static IIOMetadata getMetadata(ImageWriter writer, int delay, boolean loop)
        throws IIOInvalidTreeException
    {
        // Get the whole metadata tree node, the name is javax_imageio_gif_image_1.0
        // Not sure why I need the ImageTypeSpecifier, but it doesn't work without it
        ImageTypeSpecifier img_type = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
        IIOMetadata metadata = writer.getDefaultImageMetadata(img_type, null);
        String native_format = metadata.getNativeMetadataFormatName();
        IIOMetadataNode node_tree = (IIOMetadataNode)metadata.getAsTree(native_format);

        // Set the delay time you can see the format specification on this page
        // https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/gif_metadata.html
        IIOMetadataNode graphics_node = getNode("GraphicControlExtension", node_tree);
        graphics_node.setAttribute("delayTime", String.valueOf(delay));
        graphics_node.setAttribute("disposalMethod", "none");
        graphics_node.setAttribute("userInputFlag", "FALSE");

        if(loop)
            makeLoopy(node_tree);

        metadata.setFromTree(native_format, node_tree);

        return metadata;
    }

    // Add an extra Application Extension node if the user wants it to be loopable
    // I am not sure about this part, got the code from StackOverflow
    // TODO: Study about this
    private static void makeLoopy(IIOMetadataNode root)
    {
        IIOMetadataNode app_extensions = getNode("ApplicationExtensions", root);
        IIOMetadataNode app_node = getNode("ApplicationExtension", app_extensions);

        app_node.setAttribute("applicationID", "NETSCAPE");
        app_node.setAttribute("authenticationCode", "2.0");
        app_node.setUserObject(new byte[]{ 0x1, (byte) (0 & 0xFF), (byte) ((0 >> 8) & 0xFF)});

        app_extensions.appendChild(app_node);
        root.appendChild(app_extensions);
    }

    // Retrieve the node with the name from the parent root node
    // Append the node if the node with the given name doesn't exist
    private static IIOMetadataNode getNode(String node_name, IIOMetadataNode root)
    {
        IIOMetadataNode node = null;

        for (int i = 0; i < root.getLength(); i++)
        {
            if(root.item(i).getNodeName().compareToIgnoreCase(node_name) == 0)
            {
                node = (IIOMetadataNode) root.item(i);
                return node;
            }
        }

        // Append the node with the given name if it doesn't exist
        node = new IIOMetadataNode(node_name);
        root.appendChild(node);

        return node;
    }

    public static void main(String[] args)
    {
        String[] img_strings = {"sample-images/cool.png", "sample-images/cry.png", "sample-images/love.png", "sample-images/oh.png"};

        try
        {
            Giffer.generateFromFiles(img_strings, "sample-images/output.gif", 40, true);
        } catch (Exception ex) 
        {
            ex.printStackTrace();
        }
    }
}
EN

回答 2

Code Review用户

回答已采纳

发布于 2015-12-15 17:29:26

我同意Peter的观点,静态工厂不是一个好的模式。对于这个特定的任务,我倾向于使用构建器模式。特别是,我非常希望在某个地方有一个BufferedImage... images varargs参数,而IMO最干净的方法就是将它作为构造函数的唯一参数。

generateFromBI打开一个输出流,但似乎没有关闭它。现在做这件事的“最佳实践”方式是尝试-语句。我还没有使用Java 8,所以我不能做的更好的事情就是指给您看文档。

// Not sure why I need the ImageTypeSpecifier, but it doesn't work without it ImageTypeSpecifier img\_type = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE\_INT\_ARGB);

诚实评论的满分,但你可能还没有意识到你在做一个危险的假设。如果图像不是关于TYPE_INT_ARGB的呢?更健壮的方法是为每个映像创建一个新的IIOMetadata,使用new ImageTypeSpecifier(img)为每个映像创建一个合适的ImageTypeSpecifier

disposalMethod是硬编码到"none"的.最好是通过枚举而不是原始字符串来实现这种可配置的,特别是因为使用"doNotDispose"可以优化图像以在不改变的区域使用透明性。

(关于公开好的类型而不是神秘的原始数据的主题,Java8提供了持续时间,这比原始的厘米秒更好)。

app\_node.setUserObject(new byte[]{ 0x1, (byte) (0 & 0xFF), (byte) ((0 >> 8) & 0xFF)});

有点奇怪。它似乎在说“这里的结构非常重要,您可能想要改变它,但是我不会为您记录它”。我要么使用神奇的new byte[] { 1, 0, 0 },要么用更具描述性的名称将参数提取为new byte[]{ foo, bar, baz }

最后,有些方法名称对我来说似乎有点误导。get意味着“这个对象已经存在并存储在某个地方”,但是其中几个getXYZ方法实际上正在创建(或获取,带有创建回退)。对于每种情况,我都倾向于使用createfindOrCreate前缀进行重命名。

票数 4
EN

Code Review用户

发布于 2015-12-15 10:11:41

我希望能找到一种旧的工厂模式,而不是假想的静态方法工厂模式.静态方法总是让我有点难过,因为您不能在测试中很好地模拟静态编程单元。

方法getWriter是必不可少的!如果getWriter无论如何都失败了,那么加载类是没有意义的。此外,课堂应尽早报告问题。见此:

代码语言:javascript
运行
复制
public abstract class Giffer {
   private static final ImageWriter defaultWriter = 
       ImageIO.getImageWritersByFormatName("gif").next();
   ...
}

这门课会尽早不及格。

与通过String设置文件不同,还有一个更好的匹配类:File

试着跟随包装名称公约

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/113998

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档