从命令行转换和优化图像

Avatar of Ravgeet Dhillon
Ravgeet Dhillon on

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费信用!

图像占平均网页总大小的 50%。如果图像未经优化,用户最终会下载额外的字节。如果他们下载了额外的字节,网站不仅加载时间更长,而且用户使用更多数据,这两种情况都可以通过在下载之前优化图像来解决,至少可以部分解决。

世界各地的研究人员正在努力开发 新的图像格式,这些格式即使尺寸更小,也能保持高视觉质量,与 PNG 或 JPG 等其他格式相比。尽管这些新格式仍在开发中,并且通常浏览器支持有限,但其中一种格式 WebP 正在引起广泛关注。虽然它们与栅格图像并不完全相同,但 SVG 是近年来许多人一直在使用的另一种格式,因为它们天生轻量级。

我们可以通过多种方法来创建更小、更优化的图像。在本教程中,我们将编写 bash 脚本,以不同图像格式创建和优化图像,以针对最常见的格式,包括 JPG、PNG、WebP 和 SVG。我们的目标是在提供图像之前优化图像,以便用户获得最棒的视觉体验,而不会出现任何字节膨胀。

我们目标的图像目录
我们优化的图像目录

GitHub 仓库 包含我们正在使用的所有图像,欢迎您获取它们并跟随操作。

设置

在我们开始之前,让我们先整理好所有依赖项。同样,我们正在编写 Bash 脚本,因此我们将花时间在命令行中。

以下是我们开始优化图像所需的所有依赖项的命令

sudo apt-get update
sudo apt-get install imagemagick webp jpegoptim optipng
npm install -g svgexport svgo

在我们开始使用它们之前,了解我们正在使用什么是一个好主意

好的,我们的图像位于 GitHub 仓库的 original-images 目录中。您可以在提交 3584f9b 中跟随操作。

注意:强烈建议您在继续操作之前备份您的图像。我们即将运行更改这些图像的程序,虽然我们计划保留原始图像,但一个错误的命令可能会以某种不可逆转的方式更改它们。因此,请备份您计划在实际项目中使用的任何内容,以防止您以后后悔。

整理图像

好的,我们已经从技术上设置好了。但在我们开始优化所有内容之前,我们应该先整理一下我们的文件。让我们根据它们的 MIME 类型将它们分成不同的子目录。实际上,我们可以创建一个新的 bash 来为我们做到这一点!

以下代码创建一个名为 organize-images.sh 的脚本

#!/bin/bash

input_dir="$1"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
fi

for img in $( find $input_dir -type f -iname "*" );
do
  # get the type of the image
  img_type=$(basename `file --mime-type -b $img`)

  # create a directory for the image type
  mkdir -p $img_type

  # move the image into its type directory
  rsync -a $img $img_type
done

如果您不熟悉编写脚本,这可能看起来很令人困惑,但它实际上很简单。我们向脚本提供一个输入目录,它会在其中查找图像。然后脚本进入该输入目录,查找图像文件并识别它们的 MIME 类型。最后,它在输入文件夹中为每个 MIME 类型创建子目录,并将每个图像的副本放到各自的子目录中。

让我们运行它!

bash organize-images.sh original-images

很棒。该目录现在 看起来像这样。现在我们的图像已经整理好了,我们可以继续创建每个图像的不同版本。我们将一次处理一种图像类型。

转换为 PNG

在本教程中,我们将把三种类型的图像转换为 PNG:WebP、JPEG 和 SVG。让我们从编写一个名为 webp2png.sh 的脚本开始,它基本上说明了它的作用:将 WebP 文件转换为 PNG 文件。

#!/bin/bash

# directory containing images
input_dir="$1"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
fi

# for each webp in the input directory
for img in $( find $input_dir -type f -iname "*.webp" );
do
  dwebp $img -o ${img%.*}.png
done

