V0.1.3 Colored dithering
This commit is contained in:
parent
8382078eb4
commit
d2b78668c3
|
|
@ -20,6 +20,7 @@ struct ContentView: View {
|
||||||
.onChange(of: viewModel.brightness) { _, _ in viewModel.processImage() }
|
.onChange(of: viewModel.brightness) { _, _ in viewModel.processImage() }
|
||||||
.onChange(of: viewModel.contrast) { _, _ in viewModel.processImage() }
|
.onChange(of: viewModel.contrast) { _, _ in viewModel.processImage() }
|
||||||
.onChange(of: viewModel.pixelScale) { _, _ in viewModel.processImage() }
|
.onChange(of: viewModel.pixelScale) { _, _ in viewModel.processImage() }
|
||||||
|
.onChange(of: viewModel.colorDepth) { _, _ in viewModel.processImage() }
|
||||||
.onChange(of: viewModel.selectedAlgorithm) { _, _ in viewModel.processImage() }
|
.onChange(of: viewModel.selectedAlgorithm) { _, _ in viewModel.processImage() }
|
||||||
.onChange(of: viewModel.isGrayscale) { _, _ in viewModel.processImage() }
|
.onChange(of: viewModel.isGrayscale) { _, _ in viewModel.processImage() }
|
||||||
// File Importer at the very top level
|
// File Importer at the very top level
|
||||||
|
|
@ -143,6 +144,17 @@ struct SidebarView: View {
|
||||||
}
|
}
|
||||||
Slider(value: $viewModel.pixelScale, in: 1.0...20.0, step: 1.0)
|
Slider(value: $viewModel.pixelScale, in: 1.0...20.0, step: 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
HStack {
|
||||||
|
Text("Color Depth")
|
||||||
|
Spacer()
|
||||||
|
Text("\(Int(viewModel.colorDepth))")
|
||||||
|
.monospacedDigit()
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
Slider(value: $viewModel.colorDepth, in: 1.0...32.0, step: 1.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.formStyle(.grouped)
|
.formStyle(.grouped)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ struct RenderParameters {
|
||||||
var brightness: Float
|
var brightness: Float
|
||||||
var contrast: Float
|
var contrast: Float
|
||||||
var pixelScale: Float
|
var pixelScale: Float
|
||||||
|
var colorDepth: Float // New parameter
|
||||||
var algorithm: Int32 // 0: None, 1: Bayer 8x8, 2: Bayer 4x4
|
var algorithm: Int32 // 0: None, 1: Bayer 8x8, 2: Bayer 4x4
|
||||||
var isGrayscale: Int32 // 0: false, 1: true
|
var isGrayscale: Int32 // 0: false, 1: true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ struct RenderParameters {
|
||||||
float brightness;
|
float brightness;
|
||||||
float contrast;
|
float contrast;
|
||||||
float pixelScale;
|
float pixelScale;
|
||||||
|
float colorDepth; // New parameter: 1.0 to 32.0 (Levels)
|
||||||
int algorithm; // 0: None, 1: Bayer 2x2, 2: Bayer 4x4, 3: Bayer 8x8, 4: Cluster 4x4, 5: Cluster 8x8, 6: Blue Noise
|
int algorithm; // 0: None, 1: Bayer 2x2, 2: Bayer 4x4, 3: Bayer 8x8, 4: Cluster 4x4, 5: Cluster 8x8, 6: Blue Noise
|
||||||
int isGrayscale;
|
int isGrayscale;
|
||||||
};
|
};
|
||||||
|
|
@ -67,6 +68,16 @@ constant float blueNoise8x8[8][8] = {
|
||||||
{35.0/64.0, 24.0/64.0, 0.0/64.0, 41.0/64.0, 15.0/64.0, 52.0/64.0, 20.0/64.0, 37.0/64.0}
|
{35.0/64.0, 24.0/64.0, 0.0/64.0, 41.0/64.0, 15.0/64.0, 52.0/64.0, 20.0/64.0, 37.0/64.0}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
float ditherChannel(float value, float threshold, float limit) {
|
||||||
|
// Quantization Formula
|
||||||
|
// value: 0.0 to 1.0
|
||||||
|
// threshold: 0.0 to 1.0 (from matrix)
|
||||||
|
// limit: colorDepth (e.g. 4.0)
|
||||||
|
|
||||||
|
float ditheredValue = value + (threshold - 0.5) * (1.0 / (limit - 1.0));
|
||||||
|
return floor(ditheredValue * (limit - 1.0) + 0.5) / (limit - 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
kernel void ditherShader(texture2d<float, access::read> inputTexture [[texture(0)]],
|
kernel void ditherShader(texture2d<float, access::read> inputTexture [[texture(0)]],
|
||||||
texture2d<float, access::write> outputTexture [[texture(1)]],
|
texture2d<float, access::write> outputTexture [[texture(1)]],
|
||||||
constant RenderParameters ¶ms [[buffer(0)]],
|
constant RenderParameters ¶ms [[buffer(0)]],
|
||||||
|
|
@ -104,6 +115,7 @@ kernel void ditherShader(texture2d<float, access::read> inputTexture [[texture(0
|
||||||
if (shouldDither) {
|
if (shouldDither) {
|
||||||
uint x, y;
|
uint x, y;
|
||||||
|
|
||||||
|
// Fetch threshold from matrix
|
||||||
switch (params.algorithm) {
|
switch (params.algorithm) {
|
||||||
case 1: // Bayer 2x2
|
case 1: // Bayer 2x2
|
||||||
x = uint(sourceCoord.x / scale) % 2;
|
x = uint(sourceCoord.x / scale) % 2;
|
||||||
|
|
@ -139,7 +151,18 @@ kernel void ditherShader(texture2d<float, access::read> inputTexture [[texture(0
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
rgb = (luma > threshold) ? float3(1.0) : float3(0.0);
|
// Apply Quantized Dithering
|
||||||
|
if (params.isGrayscale > 0) {
|
||||||
|
// Apply only to luma (which is already in rgb)
|
||||||
|
rgb.r = ditherChannel(rgb.r, threshold, params.colorDepth);
|
||||||
|
rgb.g = rgb.r;
|
||||||
|
rgb.b = rgb.r;
|
||||||
|
} else {
|
||||||
|
// Apply to each channel
|
||||||
|
rgb.r = ditherChannel(rgb.r, threshold, params.colorDepth);
|
||||||
|
rgb.g = ditherChannel(rgb.g, threshold, params.colorDepth);
|
||||||
|
rgb.b = ditherChannel(rgb.b, threshold, params.colorDepth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outputTexture.write(float4(rgb, color.a), gid);
|
outputTexture.write(float4(rgb, color.a), gid);
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,8 @@ class DitherViewModel {
|
||||||
var brightness: Double = 0.0
|
var brightness: Double = 0.0
|
||||||
var contrast: Double = 1.0
|
var contrast: Double = 1.0
|
||||||
var pixelScale: Double = 4.0
|
var pixelScale: Double = 4.0
|
||||||
var selectedAlgorithm: DitherAlgorithm = .bayer4x4 // Default to Balanced
|
var colorDepth: Double = 4.0 // Default to 4 levels
|
||||||
|
var selectedAlgorithm: DitherAlgorithm = .bayer4x4
|
||||||
var isGrayscale: Bool = false
|
var isGrayscale: Bool = false
|
||||||
|
|
||||||
private let renderer = MetalImageRenderer()
|
private let renderer = MetalImageRenderer()
|
||||||
|
|
@ -67,6 +68,7 @@ class DitherViewModel {
|
||||||
brightness: Float(brightness),
|
brightness: Float(brightness),
|
||||||
contrast: Float(contrast),
|
contrast: Float(contrast),
|
||||||
pixelScale: Float(pixelScale),
|
pixelScale: Float(pixelScale),
|
||||||
|
colorDepth: Float(colorDepth),
|
||||||
algorithm: Int32(selectedAlgorithm.rawValue),
|
algorithm: Int32(selectedAlgorithm.rawValue),
|
||||||
isGrayscale: isGrayscale ? 1 : 0
|
isGrayscale: isGrayscale ? 1 : 0
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue