ゼロニクルの実験室

3兄弟の父が子育て・ダイエット・生活改善を本気で検証

Excelファイルの軽量化大作戦!画像貼り付け時にJPEGファイルにするプログラムを開発してみた

本ページはプロモーションが含まれています

課題を解決するためのツールを作成するエンジニア
無いなら作るぜ!

こんにちは。

今回はExcelに画像ファイルを貼り付けたときに、
「ファイルの容量が大きくなってしまった!これ、どうにかならないかな?」
ということについて書いていきたいと思います。

この課題について、私はどのように対応することにしたか。どうぞご覧ください。

Excelに画像を張り付けると、容量が大きくなりがちという課題に直面

私の仕事上、エビデンス画像*1を用意するとき、
スクリーンキャプチャをして、そのままExcelに貼り付けるということをよくやります。

Excelに貼り付けた画像は、元画像よりも大きな容量で保存されることがあります。
内部的な保存形式は状況によって異なりますが、非圧縮または圧縮率の低い形式で保持されるケースもあります。
すると画像1枚の容量が大きいので、数百枚となるとその分ファイルのサイズは膨れ上がってしまいます。

「これ、貼り付けるときにJPEGで貼り付けられるよう設定できないのかな?」と
Excelの設定を探したのですが、残念ながら見つけることができませんでした。

―無いなら作ればいい。

ということで、自前でスクリーンキャプチャしたものを、JPEG形式で貼り付けられるプログラム
作ることに挑戦してみることにしました。

なぜExcelに貼り付ける画像の容量は大きいのか

まず、"なぜExcelに貼り付ける画像の容量は大きいのか"ですが、
Excelに貼り付けた画像がなぜ大きくなるのか調べてみましたが、内部的な保存形式までは確認できませんでした。

ただ、JPEGのような高圧縮形式ではなく、比較的容量の大きい形式で保持されているケースがあるようです。

であれば、貼り付け前にJPEGへ変換してしまえば、ファイルサイズを抑えられるのではないかと考えました。

そこで私は、この課題を解決すべく動き出しました。

とりあえず、Geminiで

コピーした画像をJPGに変換してExcelに貼り付けるプログラムを作成することは可能か?

と聞いたところ、

結論から申し上げますと、可能です。

という回答を得ることができました。
ということで、安心してプログラム作成開始です。

ExcelにJPEG形式で貼り付けるプログラムの作成開始!

プログラム言語の選定

まずは何を使ってプログラムを作成するかですが、ここはライブラリが豊富で
比較的容易にプログラムが書けるPythonを使うことにしました。

プログラム内のフロー

プログラム内でやることは以下の通りです。

  1. ショートカットキーの監視
    ショートカットキーを監視します。今回はShift + Ctrl + Vを割り当てました。
    この技術はキーロガーでも使う仕組みなので、強固なセキュリティソフトが
    インストールされているマシンでは動かないかもしれないというのが注意点です。
  2. クリップボードの監視
    クリップボードに画像が格納されているかをチェックします。
    クリップボードに画像がなければ何もしないで次のイベント(キー入力)を待ちます。
  3. 画像フォーマットの最適化
    取得した画像が透過情報を持つ場合(RGBAモードなど)、JPEG形式で保存できるようにRGBモードへ変換します。
  4. テンポラリファイルへの書き出し
    tempfileを利用して、OSの一時フォルダに画像をJPEG形式(デフォルト値:75に設定)で保存し、ファイルサイズを軽量化します。
  5. Excelへのアクセスと座標取得
    起動中のExcelアプリケーション(アクティブなシート)に接続し、現在ユーザーが選択しているセルの位置(Left, Top)を取得します。
  6. 画像の挿入
    先ほど保存した一時ファイルのJPEG画像を、取得したセルの位置に元のサイズ(-1, -1)で挿入します。
  7. 後処理
    処理が完了(または失敗)した後、COMの接続を解除し、作成した一時ファイルをOS上から削除します。

