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

Swift中如何缩放Canvas且不改变内部像素数量适配像素画开发

固定逻辑分辨率Canvas缩放实现方案

你需要将逻辑画布尺寸和显示尺寸做分层处理,不需要修改原有绘制逻辑,仅需添加上下文缩放和手势坐标转换即可实现需求,具体实现如下:

核心实现步骤

  • 定义固定的逻辑画布尺寸(比如你需要的32x32),所有内部绘制、点坐标存储都基于这个尺寸
  • 用GeometryReader获取可用于显示的最大尺寸,计算显示缩放比例
  • 绘制时先对Canvas上下文应用缩放变换,同时设置插值模式为无插值,保证放大后像素边缘清晰不模糊
  • 手势获取的屏幕坐标反向除以缩放比例,转换为逻辑坐标后再存入线条数组

修改后的完整代码

// 定义固定逻辑画布尺寸,所有内部像素计算都基于这个值
let logicalCanvasSize: CGFloat = 32

var body: some View {
    GeometryReader { proxy in
        // 计算缩放比例:让32x32的画布等比缩放到撑满屏幕宽度
        let displayScale = proxy.size.width / logicalCanvasSize
        
        Canvas { context, size in
            // 禁用插值,保证放大后是像素硬边缘
            context.interpolationQuality = .none
            // 对上下文应用缩放,后续所有32x32范围的绘制都会自动放大到显示尺寸
            context.scaleBy(x: displayScale, y: displayScale)
            
            // 原有绘制逻辑完全不需要修改
            for line in lines {
                let path = engine.createPath(for: line.points)
                context.stroke(path, with: .color(line.color), style: StrokeStyle(lineWidth: line.lineWidth, lineCap: .round, lineJoin: .round))
            }
        }
        // 画布显示尺寸设置为逻辑尺寸*缩放比例,刚好撑满宽度
        .frame(width: logicalCanvasSize * displayScale, height: logicalCanvasSize * displayScale)
        .gesture(
            DragGesture(minimumDistance: 0, coordinateSpace: .local)
                .onChanged({ value in
                    // 手势拿到的是显示层坐标,转换为逻辑坐标再存储
                    let displayPoint = value.location
                    let newPoint = CGPoint(
                        x: displayPoint.x / displayScale,
                        y: displayPoint.y / displayScale
                    )
                    
                    if value.translation.width + value.translation.height == 0 {
                        lines.append(Line(points: [newPoint], color: selectedColor, lineWidth: selectedLineWidth))
                    } else {
                        let index = lines.count - 1
                        lines[index].points.append(newPoint)
                    }
                })
                .onEnded({ value in
                    if let last = lines.last?.points, last.isEmpty {
                        lines.removeLast()
                    }
                })
        )
    }
    // 让GeometryReader撑满屏幕宽度
    .frame(maxWidth: .infinity, alignment: .center)
}

原方案失效原因说明

  • 直接给Canvas添加frame(32,32)再调用scaleEffect的方案,会导致手势坐标空间没有同步转换,绘画位置会发生错位,且系统默认会对缩放后的View做抗锯齿处理,丢失像素画的硬边缘效果
  • 使用aspectRatio的方案会直接放大Canvas的实际渲染尺寸,内部绘制坐标也会同步放大,相当于提升了画布的物理分辨率,不符合保留内部32x32像素数量的需求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 15:06:04