上篇文章我们已经学习了 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. Convert a Private Project on bitbucket.com to a github Public Project

    Create a public repo on github, you can add README or License files on the master branch, suppose th ...

  2. Linux中DNS分离解析

    目录 一.分离解析概述 二.DNS分离解析实验 要求 实验步骤 一.分离解析概述 分离解析:同一个dns服务器,同一个域名情况下,不同网段进行访问会解析出不同的IP地址 分离解析的域名服务器实际也是主 ...

  3. 寻找写代码感觉(一)之使用 Spring Boot 快速搭建项目

    写在前面 现在已经是八月份了,我已经荒废了半年居多,不得不说谈恋爱确实是个麻烦的事,谈好了皆大欢喜,分手了就是萎靡不振,需要很长一段时间才能缓过来. 人还是要有梦想的,至于实现只不过是一个契机,但凡不 ...

  4. Elasticsearch IK分词器

    Elasticsearch-IK分词器 一.简介 因为Elasticsearch中默认的标准分词器(analyze)对中文分词不是很友好,会将中文词语拆分成一个一个中文的汉字,所以引入中文分词器-IK ...

  5. sqli-labs lesson 38-45

    从page3也就是less 38开始进入了堆叠注入(stacked injection) stacked injection: 简单来说就是进行SQL注入时注入了多条语句.因为之前我们都是只进行过注入 ...

  6. NOIP 模拟 $22\; \rm e$

    题解 对于这个 \(abs\) 就是求大于 \(r\) 的最小值,小于 \(r\) 的最大值,建权值线段树或平衡树. 因为是 \(k\) 个点的联通块,就是求它们的 \(lca\) 到它们的链,可持久 ...

  7. 题解 Game

    传送门 一有「字典序最大」什么的的就懵了--这题我颓的std 首先可以发现全局最大得分很好统计,我们令它为 \(k\) 然后我们尝试构造方案,但发现无论怎么放都可能会有后效性 发现对于一个位置,可以放 ...

  8. @NotNull-JSR-303验证

    背景 Spring Boot 引入Hibernate Validator 机制来支持 JSR-303 验证规范 实现 请求参数model类: package com.wzq.test.model; i ...

  9. 刷题-力扣-剑指 Offer 42. 连续子数组的最大和

    剑指 Offer 42. 连续子数组的最大和 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de ...

  10. vue-cli3.x中的webpack配置,优化及多页面应用开发

    官方文档 vue-cli3以下版本中,关于webpack的一些配置都在config目录文件中,可是vue-cli3以上版本中,没有了config目录,那该怎么配置webpack呢? 3.x初始化项目后 ...