以下是发生的事情

  • input_dir="$1":存储脚本的命令行输入
  • if [[ -z "$input_dir" ]]; then:如果未定义输入目录,则运行后续条件
  • for img in $( find $input_dir -type f -iname "*.webp" );:循环遍历目录中每个具有 .webp 扩展名的文件。
  • dwebp $img -o ${img%.*}.png:将 WebP 图像转换为 PNG 变体。

我们开始了

bash webp2png.sh webp

我们现在有了我们的 webp 目录中的 PNG 图像。接下来,让我们使用另一个名为 jpg2png.sh 的脚本将 JPG/JPEG 文件转换为 PNG

#!/bin/bash

# directory containing images
input_dir="$1"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
fi

# for each jpg or jpeg in the input directory
for img in $( find $input_dir -type f -iname "*.jpg" -o -iname "*.jpeg" );
do
  convert $img ${img%.*}.png
done

这使用的是我们安装的 ImageMagick 包提供的 convert 命令。与上一个脚本一样,我们提供一个包含 JPEG/JPG 图像的输入目录。该脚本在该目录中查找并为每个匹配的图像创建一个 PNG 变体。如果您仔细观察,我们在 find 中添加了 -o -iname "*.jpeg"。这指的是逻辑或,它是查找所有具有 .jpg.jpeg 扩展名的图像的脚本。

以下是我们运行它的方式

bash jpg2png.sh jpeg

现在我们已经有了 来自 JPG 的 PNG 变体,我们可以对 SVG 文件做同样的事情

#!/bin/bash

# directory containing images
input_dir="$1"

# png image width
width="$2"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$width" ]]; then
  echo "Please specify image width."
  exit 1
fi

# for each svg in the input directory
for img in $( find $input_dir -type f -iname "*.svg" );
do
  svgexport $img ${img%.*}.png $width:
done

此脚本有一个新功能。由于 SVG 是一种可缩放格式,我们可以指定 width 指令来放大或缩小 SVG。我们使用之前安装的 svgexport 包将每个 SVG 文件转换为 PNG

bash svg2png.sh svg+xml

提交 76ff80a 显示了仓库中的结果。

我们在这里做了很多很棒的工作,通过 创建大量基于其他图像格式的 PNG 文件。在我们开始真正优化它们之前,我们还需要对其他图像格式做同样的事情。

转换为 JPG

在创建 PNG 图像的基础上,我们将把 WebP、JPEG 和 SVG 转换为 JPG。让我们从编写一个名为 png2jpg.sh 的脚本开始,该脚本将 PNG 转换为 SVG

#!/bin/bash

# directory containing images
input_dir="$1"

# jpg image quality
quality="$2"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$quality" ]]; then
  echo "Please specify image quality."
  exit 1
fi

# for each png in the input directory
for img in $( find $input_dir -type f -iname "*.png" );
do
  convert $img -quality $quality% ${img%.*}.jpg
done

您现在可能已经注意到这些脚本中的模式。但是,这个脚本引入了一个新的功能,我们可以设置 -quality 指令来将 PNG 图像转换为 JPG 图像。其余部分相同。

以下是我们运行它的方式

bash png2jpg.sh png 90

哇。我们现在有了 我们 png 目录中的 JPG 图像。让我们用 webp2jpg.sh 脚本对 WebP 做同样的事情

#!/bin/bash

# directory containing images
input_dir="$1"

# jpg image quality
quality="$2"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$quality" ]]; then
  echo "Please specify image quality."
  exit 1
fi

# for each webp in the input directory
for img in $( find $input_dir -type f -iname "*.webp" );
do
  # convert to png first
  dwebp $img -o ${img%.*}.png

  # then convert png to jpg
  convert ${img%.*}.png -quality $quality% ${img%.*}.jpg
done

