惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
爱范儿
爱范儿
H
Help Net Security
V
Visual Studio Blog
J
Java Code Geeks
Stack Overflow Blog
Stack Overflow Blog
Microsoft Security Blog
Microsoft Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
MyScale Blog
MyScale Blog
The Cloudflare Blog
Martin Fowler
Martin Fowler
D
Docker
腾讯CDC
F
Fortinet All Blogs
雷峰网
雷峰网
GbyAI
GbyAI
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Blog — PlanetScale
Blog — PlanetScale
Engineering at Meta
Engineering at Meta
博客园 - 聂微东
博客园 - 叶小钗

博客园 - SHARP-EYE

Processing在P2D模式下使用 tint() 方法的注意事项 · 只对 image() 和纹理有效 Processing 的 sketch.properties 机制以及主程序与文件夹解耦的具体细节 Processing P2D 模式鼠标约束 · 限制在窗口内 · 技术报告 Kimi Code CLI 系统指令的摸索 以及 开发实战经验分享 开源重置 沙丘2 游戏 · Dune Legacy 中文本地化与渲染技术说明 基于 Irrlicht 和 WASAPI 的 Simple Audio Visualization 技术开发报告 AI Coding越来越强,我们还有必要学Processing吗? · 创意编程 嵌入式脚本语言全解析:从Lua到Wren,游戏与IoT开发的未来选型指南 PixelMentor:一个开源网站 · 调用AI视觉能力分析图片 · 提供影视后期修改意见 基于 Vanilla JS 构建高性能可视化节点创意展示编辑器 (CNE) 的技术实践与深度解析 · Creative Node Editor 关于OpenClaw部署在Ubuntu上的经验小结 P3DE (Processing 3D Editor) 三维场景编辑器 · 软件白皮书 · 基于 v0.4.8 Windows 批量文件操作风险指南 · 不完整小结 [深度复盘] 构建高性能实时弹幕系统:Node.js + Socket.io 架构设计与生产环境部署实战 如何调用CMD实现多个同类文件合并的研究 · 二进制 · 依次 · 文本图像视频音频 Processing (Java) 中实现2D任意图形的鼠标悬停检测 · 2D射线检测 · 模拟按钮 · 点击事件 PowerShell实现全屏七彩渐变 · 呼吸 · 屏保 浅谈processing-java.exe应用程序的使用(与PowerShell的联合) 使用Windows任务计划程序实现每天更换一张Processing创意桌面壁纸 CMD批处理脚本+VBScript脚本+Potplayer 实现文件夹内所有视频的截图任务(指定时间点) 批处理脚本(.bat)实现实时监测文件夹并执行命令 [假设有新文件则拷贝到远程文件夹内] PowerShell开发游戏 · 打蜜蜂 Powershell实现圆缩小放大 (实时刷新窗口)
PowerShell开发小工具 · 四张照片拼成一张
SHARP-EYE · 2025-03-01 · via 博客园 - SHARP-EYE

小工具的设计与实现------选四张照片拼成一张照片。

很经典的应用情景,市面上有很多类似的小软件,特别是手机应用。为了方便学习巩固PowerShell,今天笔者使用它来实现。

【设计思路】

  • 选择四张符合要求的照片或图片[.jpg] [.png]
  • 准备画布,计算其子区域(画布四分之一)宽高比例 $subAspectRatio,然后与每张照片宽高比 $imgAspectRatio 作对比,在比例不变(不拉伸变形)的情况下尽可能铺满。
  • 缩放照片,并拼合在一起
  • 保存该画布,导出成图片格式存入硬盘中。

【设计要点】

计算子画布(子图区域),也就是四分之一画布大小的宽高比,与待拼合的照片的宽高比,两者进行比较。即subAspectRatioimgAspectRatio

  • 如果照片较宽,则按宽度缩放
  • 如果照片较高,则按高度缩放

如下图所示:

如果理想状态,每张照片能铺满子图区域,如下图:

而有一种特殊情况,子区域是见方的(1:1宽高), 而每张图片也是见方的:

这两种情况直接等比缩放即可,而更多的情况是,子区域宽高比和每张照片的宽高比都不同,比如:

