# [メタ情報] # 識別子: PDFまたは画像から切り抜き画像の生成workflow_exe # システム名: PDFまたは画像から切り抜き画像の生成workflow # 技術種別: Misc # 機能名: Misc # 使用言語: ShellScript # 状態: 実行用 env終了 差異正常 # [/メタ情報] 要約:PDFまたは画像から自動で切り抜き画像を作成する Automator ワークフローの内容である。入力ファイルの拡張子に応じて処理を分岐し、PDFは300dpiのJPEGに変換、GIFは最初のフレームをJPEG化、その他の画像はコピーして処理用JPEGとする。その後、Python スクリプトにファイル情報を引き渡し、OpenCV で画像を2倍拡大、濃い線部分を抽出して太らせ、線画のみの PNG(_lineonly)、外周塗りつぶし PNG(_filled)を生成。さらに両者を合成して最終的な切り抜き PNG(_cropped)を出力する。最後に中間ファイルを削除して処理を終了する。 PDFまたは画像から切り抜き画像の生成.workflow ワークフローが受け取る現在の項目:ファイルまたはフォルダ 検索対象:Finder.app シェルスクリプトを実行 入力の引き渡し方法:引数として ``` #!/bin/bash # 1. 共通検索パスの設定(M1/M2 Mac双方のHomebrew環境に自動対応) export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" FILE_PATH="$1" EXTENSION="${FILE_PATH##*.}" BASENAME=$(basename "$FILE_PATH" .${EXTENSION}) DIRNAME=$(dirname "$FILE_PATH") cd "$DIRNAME" # JPEG変換(PDFのみ) if [[ "$EXTENSION" == "pdf" ]]; then pdftoppm -jpeg -r 300 "$FILE_PATH" "${BASENAME}_page" mv "${BASENAME}_page-1.jpg" "${BASENAME}_page.jpg" IMAGE_NAME="${BASENAME}_page.jpg" elif [[ "$EXTENSION" == "gif" ]]; then # GIF → JPEG(最初のフレーム)に変換 IMAGE_NAME="${BASENAME}_frame.jpg" convert "$FILE_PATH" "$IMAGE_NAME" else # その他の画像はそのまま使う cp "$FILE_PATH" "${BASENAME}_page.jpg" IMAGE_NAME="${BASENAME}_page.jpg" fi # Bash変数を Python に渡すため export export BASENAME export DIRNAME export IMAGE_NAME # 2. $HOME 環境変数を用いてユーザー名(XXXXXX)を動的解決 PYTHON_BIN="$HOME/python_scripts/venv_pdfcrop/bin/python3" # 仮想環境のPythonを安全に実行(クォーテーションをシングルに統一してエスケープを防止) "$PYTHON_BIN" <<"EOF" import cv2 import numpy as np from PIL import Image import os basename = os.environ.get('BASENAME') dirpath = os.environ.get('DIRNAME') jpg_path = os.path.join(dirpath, os.environ.get('IMAGE_NAME')) img = cv2.imread(jpg_path, cv2.IMREAD_COLOR) # === PNGやJPEGからの入力の場合は人工的に解像度アップ === img = cv2.resize(img, None, fx=2.0, fy=2.0, interpolation=cv2.INTER_LINEAR) # === 線の抽出(濃い部分全体を抽出)=== gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) _, thresh = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV) # 線を適度に太らせて塗り潰す(85%程度に抑える) kernel = np.ones((3,3), np.uint8) filled = cv2.dilate(thresh, kernel, iterations=1) # RGBA画像を生成し、黒で完全に塗りつぶし、不透明に rgba = np.zeros((img.shape[0], img.shape[1], 4), dtype=np.uint8) rgba[:, :, 0:3] = 0 rgba[:, :, 3] = filled png2_path = os.path.join(dirpath, f'{basename}_lineonly.png') cv2.imwrite(png2_path, rgba) # === 外周内側白塗り(PNG画像3)=== contours, _ = cv2.findContours(filled, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) mask = np.zeros_like(gray) cv2.drawContours(mask, contours, -1, 255, -1) white_fill = np.zeros((img.shape[0], img.shape[1], 4), dtype=np.uint8) white_fill[:, :, 0:3] = 255 white_fill[:, :, 3] = mask png3_path = os.path.join(dirpath, f'{basename}_filled.png') cv2.imwrite(png3_path, white_fill) # === 合成してPNG画像4を作成 === img2 = Image.open(png2_path).convert('RGBA') img3 = Image.open(png3_path).convert('RGBA') combined = Image.alpha_composite(img3, img2) png4_path = os.path.join(dirpath, f'{basename}_cropped.png') combined.save(png4_path) # === 中間ファイル削除 === os.remove(jpg_path) os.remove(png2_path) os.remove(png3_path) EOF ```