diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 98ec2d8..76319bb 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -46,8 +46,10 @@ jobs: using Pkg Pkg.add([ PackageSpec(name="Images", version="0.23"), + PackageSpec(name="IndirectArrays", version="0.5"), PackageSpec(name="ImageCore", version="0.8"), ]) + shell: julia --project=. --startup=no --color=yes {0} - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 diff --git a/Project.toml b/Project.toml index 305c2c0..2bf5795 100644 --- a/Project.toml +++ b/Project.toml @@ -5,16 +5,16 @@ version = "2.0.1" [deps] ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534" +IndirectArrays = "9b13fd28-a010-5f03-acff-a1bbcff69959" OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Requires = "ae029012-a4dd-5104-9daa-d747884805df" -TiledIteration = "06e1c1a7-607b-532d-9fad-de7d9aa2abac" [compat] ImageCore = "0.8.1, 0.9" +IndirectArrays = "0.5, 1.0" OffsetArrays = "1" Requires = "1" -TiledIteration = "0.3" julia = "1" [extras] diff --git a/src/DitherPunk.jl b/src/DitherPunk.jl index 122cfc5..e0828f4 100644 --- a/src/DitherPunk.jl +++ b/src/DitherPunk.jl @@ -1,16 +1,19 @@ module DitherPunk -using TiledIteration using ImageCore using ImageCore: NumberLike, Pixel, GenericImage, GenericGrayImage, MappedArrays using ImageCore.Colors: DifferenceMetric using Random +using IndirectArrays using OffsetArrays using Requires +abstract type AbstractDither end + include("compat.jl") -include("dither_api.jl") -include("colorspaces.jl") +include("utils.jl") +include("api/binary.jl") +include("api/color.jl") include("threshold.jl") include("ordered.jl") include("ordered_imagemagick.jl") diff --git a/src/api/binary.jl b/src/api/binary.jl new file mode 100644 index 0000000..d501ea7 --- /dev/null +++ b/src/api/binary.jl @@ -0,0 +1,91 @@ +# All functions in this file end up calling `binarydither!` and don't return IndirectArrays. + +""" + dither!([out,] img, alg::AbstractDither, args...; kwargs...) + +Dither image `img` using algorithm `alg`. + +# Output +If `out` is specified, it will be changed in place. Otherwise `img` will be changed in place. +""" +dither! + +""" + dither([T::Type,] img, alg::AbstractDither, args...; kwargs...) + +Dither image `img` using algorithm `alg`. + +# Output +If no return type is specified, `dither` will default to the type of the input image. +""" +dither + +############## +# Public API # +############## + +# If `out` is specified, it will be changed in place... +function dither!(out::GenericImage, img::GenericImage, alg::AbstractDither; kwargs...) + if size(out) != size(img) + throw( + ArgumentError( + "out and img should have the same shape, instead they are $(size(out)) and $(size(img))", + ), + ) + end + return _binarydither!(out, img, alg; kwargs...) +end + +# ...otherwise `img` will be changed in place. +function dither!(img::GenericImage, alg::AbstractDither; kwargs...) + tmp = copy(img) + return _binarydither!(img, tmp, alg; kwargs...) +end + +# Otherwise the return type can be chosen... +function dither(::Type{T}, img::GenericImage, alg::AbstractDither; kwargs...) where {T} + out = similar(Array{T}, axes(img)) + return _binarydither!(out, img, alg; kwargs...) +end + +# ...and defaults to the type of the input image. +function dither(img::GenericImage{T,N}, alg::AbstractDither; kwargs...) where {T<:Pixel,N} + return dither(T, img, alg; kwargs...) +end + +############################# +# Low-level algorithm calls # +############################# + +# Dispatch to binary dithering on grayscale images +# when no color palette is provided +function _binarydither!( + out::GenericGrayImage, + img::GenericGrayImage, + alg::AbstractDither; + to_linear=false, + kwargs..., +) where {T} + to_linear && (img = srgb2linear.(img)) + return binarydither!(alg, out, img; kwargs...) +end + +# Dispatch to per-channel dithering on color images when no color palette is provided +function _binarydither!( + out::GenericImage{T,2}, + img::GenericImage{T,2}, + alg::AbstractDither; + to_linear=false, + kwargs..., +) where {T<:Color{<:Real,3}} + to_linear && (@warn "Skipping transformation `to_linear` when dithering color images.") + + cvout = channelview(out) + cvimg = channelview(img) + for c in axes(cvout, 1) + # Note: the input `out` will be modified + # since binarydither! modifies the view of the channelview of `out`. + binarydither!(alg, view(cvout, c, :, :), view(cvimg, c, :, :)) + end + return out +end diff --git a/src/api/color.jl b/src/api/color.jl new file mode 100644 index 0000000..b1177a7 --- /dev/null +++ b/src/api/color.jl @@ -0,0 +1,86 @@ +# Binary dithering and color dithering can be distinguished by the extra argument `arg`, +# which is either +# - a color scheme (array of colors) +# - a ColorSchemes.jl symbol +# - the number of colors specified for clustering +# +# All functions in this file end up calling `colordither` and return IndirectArrays. + +struct ColorNotImplementedError <: Exception + algname::String + ColorNotImplementedError(alg::AbstractDither) = new("$alg") +end +function Base.showerror(io::IO, e::ColorNotImplementedError) + return print( + io, e.algname, " algorithm currently doesn't support custom color palettes." + ) +end +colordither(alg, img, cs, metric) = throw(ColorNotImplementedError(alg)) + +############## +# Public API # +############## + +# If `out` is specified, it will be changed in place... +function dither!(out::GenericImage, img::GenericImage, alg::AbstractDither, arg; kwargs...) + if size(out) != size(img) + throw( + ArgumentError( + "out and img should have the same shape, instead they are $(size(out)) and $(size(img))", + ), + ) + end + return out .= _colordither(eltype(out), img, alg, arg; kwargs...) +end + +# ...otherwise `img` will be changed in place. +function dither!(img::GenericImage, alg::AbstractDither, arg; kwargs...) + return img .= _colordither(eltype(img), img, alg, arg; kwargs...) +end + +# The return type can be chosen... +function dither(::Type{T}, img::GenericImage, alg::AbstractDither, arg; kwargs...) where {T} + return _colordither(T, img, alg, arg; kwargs...) +end + +# ...and defaults to the type of the input image. +function dither( + img::GenericImage{T,N}, alg::AbstractDither, arg; kwargs... +) where {T<:Pixel,N} + return _colordither(T, img, alg, arg; kwargs...) +end + +############################# +# Low-level algorithm calls # +############################# + +# Dispatch to dithering with custom color palettes on any image type +# when color palette is provided +function _colordither( + ::Type{T}, + img::GenericImage, + alg::AbstractDither, + cs::AbstractVector{<:Pixel}; + metric::DifferenceMetric=DE_2000(), + to_linear=false, +) where {T} + to_linear && (@warn "Skipping transformation `to_linear` when dithering in color.") + length(cs) >= 2 || + throw(DomainError(steps, "Color scheme for dither needs >= 2 colors.")) + + index = colordither(alg, img, cs, metric) + return IndirectArray(index, T.(cs)) +end + +# A special case occurs when a grayscale output image is to be dithered in colors. +# Since this is not possible, instead the return image will be of type of the color scheme. +function _colordither( + ::Type{T}, + img::GenericImage, + alg::AbstractDither, + cs::AbstractVector{<:Color{<:Any,3}}; + metric::DifferenceMetric=DE_2000(), + to_linear=false, +) where {T<:NumberLike} + return _colordither(eltype(cs), img, alg, cs; metric=metric, to_linear=to_linear) +end diff --git a/src/closest_color.jl b/src/closest_color.jl index f147a67..9e6e1c2 100644 --- a/src/closest_color.jl +++ b/src/closest_color.jl @@ -3,18 +3,16 @@ Simplest form of image quantization by turning each pixel to the closest one in the provided color palette `cs`. Technically this not a dithering algorithm as the quatization error is not "randomized". """ -struct ClosestColor <: AbstractCustomColorDither end +struct ClosestColor <: AbstractDither end function binarydither!(::ClosestColor, out::GenericGrayImage, img::GenericGrayImage) - return out .= img .> 0.5 + threshold = eltype(img)(0.5) + return out .= img .> threshold end -function colordither!( - ::ClosestColor, - out::GenericImage, - img::GenericImage, - cs::AbstractVector{<:Pixel}, - metric::DifferenceMetric, +function colordither( + ::ClosestColor, img::GenericImage, cs::AbstractVector{<:Pixel}, metric::DifferenceMetric ) - return out .= eltype(out).(map((px) -> closest_color(px, cs; metric=metric), img)) + cs = Lab.(cs) + return map(px -> _closest_color_idx(px, cs, metric), img) end diff --git a/src/clustering.jl b/src/clustering.jl index e24ba06..799227c 100644 --- a/src/clustering.jl +++ b/src/clustering.jl @@ -1,16 +1,15 @@ # These functions are only conditionally loaded with Clustering.jl # Code adapted from @cormullion's [ColorSchemeTools](https://github.com/JuliaGraphics/ColorSchemeTools.jl). -function _dither!( - out, +function _colordither( + ::Type{T}, img, alg, ncolors::Int; maxiter=Clustering._kmeans_default_maxiter, tol=Clustering._kmeans_default_tol, kwargs..., -) - T = eltype(img) +) where {T} # Cluster in Lab color space data = reshape(channelview(Lab.(img)), 3, :) @@ -22,7 +21,7 @@ function _dither!( push!(cs, Lab(R.centers[i], R.centers[i + 1], R.centers[i + 2])) end - return _dither!(out, img, alg, T.(cs); kwargs...) + return _colordither(T, img, alg, cs; kwargs...) end """ diff --git a/src/colorschemes.jl b/src/colorschemes.jl index 9961f95..bf921cb 100644 --- a/src/colorschemes.jl +++ b/src/colorschemes.jl @@ -1,9 +1,9 @@ # These functions are only conditionally loaded with ColorSchemes.jl -function _dither!(out, img, alg, cs::ColorSchemes.ColorScheme; kwargs...) - return _dither!(out, img, alg, cs.colors) +function _colordither(T, img, alg, cs::ColorSchemes.ColorScheme; kwargs...) + return _colordither(T, img, alg, cs.colors) end -function _dither!(out, img, alg, csname::Symbol; kwargs...) +function _colordither(T, img, alg, csname::Symbol; kwargs...) cs = ColorSchemes.colorschemes[csname] - return _dither!(out, img, alg, cs.colors; kwargs...) + return _colordither(T, img, alg, cs.colors; kwargs...) end diff --git a/src/dither_api.jl b/src/dither_api.jl deleted file mode 100644 index d8c4efa..0000000 --- a/src/dither_api.jl +++ /dev/null @@ -1,142 +0,0 @@ -abstract type AbstractDither end - -# Algorithms for which the user can define a custom output color palette. -# If no palette is specified, algorithms of this type will default to a binary color palette -# and act similarly to algorithms of type AbstractBinaryDither. -abstract type AbstractCustomColorDither <: AbstractDither end - -# Algorithms which strictly do binary dithering: -abstract type AbstractBinaryDither <: AbstractDither end - -struct ColorNotImplementedError <: Exception - algname::String - ColorNotImplementedError(alg::AbstractDither) = new("$alg") -end -function Base.showerror(io::IO, e::ColorNotImplementedError) - return print( - io, e.algname, " algorithm currently doesn't support custom color palettes." - ) -end -colordither!(alg, out, img, cs, metric) = throw(ColorNotImplementedError(alg)) - -############## -# Public API # -############## - -""" - dither!([out,] img, alg::AbstractDither, args...; kwargs...) - -Dither image `img` using algorithm `alg`. - -# Output -If `out` is specified, it will be changed in place. Otherwise `img` will be changed in place. -""" -dither! - -""" - dither([T::Type,] img, alg::AbstractDither, args...; kwargs...) - -Dither image `img` using algorithm `alg`. - -# Output -If no return type is specified, `dither` will default to the type of the input image. -""" -dither - -# If `out` is specified, it will be changed in place... -function dither!( - out::GenericImage, img::GenericImage, alg::AbstractDither, args...; kwargs... -) - if size(out) != size(img) - throw( - ArgumentError( - "out and img should have the same shape, instead they are $(size(out)) and $(size(img))", - ), - ) - end - return _dither!(out, img, alg, args...; kwargs...) -end - -# ...otherwise `img` will be changed in place. -function dither!(img::GenericImage, alg::AbstractDither, args...; kwargs...) - tmp = copy(img) - return _dither!(img, tmp, alg, args...; kwargs...) -end - -# The return type can be chosen... -function dither( - ::Type{T}, img::GenericImage, alg::AbstractDither, args...; kwargs... -) where {T} - out = similar(Array{T}, axes(img)) - return _dither!(out, img, alg, args...; kwargs...) -end - -# ...and defaults to the type of the input image. -function dither( - img::GenericImage{T,N}, alg::AbstractDither, args...; kwargs... -) where {T<:Pixel,N} - return dither(T, img, alg, args...; kwargs...) -end - -############################# -# Low-level algorithm calls # -############################# - -# Dispatch to binary dithering on grayscale images -# when no color palette is provided -function _dither!( - out::GenericGrayImage, img::GenericGrayImage, alg::AbstractDither; to_linear=false -) - to_linear && (img = srgb2linear.(img)) - return binarydither!(alg, out, img) -end - -# Dispatch to per-channel dithering on color images -# when no color palette is provided -function _dither!( - out::GenericImage{<:Color{<:Real,3},2}, - img::GenericImage{<:Color{<:Real,3},2}, - alg::AbstractDither; - to_linear=false, -) - to_linear && (@warn "Skipping transformation `to_linear` when dithering color images.") - - cvout = channelview(out) - cvimg = channelview(img) - for c in axes(cvout, 1) - # Note: the input `out` will be modified - # since the dithering algorithms modify the view of the channelview of `out`. - binarydither!(alg, view(cvout, c, :, :), view(cvimg, c, :, :)) - end - return out -end - -# Dispatch to dithering with custom color palettes on any image type -# when color palette is provided -function _dither!( - out::GenericImage, - img::GenericImage, - alg::AbstractDither, - cs::AbstractVector{<:Pixel}; - metric::DifferenceMetric=DE_2000(), - to_linear=false, -) - to_linear && (@warn "Skipping transformation `to_linear` when dithering in color.") - length(cs) >= 2 || - throw(DomainError(steps, "Color scheme for dither needs >= 2 colors.")) - return colordither!(alg, out, img, cs, metric) -end - -# A special case occurs when a grayscale output image is to be dithered in colors. -# Since this is not possible, instead the return image will be of type of the color scheme. -function _dither!( - out::GenericGrayImage, - img::GenericGrayImage, - alg::AbstractDither, - cs::AbstractVector{<:Color{<:Any,3}}; - metric::DifferenceMetric=DE_2000(), - to_linear=false, -) - T = eltype(cs) - return _dither!(T.(out), T.(img), alg, cs; metric=metric, to_linear=to_linear) -end diff --git a/src/error_diffusion.jl b/src/error_diffusion.jl index c13829d..253d89a 100644 --- a/src/error_diffusion.jl +++ b/src/error_diffusion.jl @@ -21,7 +21,7 @@ julia> cs = ColorSchemes.PuOr_7.colors; # using ColorSchemes.jl for color palett julia> dither!(img, alg, cs); ``` """ -struct ErrorDiffusion{T<:AbstractMatrix} <: AbstractCustomColorDither +struct ErrorDiffusion{T<:AbstractMatrix} <: AbstractDither filter::T clamp_error::Bool end @@ -32,17 +32,14 @@ function binarydither!(alg::ErrorDiffusion, out::GenericGrayImage, img::GenericG require_one_based_indexing(img) # Change from normalized intensities to Float as error will get added! - FT = floattype(eltype(out)) - # eagerly promote to the same type to make loop run faster + FT = floattype(eltype(img)) img = FT.(img) filter = eltype(FT).(alg.filter) - h, w = size(img) - fill!(out, zero(eltype(out))) - drs = axes(alg.filter, 1) dcs = axes(alg.filter, 2) + FT0, FT1 = FT(0), FT(1) @inbounds for r in axes(img, 1) @@ -67,9 +64,8 @@ function binarydither!(alg::ErrorDiffusion, out::GenericGrayImage, img::GenericG return out end -function colordither!( +function colordither( alg::ErrorDiffusion, - out::GenericImage, img::GenericImage, cs::AbstractVector{<:Pixel}, metric::DifferenceMetric, @@ -77,16 +73,17 @@ function colordither!( # this function does not yet support OffsetArray require_one_based_indexing(img) - # Change from normalized intensities to Float as error will get added! - FT = floattype(eltype(out)) + index = Matrix{UInt8}(undef, size(img)...) # allocate matrix of color indices - # eagerly promote to the same type to make loop run faster - img = FT.(img) - cs = FT.(cs) - filter = eltype(FT).(alg.filter) + # 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 - h, w = size(img) - fill!(out, zero(eltype(out))) + 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) dcs = axes(alg.filter, 2) @@ -96,9 +93,9 @@ function colordither!( px = img[r, c] alg.clamp_error && (px = clamp01(px)) - col = closest_color(px, cs; metric=metric) # round to closest color - out[r, c] = col # apply pixel to dither - err = px - col # diffuse "error" to neighborhood in filter + colorindex = _closest_color_idx(px, labcs, metric) + index[r, c] = colorindex + err = px - cs[colorindex] # diffuse "error" to neighborhood in filter for dr in drs for dc in dcs @@ -109,8 +106,7 @@ function colordither!( end end end - - return out + return index end """ diff --git a/src/ordered.jl b/src/ordered.jl index bde1f2c..f763e83 100644 --- a/src/ordered.jl +++ b/src/ordered.jl @@ -8,7 +8,7 @@ When applying the algorithm to an image, the threshold matrix is repeatedly tile to match the size of the image. It is then applied as a per-pixel threshold map. Optionally, this final threshold map can be inverted by selecting `invert_map=true`. """ -struct OrderedDither{T<:AbstractMatrix} <: AbstractBinaryDither +struct OrderedDither{T<:AbstractMatrix} <: AbstractDither mat::T end @@ -23,19 +23,16 @@ function binarydither!( mat = FT.(alg.mat) end - # TODO: add Threads.@threads to this for loop further improves the performances - # but it has unidentified memory allocations - @inbounds for R in TileIterator(axes(img), size(mat)) - mat_size = map(length, R) - if mat_size == size(mat) - # `mappedarray` creates a readonly wrapper with lazy evaluation with `srgb2linear` - # so that original `img` data is not modified. - out[R...] .= @views img[R...] .> mat - else # boundary condition - mat_inds = map(Base.OneTo, mat_size) - out_inds = map(getindex, R, mat_inds) - out[out_inds...] .= @views img[out_inds...] .> mat[mat_inds...] - end + matsize = size(mat) + rlookup = [mod1(i, matsize[1]) for i in 1:size(img)[1]] + clookup = [mod1(i, matsize[2]) for i in 1:size(img)[2]] + + T = eltype(out) + black, white = T(0), T(1) + + @inbounds @simd for i in CartesianIndices(img) + r, c = Tuple(i) + out[i] = ifelse(img[i] > mat[rlookup[r], clookup[c]], white, black) end return out end diff --git a/src/threshold.jl b/src/threshold.jl index 30e24d5..1be3970 100644 --- a/src/threshold.jl +++ b/src/threshold.jl @@ -1,4 +1,4 @@ -abstract type AbstractThresholdDither <: AbstractBinaryDither end +abstract type AbstractThresholdDither <: AbstractDither end """ WhiteNoiseThreshold() @@ -9,8 +9,7 @@ struct WhiteNoiseThreshold <: AbstractThresholdDither end function binarydither!(::WhiteNoiseThreshold, out::GenericGrayImage, img::GenericGrayImage) tmap = rand(eltype(img), size(img)) - out .= img .> tmap - return out + return out .= img .> tmap end """ @@ -30,7 +29,6 @@ struct ConstantThreshold{T<:Real} <: AbstractThresholdDither end function binarydither!(alg::ConstantThreshold, out::GenericGrayImage, img::GenericGrayImage) - tmap = fill(alg.threshold, size(img)) # constant matrix of value threshold - out .= img .> tmap - return out + threshold = eltype(img)(alg.threshold) + return out .= img .> threshold end diff --git a/src/colorspaces.jl b/src/utils.jl similarity index 55% rename from src/colorspaces.jl rename to src/utils.jl index 3cc22fe..d4b5b58 100644 --- a/src/colorspaces.jl +++ b/src/utils.jl @@ -16,13 +16,10 @@ Convert pixel `u` from linear to sRGB color space. @inline linear2srgb(u::Gray) = typeof(u)(linear2srgb(gray(u))) @inline linear2srgb(u::Bool) = u -""" - closest_color(color, cs; metric=DE_2000()) - -Return color in ColorScheme `cs` that is closest to `color` -[according to `colordiff`](http://juliagraphics.github.io/Colors.jl/dev/colordifferences/#Color-Differences) -""" -function closest_color(color::Color, cs::AbstractVector{<:Color}; metric=DE_2000()) - imin = argmin(colordiff.(color, cs; metric=metric)) - return cs[imin] +if VERSION >= v"1.7" + _closest_color_idx(px, cs, metric) = argmin(colordiff(px, c; metric=metric) for c in cs) +else + function _closest_color_idx(px, cs, metric) + return argmin([colordiff(px, c; metric=metric) for c in cs]) + end end diff --git a/test/references/color_from_gray_FloydSteinberg.txt b/test/references/color_from_gray_FloydSteinberg.txt index 7c1e785..deea3e7 100644 --- a/test/references/color_from_gray_FloydSteinberg.txt +++ b/test/references/color_from_gray_FloydSteinberg.txt @@ -1,17 +1,17 @@ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ -▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ +▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \ No newline at end of file diff --git a/test/test_color.jl b/test/test_color.jl index 2f60812..958b5fd 100644 --- a/test/test_color.jl +++ b/test/test_color.jl @@ -1,5 +1,5 @@ using DitherPunk -using DitherPunk: closest_color, ColorNotImplementedError +using DitherPunk: ColorNotImplementedError using Images using ImageCore using ImageInTerminal @@ -16,9 +16,6 @@ blue = RGB{Float32}(0, 0, 1) cs = [white, yellow, green, orange, red, blue] -# Test helper function -@test closest_color(RGB{Float32}(1, 0.1, 0.1), cs) == red - # Load test image img = testimage("fabio_color_256") img_gray = testimage("fabio_gray_256") @@ -53,7 +50,7 @@ for (name, alg) in algs imshow(img2_gray) end -# Test for argument errors on AbstractBinaryDither algorithms +# Test for argument errors on algorithms that don't support custom color palletes for alg in [Bayer(), WhiteNoiseThreshold(), ConstantThreshold()] @test_throws ColorNotImplementedError dither(img, alg, cs) end diff --git a/test/test_fixed_color.jl b/test/test_fixed_color.jl index 1e99abf..1bf425b 100644 --- a/test/test_fixed_color.jl +++ b/test/test_fixed_color.jl @@ -16,14 +16,14 @@ for C in [RGB, HSV] for (name, alg) in algs img1 = C.(img) img2 = copy(img1) - d = dither(img2, alg) + local d = dither(img2, alg) @test_reference "references/fixed_color_$(C)_$(name).txt" d @test eltype(d) <: C @test img2 == img1 # image not modified imshow(d) - d = dither!(img2, alg) + local d = dither!(img2, alg) @test eltype(d) <: C @test img2 == d # image updated in-place end