使用しているライブラリ

  • keyboard: グローバルなショートカットキー(ホットキー)を監視・登録するための外部ライブラリ。
  • PIL (Pillow): 画像処理を行う外部ライブラリ。ImageGrabモジュールを使ってクリップボード内の画像データを取得し、フォーマット変換や保存を行います。
  • win32com.client, pythoncom, pywintypes (pywin32): WindowsのCOM(Component Object Model)技術を通じて、PythonからExcelを直接操作するための外部ライブラリ群。
  • tempfile: 一時的なファイルやディレクトリを安全に作成するための標準ライブラリ。
  • os: 環境変数の取得(os.environ.get)や、ファイルの削除(os.remove)など、OS依存の機能を利用するための標準ライブラリ。

実際のコード

実際のコードはこちらです(ここをクリック)

# paste-as-jpeg.py  
# クリップボードの画像をJPGに変換し、選択中のExcelセル位置へ貼り付ける常駐ツール。  
# ショートカットキーで起動し、Excelファイルの肥大化を抑えます。  
#  
# exe化例: pyinstaller --onefile --console paste-as-jpeg.py  

import keyboard  
from PIL import ImageGrab  
import pythoncom  
import pywintypes  
import win32com.client as win32  
import tempfile  
import os  

# --- 設定 ---  
SHORTCUT = os.environ.get("PASTE_AS_JPEG_SHORTCUT", "ctrl+shift+v")  
JPEG_QUALITY = max(1, min(100, int(os.environ.get("PASTE_AS_JPEG_QUALITY", "75"))))  


def _remove_temp_file(path):  
    if path and os.path.exists(path):  
        try:  
            os.remove(path)  
        except OSError:  
            pass  


def _format_excel_error(exc):  
    if isinstance(exc, pywintypes.com_error):  
        hresult = exc.args[0] if exc.args else None  
        if hresult == -2147221021:  # MK_E_UNAVAILABLE  
            return "Excelが起動していません。Excelを開いてから再試行してください。"  
        if hresult == -2147023174:  # RPC_S_SERVER_UNAVAILABLE  
            return "Excelとの通信に失敗しました。Excelを再起動してから再試行してください。"  

    message = str(exc)  
    if "編集" in message or "edit" in message.lower():  
        return (  
            "セル編集モードの可能性があります。"  
            "EnterまたはEscで編集を終了してから再試行してください。"  
        )  
    return f"Excelへの貼り付けに失敗しました。エラー詳細: {exc}"  


def paste_as_jpg():  
    # 1. クリップボードから画像を取得  
    try:  
        img = ImageGrab.grabclipboard()  
    except Exception as exc:  
        print(f"クリップボードの取得に失敗しました。しばらく待って再試行してください。詳細: {exc}")  
        return  

    if img is None:  
        print("クリップボードに画像がありません。")  
        return  

    # RGBA形式(透過あり)などの場合は、JPG保存のためにRGBに変換  
    if img.mode in ("RGBA", "P"):  
        img = img.convert("RGB")  

    # 2. 一時フォルダにJPGとして保存  
    with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as temp_file:  
        temp_path = temp_file.name  

    # 3. 起動中のExcelを操作して画像を挿入  
    # keyboardのコールバックは別スレッドで実行されるため、COM使用前に初期化が必要  
    pythoncom.CoInitialize()  
    try:  
        img.save(temp_path, "JPEG", quality=JPEG_QUALITY)  

        excel = win32.GetActiveObject("Excel.Application")  
        sheet = excel.ActiveSheet  

        selection = excel.Selection  
        if not hasattr(selection, "Left") or not hasattr(selection, "Top"):  
            print("セルが選択されていません。貼り付け先のセルを選択してから再試行してください。")  
            return  

        left = selection.Left  
        top = selection.Top  

        # 画像を挿入 (ファイルパス, リンク貼り付けしない, 文書に保存する, 左, 上, 幅, 高さ)  
        sheet.Shapes.AddPicture(temp_path, False, True, left, top, -1, -1)  
        print(f"ExcelにJPGとして挿入しました。(品質: {JPEG_QUALITY})")  

    except Exception as exc:  
        print(_format_excel_error(exc))  
    finally:  
        pythoncom.CoUninitialize()  
        _remove_temp_file(temp_path)  


