diff --git a/CheapRetouch/Features/Editor/EditorViewModel.swift b/CheapRetouch/Features/Editor/EditorViewModel.swift index 96572cf..1e7038a 100644 --- a/CheapRetouch/Features/Editor/EditorViewModel.swift +++ b/CheapRetouch/Features/Editor/EditorViewModel.swift @@ -172,13 +172,19 @@ final class EditorViewModel { // Add operation to project if var project = project { - let maskData = MaskData(from: mask)?.data ?? Data() - let maskOp = MaskOperation(toolType: toolTypeForCurrentTool(), maskData: maskData) - let inpaintOp = InpaintOperation(maskOperationId: maskOp.id, featherAmount: Float(featherAmount)) + if let maskDataObj = MaskData(from: mask) { + let maskOp = MaskOperation( + toolType: toolTypeForCurrentTool(), + maskData: maskDataObj.data, + maskWidth: maskDataObj.width, + maskHeight: maskDataObj.height + ) + let inpaintOp = InpaintOperation(maskOperationId: maskOp.id, featherAmount: Float(featherAmount)) - project.addOperation(.mask(maskOp)) - project.addOperation(.inpaint(inpaintOp)) - self.project = project + project.addOperation(.mask(maskOp)) + project.addOperation(.inpaint(inpaintOp)) + self.project = project + } } } catch { errorMessage = error.localizedDescription diff --git a/CheapRetouch/Models/EditOperation.swift b/CheapRetouch/Models/EditOperation.swift index c326ede..7da7f77 100644 --- a/CheapRetouch/Models/EditOperation.swift +++ b/CheapRetouch/Models/EditOperation.swift @@ -24,12 +24,16 @@ struct MaskOperation: Codable, Identifiable { let id: UUID let toolType: ToolType let maskData: Data + let maskWidth: Int + let maskHeight: Int let timestamp: Date - init(id: UUID = UUID(), toolType: ToolType, maskData: Data, timestamp: Date = Date()) { + init(id: UUID = UUID(), toolType: ToolType, maskData: Data, maskWidth: Int, maskHeight: Int, timestamp: Date = Date()) { self.id = id self.toolType = toolType self.maskData = maskData + self.maskWidth = maskWidth + self.maskHeight = maskHeight self.timestamp = timestamp } } diff --git a/CheapRetouch/Utilities/ImagePipeline.swift b/CheapRetouch/Utilities/ImagePipeline.swift index 78dd0be..5af3d88 100644 --- a/CheapRetouch/Utilities/ImagePipeline.swift +++ b/CheapRetouch/Utilities/ImagePipeline.swift @@ -13,6 +13,7 @@ import UIKit actor ImagePipeline { private let context: CIContext + private let inpaintEngine = InpaintEngine() init() { self.context = CIContext(options: [ @@ -29,12 +30,17 @@ actor ImagePipeline { // Scale down for preview if needed var image = originalImage + let scaleFactor: CGFloat if image.width > maxSize || image.height > maxSize { - let scale = CGFloat(maxSize) / CGFloat(max(image.width, image.height)) - image = try scaleImage(image, scale: scale) + scaleFactor = CGFloat(maxSize) / CGFloat(max(image.width, image.height)) + image = try scaleImage(image, scale: scaleFactor) + } else { + scaleFactor = 1.0 } - // Apply operations (placeholder for now) + // Apply operations + image = try await applyOperations(operations, to: image, scaleFactor: scaleFactor) + return image } @@ -43,7 +49,68 @@ actor ImagePipeline { operations: [EditOperation] ) async throws -> CGImage { // Apply operations at full resolution - return originalImage + var image = originalImage + image = try await applyOperations(operations, to: image, scaleFactor: 1.0) + return image + } + + private func applyOperations( + _ operations: [EditOperation], + to image: CGImage, + scaleFactor: CGFloat + ) async throws -> CGImage { + var currentImage = image + + // Group mask and inpaint operations + var pendingMask: MaskOperation? + + for operation in operations { + switch operation { + case .mask(let maskOp): + pendingMask = maskOp + + case .inpaint: + if let maskOp = pendingMask { + let maskData = MaskData( + width: maskOp.maskWidth, + height: maskOp.maskHeight, + data: maskOp.maskData + ) + if let mask = maskData.toCGImage() { + // Scale mask if needed + let scaledMask: CGImage + if scaleFactor != 1.0 { + scaledMask = try scaleImage(mask, scale: scaleFactor) + } else { + scaledMask = mask + } + + // Apply inpainting + currentImage = try await inpaintEngine.inpaint( + image: currentImage, + mask: scaledMask + ) + } + } + pendingMask = nil + + case .adjustment(let adjustment): + currentImage = applyAdjustment(adjustment, to: currentImage) ?? currentImage + } + } + + return currentImage + } + + private func applyAdjustment(_ adjustment: AdjustmentOperation, to image: CGImage) -> CGImage? { + switch adjustment.type { + case .brightness: + return applyColorAdjustment(to: image, brightness: adjustment.value) + case .contrast: + return applyColorAdjustment(to: image, contrast: adjustment.value) + case .saturation: + return applyColorAdjustment(to: image, saturation: adjustment.value) + } } func applyColorAdjustment(