You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MacOS Swift中为NSBitmapImageRep右侧拼接PNG图像的问题

在macOS命令行程序中拼接NSBitmapImageRep与PNG图像的正确实现

修复后的完整代码

private func applyOverlay(existingBitmap: NSBitmapImageRep) -> NSBitmapImageRep? {
    // 直接从文件加载PNG的NSBitmapImageRep,避免NSImage的缩放因子干扰
    guard let pngData = try? Data(contentsOf: URL(fileURLWithPath: mOverlayPath!)),
          let pngImgRep = NSBitmapImageRep(data: pngData) else {
        print("Failed to open overlay file: \(mOverlayPath!)")
        return nil
    }
    
    let pngPixelWidth = pngImgRep.pixelsWide
    let pngPixelHeight = pngImgRep.pixelsHigh
    let originalPixelWidth = existingBitmap.pixelsWide
    let originalPixelHeight = existingBitmap.pixelsHigh
    
    // 计算新位图尺寸:原宽度 + PNG宽度 + 右侧边距
    let newPixelWidth = originalPixelWidth + pngPixelWidth + TEXT_MARGIN_RIGHT
    let newPixelHeight = originalPixelHeight
    
    // 创建新位图,复用原图的色彩空间、位深等参数,避免格式不兼容
    guard let combinedImgRep = NSBitmapImageRep(
        bitmapDataPlanes: nil,
        pixelsWide: newPixelWidth,
        pixelsHigh: newPixelHeight,
        bitsPerSample: existingBitmap.bitsPerSample,
        samplesPerPixel: existingBitmap.samplesPerPixel,
        hasAlpha: existingBitmap.hasAlpha,
        isPlanar: existingBitmap.isPlanar,
        colorSpaceName: existingBitmap.colorSpaceName,
        bytesPerRow: 0,
        bitsPerPixel: 0
    ) else {
        print("Failed to create combined bitmap image rep")
        return nil
    }
    
    // 填充白色背景到整个新位图
    fillBackground(bitmap: combinedImgRep, color: .white)
    
    // 复制原图像到新位图左侧
    copyPixelData(dest: combinedImgRep, src: existingBitmap, destX: 0, destY: 0)
    
    // 计算PNG垂直居中的Y坐标,复制到指定位置
    let dstY = (originalPixelHeight - pngPixelHeight) / 2
    copyPixelData(dest: combinedImgRep, src: pngImgRep, destX: originalPixelWidth, destY: dstY)
    
    return combinedImgRep
}

/// 给位图填充背景色
private func fillBackground(bitmap: NSBitmapImageRep, color: NSColor) {
    NSGraphicsContext.saveGraphicsState()
    defer { NSGraphicsContext.restoreGraphicsState() }
    
    let context = NSGraphicsContext(bitmapImageRep: bitmap)!
    NSGraphicsContext.current = context
    
    color.setFill()
    NSRect(x: 0, y: 0, width: bitmap.pixelsWide, height: bitmap.pixelsHigh).fill()
}

/// 直接复制像素数据,彻底避免绘制缩放问题
private func copyPixelData(dest: NSBitmapImageRep, src: NSBitmapImageRep, destX: Int, destY: Int) {
    guard let destData = dest.bitmapData, let srcData = src.bitmapData else {
        return
    }
    
    let srcBytesPerRow = src.bytesPerRow
    let destBytesPerRow = dest.bytesPerRow
    let bytesPerPixel = src.samplesPerPixel * (src.bitsPerSample / 8)
    
    // 逐行复制像素数据
    for row in 0..<src.pixelsHigh {
        let srcRowOffset = row * srcBytesPerRow
        let destRowOffset = (destY + row) * destBytesPerRow + destX * bytesPerPixel
        
        memcpy(destData + destRowOffset, srcData + srcRowOffset, srcBytesPerRow)
    }
}

问题分析与修复点

1. PNG图像缩放异常的解决

  • 错误根源:原代码通过NSImage中转加载PNG,会自动应用系统缩放因子(Retina屏为2x),导致生成的NSBitmapImageRep像素尺寸被压缩;同时使用srcImg.size获取的是点尺寸(非像素尺寸),绘制时触发缩放。
  • 修复方式:直接从文件数据加载NSBitmapImageRep,跳过NSImage的中间转换,保留原始像素尺寸;改用直接复制像素数据的逻辑替代draw方法,彻底避免绘制缩放问题。

2. 编译报错的解决

  • 错误根源:Swift中NSCompositingOperation的枚举值为小写驼峰格式(如.copy、.sourceOver),而非Objective-C时代的NSCompositeSourceOver;原draw方法参数也存在不匹配问题。
  • 修复方式:改用直接复制像素的逻辑,无需依赖NSCompositingOperation,同时规避了绘制API的参数兼容问题。

3. 其他代码错误修复

  • 修正原copyImage函数中destHeight = destImg.pixelsWide的笔误,改为destImg.pixelsHigh,避免绘制区域错误。
  • 创建新位图时复用原图的色彩空间、位深等参数,避免色彩偏差或格式不兼容。
  • 新增白色背景填充逻辑,满足需求中“右侧添加白色背景像素”的要求。

内容的提问来源于stack exchange,提问作者Danny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 19:50:09