上篇文章我們已經學習了 GraphicsMagick 中的許多函數,也說過 GraphicsMagick 是 ImageMagick 的一個分支,所以他們很多的函數都是一樣的使用方式和效果,相似的內容我們也就不再多說了,感興趣的朋友可以直接查閱官方文檔。
這篇文章我們要學習的是一個具體的案例,也是我在實際業務開發中所接觸過的一個案例。具體的效果就是對於微信小游戲和小程序來說,不能直接地使用動態 Gif 圖片,一張 Gif 圖片在小游戲或小程序中是不會動的。所以在我們公司的游戲開發中,需要一張將整個 Gif 動圖的每一幀拆出來的圖片拼成一張精靈圖交給前端,由他們來使用 JS+CSS 的能力動態地循環我們拆幀后的圖片,從而形成動圖的效果。
業務需求就是這么個情況,當然,最后的解決方案也正是使用了 ImageMagick 來實現的。話不多說,我們直接先看代碼。
GIF 圖拆幀
原始的圖片是這樣的一張動圖:
$imgPath = '../img/4.gif';
$imagick = new \Imagick($imgPath);
$imagick = $imagick->coalesceImages();
$imageCount = $imagick->count();
echo 'image count:', $imageCount, PHP_EOL; // image count:51
$imgAttrs = [
'width' => $imagick->getImageWidth(),
'height' => $imagick->getImageHeight(),
'frame_count' => $imageCount,
];
$column = 5;
if ($imageCount < $column) {
$column = $imageCount;
}
$row = ceil($imageCount / $column);
$spImgWidth = $imgAttrs['width'] * $column;
$spImgHeight = $imgAttrs['height'] * $row;
// 創建圖片
$spImg = new \Imagick();
$spImg->setSize($spImgWidth, $spImgHeight);
$spImg->newImage($spImgWidth, $spImgHeight, new \ImagickPixel('#ffffff00'));
$spImg->setImageFormat('png');
$i = 0;
$h = 0;
$cursor = 0;
do {
if ($i == $column) {
$i = 0;
$h++;
}
if($cursor == 0){ // 保存第一幀圖片
$imagick->writeImage($imgPath . '.first.png');
}
// 保存全部的圖片幀到一張 png 圖片中
$spImg->compositeImage($imagick, \Imagick::COMPOSITE_DEFAULT, $i * $imgAttrs['width'], $h * $imgAttrs['height']);
$i++;
$cursor++;
} while ($imagick->nextImage());
$spImg->writeImage($imgPath . '.png');
實例化 Imagick 對象就不用多說了,我們首先調用的是 coalesceImages() 這個方法。它的作用是返回合成后的 Imagick 對象。通過這個方法,我們就獲得了整個 GIF 圖里面的全部每一幀圖片的信息。這時,使用 count() 方法,就可以獲得圖片中的所有圖片幀的個數。比如我們測試的這張圖片就有 51 幀。
然后計算精靈圖的行和列以及相應需要的寬高,比如我們以 5 列為基准,也就是一行放五張拆幀出來的圖片,這樣一共需要 11 行才放得下最后生成的精靈圖。同理,寬高也是以拆出來的圖片寬高乘以相應的列和行數。
接着,根據計算出來的寬高生成一張新的圖片,作為精靈圖的背景圖,使用 newImage() 函數設置圖片寬高及背景透明。使用 setImageFormat() 方法設置圖片的格式為 PNG 格式,使用 PNG 主要是為了透明,其實按我們這樣緊密排列的圖片來說,不用透明也可以,但某些應用中比如網站前端需要的精靈圖可能不同的圖片之前是需要一定間隔的,所以一般會使用透明的底圖。
然后就是一個循環,也就是循環那 51 張拆幀出來的圖片,使用 nextImage() 不斷地獲取原始 GIF 圖中的下一幀圖片,並將他們組合保存在上面新建的背景圖片中,每一幀的圖片位置也是通過單幀圖片的寬高與行列情況計算出來的。在這段代碼中,我們還保存了第一幀的圖片,當然,這也是業務需要,你可以隨時保存任何一張每幀的圖片。
最后,使用 writeImage() 保存圖片。輸出的圖片就是下面的這個樣子:
組合成動態 GIF 圖
以上的業務功能是我在開發中實際使用過的功能,當然,除了可以對 GIF 圖進行拆幀之外,我們也可以將多張圖片組合成一個動態的 GIF 圖。
$gifImagek = new Imagick();
$gifImagek->setFormat('GIF');
for($i=1;$i<=5;$i++){
$img = new Imagick('../img/3'.$i.'.jpeg');
$img->setImageDelay(100);
$gifImagek->addImage($img);
}
$gifImagek->writeImages("../img/5.gif", true);
$gifImagek->writeImages("../img/52.gif", false);
這段代碼就比較簡單了,依然還是創建一個圖片,並且指定格式為 GIF 圖片。然后循環添加圖片,這里我們使用的是上篇文章中 GraphicsMagick 中操作過的那些圖片。setImageDelay() 用於設置圖片顯示間隔,這里我們設置的是 100 毫秒,然后再使用 addImage() 將圖片添加到我們新創建的 GIF 圖畫布中。
最后保存圖片的時候,需要使用 writeImages() 進行保存,它的作用是保存這種連續的多張圖片。它的第二個參數是指定是否將圖片保存到一張圖片中,如果是 false 的話,就類似於拆幀的效果,不過會將圖片一張一張的分開保存,比如 52-1.gif 、 52-2.gif 這樣。
最后生成的動圖就是這樣的:
總結
今天的內容有意思吧,不是那些爛大街的縮放、加水印、驗證碼之類的功能,而是比較好玩的對於 GIF 圖的操作。說實話,在業務開發中類似的業務場景還是很多的,就像自動生成精靈圖這種功能就完全可以使用 ImageMagick 來實現,而且都是 ImageMagick 擴展中自帶的函數就可以搞定了,非常方便。
測試代碼:
https://github.com/zhangyue0503/dev-blog/blob/master/php/202012/source/5.使用ImageMagick操作gif圖.php
參考文檔:
https://www.php.net/manual/zh/book.imagick.php
關注公眾號:【硬核項目經理】獲取最新文章
添加微信/QQ好友:【xiaoyuezigonggong/149844827】免費得PHP、項目管理學習資料
知乎、公眾號、抖音、頭條搜索【硬核項目經理】
B站ID:482780532