根据矩形在SwiftUI中裁剪图像

5

我有一张图片,可以使用DragGesture()拖动。我想裁剪矩形区域内可见的图像。以下是我的代码...

struct CropImage: View {

    @State private var currentPosition: CGSize = .zero
    @State private var newPosition: CGSize = .zero

    var body: some View {
        VStack {
            ZStack {
                Image("test_pic")
                    .resizable()
                    .scaledToFit()
                    .offset(x: self.currentPosition.width, y: self.currentPosition.height)

                Rectangle()
                    .fill(Color.black.opacity(0.3))
                    .frame(width: UIScreen.screenWidth * 0.7 , height: UIScreen.screenHeight/5)
                    .overlay(Rectangle().stroke(Color.white, lineWidth: 3))
            }
            .gesture(DragGesture()
                .onChanged { value in
                    self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height)
            }
            .onEnded { value in
                self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height)

                self.newPosition = self.currentPosition
            })


            Button ( action : {
                // how to crop the image according to rectangle area

            } ) {
                Text("Crop Image")
                    .padding(.all, 10)
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .shadow(color: .gray, radius: 1)
                    .padding(.top, 50)
            }
        }
    }
}

为了更容易理解,这里有一张关于IT技术的图示:

输入图像描述

3个回答

4

这里提供了一种使用.clipShape的可能方法。经测试,可用于Xcode 11.4 / iOS 13.4。

demo

struct CropFrame: Shape {
    let isActive: Bool
    func path(in rect: CGRect) -> Path {
        guard isActive else { return Path(rect) } // full rect for non active

        let size = CGSize(width: UIScreen.screenWidth * 0.7, height: UIScreen.screenHeight/5)
        let origin = CGPoint(x: rect.midX - size.width / 2, y: rect.midY - size.height / 2)
        return Path(CGRect(origin: origin, size: size).integral)
    }
}

struct CropImage: View {

    @State private var currentPosition: CGSize = .zero
    @State private var newPosition: CGSize = .zero
    @State private var clipped = false

    var body: some View {
        VStack {
            ZStack {
                Image("test_pic")
                    .resizable()
                    .scaledToFit()
                    .offset(x: self.currentPosition.width, y: self.currentPosition.height)

                Rectangle()
                    .fill(Color.black.opacity(0.3))
                    .frame(width: UIScreen.screenWidth * 0.7 , height: UIScreen.screenHeight/5)
                    .overlay(Rectangle().stroke(Color.white, lineWidth: 3))
            }
            .clipShape(
                CropFrame(isActive: clipped)
            )
            .gesture(DragGesture()
                .onChanged { value in
                    self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height)
            }
            .onEnded { value in
                self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height)

                self.newPosition = self.currentPosition
            })


            Button (action : { self.clipped.toggle() }) {
                Text("Crop Image")
                    .padding(.all, 10)
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .shadow(color: .gray, radius: 1)
                    .padding(.top, 50)
            }
        }
    }
}

2
很酷,但是如何获取图片呢?比如说,如果我想将图片保存在UserDefaults中以供临时使用。 - Asim Roy
实际上我正在寻找矩形区域的pngData/jpgData。也许是矩形区域的屏幕截图! - Asim Roy
你成功获取到图片了吗?如果是的话,你是怎么做到的呢? - Kesava

2

enter image description here

这里是代码:

//  Created by Deepak Gautam on 15/02/22.

import SwiftUI

