带有签名的图像未按预期定位

2023-12-01

我正在开发一个 SwiftUI 项目,我想将签名图像叠加在另一个图像之上,并允许用户操纵签名图像的位置、比例和旋转。但是,我在签名图像的定位方面遇到了问题,并且它没有出现在我期望的位置。

我尝试设置用于拖动、缩放和旋转签名图像的手势,但它的行为不符合预期。签名图像似乎偏移(将其保存在左上角)并且缩放不正确(无论缩放如何,它总是非常小)。

有人可以帮我确定是什么导致了签名图像的定位和缩放问题吗?

这是相关代码:

struct SignatureAddingView: View {
    
    @State var scannedImage: UIImage = UIImage()
    
    @State private var scale: CGFloat = 1
    @State private var scaleAnchor: UnitPoint = .center
    @State private var lastScale: CGFloat = 1
    @State private var offset: CGSize = .zero
    @State private var lastOffset: CGSize = .zero
    @State private var debug = ""
        
    @State private var location: CGPoint = CGPoint(x: 100, y: 100)
    @GestureState private var fingerLocation: CGPoint? = nil
    @GestureState private var startLocation: CGPoint? = nil
    
    @State private var scaling: CGFloat = 1.0 // Add a state variable for scaling
    @State private var rotationAngle: Double = 0.0 // Add a state variable for rotation
        
    var simpleDrag: some Gesture {
        DragGesture()
            .onChanged { value in
                var newLocation = startLocation ?? location // 3
                newLocation.x += value.translation.width
                newLocation.y += value.translation.height
                self.location = newLocation
            }.updating($startLocation) { (value, startLocation, transaction) in
                startLocation = startLocation ?? location // 2
            }
    }
    
    var fingerDrag: some Gesture {
        DragGesture()
            .updating($fingerLocation) { (value, fingerLocation, transaction) in
                fingerLocation = value.location
            }
    }
    
