Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions NotebookSaver/AppleIntelligenceEnhancer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Foundation
import UIKit
import CoreImage

enum AppleIntelligenceSupport {
static var isAvailable: Bool {
if #available(iOS 26.0, *) {
#if canImport(FoundationModels)
return true
#else
return false
#endif
}
return false
}
}

struct AppleIntelligenceEnhancer {
static func enhance(image: UIImage) async -> UIImage {
if #available(iOS 26.0, *) {
#if canImport(FoundationModels)
// NOTE: Replace this block with real Foundation Models integration when SDK is available.
// For now, we keep behavior identical to fallback to preserve OCR fidelity.
return fallbackEnhance(image)
#else
return fallbackEnhance(image)
#endif
} else {
return fallbackEnhance(image)
}
}

private static func fallbackEnhance(_ image: UIImage) -> UIImage {
guard let ciImage = CIImage(image: image) else { return image }

// Mild exposure increase
let exposure = CIFilter(name: "CIExposureAdjust")
exposure?.setValue(ciImage, forKey: kCIInputImageKey)
exposure?.setValue(0.25, forKey: kCIInputEVKey)
let exposureOutput = exposure?.outputImage ?? ciImage

// Slight contrast bump, reduced saturation for text clarity
let controls = CIFilter(name: "CIColorControls")
controls?.setValue(exposureOutput, forKey: kCIInputImageKey)
controls?.setValue(1.05, forKey: kCIInputContrastKey)
controls?.setValue(0.0, forKey: kCIInputSaturationKey)
let finalOutput = controls?.outputImage ?? exposureOutput

let context = CIContext()
if let cg = context.createCGImage(finalOutput, from: finalOutput.extent) {
return UIImage(cgImage: cg)
}
return image
}
}
24 changes: 21 additions & 3 deletions NotebookSaver/CameraView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,27 @@ extension CameraView {
throw CameraManager.CameraError.processingFailed("Could not create UIImage from captured data.")
}

// For now, return the original image without any processing
// This could be enhanced with resizing/optimization if needed for the UI
return imageToProcess
let modeRaw = UserDefaults.standard.string(forKey: "imageProcessingMode") ?? ImageProcessingMode.none.rawValue
let mode = ImageProcessingMode(rawValue: modeRaw) ?? .none

switch mode {
case .none:
return imageToProcess
case .optimized:
// Use existing Core Image based resizing for UI responsiveness
let processor = ImageProcessor()
let maxDimension: CGFloat = 1500
do {
return try processor.resizeImage(imageToProcess, maxDimension: maxDimension)
} catch {
print("Optimized resize failed, returning original image: \(error.localizedDescription)")
return imageToProcess
}
case .appleIntelligence:
// Use Apple Intelligence if available, otherwise fallback enhancement
let enhanced = await AppleIntelligenceEnhancer.enhance(image: imageToProcess)
return enhanced
}
}

private func extractTextFromProcessedImage(_ processedImage: UIImage) async throws -> String {
Expand Down
9 changes: 9 additions & 0 deletions NotebookSaver/ImageProcessingMode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation

enum ImageProcessingMode: String, CaseIterable, Identifiable {
case none = "None"
case optimized = "Optimized"
case appleIntelligence = "Apple Intelligence"

var id: String { rawValue }
}
31 changes: 31 additions & 0 deletions NotebookSaver/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ struct SettingsView: View {
static let visionUsesLanguageCorrection = "visionUsesLanguageCorrection"
// AI thinking toggle
static let thinkingEnabled = "thinkingEnabled"
// Image processing mode
static let imageProcessingMode = "imageProcessingMode"
}

// === Persisted Settings ===
Expand All @@ -90,6 +92,8 @@ struct SettingsView: View {
@AppStorage(StorageKeys.thinkingEnabled) private var thinkingEnabled: Bool = false // Default to false (thinking off)
// Text extraction service selection
@AppStorage("textExtractorService") private var textExtractorService: String = AppDefaults.textExtractorService
// Image processing mode
@AppStorage(StorageKeys.imageProcessingMode) private var imageProcessingMode: String = ImageProcessingMode.none.rawValue

// === State for API Key (using Keychain) ===
@State private var apiKey: String = ""
Expand Down Expand Up @@ -368,11 +372,36 @@ struct SettingsView: View {
.transition(.opacity.combined(with: .move(edge: .top)))
}

// Image Processing Mode - horizontal layout
HStack(spacing: 16) {
Text("Image Processing")
.font(.headline)
.foregroundColor(Color.orangeTabbyText.opacity(0.7))
.frame(width: 120, alignment: .leading)

Picker("Image Processing", selection: $imageProcessingMode) {
ForEach(ImageProcessingMode.allCases, id: \.rawValue) { mode in
Text(mode.rawValue).tag(mode.rawValue)
}
}
.pickerStyle(SegmentedPickerStyle())
}

// Save Photo Toggle - horizontal layout
HStack(spacing: 16) {
Text("Save Photo")
.font(.headline)
.foregroundColor(Color.orangeTabbyText.opacity(0.7))
.overlay(
Group {
if ImageProcessingMode(rawValue: imageProcessingMode) == .appleIntelligence && !AppleIntelligenceSupport.isAvailable {
Text("(AI not available on this device)")
.font(.caption)
.foregroundColor(.red)
.padding(.leading, 8)
}
}, alignment: .trailing
)

Spacer()

Expand Down Expand Up @@ -1032,6 +1061,8 @@ struct SettingsView: View {
thinkingEnabled = false
// Reset prompt to default based on thinking state
updatePromptForThinking(enabled: thinkingEnabled)
// Reset image processing mode
imageProcessingMode = ImageProcessingMode.none.rawValue
// Note: API key is not reset as it's sensitive information
}

Expand Down