π₯ v2.0.0 development is still early development. We have a lot of known issues.
β Issues are managed in v2 project
π Pixel has been renamed as Brightroom
βοΈ If you interested in v2, hit the Star button to motivate us! π€
Pixel v2 provides the following features:
- Components are built separately and run standalone using an
EditingStack
. - Create your own image editor UI by composing components.
EditingStack
manages the history of editing and renders images. It's like a headless browser.- Wide color editing support
- TODO
Crop | Face detection |
---|---|
UIKit
let uiImage: UIImage = ...
let controller = CropViewController(imageProvider: .init(image: uiImage))
controller.modalPresentationStyle = .fullScreen
controller.handlers.didCancel = { controller in
controller.dismiss(animated: true, completion: nil)
}
controller.handlers.didFinish = { [weak self] controller in
controller.dismiss(animated: true, completion: nil)
controller.editingStack.makeRenderer()?.render { [weak self] image in
// β
handle the result image.
}
}
present(controller, animated: true, completion: nil)
SwiftUI
WIP
SwiftUIPhotosCropView
WIP
- CropView - A view that previews how it crops the image. It supports zooming, scrolling, adjusting the guide and more customizable appearances.
- BlurryMaskingView - A view that drawing mask shapes with blur.
- ImagePreviewView - A view that previews the finalized image on
EditingStack
- MetalImageView - A view that displays the image powered by Metal.
- LoadingBlurryOverlayView - A view that displays a loading-indicator and blurry backdrop view.
CropView is a component that only supports cropping.
UIKit
let image: UIImage
let view = CropView(image: image)
let resultImage = view.renderImage()
SwiftUI
struct DemoCropView: View {
let editingStack: EditingStack
var body: some View {
VStack {
// β
Display a cropping view
SwiftUICropView(
editingStack: editingStack
)
// β
Renders a result image from the current editing.
Button("Done") {
let image: UIImage = editingStack.makeRenderer().render()
}
}
.onAppear {
editingStack.start()
}
}
}
How to create cube data from LUT Image for CIColorCube / CIColorCubeWithColorSpace
Regarding LUT, the format of LUT changed from v2.
We can download the neutral LUT image from lutCreator.js.
Make sure to use HALD 64 SIZE. Currently, CIColorCube supports dimension is up to 64.
Hald is a graphical representation of 3D LUT in a form of a color table which contains all of the color gradations of 3D LUT. If Hald is loaded into editing software and a color correction is applied to it, you can use 3D LUT Creator to convert your Hald into 3D LUT and apply it to a photo or a video in your editor.
- WIP
β οΈ Brightroom has not been published in CocoaPods since it's still early development. If you try to use it, following pod commands install libraries to your application.
CocoaPods
pod "Brightroom/Engine", "2.0.0-alpha.1"
pod "Brightroom/UI-Classic", "2.0.0-alpha.1"
pod "Brightroom/UI-Crop", "2.0.0-alpha.1"
Swift Package Manager
dependencies: [
.package(url: "https://github.com/muukii/Brightroom.git", exact: "2.0.0-alpha.1")
]
Image editor and engine using CoreImage
Hi, I'm working on open-source software with a lot of time. Please help me continue my work. I appreciate it.
https://github.com/sponsors/muukii
- eureka, Inc.
- Pairs
- Pairs Engage
Currently accepting PRs that impement these features.
-
βοΈ Pretty Good - πBlazing Fast (πAnyone help us!)
- Crop
- Straighten (πAnyone help us!)
- Perspective (πAnyone help us!)
- ColorCube (Look Up Table)
- Intensity
β οΈ Currently, Pixel does not contain LUT. Demo app has sample LUTs.
And also, here is interesting article
- Brightness
- Contrast
- Saturation
- Highlights
- Shadows
- Temperature
- GaussianBlur
- Vignette
- Color (Shadows / Highlights)
- Fade
- Sharpen
- Clarity
- HLS (πAnyone help us!)
- Swift 4.2 (Xcode10+)
- iOS 10+
Demo.app contains the sample code.
Please check out Sources/Demo/EditorViewController.swift
.
let image: UIImage
let controller = PixelEditViewController(image: image)
- as Modal
UINavigationController
. This is because PixelEditViewController
needs a UINavigationBar
.
let controller: PixelEditViewController
let navigationController = UINavigationController(rootViewController: controller)
self.present(navigationController, animated: true, completion: nil)
- as Push
We can push the controller in UINavigationController
.
let controller: PixelEditViewController
self.navigationController.push(controller, animated: true)
PixelEditViewController
has delegate protocol called PixelEditViewControllerDelegate
.
public protocol PixelEditViewControllerDelegate : class {
func pixelEditViewController(_ controller: PixelEditViewController, didEndEditing editingStack: SquareEditingStack)
func pixelEditViewControllerDidCancelEditing(in controller: PixelEditViewController)
}
π‘PixelEditViewController
does not know how to dismiss or pop by itself.
So we need to control PixelEditViewController
outside.
Basically, it's like following code, recommend dismiss or pop in methods of delegate.
extension EditorViewController : PixelEditViewControllerDelegate {
func pixelEditViewController(_ controller: PixelEditViewController, didEndEditing editingStack: SquareEditingStack) {
self.navigationController?.popToViewController(self, animated: true)
}
func pixelEditViewControllerDidCancelEditing(in controller: PixelEditViewController) {
self.navigationController?.popToViewController(self, animated: true)
}
}
let editingStack: SquareEditingStack
let image = editingStack.makeRenderer().render(resolution: .full)
We can take current editing as instance of EditingStack
from PixelEditViewController.editingStack
.
If we want to restore editing after closed PixelEditViewController
, we use this.
let editingStack = controller.editingStack
// close editor
// and then when show editor again
let controller = PixelEditViewController(editingStack: editingStack)
We can use LUT(LookUpTable) with CIColorCubeFilter.
LUT is like this (Dimension is 64)
import PixelEngine
let lutImage: UIImage
let filter = FilterColorCube(
name: "Filter Name",
identifier: "Filter Identifier",
lutImage: lutImage,
dimension: 64
)
let storage = ColorCubeStorage(filters: [filter])
let controller = PixelEditViewController(image: image, colorCubeStorage: storage)
And also, if we don't specify colorCubeStorage, use default
.
// set
ColorCubeStorage.default.filters = filters
// get
ColorCubeStorage.default.filters
We can customize UI for control area.
There is Options
struct in PixelEditor.
We can create options that fit our usecases.
So, If we need to change ExposureControl, override ExposureControlBase class. Then set that class to Options.
var options = Options.default
options.classes.control.brightnessControl = MyExposureControl.self
let picker = PixelEditViewController(image: image, options: options)
It's like using custom Cell in UICollectionView. If you have any better idea for this, please tell usπ‘. And also Built-In UI may need expose some properties to customize from subclassing.
We can also customize whole UI.
Override options.classes.control.rootControl
, then build UI from scratch.
For example, if you don't need the filter section but only the edit mode, you may want to create a control like:
final class EditRootControl : RootControlBase {
private let containerView = UIView()
public let colorCubeControl: ColorCubeControlBase
public lazy var editView = context.options.classes.control.editMenuControl.init(context: context)
// MARK: - Initializers
public required init(context: PixelEditContext, colorCubeControl: ColorCubeControlBase) {
self.colorCubeControl = colorCubeControl
super.init(context: context, colorCubeControl: colorCubeControl)
backgroundColor = Style.default.control.backgroundColor
layout: do {
addSubview(containerView)
containerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
containerView.topAnchor.constraint(equalTo: containerView.superview!.topAnchor),
containerView.leftAnchor.constraint(equalTo: containerView.superview!.leftAnchor),
containerView.rightAnchor.constraint(equalTo: containerView.superview!.rightAnchor),
containerView.bottomAnchor.constraint(equalTo: containerView.superview!.bottomAnchor)
])
}
}
// MARK: - Functions
override func didMoveToSuperview() {
super.didMoveToSuperview()
if superview != nil {
editView.frame = containerView.bounds
editView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
containerView.addSubview(editView)
}
}
}
And use it this way:
var options = Options.default
options.classes.control.rootControl = EditRootControl.self
let picker = PixelEditViewController(image: image, options: options)
If there are some edit options you don't need in your app, you can choose edit options you want to ignore:
var options = Options.default
options.classes.control.ignoredEditMenu = [.saturation, .gaussianBlur]
let controller = PixelEditViewController.init(image: UIImage(named: "large")!, options: options)
Strings in UI can be localized with L10n
.
import PixelEditor
PixelEditor.L10n.done = "δΏε"
// or
PixelEditor.L10n.done = NSLocalizedString...
Pixel is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'PixelEngine'
pod 'PixelEditor'
For Carthage, add the following to your Cartfile
:
github "muukii/Pixel"
If you need more features, please open issue or submit PR! Muukii may not know the approach to take for implementing them, So your PR will be very helpful.
To develop Pixel, setup environment of development with following code.
$ pod install
$ open Pixel.xcworkspace
Muukii (muukii.app@gmail.com)
Pixel is available under the MIT license. See the LICENSE file for more info.