Skip to content

Commit

Permalink
Use XYZ color space for error diffusion (#63)
Browse files Browse the repository at this point in the history
* make error diffusion color space a kwarg, defaulting to XYZ color space
* add kwarg doc strings for all error diffusion methods
  • Loading branch information
adrhill authored Feb 7, 2022
1 parent ab48da1 commit 599b2f3
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 39 deletions.
84 changes: 63 additions & 21 deletions src/error_diffusion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,34 @@
Generalized error diffusion algorithm. When calling `dither` using a color palette `cs`,
this will iterate over pixels and round them to the closest color in `cs`.
The rounding error is then "diffused" over the neighborhood defined by the matrix `filter`.
This diffused error can additionally be clamped to ``[0, 1]`` by setting
`clamp_error = true` (default).
When calling `dither` on a grayscale image without specifying a palette, `ErrorDiffusion`
algorithms will default to settings for binary dithering: `clamp_error=false` and the metric
`BinaryDitherMetric()`, which simply rounds to the closest binary number.
# Example
```julia-repl
julia> alg = FloydSteinberg() # returns ErrorDiffusion instance
DitherPunk.ErrorDiffusion{OffsetArrays.OffsetMatrix{Rational{Int64}, Matrix{Rational{Int64}}}}(Rational{Int64}[0//1 0//1 7//16; 3//16 5//16 1//16], true)
ErrorDiffusion{OffsetArrays.OffsetMatrix{Rational{Int64}, Matrix{Rational{Int64}}}, ColorTypes.XYZ}(Rational{Int64}[0//1 0//1 7//16; 3//16 5//16 1//16], true)
julia> cs = ColorSchemes.PuOr_7.colors; # using ColorSchemes.jl for color palette presets
julia> dither!(img, alg, cs);
julia> dither(img, alg, cs);
```
"""
struct ErrorDiffusion{T<:AbstractMatrix} <: AbstractDither
filter::T

_error_diffusion_kwargs = """
# Keyword arguments
- `color_space`: Color space in which the error is diffused.
Only used when dithering with a color palette. Defaults to `XYZ`.
To replicate the output of other dithering libraries, set this to `RGB`.
- `clamp_error::Bool`: Clamp accumulated error on each pixel within limits of colorant
type `color_space` before looking up the closest color. Defaults to `true`.
"""

struct ErrorDiffusion{F<:AbstractMatrix,C} <: AbstractDither
filter::F
clamp_error::Bool
end
ErrorDiffusion(filter; clamp_error=true) = ErrorDiffusion(filter, clamp_error)
function ErrorDiffusion(filter; color_space=XYZ, clamp_error=true)
return ErrorDiffusion{typeof(filter),float32(color_space)}(filter, clamp_error)
end

function binarydither!(alg::ErrorDiffusion, out::GenericGrayImage, img::GenericGrayImage)
# this function does not yet support OffsetArray
Expand Down Expand Up @@ -65,24 +71,24 @@ function binarydither!(alg::ErrorDiffusion, out::GenericGrayImage, img::GenericG
end

function colordither(
alg::ErrorDiffusion,
alg::ErrorDiffusion{F,C},
img::GenericImage,
cs::AbstractVector{<:Pixel},
metric::DifferenceMetric,
)
) where {F,C}
# this function does not yet support OffsetArray
require_one_based_indexing(img)

index = Matrix{UInt8}(undef, size(img)...) # allocate matrix of color indices

# C is the `color_space` in which the error is diffused
img = convert.(C, img)
cs_err = C.(cs)
cs_lab = Lab.(cs)

# Change from normalized intensities to Float as error will get added!
# Eagerly promote to the same type to make loop run faster.
FT = floattype(eltype(eltype(img))) # type of Float
CT = floattype(eltype(img)) # type of colorant

img = CT.(img)
cs = CT.(cs)
labcs = Lab{FT}.(cs) # otherwise each call to colordiff converts cs to Lab
filter = FT.(alg.filter)

drs = axes(alg.filter, 1)
Expand All @@ -91,11 +97,11 @@ function colordither(
@inbounds for r in axes(img, 1)
for c in axes(img, 2)
px = img[r, c]
alg.clamp_error && (px = clamp01(px))
alg.clamp_error && (px = clamp_limits(px))

colorindex = _closest_color_idx(px, labcs, metric)
colorindex = _closest_color_idx(px, cs_lab, metric)
index[r, c] = colorindex
err = px - cs[colorindex] # diffuse "error" to neighborhood in filter
err = px - cs_err[colorindex] # diffuse "error" to neighborhood in filter

for dr in drs
for dc in dcs
Expand All @@ -118,6 +124,9 @@ Error diffusion algorithm using the filter
1 0 (1//2)
```
$(_error_diffusion_kwargs)
# References
[1] Floyd, R.W. and L. Steinberg, "An Adaptive Algorithm for Spatial Gray
Scale." SID 1975, International Symposium Digest of Technical Papers,
vol 1975m, pp. 36-37.
Expand All @@ -133,6 +142,9 @@ Error diffusion algorithm using the filter
3 5 1 (1//16)
```
$(_error_diffusion_kwargs)
# References
[1] Floyd, R.W. and L. Steinberg, "An Adaptive Algorithm for Spatial Gray
Scale." SID 1975, International Symposium Digest of Technical Papers,
vol 1975m, pp. 36-37.
Expand All @@ -152,6 +164,9 @@ Error diffusion algorithm using the filter
```
Also known as the Jarvis, Judice, and Ninke filter.
$(_error_diffusion_kwargs)
# References
[1] Jarvis, J.F., C.N. Judice, and W.H. Ninke, "A Survey of Techniques for
the Display of Continuous Tone Pictures on Bi-Level Displays," Computer
Graphics and Image Processing, vol. 5, pp. 13-40, 1976.
Expand All @@ -172,6 +187,9 @@ Error diffusion algorithm using the filter
1 2 4 2 1 (1//42)
```
$(_error_diffusion_kwargs)
# References
[1] Stucki, P., "MECCA - a multiple-error correcting computation algorithm
for bilevel image hardcopy reproduction." Research Report RZ1060, IBM
Research Laboratory, Zurich, Switzerland, 1981.
Expand All @@ -192,6 +210,9 @@ Error diffusion algorithm using the filter
1 2 4 2 1 (1//42)
```
$(_error_diffusion_kwargs)
# References
[1] Burkes, D., "Presentation of the Burkes error filter for use in preparing
continuous-tone images for presentation on bi-level devices." Unpublished, 1988.
"""
Expand All @@ -209,6 +230,9 @@ Error diffusion algorithm using the filter
2 3 2 (1//32)
```
Also known as Sierra3 or three-row Sierra due to the filter shape.
$(_error_diffusion_kwargs)
"""
function Sierra(; kwargs...)
return ErrorDiffusion(
Expand All @@ -225,6 +249,8 @@ Error diffusion algorithm using the filter
1 2 3 2 1 (1//16)
```
Also known as Sierra2.
$(_error_diffusion_kwargs)
"""
function TwoRowSierra(; kwargs...)
return ErrorDiffusion(OffsetMatrix([0 0 0 4 3; 1 2 3 2 1]//16, 0:1, -2:2); kwargs...)
Expand All @@ -239,6 +265,8 @@ Error diffusion algorithm using the filter
1 1 (1//4)
```
Also known as Sierra-2-4A filter.
$(_error_diffusion_kwargs)
"""
function SierraLite(; kwargs...)
return ErrorDiffusion(OffsetMatrix([0 0 2; 1 1 0]//4, 0:1, -1:1); kwargs...)
Expand All @@ -253,6 +281,8 @@ Error diffusion algorithm using the filter
1 1 1
1 (1//8)
```
$(_error_diffusion_kwargs)
"""
function Atkinson(; kwargs...)
return ErrorDiffusion(
Expand All @@ -270,6 +300,9 @@ Error diffusion algorithm using the filter
```
A modification of the weights used in the Floyd-Steinberg algorithm.
$(_error_diffusion_kwargs)
# References
[1] Z. Fan, "A Simple Modification of Error Diffusion Weights",
IS&T's 46th Annual Conference, May 9-14, 1993, Final Program and Advanced Printing of
Paper Summaries, pp 113-115 (1993).
Expand All @@ -287,6 +320,9 @@ Error diffusion algorithm using the filter
1 1 2 (1//8)
```
$(_error_diffusion_kwargs)
# References
[1] J. N. Shiau and Z. Fan. "Method for quantization gray level pixel data with extended
distribution set", US 5353127A, United States Patent and Trademark Office, Oct. 4, 1993
"""
Expand All @@ -303,6 +339,9 @@ Error diffusion algorithm using the filter
1 1 2 4 (1//16)
```
$(_error_diffusion_kwargs)
# References
[1] J. N. Shiau and Z. Fan. "Method for quantization gray level pixel data with extended
distribution set", US 5353127A, United States Patent and Trademark Office, Oct. 4, 1993
[2] J. N. Shiau and Z. Fan. "A set of easily implementable coefficients in error-diffusion
Expand All @@ -323,9 +362,12 @@ Error diffusion algorithm using the filter
```
Occasionally, you will see this filter erroneously called the Floyd-Steinberg filter.
$(_error_diffusion_kwargs)
# Note
There is no reason to use this algorithm, which is why DitherPunk doesn't export it.
# References
[1] http://www.efg2.com/Lab/Library/ImageProcessing/DHALF.TXT
"""
function FalseFloydSteinberg(; kwargs...)
Expand Down
13 changes: 13 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,16 @@ else
return argmin([colordiff(px, c; metric=metric) for c in cs])
end
end

"""
clamp_limits(color)
Clamp colorant within the limits of each color channel.
"""
clamp_limits(c::Gray) = clamp01(c)
clamp_limits(c::RGB) = clamp01(c)
clamp_limits(c::HSV) = typeof(c)(mod(c.h, 360), clamp01(c.s), clamp01(c.v))
clamp_limits(c::Lab) = c
function clamp_limits(c::XYZ)
return typeof(c)(clamp(c.x, 0, 0.95047), clamp01(c.y), clamp(c.z, 0, 1.08883))
end
File renamed without changes.
Loading

0 comments on commit 599b2f3

Please sign in to comment.