我很討厭上傳身分證照片這種重要個資,偏偏許多標榜電子化的申請流程 App,仍遵循古法,需要上傳身分證才能完成最後一哩路。對於不得不上傳的場合,唯一防止身分證影本被移作他用的手段是在照片加上「限 XXX 申請使用」的浮水印,雖說還是可能硬幹修圖消除浮水印,但讓壞人嫌麻煩挑其他軟柿子就算成功。(這跟「不必跑得比老虎快,跑得比其他人快就好」是一樣道理 😄)

在身分證加浮水印這種普遍需求,之前好像有看過現成的 App、軟體或線上服務可以用。但畢竟身分證是重要個人隱私,這讓我的資安潔癖發作,總擺脫不了程式可能有處理瑕疵或包含惡意程式導致資料外流的憂慮。與其擔心這擔心那,最保險的做法當然是自己來,用 Word 或繪圖軟體在圖片加浮水印不是難事,但身為資深開發者,自己寫支小程式一秒搞定才是王道啊。

於是,我花了半小時,用 26 行 PowerShell 打造了一個小工具,輸入照片檔案路徑跟浮水印文字,瞬間就能得到浮水印加好加滿的身分證影本,以 .watermark.jpg 副檔名另存到來源圖檔所在資料夾:(用抽屜挖到的 2002 TechEd 學員證充當身分證示範)

為了讓小工具應用上更靈活,我加了一些可調參數:大圖自動縮小到指定寬度(預設 640 像素)、文字大小(預設 18)、透明度(預設 0.25 = 25%,愈低愈透明)、浮水印文字水平及垂直間隔(預設 10 像素)。完整程式如下:
【2022-08-08 更新】因 .NET 元件與 PowerShell 工作目錄可能不同步,程式增加 Resolve-Path 轉絕對路徑,以防止輸入相對路徑時找不到圖檔。參考:PowerShell 與 .NET 的工作目錄差異
【2022-08-28 補充】.ps1 記得要存成 UTF-8 with BOM,否則易有奇怪問題

param (
    [Parameter(Mandatory = $true)][string]$imgPath,
    [Parameter(Mandatory = $true)][string]$watermarkText,
    [int]$width = 640,
    [int]$fontSize = 18,
    [float]$opacity = 0.25,
    [int]$spacing = 10
) 
$ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
$imgPath = (Resolve-Path $imgPath).Path # 轉為絕對路徑
$src = [System.Drawing.Bitmap]::FromFile($imgPath)
$height = $src.Height * $width / $src.Width
$resized = New-Object System.Drawing.Bitmap -ArgumentList $src, $width, $height
$semiTransparentColor = [System.Drawing.Color]::FromArgb([Math]::Floor(255 * $opacity), 255, 255, 255)
$brush = New-Object System.Drawing.SolidBrush -ArgumentList $semiTransparentColor
$g = [System.Drawing.Graphics]::FromImage($resized)
$font = New-Object System.Drawing.Font -ArgumentList "微軟正黑體", $fontSize
[System.Drawing.SizeF] $size = $g.MeasureString($watermarkText, $font)
$wmWidth = $size.Width + $spacing
$wmHeight = $size.Height + $spacing
for ($y = 0; $y * $wmHeight -le $height; $y ++) {
    for ($x = 0; $x * $wmWidth -le $width; $x ++) {
        $g.DrawString($watermarkText, $font, $brush, $x * $wmWidth, $y * $wmHeight)
    }
}
$resized.Save([IO.Path]::ChangeExtension($imgPath, "Watermark.jpg"), [System.Drawing.Imaging.ImageFormat]::Jpeg)

調整參數示範:

展示影片

會寫程式很棒吧!

Example of adding watermarks to photo image by PowerShell.


Comments

# by 小黑

太棒了

# by ChrisWei

遇到一件很怪的狀況,我一開始先在 Windows 內建的 Powershell ISE 編輯黑大這個 powershell,然後在 ISE 下執行會出現 以 "1" 引數呼叫 "FromFile" 時發生例外狀況: ".\idc 錯誤訊息,如果換成在 powershell (Windows Terminal) 下操作,也是遇到同樣錯誤,後來仔細看黑大的視頻發現,黑大是在 vscode 上跑這個 powershell 的,因此我換成在 vscode 上跑,還真的就不會出現錯誤,但覺得十分的怪異,還在研究當中造成問題的差異~

# by ChrisWei

我後來找到問題了,原來 ISE 或是 powershell core 必須是在管理者模式下,否則跑這支 shell 會遇到上述錯誤訊息,也跟黑大回報一聲,再次感謝,這支程式很實用~

# by Jeffrey

to ChrisWei,感謝通報。嚴格來說是程式的 Bug,.NET 與 PowerShell 存在工作目錄可能不同步的狀況,我加了一行 Resolve-Path 轉絕對路徑以防止輸入相對路徑時找不到圖檔,相關技術細節可參見內容。

# by WuPig

請問,我在 Windows 10 PowerShell 視窗內執行(以系統管理者身份執行), 沒有報錯任何訊息... 但圖片也沒有加上浮水印字...查看圖片檔案的[修改日期] 也確認沒有改變(表示該圖並沒有被更新過) , 感謝您...

# by Jeffrey

to WuPig, 浮水印圖檔不會覆寫原檔,會以 .watermark.jpg 副檔名另存在來源圖檔目錄。(可參考展示影片)

# by WuPig

感謝指導,已經有正常出現了... 真的很實用!!

# by TLANX

請問 ChangeExtension($imgPath, "Watermark.jpg") 會報錯誤 :字串遺漏結尾字元: "。 後來改為 單引號 ChangeExtension($imgPath, 'Watermark.jpg') 就正常了 請問這是 powershell 要設定哪裡嗎?

# by Jeffrey

to TLANX,感覺是打錯字或輸入過程符號出錯。雙引號跟單引號差在雙引號時會替換 $x 等變數,而單引號是固定字串內容,不會替換變數。https://docs.microsoft.com/zh-tw/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.2#double-quoted-strings 因此 "Watermark.jpg" 跟 'Watermark.jpg' 在 PowerShell 的意義是相同的,你可以再試試換回 " 看問題會重現嗎?若會,可提供重現問題的程式碼讓大家幫看。

# by Allen

請問出現以下錯誤,建議如何調整? PS Z:\> .\Add-WaterMark.ps1 idcard.jpg test 位於 Z:\Add-WaterMark.ps1:27 字元:66 + ... mgPath, "Watermark.jpg"), [System.Drawing.Imaging.ImageFormat]::Jpeg) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 字串遺漏結尾字元: "。 + CategoryInfo : ParserError: (:) [], ParseException + FullyQualifiedErrorId : TerminatorExpectedAtEndOfString 擷圖如下: https://drive.google.com/file/d/11b92oE7Zpq8ACi6wRb35JtjpG3MrCm4n/view

# by Jeffrey

to TLANX, Allen, 請參考這篇 https://blog.darkthread.net/blog/vscode-ps-encoding/ ,將 .ps1 檔案的編碼改為 UTF-8 with BOM 問題詳解: https://blog.darkthread.net/blog/ps1-encoding-issue/

# by Allen

Hi Jeffrey, 透過您提供的方式調整,已可以正常運作,謝謝. BR, Allen

# by TLANX

to jeffrey 經由您的解說,長知識了,謝謝。

Post a comment