From 599b2f344618bacb317e1166831dae2fc782918e Mon Sep 17 00:00:00 2001 From: Adrian Hill Date: Mon, 7 Feb 2022 22:39:26 +0100 Subject: [PATCH] Use XYZ color space for error diffusion (#63) * make error diffusion color space a kwarg, defaulting to XYZ color space * add kwarg doc strings for all error diffusion methods --- src/error_diffusion.jl | 84 ++++++++++++++----- src/utils.jl | 13 +++ ...ydSteinberg.txt => FloydSteinberg_RGB.txt} | 0 .../color/FloydSteinberg_RGB_from_gray.txt | 17 ++++ test/references/color/FloydSteinberg_XYZ.txt | 17 ++++ .../color/FloydSteinberg_XYZ_from_gray.txt | 17 ++++ .../color/FloydSteinberg_from_gray.txt | 17 ---- test/test_color.jl | 3 +- test/test_utils.jl | 11 +++ 9 files changed, 140 insertions(+), 39 deletions(-) rename test/references/color/{FloydSteinberg.txt => FloydSteinberg_RGB.txt} (100%) create mode 100644 test/references/color/FloydSteinberg_RGB_from_gray.txt create mode 100644 test/references/color/FloydSteinberg_XYZ.txt create mode 100644 test/references/color/FloydSteinberg_XYZ_from_gray.txt delete mode 100644 test/references/color/FloydSteinberg_from_gray.txt diff --git a/src/error_diffusion.jl b/src/error_diffusion.jl index 253d89a..b1cdef3 100644 --- a/src/error_diffusion.jl +++ b/src/error_diffusion.jl @@ -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 @@ -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) @@ -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 @@ -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. @@ -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. @@ -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. @@ -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. @@ -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. """ @@ -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( @@ -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...) @@ -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...) @@ -253,6 +281,8 @@ Error diffusion algorithm using the filter 1 1 1 1 (1//8) ``` + +$(_error_diffusion_kwargs) """ function Atkinson(; kwargs...) return ErrorDiffusion( @@ -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). @@ -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 """ @@ -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 @@ -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...) diff --git a/src/utils.jl b/src/utils.jl index 49ce3ca..a9f077a 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -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 diff --git a/test/references/color/FloydSteinberg.txt b/test/references/color/FloydSteinberg_RGB.txt similarity index 100% rename from test/references/color/FloydSteinberg.txt rename to test/references/color/FloydSteinberg_RGB.txt diff --git a/test/references/color/FloydSteinberg_RGB_from_gray.txt b/test/references/color/FloydSteinberg_RGB_from_gray.txt new file mode 100644 index 0000000..7c1e785 --- /dev/null +++ b/test/references/color/FloydSteinberg_RGB_from_gray.txt @@ -0,0 +1,17 @@ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/references/color/FloydSteinberg_XYZ.txt b/test/references/color/FloydSteinberg_XYZ.txt new file mode 100644 index 0000000..ebb1671 --- /dev/null +++ b/test/references/color/FloydSteinberg_XYZ.txt @@ -0,0 +1,17 @@ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/references/color/FloydSteinberg_XYZ_from_gray.txt b/test/references/color/FloydSteinberg_XYZ_from_gray.txt new file mode 100644 index 0000000..b78b65a --- /dev/null +++ b/test/references/color/FloydSteinberg_XYZ_from_gray.txt @@ -0,0 +1,17 @@ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/references/color/FloydSteinberg_from_gray.txt b/test/references/color/FloydSteinberg_from_gray.txt deleted file mode 100644 index deea3e7..0000000 --- a/test/references/color/FloydSteinberg_from_gray.txt +++ /dev/null @@ -1,17 +0,0 @@ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/test_color.jl b/test/test_color.jl index 5e7d059..61d69c8 100644 --- a/test/test_color.jl +++ b/test/test_color.jl @@ -24,7 +24,8 @@ println() # Run & test custom color pallete dithering methods algs = Dict( - "FloydSteinberg" => FloydSteinberg(), + "FloydSteinberg_XYZ" => FloydSteinberg(; color_space=XYZ), + "FloydSteinberg_RGB" => FloydSteinberg(; color_space=RGB), "ClosestColor" => ClosestColor(), "Bayer" => Bayer(), ) diff --git a/test/test_utils.jl b/test/test_utils.jl index b887e7f..9d68954 100644 --- a/test/test_utils.jl +++ b/test/test_utils.jl @@ -1,4 +1,8 @@ using DitherPunk +using DitherPunk: srgb2linear, clamp_limits + +@test srgb2linear(true) == true +@test srgb2linear(false) == false A = [1 2; 3 4] @@ -10,3 +14,10 @@ A = [1 2; 3 4] 3 3 3 4 4 4 3 3 3 4 4 4 ] + +c = RGB{Float32}(2, 3, 4) +@test clamp_limits(c) == RGB{Float32}(1, 1, 1) +@test clamp_limits(Gray{Float32}(1000)) == Gray{Float32}(1) +@test clamp_limits(HSV(1000, 1000, 1000)) == HSV(280, 1, 1) +@test clamp_limits(Lab(1000, 1000, 1000)) == Lab(1000, 1000, 1000) +@test clamp_limits(XYZ(100, 100, 100)) == XYZ{Float32}(0.95047f0, 1.0f0, 1.08883f0)