Skip to content

Commit

Permalink
Merge pull request #3 from Eskils/develop
Browse files Browse the repository at this point in the history
Dithering Engine 1.6.1
  • Loading branch information
Eskils authored Nov 21, 2023
2 parents 5e827d5 + be44339 commit 4acc6f5
Show file tree
Hide file tree
Showing 12 changed files with 363 additions and 139 deletions.
8 changes: 4 additions & 4 deletions Documentation/Demo/Dithering.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Dithering/Dithering.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6;
CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = "\"Dithering/Preview Content\"";
DEVELOPMENT_TEAM = EUKTZ7725R;
ENABLE_HARDENED_RUNTIME = NO;
Expand All @@ -373,7 +373,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.6.0;
MARKETING_VERSION = 1.6.1;
PRODUCT_BUNDLE_IDENTIFIER = com.skillbreak.Dithering;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
Expand All @@ -392,7 +392,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = Dithering/Dithering.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 6;
CURRENT_PROJECT_VERSION = 7;
DEVELOPMENT_ASSET_PATHS = "\"Dithering/Preview Content\"";
DEVELOPMENT_TEAM = EUKTZ7725R;
ENABLE_HARDENED_RUNTIME = NO;
Expand All @@ -409,7 +409,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.6.0;
MARKETING_VERSION = 1.6.1;
PRODUCT_BUNDLE_IDENTIFIER = com.skillbreak.Dithering;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
Expand Down
4 changes: 2 additions & 2 deletions Documentation/Demo/Dithering/DocumentExporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import SwiftUI

struct DocumentExporter: UIViewControllerRepresentable {

var exporting: URL?
var exporting: URL

func makeUIViewController(context: UIViewControllerRepresentableContext<DocumentExporter>) -> UIDocumentPickerViewController {
let picker = UIDocumentPickerViewController(forExporting: [exporting].compactMap({$0}))
let picker = UIDocumentPickerViewController(forExporting: [exporting])
picker.allowsMultipleSelection = false
return picker
}
Expand Down
24 changes: 23 additions & 1 deletion Documentation/Demo/Dithering/Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,36 @@ func renderImageInPlace(_ image: UIImage, renderSize: CGSize, imageFrame: CGRect
let renderer = UIGraphicsImageRenderer(size: renderSize)

let frame = CGRect(x: imageFrame.minX - imageFrame.width / 2, y: imageFrame.minY - imageFrame.height / 2, width: imageFrame.width, height: imageFrame.height)
let scale = imageFrame.width / renderSize.width

let result = renderer.image { _ in
let result = renderer.image { context in
context.cgContext.interpolationQuality = interpolationQuality(forScale: scale)
image.draw(in: frame)
}

return result
}

private func interpolationQuality(forScale scale: Double) -> CGInterpolationQuality {
if scale > 3 {
return .none
}

if scale > 2 {
return .low
}

if scale > 1 {
return .medium
}

if scale <= 1 {
return .high
}

return .default
}

extension Array where Element: Numeric {

func sum() -> Element {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ struct ImageViewerView: View {

init(appState: AppState) {
self._appState = StateObject(wrappedValue: appState)

//FIXME: This doesnt work
UIImageView.appearance().layer.magnificationFilter = .nearest
UIImageView.appearance().layer.minificationFilter = .nearest
}

var body: some View {
Expand All @@ -38,6 +34,8 @@ struct ImageViewerView: View {
ZStack {
Image(uiImage: mergedImage(geo: geo, rect: imageRect, originalImage: appState.originalImage, finalImage: appState.finalImage, shouldShowOriginalImage: shouldShowOriginalImage, isRunning: appState.isRunning))
.resizable()
.interpolation(.none)
.blur(radius: blurRadius(renderSize: geo, imageFrame: imageRect))
.frame(width: geo.size.width, height: geo.size.height)
.gesture(
DragGesture().simultaneously(with: MagnificationGesture())
Expand Down Expand Up @@ -113,6 +111,28 @@ struct ImageViewerView: View {
return renderImageInPlace(img, renderSize: geo.size, imageFrame: CGRect(origin: pos, size: rect.size), isRunning: isRunning)
}

func blurRadius(renderSize: GeometryProxy, imageFrame: CGRect) -> CGFloat {
let scale = imageFrame.width / renderSize.size.width

if scale > 1 {
return 0
}

if scale > 0.8 {
return 0.5
}

if scale > 0.6 {
return 0.7
}

if scale <= 0.6 {
return 1
}

return 0
}

func shouldTransformImage(gestureResult: SimultaneousGesture<DragGesture, MagnificationGesture>.Value) {

let drag = gestureResult.first
Expand Down
18 changes: 10 additions & 8 deletions Documentation/Demo/Dithering/Views/Toolbar/ToolbarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ struct ToolbarView: View {
@State var showFilePicker = false

@State var exportURL: URL?
@State var showFileExporter = false

@State var error: Error?
@State var showErrorAlert = false
Expand Down Expand Up @@ -106,7 +105,9 @@ struct ToolbarView: View {
.onChange(of: selection) { didChoose(media: $0) }
.sheet(isPresented: $showImagePicker) { ImagePicker(selection: $selection) }
.sheet(isPresented: $showFilePicker) { DocumentPicker(selection: $selection) }
.sheet(isPresented: $showFileExporter) { DocumentExporter(exporting: exportURL) }
.sheet(item: $exportURL) { url in
DocumentExporter(exporting: url)
}
.alert("An error occured", isPresented: $showErrorAlert, actions: {
Button(action: {}) {
Text("Ok")
Expand Down Expand Up @@ -173,6 +174,7 @@ struct ToolbarView: View {

@MainActor
func didPressExport() {
self.exportProgress = 0
if viewModel.isInVideoMode {
exportVideo()
} else {
Expand All @@ -191,9 +193,6 @@ struct ToolbarView: View {
do {
let url = try documentUrlForFile(withName: "DitheredImage.png", storing: imageData)
exportURL = url
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
self.showFileExporter = true
}
} catch {
print("Could not export: ", error)
self.error = error
Expand Down Expand Up @@ -226,9 +225,6 @@ struct ToolbarView: View {
switch result {
case .success(let url):
exportURL = url
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
self.showFileExporter = true
}
case .failure(let error):
self.error = error
self.showErrorAlert = true
Expand All @@ -252,3 +248,9 @@ struct ToolbarView: View {
}

}

extension URL: Identifiable {
public var id: String {
self.absoluteString
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,16 @@ extension ToolbarView {
}
print("Output URL: ", outputURL)

originalVideo.renderSize = CGSize(width: 320, height: 1)
// Resize video to 360:-1 or -1:360
let lowerBoundDimension: CGFloat = 360
if let originalSize = originalVideo.size, originalSize.width > originalSize.height {
let aspectRatio = originalSize.width / originalSize.height
let renderSize = CGSize(width: aspectRatio * lowerBoundDimension, height: lowerBoundDimension)
originalVideo.renderSize = renderSize
} else {
originalVideo.renderSize = CGSize(width: lowerBoundDimension, height: 1)
}

videoDitheringEngine.dither(videoDescription: originalVideo, usingMethod: ditherMethod, andPalette: palette, withDitherMethodSettings: additionalDitherMethodSetting.settingsConfiguration, andPaletteSettings: additionalPalleteSettings.settingsConfiguration, outputURL: outputURL, progressHandler: progressHandler) { error in
timer.invalidate()
DispatchQueue.main.async {
Expand Down
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -520,8 +520,10 @@ videoDitheringEngine.dither(

Some key takeaways:
- Using an ordered dither method is faster, and will give the best result as the pattern will not “move” (like static noise).
- The final video has a framerate of 30. Using a video with less than 30 framerate as input is not supported (yet).
- Audio is not supported (yet)
- By default, the final video has a framerate of 30. You may adjust the final framerate by providing a frame rate when initializing VideoDitheringEngine. The final frame rate is less than or equal to the specified value.:
```swift
VideoDitheringEngine(frameRate: Int)
```

### Video Description

Expand All @@ -533,11 +535,15 @@ You set the video you want to use as input through the `VideoDescription` type.
**Properites**
| Name | Type | Default | Description |
|------|------|---------|-------------|
| renderSize | CGSize? | nil | Specifies the size for which to render the final dithered video. |
| renderSize | CGSize? { get set } | nil | Specifies the size for which to render the final dithered video. |
| framerate | Float? { get } | nominalFrameRate | Returns the number of frames per second. Nil if the asset does not contain video. |
| duration | TimeInterval { get } | duration.seconds | Returns the duration of the video. |
| sampleRate | Int? { get } | naturalTimeScale | Returns the number of audio samples per second. Nil if the asset does not contain audio. |
| size | CGSize? { get } | naturalSize | Returns the size of the video. Nil if the asset does not contain video. |

**Methods**

```swift
/// Reads the first frame in the video as an image.
func getPreviewImage() async throws -> CGImage
```
```
14 changes: 7 additions & 7 deletions Sources/DitheringEngine/DitheringEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,20 +131,20 @@ public class DitheringEngine {
return try resultImageDescription.makeCGImage()
}

func generateResultPixelBuffer(invertedColorBuffer: UnsafeMutablePointer<UInt8>) throws -> CVPixelBuffer {
func generateResultPixelBuffer() throws -> CVPixelBuffer {
guard let resultImageDescription else {
throw Error.noImageDescription
}

return try resultImageDescription.makePixelBuffer(invertedColorBuffer: invertedColorBuffer)
return try resultImageDescription.makePixelBuffer()
}

func generateOriginalImagePixelBuffer(invertedColorBuffer: UnsafeMutablePointer<UInt8>) throws -> CVPixelBuffer {
func generateOriginalImagePixelBuffer() throws -> CVPixelBuffer {
guard let imageDescription else {
throw Error.noImageDescription
}

return try imageDescription.makePixelBuffer(invertedColorBuffer: invertedColorBuffer)
return try imageDescription.makePixelBuffer()
}

}
Expand Down Expand Up @@ -204,13 +204,13 @@ extension DitheringEngine {
return try generateResultImage()
}

func ditherIntoPixelBuffer(usingMethod method: DitherMethod, andPalette palette: Palette, withDitherMethodSettings ditherSettings: SettingsConfiguration, withPaletteSettings paletteSettings: SettingsConfiguration, invertedColorBuffer: UnsafeMutablePointer<UInt8>, byteColorCache: ByteByteColorCache?, floatingColorCache: FloatByteColorCache?) throws -> CVPixelBuffer {
func ditherIntoPixelBuffer(usingMethod method: DitherMethod, andPalette palette: Palette, withDitherMethodSettings ditherSettings: SettingsConfiguration, withPaletteSettings paletteSettings: SettingsConfiguration, byteColorCache: ByteByteColorCache?, floatingColorCache: FloatByteColorCache?) throws -> CVPixelBuffer {
guard
let imageDescription,
let floatingImageDescription,
let resultImageDescription
else {
return try generateResultPixelBuffer(invertedColorBuffer: invertedColorBuffer)
return try generateResultPixelBuffer()
}

resultImageDescription.buffer.update(repeating: 255, count: resultImageDescription.count)
Expand All @@ -227,7 +227,7 @@ extension DitheringEngine {
floatingColorCache: floatingColorCache
)

return try generateResultPixelBuffer(invertedColorBuffer: invertedColorBuffer)
return try generateResultPixelBuffer()
}

}
Loading

0 comments on commit 4acc6f5

Please sign in to comment.