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

如何将MLMultiArray/CGImage导入PKCanvasView供用户编辑?

解决PKCanvasView用MLMultiArray/CGImage初始化可编辑绘图的问题

先处理你遇到的直接错误

你调用PKDrawing.init(from: dataOut)报错,是因为这个方法的参数要求是Decoder类型(比如JSONDecoder),而非直接传入原始二进制Data。更关键的是,你的dataOut是MLMultiArray的原始数值数据,根本不是PKDrawing的归档数据,所以这个方法从一开始就不适用,得换实现思路。

正确的实现思路

你的核心需求是把语义分割的掩码(0/1的MLMultiArray)转换成PKCanvasView上可编辑的内容,以下是两种可行方案:

方案1:将掩码转为CGImage作为画布背景

把MLMultiArray转换成带高亮/透明效果的CGImage,设置为PKCanvasView的背景,用户可以在背景上直接绘制修正掩码。

代码示例:

import SwiftUI
import PencilKit

struct PencilView: UIViewRepresentable {
    var inputImage: UIImage
    var rawArray: MLMultiArray
    var colorModel: ColorModel
    @Binding var drawing: PKDrawing
    
    func makeUIView(context: Context) -> PKCanvasView {
        let canvasView = PKCanvasView()
        canvasView.drawingPolicy = .pencilOnly
        canvasView.tool = PKInkingTool(.pen, color: UIColor(colorModel.overlayColor), width: 15)
        
        // 将MLMultiArray转换为掩码CGImage并设置为背景
        if let maskImage = convertMLMultiArrayToMaskImage() {
            canvasView.backgroundColor = .clear
            let imageView = UIImageView(image: maskImage)
            imageView.frame = canvasView.bounds
            imageView.contentMode = .scaleAspectFit
            canvasView.addSubview(imageView)
            canvasView.sendSubviewToBack(imageView)
        }
        
        canvasView.drawing = drawing
        return canvasView
    }
    
    func updateUIView(_ uiView: PKCanvasView, context: Context) {
        uiView.drawing = drawing
    }
    
    // MLMultiArray转掩码CGImage核心方法
    private func convertMLMultiArrayToMaskImage() -> UIImage? {
        guard let cgImage = inputImage.cgImage else { return nil }
        let width = cgImage.width
        let height = cgImage.height
        
        guard rawArray.count == width * height else { return nil }
        
        let dblPtr = rawArray.dataPointer.bindMemory(to: Double.self, capacity: width * height)
        let buffer = UnsafeBufferPointer(start: dblPtr, count: width * height)
        
        guard let colorSpace = CGColorSpace(name: CGColorSpace.sRGB) else { return nil }
        let bytesPerPixel = 4
        let bytesPerRow = width * bytesPerPixel
        let bitsPerComponent = 8
        
        guard let context = CGContext(data: nil,
                                      width: width,
                                      height: height,
                                      bitsPerComponent: bitsPerComponent,
                                      bytesPerRow: bytesPerRow,
                                      space: colorSpace,
                                      bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue) else { return nil }
        
        // 遍历像素,为1的区域设置半透明高亮色,0的区域透明
        for y in 0..<height {
            for x in 0..<width {
                let index = y * width + x
                let value = buffer[index]
                let color = value == 1 ? UIColor(colorModel.overlayColor).withAlphaComponent(0.5) : UIColor.clear
                
                var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
                color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
                
                let pixel: [UInt8] = [
                    UInt8(alpha * 255),
                    UInt8(red * 255),
                    UInt8(green * 255),
                    UInt8(blue * 255)
                ]
                context.data?.advanced(by: y * bytesPerRow + x * bytesPerPixel).copyMemory(from: pixel, count: bytesPerPixel)
            }
        }
        
        guard let maskCGImage = context.makeImage() else { return nil }
        return UIImage(cgImage: maskCGImage)
    }
}

// 假设你的ColorModel定义
struct ColorModel {
    let overlayColor: Color
}

方案2:将掩码区域转为PKStroke添加到PKDrawing

如果需要让掩码本身成为可编辑的笔触(用户可直接擦除/修改掩码笔触),可以把MLMultiArray中1的区域转换成PKStroke,添加到PKDrawing中。

简化代码示例(实际需优化轮廓提取逻辑):

private func convertMLMultiArrayToPKDrawing() -> PKDrawing {
    guard let cgImage = inputImage.cgImage else { return PKDrawing() }
    let width = cgImage.width
    let height = cgImage.height
    
    guard rawArray.count == width * height else { return PKDrawing() }
    
    let dblPtr = rawArray.dataPointer.bindMemory(to: Double.self, capacity: width * height)
    let buffer = UnsafeBufferPointer(start: dblPtr, count: width * height)
    
    var strokes = [PKStroke]()
    let tool = PKInkingTool(.pen, color: UIColor(colorModel.overlayColor), width: 1)
    
    // 简化处理:为每个1的像素生成微小笔触(建议替换为轮廓提取逻辑提升性能)
    for y in 0..<height {
        for x in 0..<width {
            let index = y * width + x
            if buffer[index] == 1 {
                let point = CGPoint(x: x, y: y)
                let path = PKStrokePath(controlPoints: [PKStrokePoint(location: point, timeOffset: 0, size: CGSize(width: 1, height: 1), opacity: 1, force: 1, azimuth: 0, altitude: 0)], creationDate: Date())
                let stroke = PKStroke(tool: tool, path: path)
                strokes.append(stroke)
            }
        }
    }
    
    return PKDrawing(strokes: strokes)
}

在makeUIView中调用该方法:

canvasView.drawing = convertMLMultiArrayToPKDrawing()

注意事项

  • 不要直接绑定PKCanvasView实例到@Binding,应绑定PKDrawing对象,UIViewRepresentable更适合通过drawing绑定同步编辑状态。
  • 方案1适合作为背景参考,用户在其上绘制修正;方案2让掩码成为可编辑笔触,但大量小笔触会影响性能,建议使用Core Graphics的轮廓检测优化。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 00:15:02