diff --git a/Sources/AppCoordinator.swift b/Sources/AppCoordinator.swift index 147d61c..9ff9cb4 100644 --- a/Sources/AppCoordinator.swift +++ b/Sources/AppCoordinator.swift @@ -242,24 +242,41 @@ final class AppCoordinator: ObservableObject { private func openEditor(image: NSImage, sourceURL: URL) { let controller = EditorWindowController(image: image, sourceURL: sourceURL) { [weak self] annotatedImage in guard let self, let annotatedImage else { return } - self.saveAnnotatedImage(annotatedImage, replacingFileAt: sourceURL) + let targetURL: URL + if sourceURL.path.hasPrefix(NSTemporaryDirectory()) { + try? FileManager.default.createDirectory(at: saveFolder, withIntermediateDirectories: true) + targetURL = saveFolder.appendingPathComponent(Self.filename(ext: imageFormat.fileExtension)) + } else { + targetURL = sourceURL + } + self.saveAnnotatedImage(annotatedImage, to: targetURL) } editorController = controller controller.showEditor() } - private func saveAnnotatedImage(_ image: NSImage, replacingFileAt url: URL) { - guard let tiffData = image.tiffRepresentation, - let bitmap = NSBitmapImageRep(data: tiffData) else { return } - + private func saveAnnotatedImage(_ image: NSImage, to url: URL) { + let bitmap: NSBitmapImageRep + if let rep = image.representations.first as? NSBitmapImageRep { + bitmap = rep + } else if let cgRef = image.cgImage(forProposedRect: nil, context: nil, hints: nil) { + bitmap = NSBitmapImageRep(cgImage: cgRef) + } else { + NSLog("SnipMC: could not create bitmap for saving") + return + } let fileType: NSBitmapImageRep.FileType = imageFormat == .jpg ? .jpeg : .png let properties: [NSBitmapImageRep.PropertyKey: Any] = fileType == .jpeg ? [.compressionFactor: 0.9] : [:] - guard let data = bitmap.representation(using: fileType, properties: properties) else { return } + guard let data = bitmap.representation(using: fileType, properties: properties) else { + NSLog("SnipMC: could not encode image as \(imageFormat.rawValue)") + return + } do { try data.write(to: url, options: .atomic) + NSLog("SnipMC: saved to \(url.path)") } catch { NSLog("SnipMC: failed to save annotated image: \(error)") } diff --git a/Sources/Editor/AnnotationRenderer.swift b/Sources/Editor/AnnotationRenderer.swift index ccb0b03..7bc5f4c 100644 --- a/Sources/Editor/AnnotationRenderer.swift +++ b/Sources/Editor/AnnotationRenderer.swift @@ -26,21 +26,39 @@ enum AnnotationRenderer { } static func render(annotations: [AnnotationItem], onto image: NSImage) -> NSImage { + guard let srcCG = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return image } + let w = srcCG.width + let h = srcCG.height let size = image.size - let result = NSImage(size: size) - result.lockFocusFlipped(true) - image.draw(in: NSRect(origin: .zero, size: size), - from: .zero, operation: .sourceOver, fraction: 1, - respectFlipped: true, hints: nil) + guard let cs = srcCG.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB), + let ctx = CGContext( + data: nil, width: w, height: h, + bitsPerComponent: 8, bytesPerRow: 0, space: cs, + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue + ) else { return image } - if let context = NSGraphicsContext.current?.cgContext { - for item in annotations { - draw(item, in: context) - } + ctx.draw(srcCG, in: CGRect(x: 0, y: 0, width: w, height: h)) + + ctx.translateBy(x: 0, y: CGFloat(h)) + let sx = CGFloat(w) / size.width + let sy = CGFloat(h) / size.height + ctx.scaleBy(x: sx, y: -sy) + + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.current = NSGraphicsContext(cgContext: ctx, flipped: true) + + for item in annotations { + draw(item, in: ctx) } - result.unlockFocus() + NSGraphicsContext.restoreGraphicsState() + + guard let resultCG = ctx.makeImage() else { return image } + let bitmapRep = NSBitmapImageRep(cgImage: resultCG) + bitmapRep.size = size + let result = NSImage(size: size) + result.addRepresentation(bitmapRep) return result }