ios – Struggling to Apply Perspective Warp to Textual content Path in SwiftUI, Textual content Simply Strikes As a substitute of Stretching or Squeezing

0
15
ios – Struggling to Apply Perspective Warp to Textual content Path in SwiftUI, Textual content Simply Strikes As a substitute of Stretching or Squeezing


I’m making an attempt to implement a perspective warp impact on a textual content in SwiftUI. Nonetheless, when I attempt to rework the textual content path, it solely strikes the textual content somewhat than stretching or squeezing it, as anticipated in perspective transformation. My objective is to distort the textual content alongside a set of factors (top-left, top-right, bottom-right, and bottom-left) to create a perspective impact much like a photograph editor.

Here is the code I’m utilizing to attain the impact:

import SwiftUI

struct PerspectiveWarpView: View {
    @State personal var factors: [CGPoint] = [] // Initially empty
    @State var shade: Shade = .white
    @State var place: CGPoint = CGPoint(x: 100, y: 300)
    @State var position2: CGPoint = CGPoint(x: 100, y: 250)
    @State personal var initialPosition: CGPoint = .zero
    
    var physique: some View {
        GeometryReader { geometry in
            ZStack {
                Shade.black.edgesIgnoringSafeArea(.all)

                if !factors.isEmpty, let warpedPath = transformTextPath() {
                    warpedPath
                        .fill(shade)
                        .place(place)
                        .gesture(DragGesture().onChanged { worth in
                            if initialPosition == .zero {
                                initialPosition = place
                            }
                            let newPosition = CGPoint(
                                x: initialPosition.x + worth.translation.width,
                                y: initialPosition.y + worth.translation.top
                            )
                            DispatchQueue.fundamental.async {
                                place = newPosition
                                position2 = CGPoint(x: newPosition.x, y: newPosition.y - 50)
                            }
                        }.onEnded({ _ in
                            initialPosition = .zero
                        })
                        )

                    PointsView(factors: $factors, path: warpedPath)
                        .place(position2)
                        .onAppear(){
                            factors =  getCorners(of: warpedPath)
                        }
                }
            }
            .onAppear {
                // Initialize factors based mostly on the display screen measurement
                let screenWidth = geometry.measurement.width
                let screenHeight = geometry.measurement.top
                let offsetX = (screenWidth - 300) / 2 // Middle horizontally
                let offsetY = (screenHeight - 200) / 2 // Middle vertically

                factors = [
                    CGPoint(x: offsetX + 0, y: offsetY + 0),       // Top-left
                    CGPoint(x: offsetX + 300, y: offsetY + 0),     // Top-right
                    CGPoint(x: offsetX + 300, y: offsetY + 200),   // Bottom-right
                    CGPoint(x: offsetX + 0, y: offsetY + 200)      // Bottom-left
                ]
            }
        }
    }
    
    

    func getCorners(of path: Path) -> [CGPoint] {
        let boundingBox = path.boundingRect
        return [
            CGPoint(x: boundingBox.minX, y: boundingBox.minY - 10), // Top-left
            CGPoint(x: boundingBox.maxX, y: boundingBox.minY - 10), // Top-right
            CGPoint(x: boundingBox.maxX, y: boundingBox.maxY + 10), // Bottom-right
            CGPoint(x: boundingBox.minX, y: boundingBox.maxY + 10)  // Bottom-left
        ]
    }

    func transformTextPath() -> Path? {
        guard !factors.isEmpty else { return nil } // Guarantee factors will not be empty
        guard let originalPath = textToPath(textual content: "ELEVATED", font: .systemFont(ofSize: 80, weight: .daring)) else {
            return nil
        }

        // Apply perspective rework to the trail
        return warpPath(originalPath, from: defaultRect(), to: factors)
    }