会发现有很多空白区域,没错,正是做了缩放处理。

算法实现:

# 计算子图宽高度
子图宽度 = 最终图片宽度 / 2
子图高度 = 最终图片高度 / 2

# 计算绘制位置
子图x坐标 = (每张照片[标号0 1 2 3] % 2) * 子图宽度
子图y坐标 = [math]::Floor(每张照片[标号0 1 2 3] / 2) * 子图高度

# 计算图片的宽高比
照片的宽高比 = 照片宽度 / 照片高度
子图的宽高比 = 子图宽度 / 子图高度

if (照片的宽高比 > 子图的宽高比) {
    # 图片较宽,按宽度缩放
    相比比例 = 子图宽度 / 照片宽度
    待绘制图片宽度 = 子图宽度
    待绘制图片高度 = [int](照片高度 * 相比比例)
    待绘制图片y坐标 = 子图y坐标 + (子图宽度 - 待绘制图片高度) / 2
    待绘制图片x坐标 = 子图x坐标
}
else {
    # 图片较高,按高度缩放
    相比比例 = 子图宽度 / 照片高度
    待绘制图片高度 = 子图高度
    待绘制图片宽度 = [int](照片宽度 * 相比比例)
    待绘制图片x坐标 = 子图x坐标 + (子图宽度 - 待绘制图片宽度) / 2
    待绘制图片y坐标 = 子图y坐标
}

# 创建缩放后的矩形区域
destRect = (
    待绘制图片x坐标,
    待绘制图片y坐标,
    待绘制图片宽度,
    待绘制图片高度
)

#缩放绘制
[将img即每张照片绘制到destRect区域内!]

【实际脚本】

  •  导入程序集 System.Drawing ,负责绘制任务
  •  定义参数,如文件夹地址输出文件名最终图片宽度和高度
  •  获取文件夹内的照片(四张jpg或png)
  •  创建目标画布
  •  读取每张照片[遍历],按照上述算法进行计算,绘制在目标画布上
  •  保存目标画布,导出成.jpg格式的照片保存至文件夹内
# 加载必要的.NET绘图程序集(需确保系统已安装.NET Framework)
try {
    Add-Type -AssemblyName System.Drawing
    Write-Host "成功加载必要的.NET绘图程序集。"
}
catch {
    Write-Error "加载.NET绘图程序集时出现错误: $_"
    return
}

# 定义输入输出参数
$inputFolder = $PSScriptRoot  # 使用 $PSScriptRoot 变量获取脚本所在文件夹
$outputFile = "combined.jpg"  # 输出文件名
$targetWidth = 2000   # 最终图片宽度
$targetHeight = 2000  # 最终图片高度

# 从指定文件夹中获取前四张jpg或png图片
Write-Host "正在从 $inputFolder 文件夹中查找前四张jpg或png图片..."
$inputFiles = Get-ChildItem -Path $inputFolder -File | Where-Object { $_.Extension -match '\.(jpg|png)' } | Select-Object -First 4 | ForEach-Object { $_.FullName }

# 检查是否找到四张图片
if ($inputFiles.Count -ne 4) {
    Write-Error "未在指定文件夹 $inputFolder 中找到四张jpg或png图片,仅找到 $($inputFiles.Count) 张。"
    return
}
else {
    Write-Host "成功找到四张图片:"
    foreach ($file in $inputFiles) {
        Write-Host "- $file"
    }
}

# 创建目标画布
Write-Host "正在创建目标画布..."
try {
    $combinedBitmap = New-Object System.Drawing.Bitmap($targetWidth, $targetHeight)
    $graphics = [System.Drawing.Graphics]::FromImage($combinedBitmap)
    $graphics.Clear([System.Drawing.Color]::White)
    Write-Host "成功创建目标画布。"
}
catch {
    Write-Error "创建目标画布时出现错误: $_"
    return
}

# 计算每个子图区域尺寸
$subWidth = $targetWidth / 2
$subHeight = $targetHeight / 2
Write-Host "已计算每个子图区域尺寸:宽度 $subWidth,高度 $subHeight。"

