上篇文章我们已经学习了 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

使用ImageMagick操作gif图的更多相关文章

  1. MySQL 服务器变量 数据操作DML-视图

    原文:MySQL 服务器变量 数据操作DML-视图 SQL语言的组成部分 常见分类: DDL:数据定义语言 DCL:数据控制语言,如授权 DML:数据操作语言 其它分类: 完整性定义语言: DDL的一 ...

  2. .net I/O操作 导图

    稍微总结下,System.IO提供了四种类型来实现,对单个文件和计算机目录结构的操作.Directory和File通过静态成员实现建立.删除.复制和移动操作(上图没有提及).而FileInfo和Dir ...

  3. MONGODB 与sql聚合操作对应图

    MongoDB 高级查询条件操作符 2012-04-25 15:35:19|  分类: MongoDB |  标签:mongodb使用  mongodb查询  |举报|字号 订阅 http://blo ...

  4. mongoDB与sql聚合操作对应图

    SQL Terms, Functions, and Concepts MongoDB Aggregation Operators WHERE $match GROUP BY $group HAVING ...

  5. windows环境下安装pymysql(操作带图)

    在windows环境下安装pymysql,首先要找到python的安装位置,如果在c盘,启动cmd的时候,要获取管理员权限. 具体步骤,一,管理员模式启动cmd.在箭头指定位置,搜索cmd,出现快捷方 ...

  6. SVN在idea中操作解析图

    进入的位置

  7. Spark GraphX 属性图操作

    package Spark_GraphX import org.apache.spark.graphx._ import org.apache.spark.rdd.RDD import org.apa ...

  8. Spark入门实战系列--9.Spark图计算GraphX介绍及实例

    [注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .GraphX介绍 1.1 GraphX应用背景 Spark GraphX是一个分布式图处理 ...

  9. MySql数据库-使用cmd操作数据库

    寄语: 针对一些公司对测试岗位掌握SQL的要求,本博文以此献给没有掌握数据库语句知识的功能测试人员,愿与广大测试同胞共同进步. 如果电脑上已安装配置好MySQL数据库,打开命令提示符,按照下图以此操作 ...

随机推荐

  1. 《Python Cookbook v3.0.0》Chapter1 数据结构和算法

    感谢: https://github.com/yidao620c/python3-cookbook 如有侵权,请联系我整改. 本文章节会严格按照原书(以便和原书对照,章节标题可能会略有修改),内容会有 ...

  2. 关于knn算法的总结思考

    更多的关于k近邻算法的思考 KNN(K- Nearest Neighbor)法即K最邻近法,数据挖掘分类技术中最简单的方法之一 对k近邻算法的总结: 优点部分 其可以解决分类问题,同时可以天然的解决多 ...

  3. SpringBoot开发九-生成验证码

    需求介绍-生成验证码 先生成随机字符串然后利用Kaptcha API生成验证图片 代码实现 先在pom.xml引入 <dependency> <groupId>com.gith ...

  4. 【XXE学习】XML外部实体注入

    一.XML外部实体注入介绍 1.1 XXE简介 XML外部实体注入(XML External Entity Injection)也就是人们(mian shi guan )常说的XXE啦,见名知意,就是 ...

  5. STM32—驱动六轴MPU6050输出欧拉角

    文章目录 一.MPU6050介绍 1.MPU6050与陀螺仪.加速度计的关系: 2.整体概括 3.引脚说明 4.基本配置及相关寄存器 电源管理寄存器1 陀螺仪配置寄存器 加速度计配置寄存器 FIFO使 ...

  6. 如何快速方便的生成好看的接口文档(apipost生成文档)

    一键生成文档 我们在"2分钟玩转APIPOST"一讲中,简单介绍了如何生成并分享接口文档: 点击分享文档 复制并打开文档地址就可以看到了完整的接口文档. 本节课主要是讲解一些需要注 ...

  7. vue3 自己做一个轻量级状态管理,带跟踪功能,知道是谁改的,还能定位代码。

    上一篇 https://www.cnblogs.com/jyk/p/14706005.html 介绍了一个自己做的轻量级的状态管理,好多网友说,状态最重要的是跟踪功能,不能跟踪算啥状态管理? 因为vu ...

  8. Windows系统搭建Redis集群三种模式(零坑、最新版)

    目录 主从复制 修改配置文件 启动各节点 验证 哨兵模式 修改配置文件 启动实例 验证 集群模式 修改配置文件 启动实例 验证 主从复制 新建以下三个目录,用来部署一主二从 redis 的安装在另外一 ...

  9. vue3.0入门(二)

    前言 最近在b站上学习了飞哥的vue教程 学习案例已上传,下载地址 指令 #id2{ // css部分 font-size: 24px; color: green; } v-bind:href=&qu ...

  10. ASP.NET真分页_接前篇引用AspNetPager.dll进行数据分页

    一.前端准备工作 1.之前我写到过<Asp.net中引用AspNetPager.dll进行数据分页>  这种分页方式只能在前台将数据分页,而每次点击查询时对目标数据库还是全查询,这样不仅会 ...