    func textToPath(textual content: String, font: UIFont) -> Path? {
        let attributedString = NSAttributedString(string: textual content, attributes: [.font: font])
        let line = CTLineCreateWithAttributedString(attributedString)
        let runArray = CTLineGetGlyphRuns(line) as NSArray

        let path = CGMutablePath()
        for run in runArray {
            let run = run as! CTRun
            let rely = CTRunGetGlyphCount(run)

            for index in 0.. [CGPoint] {
        return [
            CGPoint(x: 0, y: 0),       // Top-left
            CGPoint(x: 300, y: 0),     // Top-right
            CGPoint(x: 300, y: 200),   // Bottom-right
            CGPoint(x: 0, y: 200)      // Bottom-left
        ]
    }

    func warpPath(_ path: Path, from src: [CGPoint], to dst: [CGPoint]) -> Path {
        var newPath = Path()
        let rework = computePerspectiveTransform(from: src, to: dst)

        path.forEach { component in
            swap component {
            case .transfer(to: let level):
                newPath.transfer(to: applyPerspective(level, utilizing: rework))
            case .line(to: let level):
                newPath.addLine(to: applyPerspective(level, utilizing: rework))
            case .quadCurve(to: let level, management: let management):
                newPath.addQuadCurve(to: applyPerspective(level, utilizing: rework),
                                     management: applyPerspective(management, utilizing: rework))
            case .curve(to: let level, control1: let control1, control2: let control2):
                newPath.addCurve(to: applyPerspective(level, utilizing: rework),
                                 control1: applyPerspective(control1, utilizing: rework),
                                 control2: applyPerspective(control2, utilizing: rework))
            case .closeSubpath:
                newPath.closeSubpath()
            }
        }
        return newPath
    }

    func computePerspectiveTransform(from src: [CGPoint], to dst: [CGPoint]) -> [[CGFloat]] {
        let x0 = src[0].x, y0 = src[0].y
        let x1 = src[1].x, y1 = src[1].y
        let x2 = src[2].x, y2 = src[2].y
        let x3 = src[3].x, y3 = src[3].y

        let X0 = dst[0].x, Y0 = dst[0].y
        let X1 = dst[1].x, Y1 = dst[1].y
        let X2 = dst[2].x, Y2 = dst[2].y
        let X3 = dst[3].x, Y3 = dst[3].y

        
        let A = [
            [x0, y0, 1, 0, 0, 0, -X0*x0, -X0*y0],
            [x1, y1, 1, 0, 0, 0, -X1*x1, -X1*y1],
            [x2, y2, 1, 0, 0, 0, -X2*x2, -X2*y2],
            [x3, y3, 1, 0, 0, 0, -X3*x3, -X3*y3],
            [0, 0, 0, x0, y0, 1, -Y0*x0, -Y0*y0],
            [0, 0, 0, x1, y1, 1, -Y1*x1, -Y1*y1],
            [0, 0, 0, x2, y2, 1, -Y2*x2, -Y2*y2],
            [0, 0, 0, x3, y3, 1, -Y3*x3, -Y3*y3]
        ]
        
        let B = [X0, X1, X2, X3, Y0, Y1, Y2, Y3]

        guard let h = solveLinearSystem(A: A, B: B) else {
            return [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
        }

        return [
            [h[0], h[1], h[2]],
            [h[3], h[4], h[5]],
            [h[6], h[7], 1]
        ]
    }

    func solveLinearSystem(A: [[CGFloat]], B: [CGFloat]) -> [CGFloat]? {
        let rowCount = A.rely
        var matrix = A
        var end result = B

        for i in 0.. abs(matrix[maxRow][i]) {
                    maxRow = j
                }
            }

            matrix.swapAt(i, maxRow)
            end result.swapAt(i, maxRow)

            let pivot = matrix[i][i]
            if pivot == 0 { return nil }

            for j in i.. CGPoint {
        let x = level.x
        let y = level.y
        let denominator = (matrix[2][0] * x + matrix[2][1] * y + matrix[2][2])
        
        let newX = (matrix[0][0] * x + matrix[0][1] * y + matrix[0][2]) / denominator
        let newY = (matrix[1][0] * x + matrix[1][1] * y + matrix[1][2]) / denominator
        
        return CGPoint(x: newX, y: newY)
    }
}

struct PointsView: View {
    @Binding var factors: [CGPoint]
    var path: Path

    var physique: some View {
        ZStack {
            // Draw the remodeled path
            Path { path in
                path.transfer(to: factors[0])
                path.addLine(to: factors[1])
                path.addLine(to: factors[2])
                path.addLine(to: factors[3])
                path.closeSubpath()
            }
            .stroke(Shade.white.opacity(0.5), lineWidth: 2)

            // Draw draggable factors
            ForEach(0..

Drawback:
After I apply the transformTextPath() operate to the textual content, it merely strikes across the display screen as a substitute of stretching or squeezing based mostly on the attitude factors. I anticipated the textual content path to distort prefer it does in photograph editors when a perspective impact is utilized.

What I’ve tried:

Implementing a customized perspective rework utilizing matrix manipulation.
Attempting alternative ways to use the warp impact utilizing Path and CGAffineTransform.
Might somebody level out the place I could be going improper, or supply options on how I can obtain the proper perspective stretching/squeezing impact?

That is the way it’s seems
Out Put Image

LEAVE A REPLY

Please enter your comment!
Please enter your name here