Powershellで縦長画像を3分割する。

コンピュータ
PowerShellのあれこれ

今回はPowershellの四則演算を駆使して画像分割の位置出しをしたいと思います。

今日のpc用ディスプレイの縦横比は16:9が多く、比率の異なる雑誌などのスキャン画像を表示させようとすると、当然上手く収まりません。縦長の画像を縦に分割し数回に分けて表示する方法などがあります。以前作成したイメージビューワでは縦に2分割する機能を付けました。内容によっては分割には向かない画像もありすが、雑誌のスキャン画像などでは分割された画像が画面サイズに合わせて引き延ばされ、大きく表示されるようになりました。ただ、2分割の場合画像の高さを基準にディスプレイに合わせるためどうしても左右に余白が出来てしまいます。

今回は縦長画像を3回に分け、元画像の幅を基準にディスプレイの幅に合わせて表示させることで、分割画像が画面いっぱいに表示できる切り出し位置を計算したいと思います。

元画像のサイズはB5判の雑誌を想定して幅182高さ257とします。

 

初めに分割後高さを幅を基準として16:9になるように計算します。

    $width = Read-Host("幅を入力")
    $height = Read-Host("高さを入力")

    $dest_h = [int](($width / 16) * 9)
    Write-Host ("16:9の比率で幅{0}を基準にする場合の高さは{1}" -f $width, $dest_h)

結果

幅を入力: 182
高さを入力: 257
16:9の比率で幅182を基準にする場合の高さは102

計算した高さが3分割出来るか確認


    $min_h = ([int]($height / 3))
    Write-Host ("分割後の高さの最小値は{0}" -f $min_h)
    if ($dest_h -lt $min_h) {
        Write-Host ("最小値を下回る為分割出来ません")
        exit
    }
    

元画像の高さ÷3を分割後の高さが下回る場合は、元画像を網羅することが出来ないのでスクリプトを中止します。

 

1段目のy座標を計算

    # y座標を保存する配列
    $y = [int[]]::new(3)

    # 1段目
    $y[0] = 0
    Write-Host ("1段目のy:{0}({0}-{1})" -f $y[0], ($y[0] + $dest_h - 1))

結果

1段目のy:0(0-101)

1段目の画像は元画像の上辺を基準にしますのでy座標は0が固定値にしています。

    # 2段目
    $th = ([int]($height / 2)) # 全体の中心
    $ph = ([int]($dest_h / 2)) # 分割後の高さ半分
    $y[1] = $th - $ph + 1
    Write-Host ("2段目のy:{0}({0}-{1})" -f $y[1], ($y[1] + $dest_h - 1))

結果

2段目のy:78(78-179)

2段目の画像は元画像の高さの中心を基準に分割後の高さの半分を差引y座標を計算しています。

    # 3段目
    $y[2] = $height - $dest_h + 1
    Write-Host ("3段目のy:{0}({0}-{1})" -f $y[2], ($y[2] + $dest_h - 1))

結果

3段目のy:156(156-257)

3段目の画像は元画像の底辺から分割後の高さを差引y座標計算しています。

1段目と2段目の重なる部分が(78-101)で24、2段目と3段目が重なる部分が(156-179)で24と概ね良いバランスです。

位置出し的にはこれで良いのですが、せっかくなので画像ファイルの3分割スクリプトを作ってみます。

スクリプト

<#
.SYNOPSIS
 画像ファイルを3分割

#>

using namespace System.IO
using namespace System.Drawing

Set-StrictMode -Version Latest
$ErrorActionPreference = "STOP"


# 分割するy座標の位置出し
function GetStartPosition
{
    param(
        [int]
        $width,
        [int]
        $height
    )

    $dest_h = [int](($width / 16) * 9)

    $min_h = ([int]($height / 3))
    if ($dest_h -lt $min_h) {
        Write-Host ("最小値を下回る為分割出来ません")
        exit
    }
    

    # y座標を保存する配列
    $y = [int[]]::new(3)

    # 1段目
    $y[0] = 0

    # 2段目
    $th = ([int]($height / 2)) # 全体の真ん中
    $ph = ([int]($dest_h / 2)) # 分割後の真ん中
    $y[1] = $th - $ph + 1

    # 3段目
    $y[2] = $height - $dest_h + 1

    return $y
}