同样,这与我们用于将 WebP 转换为 PNG 的脚本相同。但是,这里有一个转折。我们无法直接将 WebP 格式转换为 JPG 格式。因此,我们需要在这里稍微发挥创意,使用 dwebp 将 WebP 转换为 PNG,然后使用 convert 将 PNG 转换为 JPG。这就是为什么在 for 循环中,我们有两个不同的步骤。

现在,让我们运行它

bash webp2jpg.sh jpeg 90

瞧!我们已经为 WebP 图像创建了 JPG 变体。现在让我们处理 SVG 到 JPG

#!/bin/bash

# directory containing images
input_dir="$1"

# jpg image width
width="$2"

# jpg image quality
quality="$3"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$width" ]]; then
  echo "Please specify image width."
  exit 1
elif [[ -z "$quality" ]]; then
  echo "Please specify image quality."
  exit 1
fi

# for each svg in the input directory
for img in $( find $input_dir -type f -iname "*.svg" );
do
    svgexport $img ${img%.*}.jpg $width: $quality%
done

您可能认为您之前见过此脚本。您确实见过!我们使用相同的脚本创建了来自 SVG 的 PNG 图像。此脚本的唯一添加是,我们可以指定 JPG 图像的 quality 指令。

bash svg2jpg.sh svg+xml 512 90

我们刚刚完成的所有操作都包含在仓库中的提交 884c6cf 中。

转换为 WebP

WebP 是一种专为现代浏览器设计的图像格式。在撰写本文时,它拥有 约 90% 的全球浏览器支持,包括 Safari 的部分支持。WebP 的最大优势是它比其他图像格式的尺寸要小得多,而不会牺牲任何视觉质量。这使其成为提供给用户的良好格式。

但闲话少说。让我们编写一个 png2webp.sh,它将——您猜对了——将 PNG 图像创建为 WebP 图像

#!/bin/bash

# directory containing images
input_dir="$1"

# webp image quality
quality="$2"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$quality" ]]; then
  echo "Please specify image quality."
  exit 1
fi

# for each png in the input directory
for img in $( find $input_dir -type f -iname "*.png" );
do
  cwebp $img -q $quality -o ${img%.*}.webp
done

这只是我们用于将 WebP 图像创建为 PNG 图像的脚本的反向操作。我们不是使用 dwebp,而是使用 cwebp

bash png2webp.sh png 90

我们有 我们的 WebP 图像。现在让我们转换 JPG 图像。棘手的是,没有办法直接将 JPG 文件转换为 WebP。因此,我们将首先将 JPG 转换为 PNG,然后在 jpg2webp.sh 脚本中将中间 PNG 转换为 WebP

#!/bin/bash

# directory containing images
input_dir="$1"

# webp image quality
quality="$2"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$quality" ]]; then
  echo "Please specify image quality."
  exit 1
fi

# for each webp in the input directory
for img in $( find $input_dir -type f -iname "*.jpg" -o -iname "*.jpeg" );
do
  # convert to png first
  convert $img ${img%.*}.png

  # then convert png to webp
  cwebp ${img%.*}.png -q $quality -o ${img%.*}.webp
done

现在我们可以这样使用它来获取我们的 JPG 文件的 WebP 变体

bash jpg2webp.sh jpeg 90

提交 6625f26 显示了结果。

将所有内容合并到一个目录中

现在我们已经完成了转换工作,离优化我们的工作又近了一步。但首先,我们将把所有图像放回一个目录中,以便用更少的命令轻松地对其进行优化。

以下代码创建一个名为 combine-images.sh 的新 bash 脚本

#!/bin/bash

input_dirs="$1"
output_dir="$2"

if [[ -z "$input_dirs" ]]; then
  echo "Please specify an input directories."
  exit 1
elif [[ -z "$output_dir" ]]; then
  echo "Please specify an output directory."
  exit 1
fi

# create a directory to store the generated images
mkdir -p $output_dir