    var body: some View {
        VStack {
            GeometryReader { geometry in
                let magnificationGesture = MagnificationGesture()
                    .onChanged{ gesture in
                        scaleAnchor = .center
                        scale = lastScale * gesture
                    }
                    .onEnded { _ in
                        fixOffsetAndScale(geometry: geometry)
                    }
                
                let dragGesture = DragGesture()
                    .onChanged { gesture in
                        var newOffset = lastOffset
                        newOffset.width += gesture.translation.width
                        newOffset.height += gesture.translation.height
                        offset = newOffset
                    }
                    .onEnded { _ in
                        fixOffsetAndScale(geometry: geometry)
                    }
                
                ZStack {
                    
                    Image(uiImage: scannedImage)
                        .resizable()
                        .scaledToFit()
                        .position(x: geometry.size.width / 2,
                                  y: geometry.size.height / 2)
                        .scaleEffect(scale, anchor: scaleAnchor)
                        .offset(offset)
                        .gesture(dragGesture)
                        .gesture(magnificationGesture)
                    
                    ZStack {
                        
                        if let image = loadImageFromDocumentDirectory(filename: "signature.png") {
                            
                            ZStack {
                                
                                Rectangle()
                                    .stroke(style: StrokeStyle(lineWidth: 1, dash: [5]))
                                    .fill(.blue)
                                
                                Image(uiImage: image)
                                    .resizable()
                                    .scaledToFit()
                                
                            }
                                VStack {
                                    Spacer()
                                    HStack {
                                        
                                        Spacer()
                                        
                                        Circle()
                                            .fill(Color.green) // Change the circle color as needed
                                            .frame(width: 20, height: 20) // Adjust the size of the circle as needed
                                    }
                                    .padding(.trailing, -18)
                                    .padding(.bottom, -13)
                                }
                            )
                            .position(location)
                            .gesture(
                                simpleDrag.simultaneously(with: fingerDrag)
                            )
                            
                        }
                            
                        
                    }
                    .frame(width: 100, height: 100)
                    .rotationEffect(.degrees(Double(rotationAngle)), anchor: .center)
                    .scaleEffect(scaling)
                }
                .frame(width: geometry.size.width, height: geometry.size.height)
            }

        }
        .background(Color.black.opacity(0.3))
        .overlay(
            VStack {
                Spacer()
                Button {
                    saveImageWithSignatures()
                } label: {
                    Text("Save")
                }
            }
        )
        .edgesIgnoringSafeArea(.all)
    }
    
    // Function to combine the original image with the signature overlay
    func combineImages() -> UIImage? {
        // Create a UIGraphicsImageRenderer to draw the combined image
        let renderer = UIGraphicsImageRenderer(size: scannedImage.size)

        let combinedImage = renderer.image { ctx in
            // Draw the original image
            scannedImage.draw(in: CGRect(origin: .zero, size: scannedImage.size))

            // Calculate the position and size of the signature overlay
            let signatureRect = CGRect(x: location.x, y: location.y, width: 100, height: 100)

            // Draw the signature overlay
            if let signatureImage = loadImageFromDocumentDirectory(filename: "signature.png") {
                signatureImage.draw(in: signatureRect)
            }
        }

        return combinedImage
    }

    // Function to save the modified image with the signature overlay
    func saveImageWithSignatures() {
        // Combine the images
        if let combinedImage = combineImages() {
            // Save the combined image to the document directory
            saveImageToDocumentDirectory(image: combinedImage, filename: "modified_img.jpeg")
        }
    }
    
    // Function to save an image to the document directory
    func saveImageToDocumentDirectory(image: UIImage, filename: String) {
        if let data = image.jpegData(compressionQuality: 1.0) {
            let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
            let fileURL = documentsDirectory.appendingPathComponent(filename)
            do {
                try data.write(to: fileURL)
                print("Image saved to document directory: \(fileURL)")
            } catch {
                print("Error saving image: \(error)")
            }
        }
    }
    
    func loadImageFromDocumentDirectory(filename: String) -> UIImage? {
        let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let fileURL = documentsDirectory.appendingPathComponent(filename)
        
        do {
            let imageData = try Data(contentsOf: fileURL)
            return UIImage(data: imageData)
        } catch {
            print("Error loading image: \(error)")
            return nil
        }
    }
    
// NOTE: - My image displaying logic
    private func fixOffsetAndScale(geometry: GeometryProxy) {
        let newScale: CGFloat = .minimum(.maximum(scale, 1), 4)
        let screenSize = geometry.size
        
        let originalScale = scannedImage.size.width / scannedImage.size.height >= screenSize.width / screenSize.height ?
            geometry.size.width / scannedImage.size.width :
            geometry.size.height / scannedImage.size.height
        
        let imageWidth = (scannedImage.size.width * originalScale) * newScale
        
        var width: CGFloat = .zero
        if imageWidth > screenSize.width {
            let widthLimit: CGFloat = imageWidth > screenSize.width ?
                (imageWidth - screenSize.width) / 2
                : 0

            width = offset.width > 0 ?
                .minimum(widthLimit, offset.width) :
                .maximum(-widthLimit, offset.width)
        }
        
        let imageHeight = (scannedImage.size.height * originalScale) * newScale
        var height: CGFloat = .zero
        if imageHeight > screenSize.height {
            let heightLimit: CGFloat = imageHeight > screenSize.height ?
                (imageHeight - screenSize.height) / 2
                : 0

            height = offset.height > 0 ?
                .minimum(heightLimit, offset.height) :
                .maximum(-heightLimit, offset.height)
        }
        
        let newOffset = CGSize(width: width, height: height)
        lastScale = newScale
        lastOffset = newOffset
        withAnimation() {
            offset = newOffset
            scale = newScale
        }
    }
}



应用不同转换的顺序很重要。我建议你做到scaling其次是rotation其次是offset。这样,如果堆栈的任何部分应从缩放中排除(例如按钮的覆盖层),则可以在应用缩放修改器后将它们添加到视图中。尤其重要的是,在旋转之后应用偏移。如果偏移发生在旋转之前,那么它会移动旋转中心,并且图像将不再绕其中点旋转。

