Skip to content

Commit

Permalink
Use ColorQuantization.jl for clustering (#98)
Browse files Browse the repository at this point in the history
* Use ColorQuantization.jl for clustering
* Update dependencies:
  * replace ImageBase with ImageCore
  * remove TiledIteration
  * remove LazyModules
  * drop IndirectArrays v0.5
  • Loading branch information
adrhill authored Aug 7, 2023
1 parent a370a43 commit ec63b36
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 62 deletions.
14 changes: 5 additions & 9 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,17 @@ authors = ["Adrian Hill"]
version = "3.0.3"

[deps]
Clustering = "aaaa29a8-35af-508c-8bc3-b662a17a0fe5"
ColorQuantization = "652893fb-f6a0-4a00-a44a-7fb8fac69e01"
ColorSchemes = "35d6a980-a343-548e-a6ea-1d62b119f2f4"
ImageBase = "c817782e-172a-44cc-b673-b171935fbb9e"
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
IndirectArrays = "9b13fd28-a010-5f03-acff-a1bbcff69959"
LazyModules = "8cdb02fc-e678-4876-92c5-9defec4f444e"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
TiledIteration = "06e1c1a7-607b-532d-9fad-de7d9aa2abac"
UnicodeGraphics = "ebadf6b4-db70-5817-83da-4a19ad584e34"

[compat]
Clustering = "0.14, 0.15"
ColorQuantization = "0.1"
ColorSchemes = "3"
ImageBase = "0.1.3"
IndirectArrays = "0.5, 1.0"
LazyModules = "0.3"
TiledIteration = "0.3, 0.4, 0.5"
ImageCore = "0.10"
IndirectArrays = "1"
UnicodeGraphics = "0.2"
julia = "1.6"
16 changes: 5 additions & 11 deletions src/DitherPunk.jl
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
module DitherPunk

using ImageBase.ImageCore.ColorTypes
using ImageBase.ImageCore.Colors: DifferenceMetric, colordiff, DE_2000, invert_srgb_compand
using ImageBase.ImageCore: channelview, floattype, clamp01
using ImageBase: restrict
using Base: require_one_based_indexing
using Random: rand
using ImageCore.ColorTypes
using ImageCore.Colors: DifferenceMetric, colordiff, DE_2000, invert_srgb_compand
using ImageCore: channelview, floattype, clamp01
using IndirectArrays: IndirectArray
using TiledIteration: TileIterator

using ColorSchemes: ColorScheme
using ColorQuantization: quantize, AbstractColorQuantizer, KMeansQuantization
using UnicodeGraphics: uprint, ustring
using LazyModules: @lazy
#! format: off
@lazy import Clustering = "aaaa29a8-35af-508c-8bc3-b662a17a0fe5"
#! format: on

abstract type AbstractDither end

Expand All @@ -37,8 +33,6 @@ include("error_diffusion.jl")
include("closest_color.jl")
include("api/default_method.jl")
include("braille.jl")

# lazily loaded features
include("clustering.jl")

export dither, dither!
Expand Down
50 changes: 9 additions & 41 deletions src/clustering.jl
Original file line number Diff line number Diff line change
@@ -1,59 +1,27 @@
# These functions are lazily loaded from Clustering.jl using LazyModules.jl
# Code adapted from @cormullion's [ColorSchemeTools](https://github.com/JuliaGraphics/ColorSchemeTools.jl).
function get_colorscheme(
img,
ncolors;
maxiter=Clustering._kmeans_default_maxiter,
tol=Clustering._kmeans_default_tol,
)::Vector{Lab}
# Cluster in Lab color space

# Clustering on the downsampled image already generates good enough colormap estimation
# This significantly reduces the algorithmic complexity.
img = _restrict_to(img, ncolors * 100)
data = reshape(channelview(Lab.(img)), 3, :)
R = Clustering.kmeans(data, ncolors; maxiter=maxiter, tol=tol)

# Make color scheme out of cluster centers
return [Lab(c...) for c in eachcol(R.centers)]
end

function _restrict_to(img, n)
length(img) <= n && return img
out = restrict(img)
while length(out) > n
out = restrict(out)
end
return out
function _colordither(::Type{T}, img, alg, ncolors::Integer; kwargs...) where {T}
quantizer = KMeansQuantization(ncolors)
return _colordither(T, img, alg, quantizer; kwargs...)
end

function _colordither(
::Type{T},
img,
alg,
ncolors::Integer;
maxiter=Clustering._kmeans_default_maxiter,
tol=Clustering._kmeans_default_tol,
kwargs...,
) where {T}
cs = get_colorscheme(img, ncolors; maxiter=maxiter, tol=tol)
function _colordither(::Type{T}, img, alg, q::AbstractColorQuantizer; kwargs...) where {T}
cs = quantize(img, q)
return _colordither(T, img, alg, cs; kwargs...)
end

"""
dither!([out,] img, alg::AbstractDither, ncolors; maxiter, tol, kwargs...)
Dither image `img` using algorithm `alg`.
A color palette with `ncolors` is computed by Clustering.jl's K-means clustering.
The amount of `maxiter` and tolerance `tol` default to those exported by Clustering.jl.
A color palette of size `ncolors` is computed by ColorQuantization.jl's `KMeansQuantization`,
which applies K-means clustering.
"""
dither!(img, alg::AbstractDither, ncolors::Integer; kwargs...)

"""
dither([T::Type,] img, alg::AbstractDither, ncolors; maxiter, tol, kwargs...)
Dither image `img` using algorithm `alg`.
A color palette with `ncolors` is computed by Clustering.jl's K-means clustering.
The amount of `maxiter` and tolerance `tol` default to those exported by Clustering.jl.
A color palette of size `ncolors` is computed by ColorQuantization.jl's `KMeansQuantization`,
which applies K-means clustering.
"""
dither(::Type, img, alg::AbstractDither, ncolors::Integer; kwargs...)
1 change: 1 addition & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ Clamp colorant within the limits of each color channel.
clamp_limits(c::Colorant) = clamp01(c)
clamp_limits(c::HSV) = typeof(c)(mod(c.h, 360), clamp01(c.s), clamp01(c.v))
clamp_limits(c::Lab) = c
clamp_limits(c::XYZ) = c
2 changes: 1 addition & 1 deletion test/test_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ cl = @inferred clamp_limits(HSV(1000, 1000, 1000))
cl = @inferred clamp_limits(Lab(1000, 1000, 1000))
@test cl == Lab(1000, 1000, 1000)
cl = @inferred clamp_limits(XYZ(100, 100, 100))
@test cl == XYZ{Float32}(1, 1, 1)
@test cl == XYZ{Float32}(100, 100, 100)

0 comments on commit ec63b36

Please sign in to comment.