-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DitherPunk API planning #39
Comments
Some comments and reflinks:
introducing per-channel operation by default can hit the performance issue of
Requires is okay, though there are two things to note: 1) by using Requires you're giving up controlling the versions of ColorSchemes, 2) introducing Symbols are okay here, but with To use Requires I can think of a global palette table const predefined_palettes = Dict{String, Vector{<:Colorant}}()
@requires <UUID> ColorSchemes add_more_palettes!(predefined_palettes)
Is the idea to do clustering with maximal type distance? If so there might be some RGB 2 gray (decolorization) papers to take a look at. I'm not an expert in this field so please allow me to blindly share a paper of my labmate https://link.springer.com/article/10.1007/s11042-021-11172-9 so that you can start the search. |
Thank you for the references!
We could also implement the same API via MD.
Good point. Maybe we can just support both options? ColorSchemes has a nice catalogue of palettes to visually check out what is available. This is what I personally tend to use.
I was thinking about using clustering to obtain optimized RGB color palettes for color quantization: combined with dithering you get pretty nice results: A possible interface would be dither(color_img, FloydSteinberg(); colors=16) # => returns color image using an optimized palette of 16 colors
dither(color_img, FloydSteinberg(), 16) # or maybe even like this? |
There is one more issue that came to my mind: Currently, calling Maybe we can break the convention here and return an image in the type of |
Agreed. This has proven to be very useful in PaddedViews and MosaicViews. julia> img = rand(Gray{Float32}, 4, 4);
julia> T = RGB{N0f8}
RGB{N0f8}
julia> RT = ImageCore.MosaicViews.promote_wrapped_type(eltype(img), T)
RGB{Float32}
julia> RT.(img) One thing that should be aware here is: if you decide to support dither(img, colorscheme::Symbol) = dither(img, get_colorscheme(colorscheme))
function dither(img, colorscheme::Vector{T}) where T
RT = ImageCore.MosaicViews.promote_wrapped_type(eltype(img), T)
RT.(img)
end
function get_colorscheme(cs::Symbol)
if cs === :RGB
return RGB{Float32}[RGB(1, 0, 0), RGB(0, 1, 0), RGB(0, 0, 1)]
elseif cs === :Gray
return Gray{Float32}[Gray(0), Gray(1)]
else
error("Unsupported color schemes")
end
end julia> @code_warntype get_colorscheme(:Gray)
Variables
#self#::Core.Const(get_colorscheme)
cs::Symbol
Body::Union{Vector{Gray{Float32}}, Vector{RGB{Float32}}}
1 ─ %1 = (cs === :RGB)::Bool
└── goto #3 if not %1
2 ─ %3 = Core.apply_type(Main.RGB, Main.Float32)::Core.Const(RGB{Float32})
│ %4 = Main.RGB(1, 0, 0)::Core.Const(RGB{N0f8}(1.0,0.0,0.0))
│ %5 = Main.RGB(0, 1, 0)::Core.Const(RGB{N0f8}(0.0,1.0,0.0))
│ %6 = Main.RGB(0, 0, 1)::Core.Const(RGB{N0f8}(0.0,0.0,1.0))
│ %7 = Base.getindex(%3, %4, %5, %6)::Vector{RGB{Float32}}
└── return %7
3 ─ %9 = (cs === :Gray)::Bool
└── goto #5 if not %9
4 ─ %11 = Core.apply_type(Main.Gray, Main.Float32)::Core.Const(Gray{Float32})
│ %12 = Main.Gray(0)::Core.Const(Gray{N0f8}(0.0))
│ %13 = Main.Gray(1)::Core.Const(Gray{N0f8}(1.0))
│ %14 = Base.getindex(%11, %12, %13)::Vector{Gray{Float32}}
└── return %14
5 ─ Main.error("Unsupported color schemes")
└── Core.Const(:(return %16))
julia> @code_warntype dither(img, :Gray)
Variables
#self#::Core.Const(dither)
img::Matrix{Gray{Float32}}
colorscheme::Symbol
Body::Union{Matrix{Gray{Float32}}, Matrix{RGB{Float32}}}
1 ─ %1 = Main.get_colorscheme(colorscheme)::Union{Vector{Gray{Float32}}, Vector{RGB{Float32}}}
│ %2 = Main.dither(img, %1)::Union{Matrix{Gray{Float32}}, Matrix{RGB{Float32}}}
└── return %2 |
Thanks a lot for the in-depth answer and for referring me to
Luckily, I think ColorSchemes.jl only exports Re colorscheme clustering: |
How would you suggest we deal with |
When dealing with in-place functions, users are responsible for allocating an appropriate output. An implicit RGB to Gray color conversion will be applied if users call julia> img = fill(Gray(1.0), 4, 4);
julia> img[1] = colorant"red"
RGB{N0f8}(1.0,0.0,0.0)
julia> img
4×4 Array{Gray{Float64},2} with eltype Gray{Float64}:
Gray{Float64}(0.298039) Gray{Float64}(1.0) Gray{Float64}(1.0) Gray{Float64}(1.0)
Gray{Float64}(1.0) Gray{Float64}(1.0) Gray{Float64}(1.0) Gray{Float64}(1.0)
Gray{Float64}(1.0) Gray{Float64}(1.0) Gray{Float64}(1.0) Gray{Float64}(1.0)
Gray{Float64}(1.0) Gray{Float64}(1.0) Gray{Float64}(1.0) Gray{Float64}(1.0) |
Thanks a lot Johnny! |
Per-channel dithering by default
Currently, to apply per-channel dithering, methods have to be wrapped in the
SeparateSpace()
meta-method.Since this can be applied to any algorithm, the API could be made more elegant by applying channel-wise dithering by default:
For convenience, a function
binary_dither
could be provided that first callsGray.()
, thendither
.Conditional dependencies
I'm not sure how well Requires.jl works, but these would come in handy for methods that support custom color palettes. Some examples:
The text was updated successfully, but these errors were encountered: