Ghostscript でラスタライズ(#4:Jpegで保存)

2022年5月28日

今回は、Ghostscript でラスタライズした画像を jpeg 形式で保存してみます。 png 保存に比べると jpeg 保存では一般に高圧縮が期待でき、さらに CMYK 形式で保存することも可能です。 ただ RGB 形式で保存する場合については、ちょっと残念な結果になってしまいました。 jpeg で保存するには、保存するカラーモード(または色空間、カラースペース)によって -sDEVICE に以下のいずれかを使い分けます。
  • -sDEVICE=jpeg … RGB形式で保存
  • -sDEVICE=jpegcmyk … CMYK形式で保存
  • -sDEVICE=jpeggray … グレースケール形式で保存
そして保存時の画質の設定については、量子化テーブルに関する以下の2種類があります。
-dJPEGQ
公式には 0 から 100 の値の整数値をとりますが、0 を指定した場合はデフォルト値が使用されます。デフォルト値は 75 になります。 数字が低いと低画質(高圧縮率)、高いと高画質(低圧縮率)です。 ただし、0 を指定した場合はデフォルト値が使用されますので、実質的な最小値は 1 になります。
-dQFactor
公式には 0.00 から 1.00 の実数値をとりますが、実は 100.0 までとれます(粗すぎて実用性皆無ですが・・・)。 上記とは逆に、数字が低いと高画質(低圧縮率)、高いと低画質(高圧縮率)です。 0 を指定した場合は -dQFactor=0.5 が使用されます。 ですから最高画質にしようと思って最小値の -QFactor=0 を指定すると、-dQFactor=0.5 相当の画質になってしまうので注意しましょう。
しかし Ghostscript のソースによると、JPEGQ と QFactor との間には以下の関係があるので、画質設定は実質1種類ということになります。  euation-QFactor-JPEGQ 仮に -dJPEGQ と -dQFactor が同時に指定された場合は、-dJPEGQ の設定値が使用されます。 また、この関係式に従って対応表を作ると、次のようになります。
-dJPEGQ110203040506070758090100
-dQFactor5052.51.671.2510.80.60.50.40.20
では実際に、画質の設定を様々に変えてみて結果を比較してみましょう。 画質の指定には -dJPEGQ を使用し、以下のように実行しました。
for i in 0 1 25 50 75 100; do \
gs -dSAFER -sDEVICE=jpeg     -r72 -dJEPGQ=$i -o out.rgb.$i.jpg  in.pdf; \
gs -dSAFER -sDEVICE=jpegcmyk -r72 -dJEPGQ=$i -o out.cmyk.$i.jpg in.pdf; \
gs -dSAFER -sDEVICE=jpeggray -r72 -dJEPGQ=$i -o out.gray.$i.jpg in.pdf; \
done
まともに書くと、画質6種類×形式3種=18行と長くなるので、for を使って若干まとめてます。 以下、その実行結果です。
-dJPEGQ-sDEVICE
jpegjpegcmykjpeggray
0logo-ghost.rgb.0.x4logo-ghost.cmyk.0.x4logo-ghost.gray.0.x4
1logo-ghost.rgb.1.x4logo-ghost.cmyk.1.x4logo-ghost.gray.1.x4
25logo-ghost.rgb.25.x4logo-ghost.cmyk.25.x4logo-ghost.gray.25.x4
50logo-ghost.rgb.50.x4logo-ghost.cmyk.50.x4logo-ghost.gray.50.x4
75logo-ghost.rgb.75.x4logo-ghost.cmyk.75.x4logo-ghost.gray.75.x4
100logo-ghost.rgb.100.x4logo-ghost.cmyk.100.x4logo-ghost.gray.100.x4
いわゆる jpeg っぽいノイズが見て取れます。 -dJPEGQ が高いほど画質が良くなるという、ごく当たり前の結果が確認できまた。 一応 -dJPEGQ=0 の設定も試してみましたが、確かに -dJPEGQ=75 と同じ画質に見えます。 最下段(-dJPEGQ=100)の jpeg と jpegcmyk の結果をよく見比べてみると、同じ画質設定値でありながら jpeg の方が若干汚く見えるかもしれません。 細かい話になりますが、jpeg 圧縮の画質は「量子化テーブルの値」と「サンプリングファクタの値」によって大体決まるようです。 前者は大雑把にいえば 64 個の数字の羅列で、それぞれが 1 近いほどに高画質になります。これらの数字は前述の -dJPEGQ または -dQFactor によって決まります。 後者の値は、Ghostscript においてはカラーモードによって固定になっていて、以下のようになっています。
カラーモード-sDEVICE色成分1色成分2色成分3色成分4備考
RGBjpegY:2×2Cb:1×1Cr:1×1Y=輝度/Cb=青系色/Cr=赤系色
CMYKjpegcmykC:1×1M:1×1Y:1×1K:1×1C=シアン/M=マゼンタ/Y=イエロー/K=墨
グレーjpeggrayY:1×1Y=輝度
通常の jpeg 圧縮では、RGB 形式は最初に YCbCr 形式に変換されます。 Ghostscript においてもそれは同様で、ただし Y 成分のサンプリングファクタのみ 2×2 と別の値が使用されています。 CMYK 形式の場合はいずれの色成分のサンプリングファクタも 1×1 です。この違いが見た目の違いに出てきたようです。 ちなみに RGB 形式のサンプリングファクタ Y:2×2 を 1×1 にするには・・・ソースをいじくってリビルドするしかテは無いようです。 該当場所は、
  case JCS_YCbCr:
    cinfo->write_JFIF_header = TRUE; /* Write a JFIF marker */
    cinfo->num_components = 3;
    /* JFIF specifies component IDs 1,2,3 */
    /* We default to 2x2 subsamples of chrominance */
    SET_COMP(0, 1, 2,2, 0, 0,0);
    SET_COMP(1, 2, 1,1, 1, 1,1);
    SET_COMP(2, 3, 1,1, 1, 1,1);
    break;
  case JCS_CMYK:
    cinfo->write_Adobe_marker = TRUE; /* write Adobe marker to flag CMYK */
    cinfo->num_components = 4;
    SET_COMP(0, 0x43 /* 'C' */, 1,1, 0, 0,0);
    SET_COMP(1, 0x4D /* 'M' */, 1,1, 0, 0,0);
    SET_COMP(2, 0x59 /* 'Y' */, 1,1, 0, 0,0);
    SET_COMP(3, 0x4B /* 'K' */, 1,1, 0, 0,0);
    break;
の 459 行目で、以下のように変更するだけです。
    SET_COMP(0, 1, 1,1, 0, 0,0);
その結果、
改変前 (RGB)logo-ghost.rgb.100.x4
改変後 (RGB)out.hq
参考 (CMYK)logo-ghost.cmyk.100.x4
CMYK形式と同程度の画質が得られるのですが・・・反則ですね・・・。 その5
使用バージョン: Photoshop CS5(12.0 x64) / Ghostscript 9.16
(*1) QFactor から JPEGQ への変換ルーチンを抜粋しました。 引数の quality が JPEGQ の値になります。 返り値は QFactor を 100倍した整数値になっています。
GLOBAL(int)
jpeg_quality_scaling (int quality)
/* Convert a user-specified quality rating to a percentage scaling factor
 * for an underlying quantization table, using our recommended scaling curve.
 * The input 'quality' factor should be 0 (terrible) to 100 (very good).
 */
{
  /* Safety limit on quality factor.  Convert 0 to 1 to avoid zero divide. */
  if (quality <= 0) quality = 1;
  if (quality > 100) quality = 100;

  /* The basic table is used as-is (scaling 100) for a quality of 50.
   * Qualities 50..100 are converted to scaling percentage 200 - 2*Q;
   * note that at Q=100 the scaling is 0, which will cause jpeg_add_quant_table
   * to make all the table entries 1 (hence, minimum quantization loss).
   * Qualities 1..50 are converted to scaling percentage 5000/Q.
   */
  if (quality < 50) 
    quality = 5000 / quality;
  else
    quality = 200 - quality*2;

  return quality;
}

(*2) まったく関係ないですが、この数式の画像はTeX を使ってみようというページから作成してみました。ソースはこんな感じです。久々に TeX 書いてみました。
\documentclass{jsarticle}
\usepackage{amsmath}

\begin{document}
\[
  \text{QFactor} = \begin{cases}
    5000 / \text{JPEGQ} / 100 & (\text{JPEGQ} < 50) \\
    (200 - \text{JPEGQ} * 2) / 100 & (\text{JPEGQ} \ge 50)
  \end{cases}
\]
\end{document}