# 遍历处理每张图片
for ($i = 0; $i -lt 4; $i++) {
    Write-Host "正在处理图片 $($inputFiles[$i])..."
    try {
        $img = [System.Drawing.Image]::FromFile($inputFiles[$i])

        # 计算绘制位置
        $x = ($i % 2) * $subWidth
        $y = [math]::Floor($i / 2) * $subHeight

        # 计算图片的宽高比
        $imgAspectRatio = $img.Width / $img.Height
        $subAspectRatio = $subWidth / $subHeight

        if ($imgAspectRatio -gt $subAspectRatio) {
            # 图片较宽,按宽度缩放
            $scale = $subWidth / $img.Width
            $newWidth = $subWidth
            $newHeight = [int]($img.Height * $scale)
            $offsetY = $y + ($subHeight - $newHeight) / 2
            $offsetX = $x
        }
        else {
            # 图片较高,按高度缩放
            $scale = $subHeight / $img.Height
            $newHeight = $subHeight
            $newWidth = [int]($img.Width * $scale)
            $offsetX = $x + ($subWidth - $newWidth) / 2
            $offsetY = $y
        }

        # 创建缩放后的矩形区域
        $destRect = New-Object System.Drawing.Rectangle(
            $offsetX, $offsetY,
            $newWidth,
            $newHeight
        )

        # 高质量缩放绘制
        $graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
        $graphics.DrawImage($img, $destRect)

        $img.Dispose()
        Write-Host "成功处理图片 $($inputFiles[$i])。"
    }
    catch {
        Write-Error "处理图片 $($inputFiles[$i]) 时出现错误: $_"
    }
}

# 保存输出文件
Write-Host "正在保存拼接后的图片到 $outputFile..."
try {
    $combinedBitmap.Save($outputFile, [System.Drawing.Imaging.ImageFormat]::Jpeg)
    Write-Host "成功保存拼接后的图片到 $outputFile。"
}
catch {
    Write-Error "保存拼接后的图片时出现错误: $_"
}

# 释放资源
Write-Host "正在释放资源..."
try {
    $graphics.Dispose()
    $combinedBitmap.Dispose()
    Write-Host "成功释放资源。"
}
catch {
    Write-Error "释放资源时出现错误: $_"
}

Write-Host "图片拼接完成,输出文件:$outputFile"

示例1:

给出四张示例照片或图片:

        

目标照片大小:2000 * 2000

将四张照片放入一个文件夹,然后将上述脚本保存成 .ps1 也放入其中,右键 - 使用PowerShell运行,之后得到同文件夹下的新照片:

示例2:

给出四张示例照片或图片:

     

目标照片大小:1600* 800

将四张照片放入一个文件夹,然后将上述脚本保存成 .ps1 也放入其中,右键 - 使用PowerShell运行,之后得到同文件夹下的新照片:

示例3:

给出四张示例照片或图片:

     

目标照片大小:2000 * 1450

将四张照片放入一个文件夹,然后将上述脚本保存成 .ps1 也放入其中,右键 - 使用PowerShell运行,之后得到同文件夹下的新照片:

示例4:

给出四张示例照片或图片:

     

目标照片大小:2000 * 2000

将四张照片放入一个文件夹,然后将上述脚本保存成 .ps1 也放入其中,右键 - 使用PowerShell运行,之后得到同文件夹下的新照片:

示例5:

给出四张示例照片或图片(四张规格大小一致,都是见方1:1):

     

目标照片大小:1600 * 1600

将四张照片放入一个文件夹,然后将上述脚本保存成 .ps1 也放入其中,右键 - 使用PowerShell运行,之后得到同文件夹下的新照片:

【结尾】

还是挺方便的,如果没有其他工具在手的话。当然,还有很多点值得优化,如:

  1. 图片的格式问题,支持更多格式
  2. 拼合的顺序,可以让用户自定义
  3. 可以让用户看到运行状态,方便调试修改
  4. 设配置文件,如目标照片大小、文件夹地址、生成文件类型等参数,可以更好的管理

总之,这是一次很好的开发经历,虽然脚本不算复杂,但是很实用,能充分体现脚本灵活易修改维护的特点。PowerShell,一直伴随你左右。。。