Qiitaに貼る画像のサイズを調整する.NET Core コンソールプログラム


# 問題
Qiitaにイメージをアップロードするとイメージが大きく表示される。

この問題はほとんど解決されている。例えば「markdown イメージ サイズ」でググるとHTMLのIMGタグに変更するやり方が出て来る。

[Qiitaに貼った画像がなんかデカいなあ〜と思ったら](https://qiita.com/hkusu/items/1ba2de200f731e5ef374)
[markdownでの画像リサイズ](https://qiita.com/shti_f/items/b819d7fd8cb79ae29687)

つまりHTMLのIMGタグにしてWidth属性で調整するということですね。

```
<img src="イメージのURL" width="最適な幅の指定" />
```

もちろんこれは解決策なんだけど、手動で変換するのが面倒くさいと感じるので、そこらへんをさらに改善できたらなと思ったのです。
なので「出来るだけ自動化すること」今回の問題です。

# 解決

結論からいうと、入力されたMarkdownコンテンツ内の全てのMarkdownのイメージ参照を、適切なWidth値のHTML IMGタグに変換するプログラムを書きました。手作業ではないことと、Widthの値を最適化している点がこの解決の特徴です。

ソースはこちらにあります。https://github.com/yoshiwatanabe/markdown-image-fix

## 使い方

ビルド済みのバイナリーはこちら　https://github.com/yoshiwatanabe/markdown-image-fix/tree/master/bin/Debug/netcoreapp2.1

.NET Core で書いているのでプラットフォーム依存はありません（が、Linux等での動作確認はしてません。ご了承ください）

Qiitaの記事の内容を編集画面からコピーして、```test.txt``` といった適当に付けた名前のファイルにいったん保存して、以下を実行します（Windowsの場合）

```
c:\> dotnet MarkdownImageFix.dll test.txt
```

同じディレクトリに```test.txt.converted``` というファイルが生成されているはずです。その内容をQiitaの記事編集画面にコピペしたら作業は完了です。

このツールを使って一括でIMGタグに変換したQiita記事がこちらになります。 https://qiita.com/yoshiwatanabe/items/9fd96972d5baccf96407

## 処理

処理の流れはいたって簡単です。

1. コンテンツを入力として受け取る
2. 正規表現でmarkdownのイメージ参照部分をすべて選択する
3. イメージURL部分を抽出してイメージデータをダウンロードしてWidthの値を得る
4. Widthの値にスケーリング係数を掛けた結果を算出する（なぜこうするかの理由は下の「調査」を読んでください）
5. 各markdownイメージ参照に対して、HTMLのIMG参照を生成する
5. コンテンツ全体に対してmarkdownイメージ参照をHTMLのIMG参照と取り換える

```:MarkdownImageConverter.cs
using System;
using System.Drawing;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace MarkdownImageFix
{
    public class MarkdownImageConverter
    {
        const string Expression = @"(!\[image.png\]\((.*?)\))";
        const string HtmlImgTagFormat = "<img src='{0}' width='{1}' />";
        const double DefaultScalingFactor = 0.56;
        private readonly Regex regex;
        private readonly HttpClient httpClient;
        private readonly double scalingFactor;

        public MarkdownImageConverter(double scalingFactor = DefaultScalingFactor)
        {
            regex = new Regex(Expression, RegexOptions.Compiled);
            httpClient = new HttpClient();
            this.scalingFactor = scalingFactor;
        }

        public string Convert(string source)
        {
            var results = regex.Matches(source);
            var builder = new StringBuilder(source);           
            var items = results.Select(async _ => (OldValue: _.Value, NewValue: await GetImageTag(_.Groups[2].Value)));
            foreach (var item in items)
            {
                builder.Replace(item.Result.OldValue, item.Result.NewValue);
            }

            return builder.ToString();
        }

        private async Task<string>GetImageTag(string imageUrl)
        {
            return string.Format(HtmlImgTagFormat, imageUrl, await GetWidth(imageUrl));
        }

        private async Task<int> GetWidth(string imageUrl)
        {
            using (var stream = await httpClient.GetStreamAsync(new Uri(imageUrl)))
            using (var image = new Bitmap(stream))
            {
                return (int) Math.Round(image.Width * scalingFactor);
            }
        }
    }
}
```

- Bitmapの操作などはプラットフォーム依存かと思ってましたが.NET Coreのライブラリがありました。
- 複数のイメージデータを取ってくるのは並列処理した方がよさげですが、全体としてそれほどの遅延はないので非同期だけ
- スケーリング係数はユーザーの環境に依存していると思われる（詳細は事項に）

## スケーリング係数について

私はSurface Book 2を 3240x2160 の解像度で全体のスケールを200％にして使っています。そうすると、スクリーンショットの幅のピクセル数がかなり大きめになります。いろいろと実験した結果、スクリーンショットのイメージの幅の56％ぐらいがちょうどいいぐらいだと分かりました。ソースには定数を埋め込んでいますが、関数レベルでしか実行時にオーバライドできるようにしていません。

```:MarkdownImageConverter.cs
const double DefaultScalingFactor = 0.56;
```

この値は人それぞれのPCの設定などに依存するものと思われます。

#まとめ

手動で検索・置き換え、さらにいい感じのサイズに画像の調整する手間が苦痛でしたが無くなりました。まだワークフローにはコピペ数回ありますが、まぁいいでしょう。
Azure Functionに置いてみるのも考えましたが、また別の機会にやります。

# 調査

どのようなIMGタグを生成したら良いかを調べた過程です。参考までに。

#### その１．イメージをアップロードしたらデフォルトで生成されるマークダウンでの表示

```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/103139/c3ab98cc-9b8d-731c-9a0b-3a18be0b80c2.png)
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/103139/c3ab98cc-9b8d-731c-9a0b-3a18be0b80c2.png)
実際のヘッダー領域に見える「Qiita」のロゴよりも多くなる。

#### その２．イメージをマークダウンからHTMLのimgタグに変更した場合

```
<img src='https://qiita-image-store.s3.amazonaws.com/0/103139/c3ab98cc-9b8d-731c-9a0b-3a18be0b80c2.png' />
```

<img src='https://qiita-image-store.s3.amazonaws.com/0/103139/c3ab98cc-9b8d-731c-9a0b-3a18be0b80c2.png' />

変化なし。大きいまま表示される。

#### その３．HTMLのimgタグに変更して、なおかつwidthを画像データ本来の幅のピクセル値に設定した場合

画像データの幅は162px。
<img src='https://qiita-image-store.s3.amazonaws.com/0/103139/ec4698cf-bfd4-52a5-d726-00882c27a59a.png' width='406' />

```width='162'``` を書き足してみる。

```
<img width='162' src='https://qiita-image-store.s3.amazonaws.com/0/103139/c3ab98cc-9b8d-731c-9a0b-3a18be0b80c2.png' />
```

<img src='https://qiita-image-store.s3.amazonaws.com/0/103139/c3ab98cc-9b8d-731c-9a0b-3a18be0b80c2.png' width='162' />


変わらない。大きいまま。ちなにに、なぜこのように大きめにレンダーされるのか、理由をご存知の方は教えてください。

#### その４．width='50%' にしてみる

```
<img width='50%' src='https://qiita-image-store.s3.amazonaws.com/0/103139/c3ab98cc-9b8d-731c-9a0b-3a18be0b80c2.png'  />
```

<img src='https://qiita-image-store.s3.amazonaws.com/0/103139/c3ab98cc-9b8d-731c-9a0b-3a18be0b80c2.png' width='50%' />

問題が悪化した。イメージのもともとの幅（162px）の50％、ではなくて画面の50％になってしまった。ということはimg タグに一律で```width='50%' ``` を設定するようにしてもうまくいかないとうこと。
 
#### その５．画像データのピクセル幅に0.5をかけた値をwidthに設定した場合

```
<img src='https://qiita-image-store.s3.amazonaws.com/0/103139/c3ab98cc-9b8d-731c-9a0b-3a18be0b80c2.png' width='81' />
```
<img src='https://qiita-image-store.s3.amazonaws.com/0/103139/c3ab98cc-9b8d-731c-9a0b-3a18be0b80c2.png' width='81' />

なんかいい感じのサイズで表示されるようになってきた！ということで、二つの要件があると分かった。

- HTML img タグに変更する
- widthを画像データのピクセル幅の半分ぐらいに設定する