我本以为,应该只对签名叠加层进行转换,而不是对底层图像进行转换。在上面的代码中,我认为您也对底层图像应用了一些转换。

缩放和旋转可以累积应用,使用上一次调整的结束状态作为下一次调整的开始状态。然而,缩放可能不应该累积应用。

我的理解是,用户应该能够通过移动底角来应用缩放。然后可以将拖动位置视为新的角位置,保持中点不变。所以:

scalingFactor = /

如果还涉及偏移和旋转,则在确定中点和角点位置时需要考虑这些。

此公式给出的缩放因子应作为绝对值应用,而不是通过应用于现有缩放因子进行复合。您可能还想强制执行最小和最大缩放因子,以便图像不会缩小到小得离谱或膨胀得太大。

要查看所有这些工作,请尝试我在我的文章中给出的示例回复你的另一个帖子。这是该答案中执行缩放的函数:

private func performScale(dragLocation: CGPoint) {
    let midPoint = midPoint
    let dX = dragLocation.x - midPoint.x
    let dY = dragLocation.y - midPoint.y
    let draggedDiagonalLength = 2 * ((dX * dX) + (dY * dY)).squareRoot()
    let unscaledDiagonalLength = (
            (defaultSignatureWidth * defaultSignatureWidth) +
            (defaultSignatureHeight * defaultSignatureHeight)
        ).squareRoot()
    let draggedScaleFactor = draggedDiagonalLength / unscaledDiagonalLength
    scaleFactor = min(1.5, max(0.5, draggedScaleFactor))
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

带有签名的图像未按预期定位 的相关文章

随机推荐

  • 将图像转换为极坐标的示例明确执行 - 想要一个灵活的矩阵方法

    我正在尝试将图像从笛卡尔坐标转换为极坐标 我知道如何使用 for 循环显式地执行此操作 但我正在寻找更紧凑的东西 我想做类似的事情 x y size CartImage minr floor min x y 2 r linspace 0 m
  • 如果渲染器进程关闭,电子全局变量垃圾会被收集吗?

    在 Electron 中 我的主进程打开了一个 BrowserWindow BrowserWindow 加载一个 html 页面 然后同一窗口最终加载另一个 html 页面 main js var mainWindow global mai
  • 更改 YII 中的语言

    使用 YII 创建新站点后 我在 protected messages 中添加了一个文件夹 fr 并添加了一个文件 site php 其中包含 返回数组 你好 gt bonjour 在 view layout main php 中 我添加了
  • 无法在头文件中声明 ifstream 类成员

    我试图在头文件中声明一个 ifstream 对象 如图所示 但收到一条错误消息 指出无法访问它 我尝试了各种方法 例如将其变成指针 在 c 文件中初始化等 但我的代码似乎无法获取它的声明的一部分 读取文件 h ifndef READFILE
  • 如何使用 open() 在 python 中使用相对路径打开文件? [复制]

    这个问题在这里已经有答案了 我试图不使用配置文件的绝对路径 因为我需要将其部署在多个环境中 这里我的最佳选择是什么 下面的代码是我尝试过的 它无法找到路径 但是我可以在同一位置找到该文件 我在 Redhat 服务器上使用 Python3 6
  • RDFa 面包屑导航和验证器的正确文档类型

    我需要弄清楚 HTML 文档类型 在此页面中 http kovo intl uk to我使用 RDFa 添加面包屑导航 但随后页面不再有效 我用谷歌搜索 发现将 doctype 更改为 现在页面 100 有效 但是 XHTML 和如此低的数
  • jquery数据表排序忽略空值

    我正在使用数据表和 jQuery 来制作漂亮的可排序表 我现在想要对行进行排序 该值是一个数值 但它也可能不可用 所以此时我将回显破折号 现在 当我对此列进行排序时 所有带有破折号的行都位于顶部 然后显示值为 1 3 6 8 10 的行 如
  • 获取 Point 两侧的 LineString 上的顶点

    我有一个匀称的LineString并定义了一个匀称的Point沿着LineString 我怎样才能找到顶点LineString哪个位于该点的两侧 将线分成两部分 找到线段LineString重点在哪里 然后将顶点分成两组LineString
  • 如何在mysql存储过程中生成5个随机数

    如何生成 5 个唯一的随机数 现在我有类似的东西 declare v counter integer declare v random integer declare v result varchar 10 select FLOOR 1 r
  • FixThreadPool 与 CachedThreadPool:两害相权取其轻

    我有一个程序可以生成线程 5 150 来执行一堆任务 最初 我使用了一个FixedThreadPool因为这个类似的问题建议它们更适合寿命较长的任务 并且由于我对多线程的了解非常有限 我考虑了线程的平均寿命 几分钟 长寿 但是 我最近添加了
  • 如何使用 Java 列出存储桶中的所有 AWS S3 对象

    使用 Java 获取 S3 存储桶中所有项目的列表的最简单方法是什么 List
  • 安装factoextra时rbind(info, getNamespaceInfo(env, "S3methods")) 出错

    我正在尝试在 Windows 上使用本地源 tar 球安装 factoextra 包 我可以用同样的方式安装其他软件包 没有问题 但是 在安装 factoextra 时 我收到与 S3methods 相关的错误 我尝试使用本地 Window
  • 如何使用 css 模糊图像,同时在图像上显示文本(悬停)

    可以 然后呢 我有个问题 我想要一张悬停时模糊的图片 同时让文字出现在它上面 我找到了一种简单的方法来模糊图像并显示文本 但不能同时显示两者 事实上 将两个代码合并在一起可以使图片看起来一点也不模糊 我认为这是因为文本实际上覆盖了图像 并且
  • WPF 中的网格表

    我需要创建一个网格 应该是可编辑的我应该设置行数和列数 例如 mygrid RowCount 3 mygrid ColumnCount 3 它应该是这样的 如何将二维数组绑定到DataGrid 您可以使用 WPF DataGrid 控件 它
  • 将 JSON 文件加载到 BigQuery 表时如何管理/处理架构更改

    我的输入文件如下所示 Id 1 Address Street MG Road City Pune Id 2 Address City Mumbai Id 3 Address Street XYZ Road Id 4 Id 5 PhoneNu
  • PostgreSQL - 不一致的复制权限错误

    我在 Windows 7 32 位计算机上使用 EnterpriseDB pgAdmin III v 1 12 1 来处理远程 Linux 服务器上的 PostgreSQL 数据库 我以用户 postgres 身份登录 这允许我访问 PGD
  • 在 JSF 模板内进行过滤是个好主意吗?

    我必须说的第一件事是 我从未在 Java Web 应用程序上使用 Web 过滤器 所以这可能是一个愚蠢的想法 我正在尝试创建一种方法 可以检查用户的权限 并在用户是否可以访问某些页面时授予其访问权限 为了更好的解释 我将举一个例子 您应该想
  • 我在尝试将 python 与 mysql 链接时遇到错误

    TypeError init takes 1 positional argument but 5 were given 这是错误 我将分享以下脚本 import pymysql print DATABASE CONNECTION SAMPL
  • 为什么 eclipse 不重新编译对我的 Java 类所做的最后更改?

    我有一个简单的文件 只有 1 行 在 main 中打印 Hello World 我执行它并在 eclipse 控制台上打印 Hello World 现在当我将字符串更改为 再见世界 它仍然打印 你好世界 事实上 如果我通过将 println
  • 带有签名的图像未按预期定位

    我正在开发一个 SwiftUI 项目 我想将签名图像叠加在另一个图像之上 并允许用户操纵签名图像的位置 比例和旋转 但是 我在签名图像的定位方面遇到了问题 并且它没有出现在我期望的位置 我尝试设置用于拖动 缩放和旋转签名图像的手势 但它的行