mirror of
https://github.com/laosb/CropImage.git
synced 2025-04-30 23:51: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(
|
let package = Package(
|
||||||
name: "CropImage",
|
name: "CropImage",
|
||||||
|
platforms: [
|
||||||
|
.iOS(.v14),
|
||||||
|
.macOS(.v13)
|
||||||
|
],
|
||||||
products: [
|
products: [
|
||||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||||
.library(
|
.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.
|
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||||
.target(
|
.target(
|
||||||
name: "CropImage",
|
name: "CropImage",
|
||||||
dependencies: []),
|
dependencies: [])
|
||||||
.testTarget(
|
|
||||||
name: "CropImageTests",
|
|
||||||
dependencies: ["CropImage"]),
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -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