keyboard.add_hotkey(SHORTCUT, paste_as_jpg)  

print(f"待機中... Excelを開いて {SHORTCUT} を押してください。")  
print(f"JPG品質: {JPEG_QUALITY}(変更: 環境変数 PASTE_AS_JPEG_QUALITY)")  
print("終了するにはこのウィンドウを閉じるか、Ctrl+Cを押してください。")  
print("※ keyboardライブラリはキーボードフックを使用します。セキュリティソフトの警告が出る場合があります。")  

try:  
    keyboard.wait()  
except KeyboardInterrupt:  
    print("\nCtrl+Cにより中断しました。")  
finally:  
    keyboard.unhook_all()  
    print("アプリを終了しました。")  

プログラムを書く上で工夫した点

  • COM操作におけるマルチスレッドへの配慮
    keyboardライブラリのコールバック関数は別スレッドで実行されるため、
    COMオブジェクトを操作する前に必ずpythoncom.CoInitialize()を呼び出すようにし、
    finallyブロックでCoUninitialize()を呼び出し、安全にスレッドを解放するようにしました。
  • 適切なエラーハンドリング
    COM特有のHRESULTエラーコード(例: -2147221021)を捕捉し、
    「Excelが起動していない」
    「セル編集モードである」
    といった、ユーザーが次にどうアクションすればよいか分かるメッセージを出力するようにしました。
  • 確実なリソース解放(ゴミを残さない設計)
    処理の成否にかかわらず、finallyブロック内で_remove_temp_file(temp_path)を実行し、
    一時ファイルを確実に削除するようにしています。
    余計なものを残さないよう、ちゃんと気遣えるプログラムを書くことを心掛けています。

  • 環境変数による設定の外部化
    ショートカットキーやJPEGの品質設定を環境変数で上書きできるように設計しました。
    環境変数に登録されていなくても、初期値となる値を設定しています。

  • 安全な終了処理
    Ctrl+Cなどでプログラムを終了する際、keyboard.unhook_all()を呼び出して
    キーボードのフックを確実に解除します。
    これにより、OSのキー入力挙動に悪影響が出ることはありません。

どれだけExcelの容量は削減できるのか検証

それでは早速どれだけExcelの容量を削減できるのか検証してみることにします。

検証方法

任意の画面をキャプチャし、従来の方式とプログラムを使ったそれぞれの方式で
10個画像をそれぞれのExcelに貼り付け、ファイルのサイズを確認する。

備考: 貼り付ける画像は同じものとする。

結果

ファイルサイズの比較
ファイルサイズの比較

- 従来の方式 プログラム使用
ファイルサイズ 235KB 138KB

このとおり約41%の削減に成功しました。

今回は10枚で検証しましたが、画像枚数が増えるほど削減できる容量の
絶対量は大きくなると考えられます。

画像10枚程度でも差が確認できたため、エビデンス画像を大量に貼り付ける業務では一定の効果が期待できそうです。


今回やりたかったことは無事に実現できました!

今後の課題としては、今はExcel限定となっているので、ほかのアプリに対しても貼り付けられるよう、
汎用的なものにしたいと考えています。

また、今回の課題はExcelの設定だけでは解決できなかったものでしたが、
自作ツールを作ることで業務上の小さなストレスを解消することができました。

こうした「無いなら作る」という発想は、今後も大切にしていきたいと思います。

*1:証拠写真みたいなもの