📷 A composable image editor using Core Image and Metal.
翻译准备中
设计Swift · MetalGitHub @FluidGroup自行安装运营者未确认
试用过了吗? 点一下就好,无需登录。
制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Build a photo editor with the included editor UI, compose your own editing interface, or use the rendering engine on its own. Brightroom keeps edits as parameters, so you can change adjustments, revisit crops, and render again from the original image.
ImageEditor.ImageEditorCanvas, which edits the same document as the built-in editor.EditingDocument keeps the source image, the edit as a value composed from typed features, and its undo history. Undo, redo, and rendering are operations on that value.SourceImage from a UIImage, a CGImage, encoded data, a file, RAW data, or a Photos asset. Download remote images yourself and pass the data.Choose the product that matches what you want to build:
| Product | Purpose | Platforms |
|---|---|---|
BrightroomUI | The built-in image editor and a reusable editing canvas for SwiftUI | iOS 17+ |
BrightroomEngine | Source images, editing documents with undo history, and rendering | iOS 17+ |
BrightroomParametric | Typed features, masks, and Core Image rendering, with no third-party dependencies and no SwiftUI | iOS 17+, macOS 14+ |
The Swift package requires Swift 6.3 or later and uses Swift 6 language mode. Use an Xcode toolchain that includes Swift 6.3 or later. The built-in UI uses UIKit internally; UIKit apps can present it through UIHostingController.
Add https://github.com/FluidGroup/Brightroom.git in Xcode's package dependencies, or declare it in your Package.swift:
dependencies: [
.package(url: "https://github.com/FluidGroup/Brightroom.git", from: "6.0.0")
]
For an image editor, add BrightroomUI, BrightroomEngine, and BrightroomParametric to your app target. In a Swift package target, use:
.target(
name: "MyApp",
dependencies: [
.product(name: "BrightroomUI", package: "Brightroom"),
.product(name: "BrightroomEngine", package: "Brightroom"),
.product(name: "BrightroomParametric", package: "Brightroom"),
]
)
For processing without the editor, add BrightroomParametric alone.
ImageEditor edits an ImageEditorDocument that your app owns. The document holds the source image, the edit (an ImageEditorFeature), and the edit's undo history. Create it before showing the editor, and keep it in your own state:
import BrightroomEngine
import BrightroomParametric
import BrightroomUI
import SwiftUI
import UIKit
/// Edits one image and delivers the exported image or a rendering error.
struct PhotoEditor: View {
@Environment(\.dismiss) private var dismiss
@State private var document: ImageEditorDocument
@State private var isExporting = false
private let onComplete: (Result<UIImage, Error>) -> Void
init(
image: UIImage,
onComplete: @escaping (Result<UIImage, Error>) -> Void
) {
_document = State(initialValue: ImageEditorDocument(source: SourceImage(image: image)))
self.onComplete = onComplete
}
var body: some View {
ImageEditor(
document: $document,
onDone: export,
onCancel: { dismiss() }
)
.disabled(isExporting)
.overlay {
if isExporting {
ProgressView()
}
}
}
private func export() {
guard !isExporting else { return }
isExporting = true
let document = document
Task {
defer { isExporting = false }
do {
let rendered = try await document.render()
onComplete(.success(try rendered.uiImage))
} catch {
onComplete(.failure(error))
}
}
}
}
The editor prepares the image itself. Each completed operation, such as a filter tap, a slider drag, a brush stroke, or an applied crop, is written to the binding once. On Cancel, the editor writes back the document it opened with, history included, discarding everything written since (your own writes to the binding too), and then calls onCancel; pass nil for onCancel to hide the Cancel button. The editor never registers anything with an UndoManager: undo and redo are document.undo() and document.redo(), so you can drive them from your own buttons.
For large images, prefer SourceImage(contentsOf:) or SourceImage(data:), which decode only when the image is rendered or prepared. SourceImage(asset:) loads a Photos asset. To start from a crop that frames a face, create the document with await ImageEditorDocument(source:initialCrop:) and .faceDetection(aspectRatio:); .aspectRatio(_:) starts from a centered crop of a ratio.
Customize the aspect ratio policy and filters through ImageEditor.Options, and button labels through ImageEditor.LocalizedStrings. Filters default to an empty list; supply your own FilterFeature values through options.filters. An effect of your own that every edit of the image should include (an automatic retouch, say) goes in the document's feature.effects.custom, evaluated after the editor's adjustments; the editor never edits it, and Reset keeps it. The sample filters are included only in the demo app. For a custom layout and controls, start with ImageEditorCanvas, which edits the same document through a binding and leaves committing undo steps to you; put it in an ImageEditorCanvasReader to rotate, reset, or apply the crop from your own controls. See the SwiftUI demo for complete integrations.
A document is a value, so keeping it keeps the edit and its undo history. To resume after the app restarts, store a reference to the source image and the feature's values in your own format, then rebuild the document with ImageEditorDocument(source:feature:history:). Saving the history is optional; see Saving parametric edits.
To export directly to disk, pass a file output to render(options:). This avoids creating a full-resolution output bitmap in memory:
let rendered = try await document.render(
options: .init(
output: .file(url: outputURL, fileType: .heif(quality: 0.9))
)
)
Choose a writable outputURL for the destination file. The file keeps a deeper outputFormat, such as .RGBA16, where its type can: 16 bits per component in a PNG and 10 in a HEIF; JPEG is always 8. The result's options.outputFormat reports the format the file stores. JPEG and PNG files are written with bounded memory; the HEIF encoder holds the whole frame in memory, about twice as much at 10 bits as at 8. The result exposes fileURL and thumbnail(maxPixelSize:). Requesting cgImage or uiImage from a file-backed result decodes the full image back into memory.
BrightroomParametric describes an edit as a feature: a Swift value you compose from typed features, the way a SwiftUI view composes its body. Features evaluate in order, and each one receives the preceding feature's output. The source image stays separate from the feature.
import BrightroomParametric
import CoreImage
/// An exposure adjustment followed by an optional crop.
struct AdjustedCrop: Feature, Equatable {
var exposure = ExposureFeature(ev: 0.5)
var crop: CropFeature?
var body: some Feature {
exposure
crop
}
}
/// Builds an exposure-adjusted image recipe without modifying the source.
func makeAdjustedImage(from source: CIImage) throws -> CIImage {
let feature = AdjustedCrop(
crop: CropFeature(rect: CGRect(x: 0, y: 0, width: 800, height: 600))
)
return try FeatureRenderer().makeImage(from: source, feature: feature)
}
The returned CIImage is a lazy recipe. Use a CIContext to materialize it, or use ExportRenderer to render a feature to an image or file. With BrightroomEngine, EditingDocument<AdjustedCrop> keeps such a feature together with its SourceImage and undo history, and renders it with render(options:).
ImageEffect (and Equatable) to add a custom effect.LocalAdjustmentFeature with a Mask to apply them selectively.if to switch features on and off; a nil member contributes nothing.AnyImageEffect for lists of effects decided by data, such as a filter catalog.Codable model and rebuild them through the public initializers; see Saving parametric edits.VideoRenderer to create an AVMutableVideoComposition for playback or export through AVFoundation.Clone the repository with its submodules and open the development project:
git clone --recurse-submodules https://github.com/FluidGroup/Brightroom.git
cd Brightroom
open Dev/Brightroom.xcodeproj
Select a scheme and a matching run destination in Xcode. To build the iOS demo from the command line:
xcodebuild \
-project Dev/Brightroom.xcodeproj \
-scheme SwiftUIDemo \
-destination 'generic/platform=iOS Simulator' \
build
ParametricDocumentCodec.Support Brightroom on GitHub Sponsors or buy me a coffee.
Brightroom is available under the MIT license.