はじまり
アレエエッ、なんでこれPDFに出来ないの?!
同じJPGなんだけどな・・・。
まずは何が起きたのか。
今回遭遇した不思議現象は、Libwebpパッケージのcwebpツールで変換圧縮したJPGファイルが、iTextSharpパッケージを使ったPDF変換処理で変換できなかったというものです。
LibwebpパッケージはScoopでインストールします。
cd C:\
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
$username = (Get-ChildItem Env:USERNAME).Value; Set-Location "C:\Users\$username\Downloads\picture_backup";
scoop update
scoop install libwebp
iTextSharpパッケージはここからダウンロードしたものを使いました。iTextSharpのバージョンは「5.5.13」です。
まずは、cwebpでJPGに変換圧縮します。また、今回はPowerShellで一連の処理を行っていきます。
function Optimize-ImageWithCompressing(){
param (
[array]$pathList,
[string]$suffix,
[int]$quality
)
Add-Type -AssemblyName System.Drawing
foreach ($file in $pathList) {
$extension = (Get-Item $file.FullName).Extension;
$outputImg = "{0}_{1:D2}{2}" -f $file.FullName.Substring(0, $file.FullName.Length-$extension.Length), $suffix, ".jpg";
Write-Output "<Source File Name: $file>";
cwebp -preset photo -metadata icc -sharp_yuv -q $quality -o $outputImg -progress -short $file.FullName; $updatedCount++;
Write-Output "----------------";
};
Write-Output("{0} .jpg images converted." -f $updatedCount);
Write-Output "----------------";
}
そしたら次に、圧縮したJPGをiTextSharpでPDFに変換します。
function Merge-ImagesIntoPdfWithITextSharp([array]$imgPathList, [string]$iTextSharpPath) {
Unblock-File -Path $iTextSharpPath;
# [System.Reflection.Assembly]::LoadFrom($iTextSharpPath);
# Add-Type -Path $iTextSharpPath
Add-Type -LiteralPath $iTextSharpPath
# $error[0].Exception.GetBaseException().LoaderExceptions
$ext = ".pdf";
$pdfName = "{0}\{1}{2}" -f $imgPathList[0].DirectoryName, $imgPathList[0].BaseName, $ext;
Write-Output $imgPathList;
Write-Host ("{0}: decided PDF file name`n" -f $MyInvocation.MyCommand.Name);
Write-Output $pdfName;
$doc = New-Object iTextSharp.text.Document;
$pdfWriter = [iTextSharp.text.pdf.PdfWriter]::GetInstance($doc, [System.IO.File]::Create($pdfName))
$doc.Open()
# Get image files to add into PDF.
foreach ($path in $imgPathList) {
Write-Output $path;
Write-Output $path.GetType().FullName;
Write-Output $path.FullName.GetType().FullName;
Write-Host ("{0}: adding an image into a PDF...`n" -f $MyInvocation.MyCommand.Name);
$image = [iTextSharp.text.Image]::GetInstance($path.FullName);
# Scale images to each page size.
$doc.SetPageSize($image);
$doc.NewPage();
$image.SetAbsolutePosition(0, 0);
# $image.ScaleToFit($doc.PageSize.Width, $doc.PageSize.Height);
$image.Alignment = [iTextSharp.text.Image]::ALIGN_CENTER;
$doc.Add($image);
}
$doc.Close();
$pdfWriter.Close();
Write-Host ("{0}: completed adding images into a PDF`n" -f $MyInvocation.MyCommand.Name);
}
すると、この処理でエラーになります。
$image = [iTextSharp.text.Image]::GetInstance($path.FullName);
表示されたエラーメッセージはこんなやつです。さて、どうしたものか・・・。
"1" 個の引数を指定して "GetInstance" を呼び出し中に例外が発生しました: "file:///C:/.../Screenshot_20250101-212015_trimmed_compressed.jpg is not a recognized imageformat."
発生場所 C:\...\hogehoge.ps1:161 文字:5
+ $image = [iTextSharp.text.Image]::GetInstance($path.FullName);
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : IOException
原因を調べたのだが分からなかった。
先程出てきたエラーメッセージなのですが、原因が全く分からなかったんですよね。
まず、cwebpで圧縮する前のJPG画像ではそのエラーは発生せずに、問題なくPDFにまとめることが出来ました。(しかし、圧縮していないので、サイズの大きなPDFファイルが出来上がってしまいます・・・。)
「iTextsharp jpg is not a recognized imageformat」とかでググっても有力そうな情報が出てこない・・・。
そこで、cwebpでJPGに圧縮する時のパラメータを変化させれば対処出来るかどうかを調べました。今回JPG圧縮に使っていたcwebpコマンドの打ち方はこうでした。
cwebp -preset photo -metadata icc -sharp_yuv -q $quality -o $outputImg -progress -short $file.FullName; $updatedCount++;
調べたパラメータの組み合わせは以下の通りです。-preset
(特定の種類のソース素材に合わせて、事前定義された一連のパラメータを指定する。)と、-sharp_yuv
(より正確でシャープな RGB -> YUV 変換を使用する。)のパラメータが怪しかったので、そこだけで総当たりさせました。
-preset パラメータ | -sharp_yuv オン | -sharp_yuv オフ |
---|---|---|
default | ✕ | ✕ |
drawing | ✕ | ✕ |
photo | ✕ | ✕ |
picture | ✕ | ✕ |
icon | ✕ | ✕ |
text | ✕ | ✕ |
調査の結果は全滅・・・。どうやらCwebp自体を使って、圧縮したJPGをPDFとしてまとめることは止めておいた方が良さそうです。
System.Drawing名前空間を使って圧縮する。
そしたら、Cwebp以外のパッケージを使ってJPGファイルを圧縮していくことにしましょう。
次に、.NET FrameworkのSystem.Drawing名前空間を使って、JPGを作っていきます。Image.Save メソッドで保存する画像ファイルの圧縮率を指定出来るエンコーダを引数に渡せば、JPGを圧縮できそうです。
しかし、公式の解説はC#でしか書かれていないので、PowerShellとして直すのが面倒ですね・・・。はい、Gemini Code Assist君に書かせたら秒で終わりました。cwebpコマンドを打ち込んでいた行で、代わりに以下の関数を呼び出せばJPGに圧縮することが出来ました。
function Optimize-ImageWithCompressing {
param (
[string]$ImagePath,
[string]$OutputPath,
[int]$Quality # 0-100, 100 is highest quality (least compression)
)
# Load System.Drawing assembly
Add-Type -AssemblyName System.Drawing
try {
# Load the image
$image = [System.Drawing.Image]::FromFile($ImagePath)
# Create an EncoderParameter object for quality
$qualityParam = New-Object System.Drawing.Imaging.EncoderParameter(
[System.Drawing.Imaging.Encoder]::Quality, $Quality
)
# Get the JPEG codec
$jpegCodec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageDecoders() | Where-Object { $_.FormatID -eq [System.Drawing.Imaging.ImageFormat]::Jpeg.Guid }
# Create an EncoderParameters object
$encoderParams = New-Object System.Drawing.Imaging.EncoderParameters(1)
$encoderParams.Param[0] = $qualityParam
# Save the image with the specified quality
$image.Save($OutputPath, $jpegCodec[0], $encoderParams)
Write-Host "Image compressed and saved to: $OutputPath"
}
catch {
Write-Error "An error occurred: $_"
}
finally {
if ($image) {
$image.Dispose()
}
}
}
上記の処理で圧縮したJPGファイルを先程のPDF変換処理に渡せば、複数のJPGを1つのPDFにまとめることが出来るようになりました!
しかしなぁ、CwebpとiTextSharpの不和の原因は一体何だったのだろう・・・。調べたら沼りそうなのでやめておきます・・・。
まとめ
今回は、iTextSharpをPowerShellで叩いて、複数枚のJpg画像を一つのPDFにまとめる処理を実装する試みでした。
以下が、本記事のまとめです。
- Libwebpパッケージのcwebpツールで変換したJPG画像は、iTextSharpパッケージでPDFに変換しようとすると失敗する。
- System.Drawing名前空間で変換したJPG画像は、iTextSharpパッケージでPDFに変換することが可能である。
同じJPGファイルを出力したというのに、使えるファイルと使えないファイルに分かれるというのが何とも不思議でした。回避できて良かった。
その他のPowerShell関連の記事
おしまい
こんなこともあるんだなぁ
これでPDFにまとめられます。
以上になります!
コメント