# split input directories comma separated string into an array
input_dirs=(${input_dirs//,/ })

# for each directory in input directory
for dir in "${input_dirs[@]}"
do
  # copy images from this directory to generated images directory
  rsync -a $dir/* $output_dir/
done

第一个参数是一个以逗号分隔的输入目录列表,这些目录将把图像传输到目标合并目录。第二个参数定义了合并目录。

bash combine-images.sh jpeg,svg+xml,webp,png generated-images

最终输出可以在 仓库中看到

优化 SVG

让我们从优化 SVG 图像开始。将以下代码添加到 optimize-svg.sh

#!/bin/bash

# directory containing images
input_dir="$1"

if [[ -z "$input_dir" ]]; then
echo "Please specify an input directory."
exit 1
fi

# for each svg in the input directory
for img in $( find $input_dir -type f -iname "*.svg" );
do
  svgo $img -o ${img%.*}-optimized.svg
done

我们在这里使用 SVGO 包。它有很多我们可以使用的选项,但为了简单起见,我们只坚持使用优化 SVG 文件的默认行为

bash optimize-svg.sh generated-images
这使我们每个图像节省了 4KB。假设我们提供 100 个 SVG 图标,我们刚刚节省了 400KB!

结果可以在提交 75045c3 的仓库中看到。

优化 PNG

让我们继续滚动,使用以下代码来优化我们的 PNG 文件,以创建一个 optimize-png.sh 命令

#!/bin/bash

# directory containing images
input_dir="$1"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
fi

# for each png in the input directory
for img in $( find $input_dir -type f -iname "*.png" );
do
  optipng $img -out ${img%.*}-optimized.png
done

在这里,我们使用 OptiPNG 包来优化我们的 PNG 图像。该脚本在输入目录中查找 PNG 图像,并为每个图像创建一个优化版本,并在文件名后添加 -optimized。有一个有趣的参数 -o,我们可以用它来指定优化级别。默认值为 2,**值的范围为 0 到 7。要优化我们的 PNG,我们运行

bash optimize-png.sh generated-images
PNG 优化取决于存储在图像中的信息。某些图像可以得到很大的优化,而另一些则几乎没有优化。

正如我们所见,OptiPNG 在优化图像方面做得很好。我们可以通过在图像质量和大小之间进行权衡来调整 -o 参数,找到合适的值。在提交 4a97f29 中查看结果。

优化 JPG

我们已经到达了最后的部分!我们将通过优化 JPG 图像来完成所有工作。将以下代码添加到 optimize-jpg.sh

#!/bin/bash

# directory containing images
input_dir="$1"

# target image quality
quality="$2"

if [[ -z "$input_dir" ]]; then
  echo "Please specify an input directory."
  exit 1
elif [[ -z "$quality" ]]; then
  echo "Please specify image quality."
  exit 1
fi

# for each jpg or jpeg in the input directory
for img in $( find $input_dir -type f -iname "*.jpg" -o -iname "*.jpeg" );
do
  cp $img ${img%.*}-optimized.jpg
  jpegoptim -m $quality ${img%.*}-optimized.jpg
done

此脚本使用 JPEGoptim。此包的问题是它没有指定输出文件的选项。我们只能就地优化图像文件。我们可以通过首先创建图像的副本,并将其命名为我们喜欢的任何名称,然后优化副本来克服这个问题。-m 参数用于指定图像质量。最好尝试一下,找到质量和文件大小之间的正确平衡。

bash optimize-jpg.sh generated-images 95

结果显示在提交 35630da 中。

总结

看到了吧?用几个脚本,我们可以直接从命令行执行繁重的图像优化,并将其用于任何项目,因为它们是全局安装的。我们可以设置 CI/CD 管道来创建每个图像的不同变体,并使用有效的 HTML、API 来提供它们,甚至设置我们自己的图像转换网站。

我希望您喜欢阅读并从这篇文章中学到一些东西,就像我享受为你们写作一样。编码愉快!