mirror of
https://github.com/laosb/CropImage.git
synced 2025-04-30 15:41:08 +00:00
WIP.
This commit is contained in:
parent
1477b29bc8
commit
645e4d6b2c
7 changed files with 196 additions and 21 deletions
|
@ -5,6 +5,10 @@ import PackageDescription
|
|||
|
||||
let package = Package(
|
||||
name: "CropImage",
|
||||
platforms: [
|
||||
.iOS(.v14),
|
||||
.macOS(.v13)
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
|
@ -20,9 +24,6 @@ let package = Package(
|
|||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "CropImage",
|
||||
dependencies: []),
|
||||
.testTarget(
|
||||
name: "CropImageTests",
|
||||
dependencies: ["CropImage"]),
|
||||
dependencies: [])
|
||||
]
|
||||
)
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
public struct CropImage {
|
||||
public private(set) var text = "Hello, World!"
|
||||
|
||||
public init() {
|
||||
}
|
||||
}
|
57
Sources/CropImage/CropImageView.swift
Normal file
57
Sources/CropImage/CropImageView.swift
Normal file
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// CropImageView.swift
|
||||
//
|
||||
//
|
||||
// Created by Shibo Lyu on 2023/7/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
public struct CropImageView: View {
|
||||
var image: PlatformImage
|
||||
var targetSize: CGSize
|
||||
var onCrop: (PlatformImage) -> Void
|
||||
|
||||
@State private var offset: CGSize = .zero
|
||||
@State private var scale: CGFloat = 1
|
||||
|
||||
public var body: some View {
|
||||
ZStack {
|
||||
MoveAndScalableImageView(offset: $offset, scale: $scale, image: image)
|
||||
RectHoleShape(size: targetSize)
|
||||
.fill(style: FillStyle(eoFill: true))
|
||||
.foregroundColor(.black.opacity(0.6))
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CropImageView_Previews: PreviewProvider {
|
||||
struct PreviewView: View {
|
||||
@State private var croppedImage: PlatformImage? = nil
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
CropImageView(
|
||||
image: .init(contentsOfFile: "/Users/laosb/Downloads/png.png")!,
|
||||
targetSize: .init(width: 100, height: 100)
|
||||
) { _ in
|
||||
|
||||
}
|
||||
if let croppedImage {
|
||||
#if os(macOS)
|
||||
Image(nsImage: croppedImage)
|
||||
#elseif os(iOS)
|
||||
Image(uiImage: croppedImage)
|
||||
#endif
|
||||
} else {
|
||||
Text("Press \(Image(systemName: "checkmark.circle.fill")) to crop.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static var previews: some View {
|
||||
PreviewView()
|
||||
}
|
||||
}
|
73
Sources/CropImage/MoveAndScalableImageView.swift
Normal file
73
Sources/CropImage/MoveAndScalableImageView.swift
Normal file
|
@ -0,0 +1,73 @@
|
|||
//
|
||||
// MoveAndScalableImageView.swift
|
||||
//
|
||||
//
|
||||
// Created by Shibo Lyu on 2023/7/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
private extension CGSize {
|
||||
static func + (lhs: CGSize, rhs: CGSize) -> CGSize {
|
||||
.init(width: lhs.width + rhs.width, height: lhs.height + rhs.height)
|
||||
}
|
||||
}
|
||||
|
||||
struct MoveAndScalableImageView: View {
|
||||
@Binding var offset: CGSize
|
||||
@Binding var scale: CGFloat
|
||||
var image: PlatformImage
|
||||
|
||||
@State private var tempOffset: CGSize = .zero
|
||||
@State private var tempScale: CGFloat = 1
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
#if os(macOS)
|
||||
Image(nsImage: image)
|
||||
.scaleEffect(scale * tempScale)
|
||||
.offset(offset + tempOffset)
|
||||
#elseif os(iOS)
|
||||
Image(uiImage: image)
|
||||
.scaleEffect(scale * tempScale)
|
||||
.offset(offset + tempOffset)
|
||||
#endif
|
||||
Color.white.opacity(0.0001)
|
||||
.gesture(
|
||||
DragGesture()
|
||||
.onChanged { value in
|
||||
tempOffset = value.translation
|
||||
}
|
||||
.onEnded { value in
|
||||
offset = offset + tempOffset
|
||||
tempOffset = .zero
|
||||
}
|
||||
)
|
||||
.gesture(
|
||||
MagnificationGesture()
|
||||
.onChanged { value in
|
||||
tempScale = value.magnitude
|
||||
}
|
||||
.onEnded { value in
|
||||
scale = scale * tempScale
|
||||
tempScale = 1
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MoveAndScalableImageView_Previews: PreviewProvider {
|
||||
struct PreviewView: View {
|
||||
@State private var offset: CGSize = .zero
|
||||
@State private var scale: CGFloat = 1
|
||||
|
||||
var body: some View {
|
||||
MoveAndScalableImageView(offset: $offset, scale: $scale, image: .init(contentsOfFile: "/Users/laosb/Downloads/png.png")!)
|
||||
}
|
||||
}
|
||||
|
||||
static var previews: some View {
|
||||
PreviewView()
|
||||
}
|
||||
}
|
16
Sources/CropImage/PlatformImage.swift
Normal file
16
Sources/CropImage/PlatformImage.swift
Normal file
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// PlatformImage.swift
|
||||
//
|
||||
//
|
||||
// Created by Shibo Lyu on 2023/7/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
public typealias PlatformImage = NSImage
|
||||
#elseif os(iOS)
|
||||
import UIKit
|
||||
public typealias PlatformImage = UIImage
|
||||
#endif
|
45
Sources/CropImage/RectHoleShape.swift
Normal file
45
Sources/CropImage/RectHoleShape.swift
Normal file
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// RectHoleShape.swift
|
||||
//
|
||||
//
|
||||
// Created by Shibo Lyu on 2023/7/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct RectHoleShape: Shape {
|
||||
let size: CGSize
|
||||
func path(in rect: CGRect) -> Path {
|
||||
let path = CGMutablePath()
|
||||
path.move(to: rect.origin)
|
||||
path.addLine(to: .init(x: rect.maxX, y: rect.minY))
|
||||
path.addLine(to: .init(x: rect.maxX, y: rect.maxY))
|
||||
path.addLine(to: .init(x: rect.minX, y: rect.maxY))
|
||||
path.addLine(to: rect.origin)
|
||||
path.closeSubpath()
|
||||
|
||||
let newRect = CGRect(origin: .init(
|
||||
x: rect.midX - size.width / 2.0,
|
||||
y: rect.midY - size.height / 2.0
|
||||
), size: size)
|
||||
|
||||
path.move(to: newRect.origin)
|
||||
path.addLine(to: .init(x: newRect.maxX, y: newRect.minY))
|
||||
path.addLine(to: .init(x: newRect.maxX, y: newRect.maxY))
|
||||
path.addLine(to: .init(x: newRect.minX, y: newRect.maxY))
|
||||
path.addLine(to: newRect.origin)
|
||||
path.closeSubpath()
|
||||
return Path(path)
|
||||
}
|
||||
}
|
||||
|
||||
struct RectHoleShape_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
RectHoleShape(size: .init(width: 100, height: 100))
|
||||
.fill(style: FillStyle(eoFill: true))
|
||||
.foregroundColor(.black.opacity(0.6))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import XCTest
|
||||
@testable import CropImage
|
||||
|
||||
final class CropImageTests: XCTestCase {
|
||||
func testExample() throws {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
XCTAssertEqual(CropImage().text, "Hello, World!")
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue