Skip to content

Commit

Permalink
Dithering Engine 1.8.0
Browse files Browse the repository at this point in the history
Merge pull request #8 from Eskils/develop
  • Loading branch information
Eskils authored Jul 24, 2024
2 parents e402ea8 + 715ceda commit dc9c401
Show file tree
Hide file tree
Showing 51 changed files with 260 additions and 140 deletions.
Original file line number Diff line number Diff line change
@@ -1,67 +1,67 @@
{
"images" : [
{
"filename" : "DitherableIconNewBackground.png",
"filename" : "DEIcon64Opaque.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"filename" : "DitherableIconNew-kopi 9.png",
"filename" : "DEIcon64Transparent-kopi 9.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "DitherableIconNew-kopi 8.png",
"filename" : "DEIcon64Transparent-kopi 8.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "DitherableIconNew-kopi 7.png",
"filename" : "DEIcon64Transparent-kopi 7.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "DitherableIconNew-kopi 6.png",
"filename" : "DEIcon64Transparent-kopi 6.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "DitherableIconNew-kopi 5.png",
"filename" : "DEIcon64Transparent-kopi 5.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "DitherableIconNew-kopi 4.png",
"filename" : "DEIcon64Transparent-kopi 3.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "DitherableIconNew-kopi 3.png",
"filename" : "DEIcon64Transparent-kopi 4.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "DitherableIconNew-kopi 2.png",
"filename" : "DEIcon64Transparent-kopi 2.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "DitherableIconNew-kopi 1.png",
"filename" : "DEIcon64Transparent-kopi 1.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "DitherableIconNew-kopi.png",
"filename" : "DEIcon64Transparent-kopi.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "DitherableIconNew.png",
"filename" : "DEIcon64Transparent.png",
"idiom" : "universal"
}
],
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.

This file was deleted.

Binary file not shown.

This file was deleted.

Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,14 @@ class CustomPaletteSettingsConfigurationWithView: PaletteSettingsConfigurationWi
case is BayerSettingsConfiguration:
let settingsConfiguration = paletteSettingsConfiguration as! BayerSettingsConfiguration

let intensitySubject: CurrentValueSubject<Float, Never> = CurrentValueSubject(0.5)

let views: [any SettingView] = [
NumberSettingViewDescription(subject: settingsConfiguration.thresholdMapSize, title: "Threshold Map Size", min: 0, max: 8),
OptionalSettingViewDescription(
subject: settingsConfiguration.intensity,
title: "Use intensity",
viewDescription: NumberSettingViewDescription(subject: intensitySubject, title: "Intensity", min: 0, max: 1)),
BooleanSettingViewDescription(isOn: settingsConfiguration.performOnCPU, title: "Perform on CPU"),
]

Expand All @@ -165,6 +171,7 @@ class CustomPaletteSettingsConfigurationWithView: PaletteSettingsConfigurationWi

let views: [any SettingView] = [
NumberSettingViewDescription(subject: settingsConfiguration.thresholdMapSize, title: "Threshold Map Size", min: 7, max: 10),
NumberSettingViewDescription(subject: settingsConfiguration.intensity, title: "Intensity", min: 0, max: 1),
BooleanSettingViewDescription(isOn: settingsConfiguration.performOnCPU, title: "Perform on CPU"),
]

Expand All @@ -174,6 +181,7 @@ class CustomPaletteSettingsConfigurationWithView: PaletteSettingsConfigurationWi
let settingsConfiguration = paletteSettingsConfiguration as! NoiseDitheringSettingsConfiguration
let views: [any SettingView] = [
CustomImageSettingViewDescription(image: settingsConfiguration.noisePattern, title: "Noise Pattern"),
NumberSettingViewDescription(subject: settingsConfiguration.intensity, title: "Intensity", min: 0, max: 1),
BooleanSettingViewDescription(isOn: settingsConfiguration.performOnCPU, title: "Perform on CPU"),
]

Expand Down
57 changes: 57 additions & 0 deletions Documentation/Demo/Dithering/SettingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,63 @@ struct NumberSettingView<Number: RepresentableAsFloatingPoint>: View {

}

struct OptionalSettingViewDescription<V, ViewDescription: SettingView>: SettingView, ViewConstructable, Identifiable where ViewDescription.T == V, ViewDescription: ViewConstructable {
typealias T = Optional<V>

let id = UUID().uuidString

let subject: CurrentValueSubject<T, Never>
let isEnabled: CurrentValueSubject<Bool, Never> = CurrentValueSubject(false)
let title: String
let viewDescription: ViewDescription

func makeView() -> AnyView {
AnyView(OptionalSettingView(description: self))
}
}

struct OptionalSettingView<V, ViewDescription: SettingView>: View where ViewDescription.T == V, ViewDescription: ViewConstructable {

let description: OptionalSettingViewDescription<V, ViewDescription>

@State
var state: Optional<V>

@State
var isEnabled: Bool

init(description: OptionalSettingViewDescription<V, ViewDescription>) {
self.description = description
self._state = State(wrappedValue: description.subject.value)
self._isEnabled = State(wrappedValue: description.subject.value != nil)
}

var body: some View {
let bindedValue = binding(_isEnabled) { val in
self.description.isEnabled.send(val)
}

VStack {
VStack {
Toggle(isOn: bindedValue, label: {
Text(description.title)
})
if isEnabled {
description.viewDescription.makeView()
}
}
}
.onReceive(Publishers.CombineLatest(description.isEnabled, description.viewDescription.subject), perform: { (isEnabled, subject) in
if isEnabled {
self.description.subject.send(subject)
} else {
self.description.subject.send(nil)
}
})
}

}

struct EnumSettingViewDescription<Enum: Nameable>: SettingView, ViewConstructable, Identifiable {
let id = UUID().uuidString

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ struct ImageViewerView: View {
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 @@ -124,28 +123,6 @@ 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
Binary file modified Documentation/Resources/DitheringEngineLogo.png
Binary file added Documentation/Resources/JJNIntellivision.png
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PackageDescription
let package = Package(
name: "DitheringEngine",
platforms: [
.iOS(.v13)
.iOS(.v13), .macCatalyst(.v13)
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
Expand Down
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Check out the [demo application](./Documentation/Demo/) for iOS and macOS.
* [CGA](#cga)
* [Apple II](#apple-ii)
* [Game Boy](#game-boy)
* [Intellivision](#intellivision)
* [Creating your own palette](#creating-your-own-palette)
* [Video Dithering Engine](#video-dithering-engine)
* [Ordered dithering is more suitable](#ordered-dithering-is-more-suitable)
Expand All @@ -51,7 +52,7 @@ let package = Package(
dependencies: [
.package(
url: "https://github.com/Eskils/DitheringEngine",
.upToNextMinor(from: "1.7.0") // or `.upToNextMajor
.upToNextMinor(from: "1.8.0") // or `.upToNextMajor
)
],
targets: [
Expand Down Expand Up @@ -87,6 +88,7 @@ Supported out of the box palettes are:
* [CGA](#cga)
* [Apple II](#apple-ii)
* [Game Boy](#game-boy)
* [Intellivision](#intellivision)

### Dithering images
Example usage:
Expand Down Expand Up @@ -237,6 +239,7 @@ Bayer dithering is a type of ordered dithering which adds a precalculated thresh
| Name | Type | Default | Description |
|------|------|---------|-------------|
| thresholdMapSize | Int | `4` | Specifies the size of the square threshold matrix. Default is 4x4. |
| intensity | Float? | `nil` | Specifies the intensity of the noise pattern. When nil, the intensity is calculated from the thresholdMapSize. |
| performOnCPU | Bool | `false` | Determines wether to perform the computation on the CPU. If false, the GPU is used for quicker performance. |

Example:
Expand All @@ -263,6 +266,7 @@ White noise dithering adds random noise to the image when converting to the sele
| Name | Type | Default | Description |
|------|------|---------|-------------|
| thresholdMapSize | Int | `7` | Specifies the size of the square threshold matrix. Default is 128x128. |
| intensity | Float | `0.5` | Specifies the intensity of the noise pattern. |
| performOnCPU | Bool | `false` | Determines wether to perform the computation on the CPU. If false, the GPU is used for quicker performance. |

Example:
Expand Down Expand Up @@ -291,6 +295,7 @@ You can provide your own noise texture to sample when performing ordered ditheri
| Name | Type | Default | Description |
|------|------|---------|-------------|
| noisePattern | CGImage? | `nil` | Specifies the noise pattern to use for ordered dithering. |
| intensity | Float | `0.5` | Specifies the intensity of the noise pattern. |
| performOnCPU | Bool | `false` | Determines wether to perform the computation on the CPU. If false, the GPU is used for quicker performance. |

Example:
Expand Down Expand Up @@ -471,6 +476,27 @@ let cgImage = try ditheringEngine.dither(
)
```

### Intellivision

The Intellivision was a game console from the late 70’s. Its graphics was powered by the Standard Television Interface Chip, which came with a 16-color palette.

![Atkinson dithering with the Game Boy palette.](Documentation/Resources/JJNIntellivision.png)

**Token:** `.intellivision`
**Settings:** `EmptyPaletteSettingsConfiguration`

Example:
```swift
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .atkinson,
andPalette: .jarvisJudiceNinke,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: EmptyPaletteSettingsConfiguration()
)
```

## Creating your own palette

You can create your own palettes using the appropriate APIs.
Expand Down
24 changes: 21 additions & 3 deletions Sources/DitheringEngine/DitherMehtod.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ public enum DitherMethod: String, CaseIterable, Codable {
case bayer
case whiteNoise
case noise

var preferNoGray: Bool {
switch self {
case .none,
.threshold,
.floydSteinberg,
.atkinson,
.jarvisJudiceNinke:
return false
case .bayer,
.whiteNoise,
.noise:
return true
}
}
}

extension DitherMethod {
Expand All @@ -36,12 +51,14 @@ extension DitherMethod {
let settings = (settings as? BayerSettingsConfiguration) ?? .init()
let thresholdMapSize = settings.size
let performOnCPU = settings.performOnCPU.value
ditherMethods.bayer(palette: lut, thresholdMapSize: thresholdMapSize, performOnCPU: performOnCPU)
let intensity = settings.intensity.value
ditherMethods.bayer(palette: lut, thresholdMapSize: thresholdMapSize, intensity: intensity, performOnCPU: performOnCPU)
case .whiteNoise:
let settings = (settings as? WhiteNoiseSettingsConfiguration) ?? .init()
let thresholdMapSize = settings.size
let performOnCPU = settings.performOnCPU.value
ditherMethods.whiteNoise(palette: lut, thresholdMapSize: thresholdMapSize, performOnCPU: performOnCPU)
let intensity = settings.intensity.value
ditherMethods.whiteNoise(palette: lut, thresholdMapSize: thresholdMapSize, intensity: intensity, performOnCPU: performOnCPU)
case .noise:
let settings = (settings as? NoiseDitheringSettingsConfiguration) ?? .init()
guard let noisePattern = settings.noisePattern.value else {
Expand All @@ -50,8 +67,9 @@ extension DitherMethod {
}
let noisePatternBuffered = ImageDescription(width: noisePattern.width, height: noisePattern.height, components: noisePattern.bytesPerRow / noisePattern.width)
let performOnCPU = settings.performOnCPU.value
let intensity = settings.intensity.value
if noisePatternBuffered.setBufferFrom(image: noisePattern) {
ditherMethods.noise(palette: lut, noisePattern: noisePatternBuffered, performOnCPU: performOnCPU)
ditherMethods.noise(palette: lut, noisePattern: noisePatternBuffered, intensity: intensity, performOnCPU: performOnCPU)
} else {
print("Could not load noise pattern.")
ditherMethods.none(palette: lut)
Expand Down
Loading

0 comments on commit dc9c401

Please sign in to comment.