1. 程式人生 > >Java對GIF的簡單刪幀操作

Java對GIF的簡單刪幀操作

問題簡介

最近在一些網站爬了一些搞笑動態圖片,沒想到儲存好之後預覽竟然是是這樣:

這裡寫圖片描述

用圖片瀏覽器逐幀看了一下,原來每一張圖片第一幀都是類似空白的畫面,所以預覽的縮圖也是第一張畫面。

那麼如果能用程式碼讀取到GIF的每一幀,刪除後在合併為一個新GIF那問題就解決了。

於是找了下Java 操作GIF圖片的類庫,最後在GitHub上找到了這個:

方法簡介

整個工程主要就兩個java檔案,一個用於解碼的GifDecoder類,一個用於編碼的AnimatedGifEncoder,另外有兩個輔助類複製進來即可。

GifDecoder類

主要方法有:

  • public int read(String name)

    指定需要解碼的GIF圖片路徑,還有兩個過載方法,支援讀取InputStreamBufferedInputStream

  • public int getDelay(int n)

    獲取指定幀的延遲時間。

  • public int getFrameCount()

    獲取GIF圖片的幀數

  • public int getLoopCount()

    獲取GIF圖片的播放次數,0表示無限迴圈播放

  • public BufferedImage getFrame(int n)

    獲取指定幀的影象資料

AnimatedGifEncoder類

另一個是用於編碼的AnimatedGifEncoder類,方法和解碼基本相反:

  • public void setDelay(int ms)

    設定每幀之間的延遲時間,GifDecoder能獲取到任意兩幀之間的延遲,而AnimatedGifEncoder貌似沒有提供兩幀之間的延遲設定,按照同一延遲時間處理。

  • public void setRepeat(int iter)

    設定播放次數,0表示無限迴圈播放

  • public boolean addFrame(BufferedImage im)

    新增幀

  • public void setFrameRate(float fps)

    設定幀率,與delay作用類似,相對設定delay為Math.round(100f / fps)

  • public boolean finish()

    新增幀並配置好之後呼叫這個方法關閉檔案輸出流等

  • public boolean start(String file)

    設定輸出的檔案路徑,另一個過載方法以流的形式輸出

另外AnimatedGifEncoder還提供一些其他方法,包括設定透明度、背景顏色、圖片質量、尺寸等。

程式碼

知道用法之後就簡單了,先解碼將原始GIF的所有幀資料、幀延遲、播放次數讀取出來,然後再編碼回去,同時刪除第一幀即可。
這裡除了點小問題,我的環境中Eclipse突然無法讀取絕對路徑,所以使用File.separator代替斜槓。

    private String baseOutPath = "D:" + File.separator + "p" + File.separator+ "2" + File.separator;
    //存放解碼出來的所有幀
    private BufferedImage[] bi;
    //延遲時間
    private int delay;

    public static void main(String[] args) throws IOException {
        GIFHandler handler = new GIFHandler();
        dt.start();
    }

    private void start() {
        //"D:/p/1",要轉換的GIF圖片目錄
        File gifPath = new File("D:" + File.separator + "p" + File.separator+ "1");
        File[] gifs = null;
        //讀取出所有GIF圖片
        if (gifPath.isDirectory()) {
            gifs = gifPath.listFiles();
        }
        if (gifs != null && gifs.length != 0) {
            for (File f : gifs) {
                trans(f);
                System.out.println(f.getName());
            }
        }
        System.out.println("轉換完成");
    }

    /**
     *  轉換
     */
    private void trans(File transFile) {
        decode(transFile);
        encode(baseOutPath + transFile.getName());
    }

    private void encode(String outPath) {
        AnimatedGifEncoder encoder = new AnimatedGifEncoder();
        // 設定迴圈模式,0為無限迴圈 這裡沒有使用原始檔的播放次數
        encoder.setRepeat(0);
        // 設定輸出路徑
        encoder.start(outPath);
        //這裡從1開始,去掉第一幀
        for (int i = 1; i < bi.length; i++) {
            encoder.setDelay(delay);
            encoder.addFrame(bi[i]);
        }
        encoder.finish();
    }

    private void decode(File f) {
        GifDecoder decoder = new GifDecoder();
        try {
            decoder.read(new FileInputStream(f));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        // 獲取到幀數
        int frameCount = decoder.getFrameCount();
        bi = new BufferedImage[frameCount];
        // 獲取到每一幀的資料儲存到bi
        for (int i = 0; i < frameCount; i++) {
            bi[i] = decoder.getFrame(i);
        }
        // 獲取到每幀之間的延遲時間,這裡只取第一幀的
        delay = decoder.getDelay(0);
        // ImageIO.write(frame, "jpeg", out); 該方法用於輸出分解得到的單個圖片檔案
    }

使用這兩個類可以進行一些簡單的幀操作,比如新增水印、設定延遲等。另外原始碼也不復雜,可以自行定義。