mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-07-10 06:42:43 +02:00
feat: implement photo insertion
This commit is contained in:
@@ -84,7 +84,8 @@
|
|||||||
ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FB2BE61C5200A4542E /* Persistence.swift */; };
|
ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FB2BE61C5200A4542E /* Persistence.swift */; };
|
||||||
ECA739082BE623F300A4542E /* PenDock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739072BE623F300A4542E /* PenDock.swift */; };
|
ECA739082BE623F300A4542E /* PenDock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739072BE623F300A4542E /* PenDock.swift */; };
|
||||||
ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */; };
|
ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */; };
|
||||||
ECBE52992C1D60E5006BDB3D /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE52982C1D60E5006BDB3D /* CameraView.swift */; };
|
ECBE529C2C1D94A4006BDB3D /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE529A2C1D94A4006BDB3D /* CameraView.swift */; };
|
||||||
|
ECBE529E2C1DAB21006BDB3D /* UIImage++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE529D2C1DAB21006BDB3D /* UIImage++.swift */; };
|
||||||
ECD12A862C19EE3900B96E12 /* ElementObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A852C19EE3900B96E12 /* ElementObject.swift */; };
|
ECD12A862C19EE3900B96E12 /* ElementObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A852C19EE3900B96E12 /* ElementObject.swift */; };
|
||||||
ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A892C19EFB000B96E12 /* Element.swift */; };
|
ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A892C19EFB000B96E12 /* Element.swift */; };
|
||||||
ECD12A8C2C1AEAA900B96E12 /* PhotoObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */; };
|
ECD12A8C2C1AEAA900B96E12 /* PhotoObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */; };
|
||||||
@@ -184,7 +185,8 @@
|
|||||||
ECA738FB2BE61C5200A4542E /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
ECA738FB2BE61C5200A4542E /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
||||||
ECA739072BE623F300A4542E /* PenDock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenDock.swift; sourceTree = "<group>"; };
|
ECA739072BE623F300A4542E /* PenDock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenDock.swift; sourceTree = "<group>"; };
|
||||||
ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPreview.swift; sourceTree = "<group>"; };
|
ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPreview.swift; sourceTree = "<group>"; };
|
||||||
ECBE52982C1D60E5006BDB3D /* CameraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = "<group>"; };
|
ECBE529A2C1D94A4006BDB3D /* CameraView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = "<group>"; };
|
||||||
|
ECBE529D2C1DAB21006BDB3D /* UIImage++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage++.swift"; sourceTree = "<group>"; };
|
||||||
ECD12A852C19EE3900B96E12 /* ElementObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementObject.swift; sourceTree = "<group>"; };
|
ECD12A852C19EE3900B96E12 /* ElementObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementObject.swift; sourceTree = "<group>"; };
|
||||||
ECD12A892C19EFB000B96E12 /* Element.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = "<group>"; };
|
ECD12A892C19EFB000B96E12 /* Element.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = "<group>"; };
|
||||||
ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoObject.swift; sourceTree = "<group>"; };
|
ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoObject.swift; sourceTree = "<group>"; };
|
||||||
@@ -234,7 +236,7 @@
|
|||||||
EC1B783A2BF9C68C005A34E2 /* Views */ = {
|
EC1B783A2BF9C68C005A34E2 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
ECBE52972C1D6087006BDB3D /* CameraView */,
|
ECBE529B2C1D94A4006BDB3D /* CameraView */,
|
||||||
ECFC51252BF8885000D0D051 /* ColorPicker */,
|
ECFC51252BF8885000D0D051 /* ColorPicker */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
@@ -509,6 +511,7 @@
|
|||||||
EC3565532BEFC6AD00A4E0BF /* View++.swift */,
|
EC3565532BEFC6AD00A4E0BF /* View++.swift */,
|
||||||
EC3565552BEFC7B300A4E0BF /* NSManagedObject++.swift */,
|
EC3565552BEFC7B300A4E0BF /* NSManagedObject++.swift */,
|
||||||
EC35655B2BF0712A00A4E0BF /* Float++.swift */,
|
EC35655B2BF0712A00A4E0BF /* Float++.swift */,
|
||||||
|
ECBE529D2C1DAB21006BDB3D /* UIImage++.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -679,10 +682,10 @@
|
|||||||
path = PhotoPreview;
|
path = PhotoPreview;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
ECBE52972C1D6087006BDB3D /* CameraView */ = {
|
ECBE529B2C1D94A4006BDB3D /* CameraView */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
ECBE52982C1D60E5006BDB3D /* CameraView.swift */,
|
ECBE529A2C1D94A4006BDB3D /* CameraView.swift */,
|
||||||
);
|
);
|
||||||
path = CameraView;
|
path = CameraView;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -863,6 +866,7 @@
|
|||||||
ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */,
|
ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */,
|
||||||
ECA738D72BE60FC100A4542E /* SolidPointStrokeGenerator.swift in Sources */,
|
ECA738D72BE60FC100A4542E /* SolidPointStrokeGenerator.swift in Sources */,
|
||||||
EC3565542BEFC6AD00A4E0BF /* View++.swift in Sources */,
|
EC3565542BEFC6AD00A4E0BF /* View++.swift in Sources */,
|
||||||
|
ECBE529E2C1DAB21006BDB3D /* UIImage++.swift in Sources */,
|
||||||
EC2BEBF62C0F600D005DB0AF /* Box.swift in Sources */,
|
EC2BEBF62C0F600D005DB0AF /* Box.swift in Sources */,
|
||||||
ECA738832BE5FEFE00A4542E /* RenderPass.swift in Sources */,
|
ECA738832BE5FEFE00A4542E /* RenderPass.swift in Sources */,
|
||||||
ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */,
|
ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */,
|
||||||
@@ -901,7 +905,7 @@
|
|||||||
EC0D14212BF79C73009BFE5F /* ToolObject.swift in Sources */,
|
EC0D14212BF79C73009BFE5F /* ToolObject.swift in Sources */,
|
||||||
EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */,
|
EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */,
|
||||||
EC9AB09F2C1401A40076AF58 /* EraserObject.swift in Sources */,
|
EC9AB09F2C1401A40076AF58 /* EraserObject.swift in Sources */,
|
||||||
ECBE52992C1D60E5006BDB3D /* CameraView.swift in Sources */,
|
ECBE529C2C1D94A4006BDB3D /* CameraView.swift in Sources */,
|
||||||
ECA7389E2BE601CB00A4542E /* QuadVertex.swift in Sources */,
|
ECA7389E2BE601CB00A4542E /* QuadVertex.swift in Sources */,
|
||||||
ECA738B32BE60D9E00A4542E /* CanvasView.swift in Sources */,
|
ECA738B32BE60D9E00A4542E /* CanvasView.swift in Sources */,
|
||||||
ECA738C42BE60E8800A4542E /* MarkerPenStyle.swift in Sources */,
|
ECA738C42BE60E8800A4542E /* MarkerPenStyle.swift in Sources */,
|
||||||
|
|||||||
@@ -304,11 +304,11 @@ extension GraphicContext {
|
|||||||
|
|
||||||
// MARK: - Photo
|
// MARK: - Photo
|
||||||
extension GraphicContext {
|
extension GraphicContext {
|
||||||
func insertPhoto(at point: CGPoint) {
|
func insertPhoto(at point: CGPoint, url: URL) {
|
||||||
let size = CGSize(width: 100, height: 100)
|
let size = CGSize(width: 100, height: 100)
|
||||||
let origin = point
|
let origin = point
|
||||||
let bounds = [origin.x - size.width / 2, origin.y - size.height / 2, origin.x + size.width / 2, origin.y + size.height / 2]
|
let bounds = [origin.x - size.width / 2, origin.y - size.height / 2, origin.x + size.width / 2, origin.y + size.height / 2]
|
||||||
let photo = Photo(size: size, origin: origin, bounds: bounds, createdAt: .now)
|
let photo = Photo(url: url, size: size, origin: origin, bounds: bounds, createdAt: .now)
|
||||||
tree.insert(.photo(photo), in: photo.photoBox)
|
tree.insert(.photo(photo), in: photo.photoBox)
|
||||||
self.previousElement = .photo(photo)
|
self.previousElement = .photo(photo)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,8 +128,31 @@ extension Canvas {
|
|||||||
|
|
||||||
// MARK: - Photo
|
// MARK: - Photo
|
||||||
extension Canvas {
|
extension Canvas {
|
||||||
func insertPhoto(at point: CGPoint) {
|
func insertPhoto(at point: CGPoint, url: URL) {
|
||||||
graphicContext.insertPhoto(at: point)
|
graphicContext.insertPhoto(at: point, url: url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func savePhoto(_ data: Data) -> URL? {
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
guard let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil }
|
||||||
|
let fileName = "\(UUID().uuidString)-\(Date.now.timeIntervalSince1970)"
|
||||||
|
let folder = directory.appendingPathComponent(canvasID.uriRepresentation().lastPathComponent, conformingTo: .folder)
|
||||||
|
if !fileManager.fileExists(atPath: folder.path()) {
|
||||||
|
do {
|
||||||
|
try fileManager.createDirectory(at: folder, withIntermediateDirectories: true)
|
||||||
|
} catch {
|
||||||
|
NSLog("[Memola] - \(error.localizedDescription)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let file = folder.appendingPathComponent(fileName, conformingTo: .jpeg)
|
||||||
|
do {
|
||||||
|
try data.write(to: file)
|
||||||
|
} catch {
|
||||||
|
NSLog("[Memola] - \(error.localizedDescription)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,18 @@ class Textures {
|
|||||||
return penTexture
|
return penTexture
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
static func createPhotoTexture(for url: URL, on device: MTLDevice) -> MTLTexture? {
|
||||||
|
let textureLoader = MTKTextureLoader(device: device)
|
||||||
|
do {
|
||||||
|
let photoTexture = try textureLoader.newTexture(URL: url, options: [.SRGB: false])
|
||||||
|
return photoTexture
|
||||||
|
} catch {
|
||||||
|
NSLog("[Memola] - \(error.localizedDescription)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static func createGraphicTexture(
|
static func createGraphicTexture(
|
||||||
from renderer: Renderer,
|
from renderer: Renderer,
|
||||||
size: CGSize,
|
size: CGSize,
|
||||||
|
|||||||
@@ -12,18 +12,31 @@ final class Photo: @unchecked Sendable, Equatable, Comparable {
|
|||||||
var id: UUID = UUID()
|
var id: UUID = UUID()
|
||||||
var size: CGSize
|
var size: CGSize
|
||||||
var origin: CGPoint
|
var origin: CGPoint
|
||||||
|
var image: UIImage?
|
||||||
|
var url: URL?
|
||||||
var bounds: [CGFloat]
|
var bounds: [CGFloat]
|
||||||
var createdAt: Date
|
var createdAt: Date
|
||||||
|
|
||||||
var object: PhotoObject?
|
var object: PhotoObject?
|
||||||
|
|
||||||
|
var texture: MTLTexture?
|
||||||
var vertices: [PhotoVertex] = []
|
var vertices: [PhotoVertex] = []
|
||||||
var vertexCount: Int = 0
|
var vertexCount: Int = 0
|
||||||
var vertexBuffer: MTLBuffer?
|
var vertexBuffer: MTLBuffer?
|
||||||
|
|
||||||
init(size: CGSize, origin: CGPoint, bounds: [CGFloat], createdAt: Date) {
|
init(image: UIImage?, size: CGSize, origin: CGPoint, bounds: [CGFloat], createdAt: Date) {
|
||||||
self.size = size
|
self.size = size
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
|
self.image = image
|
||||||
|
self.bounds = bounds
|
||||||
|
self.createdAt = createdAt
|
||||||
|
generateVertices()
|
||||||
|
}
|
||||||
|
|
||||||
|
init(url: URL?, size: CGSize, origin: CGPoint, bounds: [CGFloat], createdAt: Date) {
|
||||||
|
self.size = size
|
||||||
|
self.origin = origin
|
||||||
|
self.url = url
|
||||||
self.bounds = bounds
|
self.bounds = bounds
|
||||||
self.createdAt = createdAt
|
self.createdAt = createdAt
|
||||||
generateVertices()
|
generateVertices()
|
||||||
@@ -31,6 +44,7 @@ final class Photo: @unchecked Sendable, Equatable, Comparable {
|
|||||||
|
|
||||||
convenience init(object: PhotoObject) {
|
convenience init(object: PhotoObject) {
|
||||||
self.init(
|
self.init(
|
||||||
|
image: UIImage(data: object.image ?? .init()),
|
||||||
size: .init(width: object.width, height: object.height),
|
size: .init(width: object.width, height: object.height),
|
||||||
origin: .init(x: object.originX, y: object.originY),
|
origin: .init(x: object.originX, y: object.originY),
|
||||||
bounds: object.bounds,
|
bounds: object.bounds,
|
||||||
@@ -45,25 +59,31 @@ final class Photo: @unchecked Sendable, Equatable, Comparable {
|
|||||||
let minY = origin.y - size.height / 2
|
let minY = origin.y - size.height / 2
|
||||||
let maxY = origin.y + size.height / 2
|
let maxY = origin.y + size.height / 2
|
||||||
vertices = [
|
vertices = [
|
||||||
PhotoVertex(x: minX, y: minY, textCoord: CGPoint(x: 0, y: 1)),
|
PhotoVertex(x: minX, y: minY, textCoord: CGPoint(x: 0, y: 0)),
|
||||||
PhotoVertex(x: minX, y: maxY, textCoord: CGPoint(x: 0, y: 0)),
|
PhotoVertex(x: minX, y: maxY, textCoord: CGPoint(x: 0, y: 1)),
|
||||||
PhotoVertex(x: maxX, y: minY, textCoord: CGPoint(x: 1, y: 1)),
|
PhotoVertex(x: maxX, y: minY, textCoord: CGPoint(x: 1, y: 0)),
|
||||||
PhotoVertex(x: maxX, y: maxY, textCoord: CGPoint(x: 1, y: 0)),
|
PhotoVertex(x: maxX, y: maxY, textCoord: CGPoint(x: 1, y: 1)),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Photo: Drawable {
|
extension Photo: Drawable {
|
||||||
func prepare(device: any MTLDevice) {
|
func prepare(device: any MTLDevice) {
|
||||||
guard vertexBuffer == nil else { return }
|
if vertexBuffer == nil {
|
||||||
vertexCount = vertices.endIndex
|
vertexCount = vertices.endIndex
|
||||||
vertexBuffer = device.makeBuffer(bytes: vertices, length: vertexCount * MemoryLayout<PhotoVertex>.stride, options: [])
|
vertexBuffer = device.makeBuffer(bytes: vertices, length: vertexCount * MemoryLayout<PhotoVertex>.stride, options: [])
|
||||||
|
}
|
||||||
|
if texture == nil, let url {
|
||||||
|
texture = Textures.createPhotoTexture(for: url, on: device)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func draw(device: any MTLDevice, renderEncoder: any MTLRenderCommandEncoder) {
|
func draw(device: any MTLDevice, renderEncoder: any MTLRenderCommandEncoder) {
|
||||||
prepare(device: device)
|
prepare(device: device)
|
||||||
|
renderEncoder.setFragmentTexture(texture, index: 0)
|
||||||
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
||||||
renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: vertices.count)
|
renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: vertices.count)
|
||||||
|
texture = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,11 +37,10 @@ vertex VertexOut vertex_photo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fragment float4 fragment_photo(
|
fragment float4 fragment_photo(
|
||||||
VertexOut out [[stage_in]]
|
VertexOut out [[stage_in]],
|
||||||
// texture2d<float> texture [[texture(0)]]
|
texture2d<float> texture [[texture(0)]]
|
||||||
) {
|
) {
|
||||||
// constexpr sampler textureSampler(mag_filter::linear, min_filter::linear);
|
constexpr sampler textureSampler(mag_filter::linear, min_filter::linear);
|
||||||
// float4 color = float4(texture.sample(textureSampler, out.textCoord));
|
float4 color = float4(texture.sample(textureSampler, out.textCoord));
|
||||||
// return color;
|
return color;
|
||||||
return float4(1, 0, 1, 1);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class Tool: NSObject, ObservableObject {
|
|||||||
@Published var selectedPen: Pen?
|
@Published var selectedPen: Pen?
|
||||||
@Published var draggedPen: Pen?
|
@Published var draggedPen: Pen?
|
||||||
// MARK: - Photo
|
// MARK: - Photo
|
||||||
@Published var selectedImage: UIImage?
|
@Published var selectedImageURL: URL?
|
||||||
|
|
||||||
@Published var selection: ToolSelection = .none
|
@Published var selection: ToolSelection = .none
|
||||||
|
|
||||||
|
|||||||
@@ -233,8 +233,12 @@ extension CanvasViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func recognizeTapGesture(_ gesture: UITapGestureRecognizer) {
|
@objc func recognizeTapGesture(_ gesture: UITapGestureRecognizer) {
|
||||||
|
guard let url = tool.selectedImageURL else { return }
|
||||||
|
withAnimation {
|
||||||
|
tool.selectedImageURL = nil
|
||||||
|
}
|
||||||
let point = gesture.location(in: drawingView)
|
let point = gesture.location(in: drawingView)
|
||||||
canvas.insertPhoto(at: point.muliply(by: drawingView.ratio))
|
canvas.insertPhoto(at: point.muliply(by: drawingView.ratio), url: url)
|
||||||
drawingView.draw()
|
drawingView.draw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct CameraView: UIViewControllerRepresentable {
|
struct CameraView: UIViewControllerRepresentable {
|
||||||
@Binding var image: UIImage?
|
@Binding var url: URL?
|
||||||
|
|
||||||
|
@ObservedObject var canvas: Canvas
|
||||||
|
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
@@ -33,7 +35,10 @@ struct CameraView: UIViewControllerRepresentable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
||||||
parent.image = info[.originalImage] as? UIImage
|
let image = (info[.originalImage] as? UIImage)?.imageWithUpOrientation()
|
||||||
|
if let image, let data = image.jpegData(compressionQuality: 1) {
|
||||||
|
parent.url = parent.canvas.savePhoto(data)
|
||||||
|
}
|
||||||
parent.dismiss()
|
parent.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// UIImage++.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 6/15/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension UIImage {
|
||||||
|
func imageWithUpOrientation() -> UIImage? {
|
||||||
|
switch imageOrientation {
|
||||||
|
case .up:
|
||||||
|
return self
|
||||||
|
default:
|
||||||
|
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
||||||
|
draw(in: CGRect(origin: .zero, size: size))
|
||||||
|
let result = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,8 +35,8 @@ struct MemoView: View {
|
|||||||
PenDock(tool: tool, canvas: canvas)
|
PenDock(tool: tool, canvas: canvas)
|
||||||
.transition(.move(edge: .trailing))
|
.transition(.move(edge: .trailing))
|
||||||
case .photo:
|
case .photo:
|
||||||
if let image = tool.selectedImage {
|
if let url = tool.selectedImageURL {
|
||||||
PhotoPreview(image: image, tool: tool)
|
PhotoPreview(url: url, tool: tool)
|
||||||
.transition(.move(edge: .trailing))
|
.transition(.move(edge: .trailing))
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -8,11 +8,15 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct PhotoPreview: View {
|
struct PhotoPreview: View {
|
||||||
let image: UIImage
|
let url: URL
|
||||||
@ObservedObject var tool: Tool
|
@ObservedObject var tool: Tool
|
||||||
|
|
||||||
|
var data: Data {
|
||||||
|
(try? Data(contentsOf: url)) ?? Data()
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Image(uiImage: image)
|
Image(uiImage: UIImage(data: data) ?? UIImage())
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
@@ -27,7 +31,7 @@ struct PhotoPreview: View {
|
|||||||
.overlay(alignment: .topLeading) {
|
.overlay(alignment: .topLeading) {
|
||||||
Button {
|
Button {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
tool.selectedImage = nil
|
tool.selectedImageURL = nil
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "xmark.circle.fill")
|
Image(systemName: "xmark.circle.fill")
|
||||||
|
|||||||
@@ -61,8 +61,9 @@ struct Toolbar: View {
|
|||||||
Task {
|
Task {
|
||||||
let data = try? await newValue?.loadTransferable(type: Data.self)
|
let data = try? await newValue?.loadTransferable(type: Data.self)
|
||||||
if let data {
|
if let data {
|
||||||
|
let url = canvas.savePhoto(data)
|
||||||
withAnimation {
|
withAnimation {
|
||||||
tool.selectedImage = UIImage(data: data)
|
tool.selectedImageURL = url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
photoItem = nil
|
photoItem = nil
|
||||||
@@ -70,7 +71,7 @@ struct Toolbar: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.fullScreenCover(isPresented: $opensCamera) {
|
.fullScreenCover(isPresented: $opensCamera) {
|
||||||
CameraView(image: $tool.selectedImage)
|
CameraView(url: $tool.selectedImageURL, canvas: canvas)
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
}
|
}
|
||||||
.alert("Camera Access Denied", isPresented: $isCameraAccessDenied) {
|
.alert("Camera Access Denied", isPresented: $isCameraAccessDenied) {
|
||||||
@@ -166,7 +167,7 @@ struct Toolbar: View {
|
|||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
PhotosPicker(selection: $photoItem, matching: .images) {
|
PhotosPicker(selection: $photoItem, matching: .images, preferredItemEncoding: .compatible) {
|
||||||
Image(systemName: "photo.fill.on.rectangle.fill")
|
Image(systemName: "photo.fill.on.rectangle.fill")
|
||||||
.contentShape(.circle)
|
.contentShape(.circle)
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
|
|||||||
Reference in New Issue
Block a user