struct ImageCropView: View {
@Environment(\.presentationMode) var pm
@State var imageWidth:CGFloat = 0
@State var imageHeight:CGFloat = 0
@Binding var image : UIImage

@State var dotSize:CGFloat = 13
var dotColor = Color.init(white: 1).opacity(0.9)

@State var center:CGFloat = 0
@State var activeOffset:CGSize = CGSize(width: 0, height: 0)
@State var finalOffset:CGSize = CGSize(width: 0, height: 0)

@State var rectActiveOffset:CGSize = CGSize(width: 0, height: 0)
@State var rectFinalOffset:CGSize = CGSize(width: 0, height: 0)

@State var activeRectSize : CGSize = CGSize(width: 200, height: 200)
@State var finalRectSize : CGSize = CGSize(width: 200, height: 200)


var body: some View {
    ZStack {
        Image(uiImage: image)
            .resizable()
            .scaledToFit()
            .overlay(GeometryReader{geo -> AnyView in
                DispatchQueue.main.async{
                    self.imageWidth = geo.size.width
                    self.imageHeight = geo.size.height
                }
                return AnyView(EmptyView())
            })

        Text("Crop")
            .padding(6)
            .foregroundColor(.white)
            .background(Capsule().fill(Color.blue))
            .offset(y: -250)
            .onTapGesture {
                let cgImage: CGImage = image.cgImage!
                let scaler = CGFloat(cgImage.width)/imageWidth
                if let cImage = cgImage.cropping(to: CGRect(x: getCropStartCord().x * scaler, y: getCropStartCord().y * scaler, width: activeRectSize.width * scaler, height: activeRectSize.height * scaler)){
                    image = UIImage(cgImage: cImage)
                }
                pm.wrappedValue.dismiss()
            }
        
        
        Rectangle()
            .stroke(lineWidth: 1)
            .foregroundColor(.white)
            .offset(x: rectActiveOffset.width, y: rectActiveOffset.height)
            .frame(width: activeRectSize.width, height: activeRectSize.height)
        
        Rectangle()
            .stroke(lineWidth: 1)
            .foregroundColor(.white)
            .background(Color.green.opacity(0.3))
            .offset(x: rectActiveOffset.width, y: rectActiveOffset.height)
            .frame(width: activeRectSize.width, height: activeRectSize.height)
            .gesture(
                DragGesture()
                    .onChanged{drag in
                        let workingOffset = CGSize(
                            width: rectFinalOffset.width + drag.translation.width,
                            height: rectFinalOffset.height + drag.translation.height
                        )
                        self.rectActiveOffset.width = workingOffset.width
                        self.rectActiveOffset.height = workingOffset.height
                        
                        activeOffset.width = rectActiveOffset.width - activeRectSize.width / 2
                        activeOffset.height = rectActiveOffset.height - activeRectSize.height / 2
                    }
                    .onEnded{drag in
                        self.rectFinalOffset = rectActiveOffset
                        self.finalOffset = activeOffset
                    }
        )
        
        Image(systemName: "arrow.up.left.and.arrow.down.right")
            .font(.system(size: 12))
            .background(Circle().frame(width: 20, height: 20).foregroundColor(dotColor))
            .frame(width: dotSize, height: dotSize)
            .foregroundColor(.black)
            .offset(x: activeOffset.width, y: activeOffset.height)
            .gesture(
                DragGesture()
                    .onChanged{drag in
                        let workingOffset = CGSize(
                            width: finalOffset.width + drag.translation.width,
                            height: finalOffset.height + drag.translation.height
                        )
                        
                        let changeInXOffset = finalOffset.width - workingOffset.width
                        let changeInYOffset = finalOffset.height - workingOffset.height
                        
                        if finalRectSize.width + changeInXOffset > 40 && finalRectSize.height + changeInYOffset > 40{
                            self.activeOffset.width = workingOffset.width
                            self.activeOffset.height = workingOffset.height
                            
                            activeRectSize.width = finalRectSize.width + changeInXOffset
                            activeRectSize.height = finalRectSize.height + changeInYOffset
                            
                            rectActiveOffset.width = rectFinalOffset.width - changeInXOffset / 2
                            rectActiveOffset.height = rectFinalOffset.height - changeInYOffset / 2
                        }
                        
                    }
                    .onEnded{drag in
                        self.finalOffset = activeOffset
                        finalRectSize = activeRectSize
                        rectFinalOffset = rectActiveOffset
                    }
        )
    }
    .onAppear {
        activeOffset.width = rectActiveOffset.width - activeRectSize.width / 2
        activeOffset.height = rectActiveOffset.height - activeRectSize.height / 2
        finalOffset = activeOffset
    }
}

func getCropStartCord() -> CGPoint{
    var cropPoint : CGPoint = CGPoint(x: 0, y: 0)
    cropPoint.x = imageWidth / 2 - (activeRectSize.width / 2 - rectActiveOffset.width )
    cropPoint.y = imageHeight / 2 - (activeRectSize.height / 2 - rectActiveOffset.height )
    return cropPoint
}
}

struct TestCrop : View{
@State var imageWidth:CGFloat = 0
@State var imageHeight:CGFloat = 0
@State var image:UIImage
@State var showCropper : Bool = false
var body: some View{
    VStack{
        Text("Open Cropper")
            .font(.system(size: 17, weight: .medium))
            .padding(.horizontal, 15)
            .padding(.vertical, 10)
            .foregroundColor(.white)
            .background(Capsule().fill(Color.blue))
            .onTapGesture {
                showCropper = true
            }
            
        
        Image(uiImage: image)
            .resizable()
            .scaledToFit()
    }
    .sheet(isPresented: $showCropper) {
        //
    } content: {
        ImageCropView(image: $image)
    }

}
}

struct TestViewFinder_Previews: PreviewProvider {
static var originalImage = UIImage(named: "food")
static var previews: some View {
    TestCrop(image: originalImage ?? UIImage())
 }
}

工作得很好....! - undefined

2
感谢 Asperi的回答,我已经实现了一个轻量级的SwiftUI库来裁剪图像。这是该库和演示。演示 魔法在下面:
public var body: some View {
        GeometryReader { proxy  in
           // ...
                        
                        Button(action: {
  // how to crop the image according to rectangle area
                            if self.tempResult == nil {
                                self.cropTheImageWithImageViewSize(proxy.size)
                            }
                            self.resultImage = self.tempResult
                        })  {
            Text("Crop Image")
                .padding(.all, 10)
                .background(Color.blue)
                .foregroundColor(.white)
                .shadow(color: .gray, radius: 1)
                .padding(.top, 50)
        }
                    }
  }

func cropTheImageWithImageViewSize(_ size: CGSize) {

    let imsize =  inputImage.size
    let scale = max(inputImage.size.width / size.width,
                    inputImage.size.height / size.height)

    
    let zoomScale = self.scale

    let currentPositionWidth = self.dragAmount.width * scale
        let currentPositionHeight = self.dragAmount.height * scale
    
    let croppedImsize = CGSize(width: (self.cropSize.width * scale) / zoomScale, height: (self.cropSize.height * scale) / zoomScale)
     
    let xOffset = (( imsize.width - croppedImsize.width) / 2.0) - (currentPositionWidth  / zoomScale)
    let yOffset = (( imsize.height - croppedImsize.height) / 2.0) - (currentPositionHeight  / zoomScale)
    let croppedImrect: CGRect = CGRect(x: xOffset, y: yOffset, width: croppedImsize.width, height: croppedImsize.height)
          
    if let cropped = inputImage.cgImage?.cropping(to: croppedImrect) {
       //uiimage here can write to data in png or jpeg
        let croppedIm = UIImage(cgImage: cropped)
        tempResult = croppedIm
        result = Image(uiImage: croppedIm)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接