RでグラフのPNG画像を高画質で保存したい

Rで描いたグラフの画像をQiitaの記事に埋め込むためPNG形式でファイルに保存したい[^1]。グラフのPNG画像を作成するには、Rの[`png()`](https://search.r-project.org/R/refmans/grDevices/html/png.html)関数や[`quartz.save()`](https://search.r-project.org/R/refmans/grDevices/html/quartz.html)関数を使えばできます。ただ、これらの関数をデフォルトの引数で実行した場合、出力されたPNG画像は寸法が小さくて画質もよくありません[^2]。Rの関数を使ってスクリーンショットのように高画質な画像で保存したい。その方法のメモです。
[^1]: 2023年6月現在、Qiitaにアップロード可能な画像はgif , jpeg , png , tiff形式のみ。https://help.qiita.com/ja/articles/qiita-image-upload
[^2]: png()関数で書き出した場合のデフォルトは480px x 480px。quartz.save()のデフォルトは699px x 699px (7in x 7in、100dpi）。

**実行環境**
R 4.3.1, macOS 12.6.5, iMac (Retina 5K)

スクリーンショットの代わりにわざわざRの関数を使う理由は、MacのScreenshot.appを使ってウインドウをキャプチャした場合、図1のようにウインドウのタイトルバーも画像に含まれてしまい、余分なのでトリミング作業が必要になるからです。画像が1枚だけなら取り込みたい部分をドラッグし範囲指定してキャプチャするのもよいですが、撮りたいグラフが複数あって、別々の画像に大きさや位置を揃えて作りたい場合、ウインドウの位置を移動してしまった時に、取り込み範囲を前回の画像と同じに合わせるのが難しいです（翌日に作業したときとか）。

**図1**
![screenshot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/aeb16332-af04-7937-d4a7-615e251e4607.png)

:::note info
スクリーンショットの撮り方: Shift + ⌘ + 4 の後に Space bar を押してカメラのポインターをグラフのウィンドウの上に移動し、⌥ + Click で選択すると[ウインドウの影を含めないで](https://support.apple.com/ja-jp/HT201361)撮れます。
:::

## 方法1. png()関数でファイルに書き出す
はじめに`png()`関数を使う方法です。例1のスニペットは、`png()`で描画の出力先をファイルに指定し、グラフのPNG画像をスクリーンショット（図1）とほぼ同じ画質でファイルに書き出します。

```R:例1
png("graph.png", type="quartz", width=1520, height=1520, res=218)
hist(data)
dev.off()
```
ここで、解像度(ppi: pixels per inch)を指定する引数`res`の値は次のようにして求めました。

まず、Rのヘルプ[macOS Quartz Device](https://search.r-project.org/R/refmans/grDevices/html/quartz.html)によると、Quartzウインドウのキャンバスの幅と高さのデフォルトは7インチとあります。Rの[`dev.size()`](https://search.r-project.org/R/refmans/grDevices/html/dev.size.html)関数でデバイスの画面サイズを確認しても7インチになっていました。

```R
> dev.size("in")
[1] 7 7
> dev.size("px")
[1] 504 504
> dev.size("cm")
[1] 17.78 17.78
> 
```

`dev.size()`でブランクのQuartzウインドウを表示させて、タイトルバーを除くキャンバス部分の寸法を定規で測ってみると、筆者のモニターでは約178mm x 178mmでしたので、`dev.size()`を"in"や"cm"単位で指定したときの戻り値は、画面上の実際の寸法を表しています。

一方、`dev.size("px")`の戻り値は、筆者のモニター上でのピクセル数と一致しません。Screenshot.appでキャンバス部分を範囲指定すると画面には"760 x 752"と表示されます。ブランクのQuartzウインドウをスクリーンショットに撮り、Photoshopを使ってキャンバス部分だけトリミングしてサイズを調べると、画像の幅1520px, 高さ1505pxでした。使用しているモニターのデバイスピクセル比が2なので、元のピクセル数は幅760px, 高さ752.5pxということになりScreenshot.appの表示とほぼ一致します。

キャンバスの画面上の高さの実測値が幅より僅かに短いのが気になりますが、計算を簡単にするため、キャンバスの形状を正方形とみなします。この正方形の一辺の長さ（ピクセル数）は、図1のPNG画像の解像度情報[^3]からもわかります（図2）。高さの値はタイトルバーを含んでいるため、幅の方の値1520pxを採用します。
[^3]: MacのプレビューでPNG画像を開き、**ツール**＞**インスペクターを表示**をクリック

**図2**
![screenshot_info_resize.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/77db40f5-aa60-4011-ebcd-8680c7918197.png)
[iMac (Retina 5K, 27-inch, 2017) Technical Specifications](https://niwtech.com/guides/apple/imac-imac-pro/technical-specifications/imac-retina-5k-27-inch-2017/)によると、筆者が使っているモニターの解像度は218 ppiです[^4]。一方、図2から解像度を算出すると、
1520 px / 7 inch = 217.14 ppi
になり、仕様とほぼ一致します。
[^4]: 筆者が使っているディスプレイは27インチ、5120 x 2880なので、X軸の解像度ppi = 5120 / (cos(arctan(2880/5120)) * 27) = 217.571。真の値は217と218の間と思われる

以上より、引数`res`の値を218とします。

図3は、例1のスニペットを実行して出力されたPNG画像です。その解像度情報が図4です。図1と図3の画像を比較すると、ほぼ同じ画質に見えます。

**図3**
![graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/b02ebecd-3ec5-ca91-8350-b7fe84c73fea.png)
**図4**
![graph_info_resize.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/c2ac2354-e788-46b3-eef0-1087a8ecf6d3.png)
この方法で実行した場合、グラフの描画のためのQuartzウインドウは画面に表示されません。なぜなら、`png()`関数が呼ばれた後、[`dev.off()`](https://search.r-project.org/CRAN/refmans/rasterpdf/html/dev.off.html)関数が呼ばれるまでの間、グラフィックデバイスへのグラフの描画はファイルに向けられるからです。これだとQuartzウインドウでグラフを見ながらPNG画像をファイルに保存したいとき不便です。次に説明する`quartz.save()`関数を使えば、グラフの画面出力とファイル保存が可能です。

## 方法2. quartz.save()関数でファイルに書き出す
次に`quartz.save()`関数を使う方法です。例2のスニペットは、Quartzウインドウに描画されたグラフに対して、`quartz.save()`でPNG形式の画像としてファイルに書き出します。

```R:例2
par(bg="white")
hist(data)
quartz.save("quartz.png", dpi=218)
```

例1の手順と異なり、グラフを描画する前に[`par()`](https://search.r-project.org/R/refmans/graphics/html/par.html)関数により現在のプロットデバイスに対して背景色を`white`に設定しておく必要があります。

今回、Quartzウインドウのキャンバスの寸法はデフォルトのままなので、`quartz.save()`の引数に指定するのは出力先のファイル名と解像度`dpi=218`だけで十分です。

図5は、例2のスニペットを実行して出力されたPNG画像です。その解像度情報が図6です。図1と図5の画像を比較すると、こちらもほぼ同じ画質に見えます。

**図5**
![quartz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/470fcef6-32fd-b8f2-2c32-16e261e49525.png)
**図6**
![quartz_info_resize.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/f4b30d22-78d4-aebc-8486-7d63e3115d20.png)


（続きはこちら）

https://qiita.com/kazhashimoto/items/02d3581c1af9fc117dcb