#画像を縦に3分割
function SplitImage
{
    param (
        [string]
        $Src,
        [string]
        $Dst = (Resolve-Path ".").Path
    )

    # 画像ファイルの読み込み
    if ($fs = [FileStream]::new($Src,[FileMode]::Open,[FileAccess]::Read)) {
        try {
            $img = [Image]::FromStream($fs)
        } finally {
            $fs.Close()
        }
    }


    Write-Host ("画像の幅:{0}高さ:{1}" -f $img.Width, $img.Height)

    $y = GetStartPosition $img.Width $img.Height

    $w = $img.width
    $h = [int](($w / 16) * 9)

    $out = [Bitmap]::new($w, $h)
    $gra = [Graphics]::FromImage($out)


    for($i = 0; $i -lt 3; $i++) {

        $s = [Rectangle]::new(0, $y[$i], $w, $h)
        $d = [Rectangle]::new(0, 0, $w, $h)

        $gra.DrawImage($img, $d, $s, 2)

        $out_path = Join-Path $Dst ((Get-Item $Src).BaseName + ("-{0}.png" -f ($i+1)))

        $out.Save($out_path, "Png") 
    }



    $gra.Dispose()
    $out.Dispose()
    $img.Dispose()
}

function Main
{
    SplitImage (Resolve-Path ".\0001.png").Path

}
Main

元画像:0001.png

分割後画像1段目:0001-1.png

アインシュタイン先生のお言葉が途中できれていますが、

 

分割後画像2段目:0001-2.png

2枚目の画像で全文が見えます。各画像の重複部分がいい仕事をしています。

分割後画像3段目:0001-3.png

横書きの文書であれば結構いい感じになります。。

今回は縦長画像を画面いっぱいに表示するように16:9になるように3分割してみましたが、画像の内容によってはカット後の高さが足りない場合もあります。その場合、16:9にこだわらず縦に2分割し、切れ目を補完する為中間用に1枚画像を切り出すと良い感じになります。
その中間画像は2分割した画像の上下半分ずつにまたがるように切り出します。先の3分割でも重複部分を取りましたが、こちらの方法は重複部分がさらに大きくなるようになります。

<#
.SYNOPSIS
 画像ファイルを2.5分割

#>

using namespace System.IO
using namespace System.Drawing

Set-StrictMode -Version Latest
$ErrorActionPreference = "STOP"


# 分割するy座標の位置出し
function GetStartPosition
{
    param(
        [int]
        $width,
        [int]
        $height
    )

    $dest_h = [int]($height / 2)

    # y座標を保存する配列
    $y = [int[]]::new(3)

    # 1段目
    $y[0] = 0

    # 2段目
    $y[1] = [int]($height / 4)

    # 3段目
    $y[2] = [int]($height / 2)

    return $y
}

#画像を縦に2.5分割
function SplitImage
{
    param (
        [string]
        $Src,
        [string]
        $Dst = (Resolve-Path ".").Path
    )

    # 画像ファイルの読み込み
    if ($fs = [FileStream]::new($Src,[FileMode]::Open,[FileAccess]::Read)) {
        try {
            $img = [Image]::FromStream($fs)
        } finally {
            $fs.Close()
        }
    }


    Write-Host ("画像の幅:{0}高さ:{1}" -f $img.Width, $img.Height)

    $y = GetStartPosition $img.Width $img.Height

    $w = $img.width
    $h = [int]($img.Height / 2)

    $out = [Bitmap]::new($w, $h)
    $gra = [Graphics]::FromImage($out)


    for($i = 0; $i -lt 3; $i++) {

        $s = [Rectangle]::new(0, $y[$i], $w, $h)
        $d = [Rectangle]::new(0, 0, $w, $h)

        $gra.DrawImage($img, $d, $s, 2)

        $out_path = Join-Path $Dst ((Get-Item $Src).BaseName + ("-{0}.png" -f ($i+1)))

        $out.Save($out_path, "Png") 
    }



    $gra.Dispose()
    $out.Dispose()
    $img.Dispose()
}

function Main
{
    SplitImage (Resolve-Path ".\0001.png").Path

}
Main

以上

コメント