text:
using ColorTypes: RGB
using GeometryBasics: meta, SimplexFace, Mesh, Point3f, Vec3f
using GLMakie: Scene, lines!, mesh!,
    VideoStream, cameracontrols, update_cam!, recordframe!, save
using MultiFloats: Float64x2
using StaticArrays: SVector, dot, cross, norm
using UUIDs: UUID

push!(LOAD_PATH, @__DIR__)
using PCREO

function nearest_neighbor_distance(
    points::AbstractVector{SVector{N,T}},
    pairs::AbstractVector{Tuple{Int,Int}}
) where {T,N}
    result = typemax(T)
    for (i, j) in pairs
        @inbounds dist = norm(points[i] - points[j])
        result = min(result, dist)
    end
    return result
end

function farthest_neighbor_distance(
    points::AbstractVector{SVector{N,T}},
    pairs::AbstractVector{Tuple{Int,Int}}
) where {T,N}
    result = zero(T)
    for (i, j) in pairs
        @inbounds dist = norm(points[i] - points[j])
        result = max(result, dist)
    end
    return result
end

normalize(x::SVector{N,T}) where {T,N} = x / norm(x)

function point_markers(points::AbstractVector{SVector{3,T}},
                       m::Int, r::Float32) where {T}
    n = length(points)
    coords = Vector{SVector{3,Float32}}(undef, (m+1)*n)
    faces = Matrix{Int}(undef, m*n, 3)
    for i = 1 : n
        a_ = normalize(points[i])
        b_ = normalize(cross(a_, ifelse(a_[3] * a_[3] < T(0.5),
            SVector{3,T}(zero(T), zero(T), one(T)),
            SVector{3,T}(zero(T), one(T), zero(T))
        )))
        c = Float32.(cross(a_, b_))
        b = Float32.(b_)
        a = Float32.(a_)
        coords[(m+1)*(i-1)+1] = 1.003f0 * a
        @simd ivdep for j = 1 : m
            t = (2.0f0 * Float32(j)) / Float32(m)
            st, ct = sincospi(t)
            coords[(m+1)*(i-1)+j+1] = a + r*(ct*b + st*c)
        end
        @simd ivdep for j = 1 : m-1
            faces[m*(i-1)+j,1] = (m+1)*(i-1)+1
            faces[m*(i-1)+j,2] = (m+1)*(i-1)+j+1
            faces[m*(i-1)+j,3] = (m+1)*(i-1)+j+2
        end
        faces[m*i,1] = (m+1)*(i-1)+1
        faces[m*i,2] = (m+1)*(i-1)+m+1
        faces[m*i,3] = (m+1)*(i-1)+2
    end
    return (coords, faces)
end

function spherical_line_impl!(coords::AbstractVector{SVector{3,Float32}},
                              a::SVector{3,T}, b::SVector{3,T},
                              r::T, n::Int) where {T}
    if n == 0
        push!(coords, Float32.(r * a))
    else
        ab = normalize(a + b)
        spherical_line_impl!(coords,  a, ab, r, n - 1)
        spherical_line_impl!(coords, ab,  b, r, n - 1)
    end
    return nothing
end

function spherical_line!(coords::AbstractVector{SVector{3,Float32}},
                         a::SVector{3,T}, b::SVector{3,T},
                         r::T, n::Int) where {T}
    @assert n >= 0
    na = normalize(a)
    nb = normalize(b)
    spherical_line_impl!(coords, na, nb, r, n)
    push!(coords, Float32.(r * b))
    push!(coords, SVector{3,Float32}(NaN32, NaN32, NaN32))
    return nothing
end

function spherical_triangle_impl!(
    coords::AbstractVector{SVector{3,Float32}},
    colors::AbstractVector{RGB{U}},
    a::SVector{3,T}, b::SVector{3,T}, c::SVector{3,T},
    color::RGB{U}, n::Int
) where {T,U}
    if n == 0
        push!(coords, Float32.(a))
        push!(coords, Float32.(b))
        push!(coords, Float32.(c))
        push!(colors, color)
        push!(colors, color)
        push!(colors, color)
    else
        ab = normalize(a + b)
        bc = normalize(b + c)
        ca = normalize(c + a)
        spherical_triangle_impl!(coords, colors,  a, ab, ca, color, n - 1)
        spherical_triangle_impl!(coords, colors,  b, bc, ab, color, n - 1)
        spherical_triangle_impl!(coords, colors,  c, ca, bc, color, n - 1)
        spherical_triangle_impl!(coords, colors, ab, bc, ca, color, n - 1)
    end
    return nothing
end

function spherical_triangle!(coords::AbstractVector{SVector{3,Float32}},
                             colors::AbstractVector{RGB{U}},
                             a::SVector{3,T}, b::SVector{3,T}, c::SVector{3,T},
                             color::RGB{U}, n::Int) where {T,U}
    @assert n >= 0
    orientation = dot(a, cross(b - a, c - a))
    if !signbit(orientation)
        spherical_triangle_impl!(coords, colors,
                                 normalize(a), normalize(b), normalize(c),
                                 color, n)
    else
        spherical_triangle_impl!(coords, colors,
                                 normalize(a), normalize(c), normalize(b),
                                 color, n)
    end
    return nothing
end

const DEGREE_COLOR = Dict{Int,RGB{Float32}}(
    3 => RGB{Float32}(1.0f0, 0.5f0, 0.0f0),
    4 => RGB{Float32}(1.0f0, 1.0f0, 0.0f0),
    5 => RGB{Float32}(1.0f0, 0.0f0, 0.0f0),
    6 => RGB{Float32}(0.0f0, 1.0f0, 0.0f0),
    7 => RGB{Float32}(0.0f0, 0.0f0, 1.0f0),
    8 => RGB{Float32}(1.0f0, 0.0f0, 1.0f0),
)

function spherical_mesh(points::AbstractVector{SVector{3,T}},
                        degrees::AbstractDict{Int,Int},
                        facets::AbstractVector{Vector{Int}},
                        facet_centers::AbstractVector{SVector{3,T}},
                        adjacent_facets::AbstractVector{Tuple{Int,Int}},
                        n::Int) where {T}
    coords = SVector{3,Float32}[]
    colors = RGB{Float32}[]
    for (i, j) in adjacent_facets
        for k in intersect(facets[i], facets[j])
            spherical_triangle!(coords, colors,
                                points[k], facet_centers[i], facet_centers[j],
                                DEGREE_COLOR[degrees[k]], n)
        end
    end
    return Mesh(
        meta(Point3f.(coords); normals=Vec3f.(coords), color=colors),
        [SimplexFace{3,Int}(i, i+1, i+2) for i = 1 : 3 : length(coords)]
    )
end

function save_pcreo_movie(moviepath::AbstractString, rec::PCREORecord)

    points = to_point_vector(Val{3}(), rec.points)
    facet_centers = [
        spherical_circumcenter(points, facet)
        for facet in rec.facets
    ]

    degrees = incidence_degrees(rec.facets)
    adjacent_vertices, adjacent_facets = adjacency_structure(rec.facets)
    nndist = nearest_neighbor_distance(points, adjacent_vertices)
    fndist = farthest_neighbor_distance(points, adjacent_vertices)

    m = 0
    while fndist / 2^m >= Float64x2(0.125)
        m += 1
    end

    line_coordinates = SVector{3,Float32}[]
    for (i, j) in adjacent_facets
        spherical_line!(line_coordinates, facet_centers[i], facet_centers[j],
                        Float64x2(1.003), m)
    end

    scene = Scene(resolution=(800, 800))
    mesh!(scene, point_markers(points, 25, Float32(nndist) / 12.0f0)...;
          show_axis=false, color=:black, shading=false)
    lines!(scene, line_coordinates;
           linewidth=min(10.0f0*Float32(nndist), 3.0f0))
    mesh!(scene,
          spherical_mesh(points, degrees, rec.facets,
                         facet_centers, adjacent_facets, m);
          shading=true)

    stream = VideoStream(scene; framerate=60)
    cam = cameracontrols(scene)

    for t in range(0.0f0, 2.0f0; length=450)
        st, ct = sincospi(t)
        cam.eyeposition[] = 20.0f0 * normalize(SVector{3,Float32}(
            ct, st, 0.6f0 * sinpi(2.0f0 * t)
        ))
        cam.zoom_mult[] = 0.132f0
        cam.far[] = 30.0f0
        update_cam!(scene, cam)
        recordframe!(stream)
    end

    save(moviepath, stream; compression=0, framerate=60)
    return nothing
end

function main(a::Int, b::Int)

    @assert isdir(ENV["PCREO_DATABASE_DIRECTORY"])
    @assert isdir(ENV["PCREO_IMAGE_DIRECTORY"])

    for num_dir in lsdir(ENV["PCREO_DATABASE_DIRECTORY"]; join=true)
        num_str = basename(num_dir)
        n = parse(Int, num_str)
        if n % b == a
            for entry_dir in lsdir(num_dir; join=true)
                uuid = UUID(basename(entry_dir))
                moviename = "PCREO-03-$num_str-$uuid.webm"
                moviepath = joinpath(ENV["PCREO_IMAGE_DIRECTORY"], moviename)
                if isfile(moviepath)
                    println("$moviepath already exists.")
                    flush(stdout)
                else
                    println("Generating PCREO movie $moviepath...")
                    flush(stdout)
                    filename = "PCREO-03-$num_str-$uuid.csv"
                    save_pcreo_movie(
                        moviepath,
                        PCREORecord(joinpath(entry_dir, filename))
                    )
                end
            end
        end
    end
end

main(parse(Int, ARGS[1]), parse(Int, ARGS[2]))


text: ver = "0.0.1"

ext: jl


text:
using Documenter
using MyAwesomePackage

push!(LOAD_PATH,"../src/")
makedocs(sitename="MyAwesomePackage.jl Documentation",
         pages = [
            "Index" => "index.md",
            "An other page" => "anotherPage.md",
         ],
         format = Documenter.HTML(prettyurls = false)
)
# Documenter can also automatically deploy documentation to gh-pages.
# See "Hosting Documentation" and deploydocs() in the Documenter manual
# for more information.
deploydocs(
    repo = "github.com/sylvaticus/MyAwesomePackage.jl.git",
    devbranch = "main"
)

max_stars_repo_name: sylvaticus/MyAwesomePackage

max_issues_repo_name: sylvaticus/MyAwesomePackage

max_forks_repo_name: sylvaticus/MyAwesomePackage


text:
using Ripserer:
    BoundaryMatrix,
    CoboundaryMatrix,
    add!,
    clean!,
    coboundary,
    columns_to_reduce,
    compute_death_simplices!,
    compute_intervals!,
    dim,
    ordering,
    finalize!,
    heapmove!,
    heappop!,
    heappush!,
    is_cohomology,
    next_matrix,
    @prog_println,
    zeroth_intervals

export ReductionPlot

struct ReductionPlot{
    D,F,L,O1<:ObservableChain,O2<:ObservableChain,O3<:ObservableChain,O4<:ObservableChain
}
    # Stuff to plot
    data::D
    filtration::F

    # Plots
    scene::Scene
    layout::GridLayout
    stream::Ref{Union{VideoStream,Nothing}}
    data_axis::LScene
    diagram_axis::L

    # Plotting data
    chain_a::O1 # current working coboundary
    chain_b::O2 # column to add
    chain_c::O3 # death simplex and pivot
    chain_d::O4 # columns to reduce and cocycles
    diagram::ObservableDiagram

    # Settings
    rotation_speed::Float64
    framerate::Int
    show_diagram::Bool
    show_cocycles::Bool
    birth_frames::Int
    pivot_frames::Int
    column_frames::Int
    cooldown_frames::Int
    debug::Bool

    # Number of recorded frames. Can be listened to with `on`.
    frames::Observable{Int}
end

function ReductionPlot(
    data,
    filtration=Alpha(data);
    palette=DEFAULT_PALETTE,
    rotation_speed=0.01,
    framerate=30,
    show_diagram=false,
    debug=false,
    show_cocycles=true,
    birth_frames=1,
    pivot_frames=1,
    column_frames=1,
    cooldown_frames=5,
)
    data = convert_arguments(Scatter, data)[1]
    chain_a = ObservableChain(data)
    chain_b = ObservableChain(data)
    chain_c = ObservableChain(data)
    chain_d = ObservableChain(data)

    t_min = minimum(Ripserer.births(filtration))
    t_max = threshold(filtration)
    if isnothing(t_max) || !isfinite(t_max)
        t_max = Ripserer.radius(data)
    end
    diagram = ObservableDiagram(t_min, t_max)

    # Set up plots.
    scene, layout = layoutscene()
    stream = Ref{Union{VideoStream,Nothing}}(nothing)
    if !debug
        stream[] = VideoStream(scene; framerate)
    end

    data_axis = layout[1:2, 1:2] = LScene(scene; title="Data")
    plot!(data_axis, data; color=PlotUtils.get_colorscheme(palette)[1])
    plot!(data_axis, chain_a; color=2, palette)
    plot!(data_axis, chain_b; color=3, palette)
    plot!(data_axis, chain_c; color=4, palette)
    plot!(data_axis, chain_d; color=5, palette)

    if show_diagram
        diagram_axis = layout[1:2, 3] = LAxis(scene; title="Diagram")
        plot!(diagram_axis, diagram)
        xlims!(diagram_axis, t_min, t_max)
        ylims!(diagram_axis, t_min, t_max)
        tightlimits!(diagram_axis)
    else
        diagram_axis = nothing
    end

    # The scene needs to be displayed again, or only the last axis created is shown.
    !debug && display(scene)

    return ReductionPlot(
        data,
        filtration,
        scene,
        layout,
        stream,
        data_axis,
        diagram_axis,
        chain_a,
        chain_b,
        chain_c,
        chain_d,
        diagram,
        Float64(rotation_speed),
        framerate,
        show_diagram,
        show_cocycles,
        birth_frames,
        pivot_frames,
        column_frames,
        cooldown_frames,
        debug,
        Observable(0),
    )
end

function Base.show(io::IO, plot::ReductionPlot)
    !plot.debug && display(plot.scene)
    println(io, "ReductionPlot:")
    println(io, " data:            ", summary(plot.data))
    println(io, " filtration:      ", plot.filtration)
    return println(io, " frames recorded: ", plot.frames[])
end

function Base.empty!(plot)
    plot.frames[] = 0
    if !plot.debug
        plot.stream[] = VideoStream(plot.scene; framerate=plot.framerate)
    else
        plot.stream[] = nothing
    end
    plot.chain_a[] = ()
    plot.chain_b[] = ()
    plot.chain_c[] = ()
    plot.chain_d[] = ()

    if plot.show_diagram
        empty!(plot.diagram)
        empty!(plot.diagram_axis.scene.plots)
        start = minimum(Ripserer.births(plot.filtration))
        thresh = threshold(plot.filtration)
        if isnothing(thresh) || !isfinite(thresh)
            thresh = Ripserer.radius(plot.data)
        end
        diagrambackground!(plot.diagram_axis, start, thresh, thresh)
    end
    return plot
end

function AbstractPlotting.save(filename, plot::ReductionPlot; kwargs...)
    return save(filename, plot.stream[]; kwargs...)
end

function recordframes!(plot, nframes)
    for _ in 1:nframes
        !plot.debug && recordframe!(plot.stream[])
        if cameracontrols(plot.data_axis.scene) isa Camera3D
            rotate_cam!(plot.data_axis.scene, Vec3(plot.rotation_speed, 0.0, 0.0))
        end
        plot.frames[] += 1
    end
end

"""
    VisualMatrix{M, R<:ReductionPlot, C1, C2}

Wrapper around `CoboundaryMatrix` or `BoundaryMatrix` that also records operations to a
`ReductionPlot`.
"""
struct VisualMatrix{M,R<:ReductionPlot,C1,C2}
    matrix::M
    plot::R
    # Buffers change types so they are stored here.
    _buffer::C1
    _cobuffer::C2
end

function VisualMatrix(matrix, plot::ReductionPlot)
    buffer = copy(matrix.chain)
    cobuffer = copy(matrix.buffer)
    return VisualMatrix(matrix, plot, buffer, cobuffer)
end

# Make it pretend it's a (co)boundary matrix.
function Base.getproperty(v::VisualMatrix, k::Symbol)
    if k == :filtration
        return getfield(v, :matrix).filtration
    elseif k == :reduced
        return getfield(v, :matrix).reduced
    elseif k == :chain
        return getfield(v, :matrix).chain
    elseif k == :columns_to_reduce
        return getfield(v, :matrix).columns_to_reduce
    elseif k == :columns_to_skip
        return getfield(v, :matrix).columns_to_skip
    elseif k == :buffer
        return getfield(v, :matrix).buffer
    elseif k == :data
        return getfield(v, :plot).data
    else
        return getfield(v, k)
    end
end

for f in (:dim, :is_implicit, :is_cohomology, :ordering, :field_type)
    @eval begin
        Ripserer.$f(v::VisualMatrix) = Ripserer.$f(v.matrix)
    end
end

function Ripserer.coboundary(matrix::VisualMatrix, simplex::AbstractSimplex)
    return coboundary(matrix.matrix, simplex)
end

function Ripserer.next_matrix(matrix::VisualMatrix, progress)
    next = next_matrix(matrix.matrix, progress)
    return VisualMatrix(next, matrix.plot)
end

function clear_plot!(matrix)
    matrix.plot.chain_a[] = ()
    matrix.plot.chain_b[] = ()
    matrix.plot.chain_c[] = ()
    matrix.plot.chain_d[] = ()
    if matrix.plot.show_diagram
        diagram = matrix.plot.diagram
        b, d = diagram.intervals[][end]
        b == d && pop!(diagram)
    end
end

function recordframes!(matrix::VisualMatrix, sym::Symbol)
    recordframes!(matrix.plot, getfield(matrix.plot, sym))
    return nothing
end

function display_birth!(matrix, column_to_reduce)
    clear_plot!(matrix)
    empty!(matrix._buffer)
    empty!(matrix._cobuffer)
    plot = matrix.plot
    plot.chain_d[] = column_to_reduce

    if plot.show_diagram
        if is_cohomology(matrix)
            push!(plot.diagram, (birth(column_to_reduce), Inf), dim(matrix) + 1)
        else
            push!(plot.diagram, (0, birth(column_to_reduce)), dim(matrix) + 1)
        end
    end

    recordframes!(matrix, :birth_frames)
    if !is_cohomology(matrix)
        plot.chain_d[] = ()
    end
    return nothing
end

function display_chain!(matrix)
    buffer = matrix._buffer
    empty!(buffer)
    heapmove!(buffer, matrix.chain, ordering(matrix))
    copy!(matrix.chain, buffer)
    matrix.plot.chain_a[] = buffer
    return nothing
end

function display_pivot!(matrix, pivot)
    plot = matrix.plot
    plot.chain_c[] = pivot
    if plot.show_diagram && pivot != ()
        curr_b, curr_d = plot.diagram.intervals[][end]
        if is_cohomology(matrix)
            edit_last!(plot.diagram, (curr_b, birth(pivot)))
        else
            edit_last!(plot.diagram, (birth(pivot), curr_d))
        end
    end
    recordframes!(matrix, :pivot_frames)
    return nothing
end

function display_cocycle!(matrix, column_to_reduce)
    if is_cohomology(matrix) && !isempty(matrix.buffer) && matrix.plot.show_cocycles
        cobuffer = matrix._cobuffer
        empty!(cobuffer)
        copy!(cobuffer, matrix.buffer)
        push!(cobuffer, column_to_reduce)
        clean!(cobuffer, ordering(matrix))
        matrix.plot.chain_d[] = cobuffer
    end
end

function display_column!(matrix, column)
    buffer = matrix._buffer
    empty!(buffer)
    if is_cohomology(matrix)
        for elem in column
            coef = coefficient(elem)
            for cofacet in coboundary(matrix, simplex(elem))
                push!(buffer, (cofacet, coef))
            end
        end
        clean!(buffer, ordering(matrix))
    else
        copy!(buffer, column)
    end
    matrix.plot.chain_b[] = buffer
    return nothing
end

function display_reduced!(matrix, column_to_reduce)
    plot = matrix.plot
    if plot.show_diagram
        if is_cohomology(matrix)
            edit_last!(plot.diagram, (birth(column_to_reduce), Inf))
        else
            pop!(plot.diagram)
        end
    end
    recordframes!(matrix, :pivot_frames)
    return nothing
end

function clear_chain!(matrix)
    if is_cohomology(matrix) # in homology, we want to show the final cycle
        matrix.plot.chain_a[] = ()
    end
    return nothing
end
function clear_column!(matrix)
    matrix.plot.chain_b[] = ()
    return nothing
end

function Ripserer.reduce_column!(matrix::VisualMatrix, column_to_reduce)
    empty!(matrix.buffer)
    ###
    display_birth!(matrix, column_to_reduce)
    ###

    # Don't do emergent pairs.
    empty!(matrix.chain)
    for cofacet in coboundary(matrix, column_to_reduce)
        heappush!(matrix.chain, cofacet, ordering(matrix))
    end
    pivot = heappop!(matrix.chain, ordering(matrix))

    while !isnothing(pivot)
        ###
        display_chain!(matrix)
        display_pivot!(matrix, pivot)
        ###
        column = matrix.reduced[pivot]
        isempty(column) && break
        add!(matrix, column, pivot)
        ###
        display_cocycle!(matrix, column_to_reduce)
        display_column!(matrix, column)
        display_pivot!(matrix, pivot)
        clear_column!(matrix)
        display_chain!(matrix)
        display_pivot!(matrix, ())
        ###
        pivot = heappop!(matrix.chain, ordering(matrix))
    end
    if !isnothing(pivot)
        ###
        display_chain!(matrix)
        display_pivot!(matrix, pivot)
        if is_cohomology(matrix)
            clear_chain!(matrix)
            display_pivot!(matrix, pivot)
        end
        ###
        finalize!(matrix, column_to_reduce, pivot)
    else
        display_reduced!(matrix, pivot)
    end
    clear_plot!(matrix)

    return pivot
end

function Ripserer.ripserer(
    plot::ReductionPlot; dim_max=1, modulus=2, field=Mod{modulus}, alg=:cohomology
)
    empty!(plot)
    !plot.debug && display(plot.stream)
    start_time = time_ns()
    zeroth, to_reduce, to_skip = zeroth_intervals(plot.filtration, 0, true, field, false)
    append!(plot.diagram, zeroth)
    result = _ripserer(Val(alg), plot, zeroth, to_reduce, to_skip, dim_max, field)

    # Logging stuff.
    elapsed = round((time_ns() - start_time) / 1e9; digits=3)
    @prog_println true "Done. Time: " ProgressMeter.durationstring(elapsed)
    frames = plot.frames[]
    video_length = ProgressMeter.durationstring(frames / plot.framerate)
    fps = round(frames / elapsed; digits=3)
    @prog_println true "$frames frames recorded at $(fps)fps. Video length: $video_length"
    return result
end

function _ripserer(::Val{:cohomology}, plot, zeroth, to_reduce, to_skip, dim_max, field)
    result = PersistenceDiagram[]
    push!(result, zeroth)
    if dim_max > 0
        matrix = CoboundaryMatrix{true}(field, plot.filtration, to_reduce, to_skip)
        vmatrix = VisualMatrix(matrix, plot)
        for dim in 1:dim_max
            push!(result, compute_intervals!(vmatrix, 0, true, false))
            recordframes!(plot, plot.cooldown_frames)
            if dim < dim_max
                vmatrix = next_matrix(vmatrix, true)
            end
        end
    end
    return result
end

function _ripserer(::Val{:homology}, plot, zeroth, to_reduce, to_skip, dim_max, field)
    result = PersistenceDiagram[]
    push!(result, zeroth)
    filtration = plot.filtration

    if dim_max > 0
        simplices = columns_to_reduce(filtration, Iterators.flatten((to_reduce, to_skip)))
        for dim in 1:dim_max
            matrix = BoundaryMatrix{false}(field, filtration, simplices)
            vmatrix = VisualMatrix(matrix, plot)
            push!(result, compute_intervals!(vmatrix, 0, true, false))
            recordframes!(plot, plot.cooldown_frames)
            if dim < dim_max
                simplices = columns_to_reduce(filtration, simplices)
            end
        end
    end
    return result
end

function _ripserer(::Val{:involuted}, plot, zeroth, to_reduce, to_skip, dim_max, field)
    result = PersistenceDiagram[]
    push!(result, zeroth)
    filtration = plot.filtration

    if dim_max > 0
        comatrix = CoboundaryMatrix{true}(field, filtration, to_reduce, to_skip)
        for dim in 1:dim_max
            columns, inf_births = compute_death_simplices!(comatrix, true, 0)
            matrix = BoundaryMatrix{false}(field, filtration, columns)
            vmatrix = VisualMatrix(matrix, plot)
            diagram = compute_intervals!(vmatrix, 0, true, false)
            recordframes!(plot, plot.cooldown_frames)
            for birth_simplex in inf_births
                push!(
                    diagram.intervals,
                    interval(comatrix, birth_simplex, nothing, 0, _reps(reps, dim)),
                )
            end
            push!(result, diagram)
            if dim < dim_max
                comatrix = next_matrix(comatrix, true)
            end
        end
    end
    return result
end


text:
using Test: @testset, @test, @test_throws, @test_broken
using Random
using CBinding

function checkJL(expr, val)
	def = expr
	def = replace(def, r"(\w+)::(\w+):(\d+)" => s"(\1:\3)::\2")

	X = @eval module $(gensym())
		using CBinding
		@ctypedef X $(Meta.parse(def))
	end

	x = Base.invokelatest(X.X)
	result = []
	push!(result, "$(string(sizeof(x))): $(bytes2hex(UInt8[getfield(x, :mem)...,]))")
	types = Base.invokelatest(CBinding.propertytypes, x)
	for (ind, prop) in enumerate(Base.invokelatest(propertynames, x))
		x = Base.invokelatest(X.X)
		v1 = sizeof(types[ind]) < sizeof(val) ? Core.Intrinsics.trunc_int(types[ind], val) : reinterpret(types[ind], val)
		Base.invokelatest(setproperty!, x, prop, v1)
		v2 = Base.invokelatest(getproperty, x, prop)
		Base.invokelatest(setproperty!, x, prop, v2)
		v3 = Base.invokelatest(getproperty, x, prop)
		@test v2 === v3
		push!(result, "$(String(prop)): $(bytes2hex(UInt8[getfield(x, :mem)...,]))")
	end
	return result
end

FILE_COUNT = 0
function checkC(expr, val)
	global FILE_COUNT
	FILE_COUNT += 1
	file = string(FILE_COUNT, pad = 4)
	expectedFile = joinpath(ifelse(Sys.iswindows(), "expected-windows", "expected"), "$(file).txt")
	if !isfile(expectedFile) || get(ENV, "GENERATE_EXPECTED", nothing) == "1"
		props = []
		for line in split(expr, '\n')
			m = match(r"^\s*([^:]+)::(Cchar|Cshort|Cint|Clonglong|Cuchar|Cushort|Cuint|Culonglong|Culong|Cfloat|Cdouble|Clongdouble|Clong|@cenum)", line)
			isnothing(m) || push!(props, m.captures[end-1] => m.captures[end])
		end
		m = match(r"^\s*@c(struct|union)\s+(\S+)", expr)
		(kind, name) = m.captures[end-1:end]

		def = expr
		def = replace(def, r"(\n\s+)([^:\n]+)(::@cenum)([^}]+)(})" => s"\1enum\4\5 \2")
		def = replace(def, "\n" => ";\n")
		def = replace(def, "{;" => "{")
		def = replace(def, ",;" => ",")
		def = replace(def, "__packed__" => "__attribute__((__packed__))")
		def = replace(def, r"@c(struct|union)" => s"\1")
		def = replace(def, r"@calign\s+([^;]+);\n" => s"alignas(\1)")
		def = replace(def, r"(\w+)::(Cchar|Cshort|Cint|Clonglong|Cuchar|Cushort|Cuint|Culonglong|Culong|Cfloat|Cdouble|Clongdouble|Clong)" => s"\2 \1")
		def = replace(def, "Cchar" => "signed char")
		def = replace(def, "Cshort" => "signed short")
		def = replace(def, "Cint" => "signed int")
		def = replace(def, "Clonglong" => "signed long long")
		def = replace(def, "Cuchar" => "unsigned char")
		def = replace(def, "Cushort" => "unsigned short")
		def = replace(def, "Cuint" => "unsigned int")
		def = replace(def, "Culonglong" => "unsigned long long")
		def = replace(def, "Culong" => "unsigned long")
		def = replace(def, "Cfloat" => "float")
		def = replace(def, "Cdouble" => "double")
		def = replace(def, "Clongdouble" => "long double")
		def = replace(def, "Clong" => "signed long")
		def = replace(def, r"} (\w+) (__attribute__[^;]+)" => s"} \2 \1")

		use = []
		push!(use, """
			$(kind) $(name) x; memset(&x, 0, sizeof(x));
			printf("%ld: ", sizeof(x));
			for (int i = 0;i < sizeof(x);i++) printf("%02x", (unsigned int)(*(((unsigned char*)&x)+i)));
			printf("\\n");
			"""
		)
		for (prop, typ) in props
			setprop = "x.$(prop)"
			if typ == "Cfloat"
				setprop = "*((unsigned int *)&($(setprop))) = $(repr(Core.Intrinsics.trunc_int(UInt64, val)));"
			elseif typ == "Cdouble"
				setprop = "*((unsigned long long *)&($(setprop))) = $(repr(Core.Intrinsics.trunc_int(UInt64, val)));"
			elseif typ == "Clongdouble"
				setprop = """
					*(((unsigned long long *)&($(setprop)))+0) = $(repr(Core.Intrinsics.trunc_int(UInt64, val)));
					*(((unsigned long long *)&($(setprop)))+1) = $(repr(Core.Intrinsics.trunc_int(UInt64, val>>64)));"""
			else
				setprop = "$(setprop) = $(repr(Core.Intrinsics.trunc_int(UInt64, val)));"
			end
			push!(use, """
				$(kind) $(name) x; memset(&x, 0, sizeof(x));
				$(setprop)
				printf("%s: ", $(repr(prop)));
				for (int i = 0;i < sizeof(x);i++) printf("%02x", (unsigned int)(*(((unsigned char*)&x)+i)));
				printf("\\n");
				"""
			)
		end
		use = "{\n"*join(use, "}\n{\n")*"}\n"

		code = """
			#include <stdio.h>
			#include <stdalign.h>
			#include <string.h>
			#include <inttypes.h>

			$(def)

			int main(int argc, char **argv) {
				$(use)

				return 0;
			}
			"""

		open(f -> write(f, code), ".$(file).c", "w+")
		run(`gcc -Wno-overflow -std=c99 -o check .$(file).c`)
		expected = read(`./check`)
		open(f -> write(f, expected), expectedFile, "w+")
	end

	return readlines(expectedFile)
end

include("layout-tests.jl")

@testset "CBinding" begin
	@testset "Layout algorithms" begin
		rng = MersenneTwister(0)
		for _test in LAYOUT_TESTS
			for aggregate in ("@cstruct", "@cunion")
				for strategy in ("__packed__", "__native__")
					for val in (:random, :ones)
						val = val === :ones ? Core.Intrinsics.sext_int(UInt128, 0xff) : rand(rng, UInt128)
						test = replace(_test, r"^\s*@cstruct" => aggregate)
						test = replace(test, r"}\s*$" => "} $(strategy)\n")
						@debug test
						@test checkC(test, val) == checkJL(test, val)
					end
				end
			end
		end
	end

	include("cstruct.jl")
	include("cunion.jl")
	include("carray.jl")
	include("cenum.jl")
	include("ctypedef.jl")
	include("cconst.jl")
	include("clibrary.jl")
	include("cglobal.jl")
	include("cfunction.jl")
	include("cextern.jl")
end


text:
using ColorTypes
using ColorTypes.FixedPointNumbers
using Test
using ColorTypes: ColorTypeResolutionError

@testset "rgb promotions" begin
    @test promote( RGB{N0f8}(0.2,0.3,0.4),  RGB(0.3,0.8,0.1)) === ( RGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8),  RGB{Float64}(0.3,0.8,0.1))
    @test promote( RGB{N0f8}(0.2,0.3,0.4), RGBA(0.3,0.8,0.1)) === (RGBA{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), RGBA{Float64}(0.3,0.8,0.1))
    @test promote( RGB{N0f8}(0.2,0.3,0.4), ARGB(0.3,0.8,0.1)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), ARGB{Float64}(0.3,0.8,0.1))
    @test promote(RGBA{N0f8}(0.2,0.3,0.4),  RGB(0.3,0.8,0.1)) === (RGBA{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), RGBA{Float64}(0.3,0.8,0.1))
    @test promote(RGBA{N0f8}(0.2,0.3,0.4), RGBA(0.3,0.8,0.1)) === (RGBA{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), RGBA{Float64}(0.3,0.8,0.1))
    @test promote(ARGB{N0f8}(0.2,0.3,0.4),  RGB(0.3,0.8,0.1)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), ARGB{Float64}(0.3,0.8,0.1))
    @test promote(ARGB{N0f8}(0.2,0.3,0.4), ARGB(0.3,0.8,0.1)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), ARGB{Float64}(0.3,0.8,0.1))

    @test promote( RGB24(0.2,0.3,0.4),  RGB(0.3,0.8,0.1)) === ( RGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8),  RGB{Float64}(0.3,0.8,0.1))
    @test promote( RGB24(0.2,0.3,0.4), RGBA(0.3,0.8,0.1)) === (RGBA{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), RGBA{Float64}(0.3,0.8,0.1))
    @test promote( RGB24(0.2,0.3,0.4), ARGB(0.3,0.8,0.1)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), ARGB{Float64}(0.3,0.8,0.1))
    @test promote(ARGB32(0.2,0.3,0.4),  RGB(0.3,0.8,0.1)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), ARGB{Float64}(0.3,0.8,0.1))
    @test promote(ARGB32(0.2,0.3,0.4), ARGB(0.3,0.8,0.1)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), ARGB{Float64}(0.3,0.8,0.1))

    @test promote( RGB24(0.2,0.3,0.4),  RGB{N0f8}(0.3,0.8,0.1)) === ( RGB{N0f8}(0.2,0.3,0.4),  RGB{N0f8}(0.3,0.8,0.1))
    @test promote( RGB24(0.2,0.3,0.4), RGBA{N0f8}(0.3,0.8,0.1)) === (RGBA{N0f8}(0.2,0.3,0.4), RGBA{N0f8}(0.3,0.8,0.1))
    @test promote( RGB24(0.2,0.3,0.4), ARGB{N0f8}(0.3,0.8,0.1)) === (ARGB{N0f8}(0.2,0.3,0.4), ARGB{N0f8}(0.3,0.8,0.1))
    @test promote(ARGB32(0.2,0.3,0.4),  RGB{N0f8}(0.3,0.8,0.1)) === (ARGB{N0f8}(0.2,0.3,0.4), ARGB{N0f8}(0.3,0.8,0.1))
    @test promote(ARGB32(0.2,0.3,0.4), ARGB{N0f8}(0.3,0.8,0.1)) === (ARGB{N0f8}(0.2,0.3,0.4), ARGB{N0f8}(0.3,0.8,0.1))

    @test promote(RGBX{N0f8}(0.2,0.3,0.4),  RGB(0.3,0.8,0.1)) === (RGBX{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), RGBX{Float64}(0.3,0.8,0.1))
    @test promote(RGBX{N0f8}(0.2,0.3,0.4), RGBA(0.3,0.8,0.1)) === (RGBA{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), RGBA{Float64}(0.3,0.8,0.1))
    @test promote(RGBX{N0f8}(0.2,0.3,0.4), ARGB(0.3,0.8,0.1)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), ARGB{Float64}(0.3,0.8,0.1))
    @test promote(XRGB{N0f8}(0.2,0.3,0.4),  RGB(0.3,0.8,0.1)) === (XRGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), XRGB{Float64}(0.3,0.8,0.1))
    @test promote(XRGB{N0f8}(0.2,0.3,0.4), RGBA(0.3,0.8,0.1)) === (RGBA{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), RGBA{Float64}(0.3,0.8,0.1))
    @test promote(XRGB{N0f8}(0.2,0.3,0.4), ARGB(0.3,0.8,0.1)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), ARGB{Float64}(0.3,0.8,0.1))

    @test promote(RGBX(0.2,0.3,0.4),  RGB{N0f8}(0.3,0.8,0.1)) === (RGBX{Float64}(0.2,0.3,0.4), RGBX{Float64}(0.3N0f8,0.8N0f8,0.1N0f8))
    @test promote(RGBX(0.2,0.3,0.4), RGBA{N0f8}(0.3,0.8,0.1)) === (RGBA{Float64}(0.2,0.3,0.4), RGBA{Float64}(0.3N0f8,0.8N0f8,0.1N0f8))
    @test promote(RGBX(0.2,0.3,0.4), ARGB{N0f8}(0.3,0.8,0.1)) === (ARGB{Float64}(0.2,0.3,0.4), ARGB{Float64}(0.3N0f8,0.8N0f8,0.1N0f8))
    @test promote(XRGB(0.2,0.3,0.4),  RGB{N0f8}(0.3,0.8,0.1)) === (XRGB{Float64}(0.2,0.3,0.4), XRGB{Float64}(0.3N0f8,0.8N0f8,0.1N0f8))
    @test promote(XRGB(0.2,0.3,0.4), RGBA{N0f8}(0.3,0.8,0.1)) === (RGBA{Float64}(0.2,0.3,0.4), RGBA{Float64}(0.3N0f8,0.8N0f8,0.1N0f8))
    @test promote(XRGB(0.2,0.3,0.4), ARGB{N0f8}(0.3,0.8,0.1)) === (ARGB{Float64}(0.2,0.3,0.4), ARGB{Float64}(0.3N0f8,0.8N0f8,0.1N0f8))

    @test promote(RGB24(0.2,0.3,0.4), ARGB32(0.3,0.8,0.1)) === (ARGB32(0.2,0.3,0.4), ARGB32(0.3,0.8,0.1))

    @test promote_type(RGB, RGB) === RGB
    @test promote_type(RGB, RGB{Float16}) === RGB

    @test promote_type(RGB, RGBA) === RGBA
    @test promote_type(ARGB, RGB) === ARGB
    @test promote_type(RGB, RGBA{Float16}) === RGBA
    @test promote_type(ARGB, RGB{Float16}) === ARGB

    @test promote_type(RGBA, RGBA) === RGBA
    @test promote_type(ARGB, ARGB) === ARGB
    @test promote_type(ARGB, RGBA) === ARGB
    @test promote_type(RGBA, RGBA{Float16}) === RGBA
    @test promote_type(ARGB, ARGB{Float16}) === ARGB
    @test promote_type(ARGB, RGBA{Float16}) === ARGB

    @test promote_type(RGB, RGB24) === RGB
    @test promote_type(BGR, RGB24) === RGB
    @test promote_type(XRGB, RGB24) === XRGB
    @test promote_type(RGBX, RGB24) === RGBX
    @test promote_type(RGB, ARGB32) === ARGB
    @test promote_type(BGR, ARGB32) === ARGB
    @test promote_type(XRGB, ARGB32) === ARGB
    @test promote_type(RGBX, ARGB32) === ARGB

    @test promote_type(RGB, BGR) === RGB
    @test promote_type(RGB, BGR{Float16}) === RGB
    @test promote_type(BGR, RGB{Float16}) === RGB

    @test promote_type(RGB, RGBX) === RGBX
    @test promote_type(XRGB, RGB) === XRGB
    @test promote_type(RGB, RGBX{Float16}) === RGBX
    @test promote_type(XRGB, RGB{Float16}) === XRGB

    @test promote_type(AbstractRGB, RGB{Float16}) === RGB
    @test promote_type(RGB, AbstractRGB{Float16}) === RGB
    @test promote_type(AbstractRGB{Float16}, RGB{Float16}) === RGB{Float16}
    @test promote_type(AbstractRGB{Float16}, RGB24) === RGB{Float32}
end

@testset "hsv promotions" begin
    @test promote( HSV{Float32}(100,0.3,0.4),  HSV(200,0.8,0.1)) === ( HSV{Float64}(100,0.3f0,0.4f0),  HSV{Float64}(200,0.8,0.1))
    @test promote( HSV{Float32}(100,0.3,0.4), HSVA(200,0.8,0.1)) === (HSVA{Float64}(100,0.3f0,0.4f0), HSVA{Float64}(200,0.8,0.1))
    @test promote( HSV{Float32}(100,0.3,0.4), AHSV(200,0.8,0.1)) === (AHSV{Float64}(100,0.3f0,0.4f0), AHSV{Float64}(200,0.8,0.1))
    @test promote(HSVA{Float32}(100,0.3,0.4),  HSV(200,0.8,0.1)) === (HSVA{Float64}(100,0.3f0,0.4f0), HSVA{Float64}(200,0.8,0.1))
    @test promote(HSVA{Float32}(100,0.3,0.4), HSVA(200,0.8,0.1)) === (HSVA{Float64}(100,0.3f0,0.4f0), HSVA{Float64}(200,0.8,0.1))
    @test promote(AHSV{Float32}(100,0.3,0.4),  HSV(200,0.8,0.1)) === (AHSV{Float64}(100,0.3f0,0.4f0), AHSV{Float64}(200,0.8,0.1))
    @test promote(AHSV{Float32}(100,0.3,0.4), AHSV(200,0.8,0.1)) === (AHSV{Float64}(100,0.3f0,0.4f0), AHSV{Float64}(200,0.8,0.1))

    @test promote_type(HSV, HSV) === HSV
    @test promote_type(HSV, HSV{Float16}) === HSV

    @test promote_type(HSV, HSVA) === HSVA
    @test promote_type(AHSV, HSV) === AHSV
    @test promote_type(HSV, HSVA{Float16}) === HSVA
    @test promote_type(AHSV, HSV{Float16}) === AHSV
end

@testset "gray promotions" begin
    @test promote( Gray{N0f8}(0.2),  Gray(0.3)) === ( Gray{Float64}(0.2N0f8),  Gray{Float64}(0.3))
    @test promote( Gray{N0f8}(0.2), GrayA(0.3)) === (GrayA{Float64}(0.2N0f8), GrayA{Float64}(0.3))
    @test promote( Gray{N0f8}(0.2), AGray(0.3)) === (AGray{Float64}(0.2N0f8), AGray{Float64}(0.3))
    @test promote(GrayA{N0f8}(0.2),  Gray(0.3)) === (GrayA{Float64}(0.2N0f8), GrayA{Float64}(0.3))
    @test promote(GrayA{N0f8}(0.2), GrayA(0.3)) === (GrayA{Float64}(0.2N0f8), GrayA{Float64}(0.3))
    @test promote(AGray{N0f8}(0.2),  Gray(0.3)) === (AGray{Float64}(0.2N0f8), AGray{Float64}(0.3))
    @test promote(AGray{N0f8}(0.2), AGray(0.3)) === (AGray{Float64}(0.2N0f8), AGray{Float64}(0.3))

    @test promote( Gray24(0.2),  Gray(0.3)) === ( Gray{Float64}(0.2N0f8),  Gray{Float64}(0.3))
    @test promote( Gray24(0.2), GrayA(0.3)) === (GrayA{Float64}(0.2N0f8), GrayA{Float64}(0.3))
    @test promote( Gray24(0.2), AGray(0.3)) === (AGray{Float64}(0.2N0f8), AGray{Float64}(0.3))
    @test promote(AGray32(0.2),  Gray(0.3)) === (AGray{Float64}(0.2N0f8), AGray{Float64}(0.3))
    @test promote(AGray32(0.2), AGray(0.3)) === (AGray{Float64}(0.2N0f8), AGray{Float64}(0.3))
    @test promote( Gray24(0.2),  Gray{N0f8}(0.3)) === ( Gray{N0f8}(0.2),  Gray{N0f8}(0.3))
    @test promote( Gray24(0.2), GrayA{N0f8}(0.3)) === (GrayA{N0f8}(0.2), GrayA{N0f8}(0.3))
    @test promote( Gray24(0.2), AGray{N0f8}(0.3)) === (AGray{N0f8}(0.2), AGray{N0f8}(0.3))
    @test promote(AGray32(0.2),  Gray{N0f8}(0.3)) === (AGray{N0f8}(0.2), AGray{N0f8}(0.3))
    @test promote(AGray32(0.2), AGray{N0f8}(0.3)) === (AGray{N0f8}(0.2), AGray{N0f8}(0.3))

    @test promote(AGray32(0.2), Gray24(0.3)) === (AGray32(0.2N0f8), AGray32(0.3))

    @test promote_type(Gray, Gray) === Gray
    @test promote_type(Gray, Gray{Float16}) === Gray

    @test promote_type(Gray, GrayA) === GrayA
    @test promote_type(AGray, Gray) === AGray
    @test promote_type(Gray, GrayA{Float16}) === GrayA
    @test promote_type(AGray, Gray{Float16}) === AGray

    @test promote_type(AGray, Gray{Bool}) === AGray

    @test promote_type(Gray, Gray24) === Gray
    @test promote_type(Gray, AGray32) === AGray

    @test promote_type(AbstractGray, Gray{Float16}) === Gray
    @test promote_type(Gray, AbstractGray{Float16}) === Gray
    @test promote_type(AbstractGray{Float16}, Gray{Float16}) === Gray{Float16}
    @test promote_type(AbstractGray{Float16}, Gray24) === Gray{Float32}
end

@testset "rgb and gray promotions" begin
    @test promote( RGB(0.2,0.3,0.4),  Gray(0.8)) === (RGB{Float64}(0.2,0.3,0.4), RGB{Float64}(0.8,0.8,0.8))
    @test promote( RGB(0.2,0.3,0.4), GrayA(0.8)) === (RGBA{Float64}(0.2,0.3,0.4,1), RGBA{Float64}(0.8,0.8,0.8,1))
    @test promote( RGB(0.2,0.3,0.4), AGray(0.8)) === (ARGB{Float64}(0.2,0.3,0.4,1), ARGB{Float64}(0.8,0.8,0.8,1))
    @test promote(RGBA(0.2,0.3,0.4),  Gray(0.8)) === (RGBA{Float64}(0.2,0.3,0.4,1), RGBA{Float64}(0.8,0.8,0.8,1))
    @test promote(RGBA(0.2,0.3,0.4), GrayA(0.8)) === (RGBA{Float64}(0.2,0.3,0.4,1), RGBA{Float64}(0.8,0.8,0.8,1))
    @test promote(RGBA(0.2,0.3,0.4), AGray(0.8)) === (RGBA{Float64}(0.2,0.3,0.4,1), RGBA{Float64}(0.8,0.8,0.8,1))
    @test promote(ARGB(0.2,0.3,0.4),  Gray(0.8)) === (ARGB{Float64}(0.2,0.3,0.4,1), ARGB{Float64}(0.8,0.8,0.8,1))
    @test promote(ARGB(0.2,0.3,0.4), GrayA(0.8)) === (ARGB{Float64}(0.2,0.3,0.4,1), ARGB{Float64}(0.8,0.8,0.8,1))
    @test promote(ARGB(0.2,0.3,0.4), AGray(0.8)) === (ARGB{Float64}(0.2,0.3,0.4,1), ARGB{Float64}(0.8,0.8,0.8,1))

    @test promote( RGB{N0f8}(0.2,0.3,0.4),  Gray(0.8)) === (RGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), RGB{Float64}(0.8,0.8,0.8))
    @test promote( RGB{N0f8}(0.2,0.3,0.4), GrayA(0.8)) === (RGBA{Float64}(0.2N0f8,0.3N0f8,0.4N0f8,1), RGBA{Float64}(0.8,0.8,0.8,1))
    @test promote( RGB{N0f8}(0.2,0.3,0.4), AGray(0.8)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8,1), ARGB{Float64}(0.8,0.8,0.8,1))
    @test promote(RGBA{N0f8}(0.2,0.3,0.4),  Gray(0.8)) === (RGBA{Float64}(0.2N0f8,0.3N0f8,0.4N0f8,1), RGBA{Float64}(0.8,0.8,0.8,1))
    @test promote(RGBA{N0f8}(0.2,0.3,0.4), GrayA(0.8)) === (RGBA{Float64}(0.2N0f8,0.3N0f8,0.4N0f8,1), RGBA{Float64}(0.8,0.8,0.8,1))
    @test promote(RGBA{N0f8}(0.2,0.3,0.4), AGray(0.8)) === (RGBA{Float64}(0.2N0f8,0.3N0f8,0.4N0f8,1), RGBA{Float64}(0.8,0.8,0.8,1))
    @test promote(ARGB{N0f8}(0.2,0.3,0.4),  Gray(0.8)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8,1), ARGB{Float64}(0.8,0.8,0.8,1))
    @test promote(ARGB{N0f8}(0.2,0.3,0.4), GrayA(0.8)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8,1), ARGB{Float64}(0.8,0.8,0.8,1))
    @test promote(ARGB{N0f8}(0.2,0.3,0.4), AGray(0.8)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8,1), ARGB{Float64}(0.8,0.8,0.8,1))

    @test promote( RGB(0.2,0.3,0.4),  Gray{N0f8}(0.8)) === (RGB{Float64}(0.2,0.3,0.4), RGB{Float64}(0.8N0f8,0.8N0f8,0.8N0f8))
    @test promote( RGB(0.2,0.3,0.4), GrayA{N0f8}(0.8)) === (RGBA{Float64}(0.2,0.3,0.4,1), RGBA{Float64}(0.8N0f8,0.8N0f8,0.8N0f8,1))
    @test promote( RGB(0.2,0.3,0.4), AGray{N0f8}(0.8)) === (ARGB{Float64}(0.2,0.3,0.4,1), ARGB{Float64}(0.8N0f8,0.8N0f8,0.8N0f8,1))
    @test promote(RGBA(0.2,0.3,0.4),  Gray{N0f8}(0.8)) === (RGBA{Float64}(0.2,0.3,0.4,1), RGBA{Float64}(0.8N0f8,0.8N0f8,0.8N0f8,1))
    @test promote(RGBA(0.2,0.3,0.4), GrayA{N0f8}(0.8)) === (RGBA{Float64}(0.2,0.3,0.4,1), RGBA{Float64}(0.8N0f8,0.8N0f8,0.8N0f8,1))
    @test promote(RGBA(0.2,0.3,0.4), AGray{N0f8}(0.8)) === (RGBA{Float64}(0.2,0.3,0.4,1), RGBA{Float64}(0.8N0f8,0.8N0f8,0.8N0f8,1))
    @test promote(ARGB(0.2,0.3,0.4),  Gray{N0f8}(0.8)) === (ARGB{Float64}(0.2,0.3,0.4,1), ARGB{Float64}(0.8N0f8,0.8N0f8,0.8N0f8,1))
    @test promote(ARGB(0.2,0.3,0.4), GrayA{N0f8}(0.8)) === (ARGB{Float64}(0.2,0.3,0.4,1), ARGB{Float64}(0.8N0f8,0.8N0f8,0.8N0f8,1))
    @test promote(ARGB(0.2,0.3,0.4), AGray{N0f8}(0.8)) === (ARGB{Float64}(0.2,0.3,0.4,1), ARGB{Float64}(0.8N0f8,0.8N0f8,0.8N0f8,1))

    @test promote( RGB24(0.2,0.3,0.4),  Gray(0.8)) === (RGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8), RGB{Float64}(0.8,0.8,0.8))
    @test promote( RGB24(0.2,0.3,0.4), GrayA(0.8)) === (RGBA{Float64}(0.2N0f8,0.3N0f8,0.4N0f8,1), RGBA{Float64}(0.8,0.8,0.8,1))
    @test promote( RGB24(0.2,0.3,0.4), AGray(0.8)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8,1), ARGB{Float64}(0.8,0.8,0.8,1))
    @test promote(ARGB32(0.2,0.3,0.4),  Gray(0.8)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8,1), ARGB{Float64}(0.8,0.8,0.8,1))
    @test promote(ARGB32(0.2,0.3,0.4), GrayA(0.8)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8,1), ARGB{Float64}(0.8,0.8,0.8,1))
    @test promote(ARGB32(0.2,0.3,0.4), AGray(0.8)) === (ARGB{Float64}(0.2N0f8,0.3N0f8,0.4N0f8,1), ARGB{Float64}(0.8,0.8,0.8,1))

    @test promote( RGB(0.2,0.3,0.4),  Gray24(0.8)) === (RGB{Float64}(0.2,0.3,0.4), RGB{Float64}(0.8N0f8,0.8N0f8,0.8N0f8))
    @test promote( RGB(0.2,0.3,0.4), AGray32(0.8)) === (ARGB{Float64}(0.2,0.3,0.4,1), ARGB{Float64}(0.8N0f8,0.8N0f8,0.8N0f8,1))
    @test promote(RGBA(0.2,0.3,0.4),  Gray24(0.8)) === (RGBA{Float64}(0.2,0.3,0.4,1), RGBA{Float64}(0.8N0f8,0.8N0f8,0.8N0f8,1))
    @test promote(RGBA(0.2,0.3,0.4), AGray32(0.8)) === (RGBA{Float64}(0.2,0.3,0.4,1), RGBA{Float64}(0.8N0f8,0.8N0f8,0.8N0f8,1))
    @test promote(ARGB(0.2,0.3,0.4),  Gray24(0.8)) === (ARGB{Float64}(0.2,0.3,0.4,1), ARGB{Float64}(0.8N0f8,0.8N0f8,0.8N0f8,1))
    @test promote(ARGB(0.2,0.3,0.4), AGray32(0.8)) === (ARGB{Float64}(0.2,0.3,0.4,1), ARGB{Float64}(0.8N0f8,0.8N0f8,0.8N0f8,1))

    @test promote( RGB24(0.2,0.3,0.4),  Gray24(0.8)) === (RGB24(0.2,0.3,0.4), RGB24(0.8,0.8,0.8))
    @test promote( RGB24(0.2,0.3,0.4), AGray32(0.8)) === (ARGB32(0.2,0.3,0.4,1), ARGB32(0.8,0.8,0.8,1))
    @test promote(ARGB32(0.2,0.3,0.4),  Gray24(0.8)) === (ARGB32(0.2,0.3,0.4,1), ARGB32(0.8,0.8,0.8,1))
    @test promote(ARGB32(0.2,0.3,0.4), AGray32(0.8)) === (ARGB32(0.2,0.3,0.4,1), ARGB32(0.8,0.8,0.8,1))

    @test promote(RGB(0.2,0.3,0.4), Gray{Bool}(1)) === (RGB{Float64}(0.2,0.3,0.4), RGB{Float64}(1,1,1))
    @test promote(RGB{N0f8}(0.2,0.3,0.4), Gray{Bool}(1)) === (RGB{N0f8}(0.2,0.3,0.4), RGB{N0f8}(1,1,1))

    @test promote_type(RGB, Gray) === RGB
    @test promote_type(RGB, Gray{Float16}) === RGB
    @test promote_type(Gray, RGB{Float16}) === RGB

    @test promote_type(RGB, GrayA) === RGBA
    @test promote_type(AGray, RGB) === ARGB
    @test promote_type(RGB, GrayA{Float16}) === RGBA
    @test promote_type(AGray, RGB{Float16}) === ARGB

    @test promote_type(Gray, RGBA) === RGBA
    @test promote_type(ARGB, Gray) === ARGB
    @test promote_type(Gray, RGBA{Float16}) === RGBA
    @test promote_type(ARGB, Gray{Float16}) === ARGB

    @test promote_type(RGB, Gray{Bool}) === RGB
    @test promote_type(ARGB, Gray{Bool}) === ARGB
    @test promote_type(RGBA, Gray{Bool}) === RGBA

    @test promote_type(RGB, Gray24) === RGB
    @test promote_type(RGBA, Gray24) === RGBA
    @test promote_type(ARGB, AGray32) === ARGB
    @test promote_type(RGB24, Gray) === RGB
    @test promote_type(RGB24, GrayA) === RGBA
    @test promote_type(ARGB32, Gray) === ARGB
    @test promote_type(ARGB32, GrayA) === ARGB

    @test promote_type(AbstractRGB, Gray{Float16}) === RGB
    @test promote_type(RGB, AbstractGray{Float16}) === RGB
    @test promote_type(AbstractRGB{Float16}, Gray{Float16}) === RGB{Float16}
    @test promote_type(AbstractRGB{Float16}, Gray24) === RGB{Float32}

    @test promote_type(AbstractGray, RGB{Float16}) === RGB
    @test promote_type(Gray, AbstractRGB{Float16}) === RGB
    @test promote_type(AbstractGray{Float16}, RGB{Float16}) === RGB{Float16}
    @test promote_type(AbstractGray{Float16}, RGB24) === RGB{Float32}

    @test promote_type(AbstractRGB, AGray{Float16}) === ARGB
    @test promote_type(RGB, AbstractAGray{Gray{Float16},Float16}) === ARGB
    @test promote_type(AbstractRGB{Float16}, AGray{Float16}) === ARGB{Float16}
    @test promote_type(AbstractRGB{Float16}, AGray32) === ARGB{Float32}

    @test promote_type(AbstractGray, ARGB{Float16}) === ARGB
    @test promote_type(Gray, AbstractARGB{RGB{Float16},Float16}) === ARGB
    @test promote_type(AbstractGray{Float16}, ARGB{Float16}) === ARGB{Float16}
    @test promote_type(AbstractGray{Float16}, ARGB32) === ARGB{Float32}

    @test promote_type(AbstractARGB, AGray{Float16}) === ARGB
    @test promote_type(ARGB, AbstractAGray{Gray{Float16},Float16}) === ARGB
    @test promote_type(AbstractARGB{RGB{Float16},Float16}, AGray{Float16}) === ARGB{Float16}
    @test promote_type(AbstractARGB{RGB{Float16},Float16}, AGray32) === ARGB{Float32}
end

@testset "hsv and gray promotions" begin
    @test promote_type( HSV{Float16},  Gray{N0f8}) === HSV{Float32}
    @test promote_type( HSV{Float16}, GrayA{N0f8}) === HSVA{Float32}
    @test promote_type( HSV{Float16}, AGray{N0f8}) === AHSV{Float32}
    @test promote_type(HSVA{Float16},  Gray{N0f8}) === HSVA{Float32}
    @test promote_type(HSVA{Float16}, GrayA{N0f8}) === HSVA{Float32}
    @test promote_type(HSVA{Float16}, AGray{N0f8}) === HSVA{Float32}
    @test promote_type(AHSV{Float16},  Gray{N0f8}) === AHSV{Float32}
    @test promote_type(AHSV{Float16}, GrayA{N0f8}) === AHSV{Float32}
    @test promote_type(AHSV{Float16}, AGray{N0f8}) === AHSV{Float32}

    @test promote_type( HSV{Float16},  Gray24) === HSV{Float32}
    @test promote_type( HSV{Float16}, AGray32) === AHSV{Float32}
    @test promote_type(HSVA{Float16},  Gray24) === HSVA{Float32}
    @test promote_type(HSVA{Float16}, AGray32) === HSVA{Float32}
    @test promote_type(AHSV{Float16},  Gray24) === AHSV{Float32}
    @test promote_type(AHSV{Float16}, AGray32) === AHSV{Float32}

    @test promote_type(HSV{Float64}, Gray{Bool}) === HSV{Float64}
    @test promote_type(HSV{Float32}, Gray{Bool}) === HSV{Float32}

    @test promote_type(HSV, Gray) === HSV
    @test promote_type(HSV, Gray{Float16}) === HSV
    @test promote_type(Gray, HSV{Float16}) === HSV

    @test promote_type(HSV, GrayA) === HSVA
    @test promote_type(AGray, HSV) === AHSV
    @test promote_type(HSV, GrayA{Float16}) === HSVA
    @test promote_type(AGray, HSV{Float16}) === AHSV

    @test promote_type(Gray, HSVA) === HSVA
    @test promote_type(AHSV, Gray) === AHSV
    @test promote_type(Gray, HSVA{Float16}) === HSVA
    @test promote_type(AHSV, Gray{Float16}) === AHSV

    @test promote_type(GrayA, HSVA) === HSVA
    @test promote_type(AHSV, AGray) === AHSV
    @test promote_type(AGray, HSVA) === HSVA
    @test promote_type(AHSV, GrayA) === AHSV

    @test promote_type( HSV,  Gray24) === HSV
    @test promote_type( HSV, AGray32) === AHSV
    @test promote_type(HSVA,  Gray24) === HSVA
    @test promote_type(HSVA, AGray32) === HSVA
    @test promote_type(AHSV,  Gray24) === AHSV
    @test promote_type(AHSV, AGray32) === AHSV
end

# promotions between different color spaces
@testset "rgb and hsv promotions" begin
    # the current implementation is like `typejoin`.
    @test promote_type( RGB{Float64},  HSV{Float64}) == Color3{Float64}
    @test promote_type( RGB{Float64}, HSVA{Float64}) == ColorAlpha{C,Float64,4} where {C<:Color3{Float64}}
    @test promote_type( RGB{Float64}, AHSV{Float64}) == AlphaColor{C,Float64,4} where {C<:Color3{Float64}}
    @test promote_type(RGBA{Float64},  HSV{Float64}) == ColorAlpha{C,Float64,4} where {C<:Color3{Float64}}
    @test promote_type(RGBA{Float64}, HSVA{Float64}) == ColorAlpha{C,Float64,4} where {C<:Color3{Float64}}
    @test promote_type(RGBA{Float64}, AHSV{Float64}) == AlphaColor{C,Float64,4} where {C<:Color3{Float64}}
    @test promote_type(ARGB{Float64},  HSV{Float64}) == AlphaColor{C,Float64,4} where {C<:Color3{Float64}}
    @test promote_type(ARGB{Float64}, HSVA{Float64}) == AlphaColor{C,Float64,4} where {C<:Color3{Float64}}
    @test promote_type(ARGB{Float64}, AHSV{Float64}) == AlphaColor{C,Float64,4} where {C<:Color3{Float64}}

    @test promote_type( RGB{N0f8},  HSV{Float16}) == Color3{Float32}
    @test promote_type( RGB{N0f8}, HSVA{Float16}) == ColorAlpha{C,Float32,4} where {C<:Color3{Float32}}
    @test promote_type( RGB{N0f8}, AHSV{Float16}) == AlphaColor{C,Float32,4} where {C<:Color3{Float32}}
    @test promote_type(RGBA{N0f8},  HSV{Float16}) == ColorAlpha{C,Float32,4} where {C<:Color3{Float32}}
    @test promote_type(RGBA{N0f8}, HSVA{Float16}) == ColorAlpha{C,Float32,4} where {C<:Color3{Float32}}
    @test promote_type(RGBA{N0f8}, AHSV{Float16}) == AlphaColor{C,Float32,4} where {C<:Color3{Float32}}
    @test promote_type(ARGB{N0f8},  HSV{Float16}) == AlphaColor{C,Float32,4} where {C<:Color3{Float32}}
    @test promote_type(ARGB{N0f8}, HSVA{Float16}) == AlphaColor{C,Float32,4} where {C<:Color3{Float32}}
    @test promote_type(ARGB{N0f8}, AHSV{Float16}) == AlphaColor{C,Float32,4} where {C<:Color3{Float32}}

    @test promote_type( RGB24,  HSV{Float16}) == Color3{Float32}
    @test promote_type( RGB24, HSVA{Float16}) == ColorAlpha{C,Float32,4} where {C<:Color3{Float32}}
    @test promote_type( RGB24, AHSV{Float16}) == AlphaColor{C,Float32,4} where {C<:Color3{Float32}}
    @test promote_type(ARGB32,  HSV{Float16}) == AlphaColor{C,Float32,4} where {C<:Color3{Float32}}
    @test promote_type(ARGB32, HSVA{Float16}) == AlphaColor{C,Float32,4} where {C<:Color3{Float32}}
    @test promote_type(ARGB32, AHSV{Float16}) == AlphaColor{C,Float32,4} where {C<:Color3{Float32}}

    @test promote_type(RGB, HSV) == Color3
    @test promote_type(RGB, HSV{Float16}) == Color3
    @test promote_type(HSV, RGB{Float16}) == Color3

    @test promote_type(RGB, HSVA) == ColorAlpha{C,T,4} where {T, C<:Color3{T}}
    @test promote_type(AHSV, RGB) == AlphaColor{C,T,4} where {T, C<:Color3{T}}
    @test promote_type(RGB, HSVA{Float16}) == ColorAlpha{C,T,4} where {T, C<:Color3{T}}
    @test promote_type(AHSV, RGB{Float16}) == AlphaColor{C,T,4} where {T, C<:Color3{T}}

    @test promote_type(HSV, RGBA) == ColorAlpha{C,T,4} where {T, C<:Color3{T}}
    @test promote_type(ARGB, HSV) == AlphaColor{C,T,4} where {T, C<:Color3{T}}
    @test promote_type(HSV, RGBA{Float16}) == ColorAlpha{C,T,4} where {T, C<:Color3{T}}
    @test promote_type(ARGB, HSV{Float16}) == AlphaColor{C,T,4} where {T, C<:Color3{T}}

    @test promote_type(HSVA, RGBA) == ColorAlpha{C,T,4} where {T, C<:Color3{T}}
    @test promote_type(ARGB, AHSV) == AlphaColor{C,T,4} where {T, C<:Color3{T}}
    @test promote_type(HSVA, RGBA{Float16}) == ColorAlpha{C,T,4} where {T, C<:Color3{T}}
    @test promote_type(ARGB, AHSV{Float16}) == AlphaColor{C,T,4} where {T, C<:Color3{T}}

    @test promote_type(AHSV, RGBA) == AlphaColor{C,T,4} where {T, C<:Color3{T}} # != Transparent3
    @test promote_type(ARGB, HSVA) == AlphaColor{C,T,4} where {T, C<:Color3{T}} # != Transparent3
    @test promote_type(AHSV, RGBA{Float16}) == AlphaColor{C,T,4} where {T, C<:Color3{T}} # != Transparent3
    @test promote_type(ARGB, HSVA{Float16}) == AlphaColor{C,T,4} where {T, C<:Color3{T}} # != Transparent3

    @test promote_type(RGB24, HSV) == Color3
    @test promote_type(RGB24, HSVA{Float16}) == ColorAlpha{C,Float32,4} where C<:Color3{Float32}
    @test promote_type(ARGB32, HSV) == AlphaColor{C,T,4} where {T, C<:Color3{T}}
    @test promote_type(ARGB32, HSVA{Float16}) == AlphaColor{C,Float32,4} where C<:Color3{Float32}
end

@testset "promotions with abstract types" begin
    @test promote_type(Colorant, RGB{Float64}) == AlphaColor{C,T} where {T, C<:Color{T}}
    @test promote_type(Colorant{N0f8}, RGB{Float64}) == AlphaColor{C,Float64} where {C<:Color{Float64}}
    @test promote_type(Colorant{Float32,3}, RGB{Float64}) == AlphaColor{C,Float64,4} where C<:Color{Float64,3}
    @test promote_type(Color, RGB{Float64}) === Color
    @test promote_type(Color{N0f8}, RGB{Float64}) === Color{Float64}
    @test promote_type(Color{Float32,3}, RGB{Float64}) === Color{Float64,3}
    @test promote_type(AbstractRGB, RGB{Float64}) === RGB
    @test promote_type(AbstractRGB{N0f8}, RGB{Float64}) === RGB{Float64}
    @test promote_type(TransparentColor, RGB{Float64}) == AlphaColor{C,T} where {T, C<:Color{T}}
    @test promote_type(TransparentColor{RGB{N0f8}}, RGB{Float64}) === ARGB
    @test promote_type(TransparentColor{RGB{N0f8},N0f8}, RGB{Float64}) === ARGB{Float64}
    @test promote_type(TransparentColor{RGB{N0f8},N0f8,4}, RGB{Float64}) === ARGB{Float64}
    @test promote_type(AlphaColor, RGB{Float64}) == AlphaColor{C,T} where {T, C<:Color{T}}
    @test promote_type(AlphaColor{RGB{N0f8}}, RGB{Float64}) === ARGB
    @test promote_type(AlphaColor{RGB{N0f8},N0f8}, RGB{Float64}) === ARGB{Float64}
    @test promote_type(AlphaColor{RGB{N0f8},N0f8,4}, RGB{Float64}) === ARGB{Float64}
    @test promote_type(ColorAlpha, RGB{Float64}) === ColorAlpha{C,T} where {T, C<:Color{T}}
    @test promote_type(ColorAlpha{RGB{N0f8}}, RGB{Float64}) === RGBA
    @test promote_type(ColorAlpha{RGB{N0f8},N0f8}, RGB{Float64}) === RGBA{Float64}
    @test promote_type(ColorAlpha{RGB{N0f8},N0f8,4}, RGB{Float64}) === RGBA{Float64}

    @test promote_type(Colorant, ARGB{Float64}) == AlphaColor{C,T} where {T, C<:Color{T}}
    @test promote_type(Colorant{N0f8}, ARGB{Float64}) == AlphaColor{C,Float64} where C<:Color{Float64}
    @test_broken promote_type(Colorant{Float32,4}, ARGB{Float64}) == AlphaColor{C,Float64,4} where C<:Color3{Float64}
    @test promote_type(Color, ARGB{Float64}) == AlphaColor{C,T} where {T, C<:Color{T}}
    @test promote_type(AbstractRGB, ARGB{Float64}) === ARGB
    @test promote_type(AbstractRGB{N0f8}, ARGB{Float64}) === ARGB{Float64}
    @test promote_type(TransparentColor, ARGB{Float64}) == AlphaColor{C,T} where {T, C<:Color{T}}
    @test promote_type(AlphaColor, ARGB{Float64}) == AlphaColor{C,T} where {T, C<:Color{T}}
    @test promote_type(ColorAlpha, ARGB{Float64}) == AlphaColor{C,T} where {T, C<:Color{T}}

    @test promote_type(Colorant, RGBA{Float64}) == AlphaColor{C,T} where {T, C<:Color{T}}
    @test promote_type(Colorant{N0f8}, RGBA{Float64}) == AlphaColor{C,Float64} where C<:Color{Float64}
    @test promote_type(Colorant{Float32,3}, RGBA{Float64}) == AlphaColor{C,Float64,4} where C<:Color3{Float64}
    @test promote_type(Color, RGBA{Float64}) == ColorAlpha{C,T} where {T, C<:Color{T}}
    @test promote_type(AbstractRGB, RGBA{Float64}) === RGBA
    @test promote_type(AbstractRGB{N0f8}, RGBA{Float64}) === RGBA{Float64}
    @test promote_type(TransparentColor, RGBA{Float64}) == AlphaColor{C,T} where {T, C<:Color{T}}
    @test promote_type(AlphaColor, RGBA{Float64}) == AlphaColor{C,T} where {T, C<:Color{T}}
    @test promote_type(ColorAlpha, RGBA{Float64}) == ColorAlpha{C,T} where {T, C<:Color{T}}
end

@testset "rgb conversions with abstract types" begin
    c = RGB(1, 0.6, 0)
    @test convert(Colorant, c) === RGB{Float64}(1, 0.6, 0)
    @test convert(Colorant{N0f8}, c) === RGB{N0f8}(1, 0.6, 0)
    @test convert(Colorant{Float32,3}, c) === RGB{Float32}(1, 0.6, 0)
    @test convert(Color, c) === RGB{Float64}(1, 0.6, 0)
    @test convert(Color{N0f8}, c) === RGB{N0f8}(1, 0.6, 0)
    @test convert(Color{Float32,3}, c) === RGB{Float32}(1, 0.6, 0)
    @test convert(AbstractRGB, c) === RGB{Float64}(1, 0.6, 0)
    @test convert(AbstractRGB{N0f8}, c) === RGB{N0f8}(1, 0.6, 0)
    @test_throws ColorTypeResolutionError convert(TransparentColor, c)
    @test_throws ColorTypeResolutionError convert(TransparentColor{RGB{N0f8}}, c)
    @test_throws ColorTypeResolutionError convert(TransparentColor{RGB{N0f8},N0f8}, c)
    @test_throws ColorTypeResolutionError convert(TransparentColor{RGB{N0f8},N0f8,4}, c)
    @test convert(AlphaColor, c) === ARGB{Float64}(1, 0.6, 0, 1)
    @test_broken convert(AlphaColor{RGB{N0f8}}, c) === ARGB{N0f8}(1, 0.6, 0, 1)
    @test convert(AlphaColor{RGB{N0f8},N0f8}, c) === ARGB{N0f8}(1, 0.6, 0, 1)
    @test convert(AlphaColor{RGB{N0f8},N0f8,4}, c) === ARGB{N0f8}(1, 0.6, 0, 1)
    @test convert(ColorAlpha, c) === RGBA{Float64}(1, 0.6, 0, 1)
    @test_broken convert(ColorAlpha{RGB{N0f8}}, c) === RGBA{N0f8}(1, 0.6, 0, 1)
    @test convert(ColorAlpha{RGB{N0f8},N0f8}, c) === RGBA{N0f8}(1, 0.6, 0, 1)
    @test convert(ColorAlpha{RGB{N0f8},N0f8,4}, c) === RGBA{N0f8}(1, 0.6, 0, 1)

    ac = ARGB(1, 0.6, 0, 0.8)
    @test convert(Colorant, ac) === ARGB{Float64}(1, 0.6, 0, 0.8)
    @test convert(Colorant{N0f8}, ac) === ARGB{N0f8}(1, 0.6, 0, 0.8)
    @test_broken convert(Colorant{Float32,3}, ac) === RGB{Float32}(1, 0.6, 0)
    @test convert(Color, ac) === RGB{Float64}(1, 0.6, 0)
    @test convert(AbstractRGB, ac) === RGB{Float64}(1, 0.6, 0)
    @test convert(AbstractRGB{N0f8}, ac) === RGB{N0f8}(1, 0.6, 0)
    @test convert(TransparentColor, ac) === ARGB{Float64}(1, 0.6, 0, 0.8)
    @test convert(AlphaColor, ac) === ARGB{Float64}(1, 0.6, 0, 0.8) # issue #126
    @test convert(ColorAlpha, ac) === RGBA{Float64}(1, 0.6, 0, 0.8) # issue #126

    ca = RGBA(1, 0.6, 0, 0.8)
    @test convert(Colorant, ca) === RGBA{Float64}(1, 0.6, 0, 0.8)
    @test convert(Colorant{N0f8}, ca) === RGBA{N0f8}(1, 0.6, 0, 0.8)
    @test_broken convert(Colorant{Float32,3}, ca) === RGB{Float32}(1, 0.6, 0)
    @test convert(Color, ca) === RGB{Float64}(1, 0.6, 0)
    @test convert(AbstractRGB, ca) === RGB{Float64}(1, 0.6, 0)
    @test convert(AbstractRGB{N0f8}, ca) === RGB{N0f8}(1, 0.6, 0)
    @test convert(TransparentColor, ca) === RGBA{Float64}(1, 0.6, 0, 0.8)
    @test convert(AlphaColor, ca) === ARGB{Float64}(1, 0.6, 0, 0.8) # issue #126
    @test convert(ColorAlpha, ca) === RGBA{Float64}(1, 0.6, 0, 0.8) # issue #126

    rgb24 = RGB24(1, 0.6, 0)
    @test convert(Colorant, rgb24) === RGB24(1, 0.6, 0)
    @test convert(Colorant{N0f8}, rgb24) === RGB24(1, 0.6, 0)
    @test_broken convert(Colorant{Float32,3}, rgb24) === RGB{Float32}(1, 0.6, 0)
    @test convert(Color, rgb24) === RGB24(1, 0.6, 0)
    @test convert(AbstractRGB, rgb24) === RGB24(1, 0.6, 0)
    @test convert(AbstractRGB{N0f8}, rgb24) === RGB24(1, 0.6, 0)
    @test_throws ColorTypeResolutionError convert(TransparentColor, rgb24)
    @test convert(AlphaColor, rgb24) === ARGB32(1, 0.6, 0, 1)
    @test_throws MethodError convert(ColorAlpha, rgb24)

    argb32 = ARGB32(1, 0.6, 0, 0.8)
    @test convert(Colorant, argb32) === ARGB32(1, 0.6, 0, 0.8)
    @test convert(Colorant{N0f8}, argb32) === ARGB32(1, 0.6, 0, 0.8)
    @test_broken convert(Colorant{Float32,3}, argb32) === RGB{Float32}(1, 0.6, 0)
    @test convert(Color, argb32) === RGB24(1, 0.6, 0)
    @test convert(AbstractRGB, argb32) === RGB24(1, 0.6, 0)
    @test convert(AbstractRGB{N0f8}, argb32) === RGB24(1, 0.6, 0)
    @test convert(TransparentColor, argb32) === ARGB32(1, 0.6, 0, 0.8)
    @test convert(AlphaColor, argb32) === ARGB32(1, 0.6, 0, 0.8)
    @test_throws MethodError convert(ColorAlpha, argb32)

    @test convert(AbstractARGB{RGB,N0f8}, c, 0.2) === ARGB{N0f8}(1, 0.6, 0, 0.2)
    @test convert(AbstractRGBA{RGB,N0f8}, c, 0.2) === RGBA{N0f8}(1, 0.6, 0, 0.2)
    @test convert(AbstractARGB{RGB,N0f8}, rgb24, 0.2) === ARGB{N0f8}(1, 0.6, 0, 0.2)
    @test_throws MethodError convert(AbstractARGB{RGB,N0f8}, ac, 0.2)
    @test_throws MethodError convert(AbstractARGB{RGB,N0f8}, ca, 0.2)
    @test_throws MethodError convert(AbstractARGB{RGB,N0f8}, argb32, 0.2)
    @test convert(AbstractARGB{RGB24,N0f8}, rgb24, 0.2) === ARGB32(1, 0.6, 0, 0.2)
end

@testset "gray conversions with abstract types" begin
    c = Gray(0.4)
    @test convert(Colorant, c) === Gray{Float64}(0.4)
    @test convert(Colorant{N0f8}, c) === Gray{N0f8}(0.4)
    @test convert(Colorant{Float32,1}, c) === Gray{Float32}(0.4)
    @test convert(Color, c) === Gray{Float64}(0.4)
    @test_throws ColorTypeResolutionError convert(TransparentColor, c)
    @test convert(AlphaColor, c) === AGray{Float64}(0.4, 1)
    @test convert(ColorAlpha, c) === GrayA{Float64}(0.4, 1)

    ac = AGray(0.4, 0.8)
    @test convert(Colorant, ac) === AGray{Float64}(0.4, 0.8)
    @test convert(Colorant{N0f8}, ac) === AGray{N0f8}(0.4, 0.8)
    @test_broken convert(Colorant{Float32,1}, ac) === Gray{Float32}(0.4, 0.8)
    @test convert(Color, ac) === Gray{Float64}(0.4)
    @test convert(TransparentColor, ac) == AGray{Float64}(0.4, 0.8)
    @test convert(AlphaColor, ac) === AGray{Float64}(0.4, 0.8)
    @test convert(ColorAlpha, ac) === GrayA{Float64}(0.4, 0.8)

    ca = GrayA(0.4, 0.8)
    @test convert(Colorant, ca) === GrayA{Float64}(0.4, 0.8)
    @test convert(Colorant{N0f8}, ca) === GrayA{N0f8}(0.4, 0.8)
    @test_broken convert(Colorant{Float32,1}, ca) === Gray{Float32}(0.4)
    @test convert(Color, ca) === Gray{Float64}(0.4)
    @test convert(TransparentColor, ca) == GrayA{Float64}(0.4, 0.8)
    @test convert(AlphaColor, ca) === AGray{Float64}(0.4, 0.8)
    @test convert(ColorAlpha, ca) === GrayA{Float64}(0.4, 0.8)

    gray24 = Gray24(0.4)
    @test convert(Colorant, gray24) === Gray24(0.4)
    @test convert(Colorant{N0f8}, gray24) === Gray24(0.4)
    @test_broken convert(Colorant{Float32,1}, gray24) === Gray{Float32}(0.4)
    @test convert(Color, gray24) === Gray24(0.4)
    @test_throws ColorTypeResolutionError convert(TransparentColor, gray24)
    @test convert(AlphaColor, gray24) === AGray32(0.4, 1)
    @test_throws MethodError convert(ColorAlpha, gray24) # TODO: need docs

    agray32 = AGray32(0.4, 0.8)
    @test convert(Colorant, agray32) === AGray32(0.4, 0.8)
    @test convert(Colorant{N0f8}, agray32) === AGray32(0.4, 0.8)
    @test_broken convert(Colorant{Float32,1}, agray32) === Gray{Float32}(0.4)
    @test convert(Color, agray32) === Gray24(0.4)
    @test convert(TransparentColor, agray32) === AGray32(0.4, 0.8)
    @test convert(AlphaColor, agray32) === AGray32(0.4, 0.8)
    @test_throws MethodError convert(ColorAlpha, agray32) # TODO: need docs

    @test convert(AbstractAGray{Gray,N0f8}, c, 0.2) === AGray{N0f8}(0.4, 0.2)
    @test convert(AbstractGrayA{Gray,N0f8}, c, 0.2) === GrayA{N0f8}(0.4, 0.2)
    # the following is ok, but not consistent with the case of RGB24
    @test convert(AbstractAGray{Gray,N0f8}, gray24, 0.2) === AGray{N0f8}(0.4, 0.2)
    @test convert(AbstractAGray{Gray24,N0f8}, gray24, 0.2) === AGray32(0.4, 0.2)
    @test_throws MethodError convert(AbstractAGray{Gray,N0f8}, ac, 0.2)
    @test_throws MethodError convert(AbstractAGray{Gray,N0f8}, ca, 0.2)
    @test_throws MethodError convert(AbstractAGray{Gray,N0f8}, agray32, 0.2)
end

@testset "conversions from/to real numbers" begin
    @test convert(Float64, Gray(0.6)) === 0.6
    @test Float32(Gray(0.6)) === 0.6f0
    @test float(Gray(0.6)) === 0.6
    @test real(Gray(0.6)) === 0.6
    @test real(Gray{Float16}) === Float16

    @test convert(N0f8, Gray24(0.6)) === N0f8(0.6)
    @test convert(Float64, Gray24(0.6)) === 0.6
    @test Float32(Gray24(0.6)) === 0.6f0
    @test float(Gray24(0.6)) === 0.6
    @test real(Gray24(0.6)) === N0f8(0.6)
    @test real(Gray24) === N0f8

    @test convert(Gray, 0.6) === Gray{Float64}(0.6)
    @test convert(Gray, 0.6f0) === Gray{Float32}(0.6)
    @test convert(Gray, 0.6N0f8) === Gray{N0f8}(0.6)
    @test convert(Gray, true) === Gray{Bool}(1)
    @test convert(Gray, false) === Gray{Bool}(0)
    @test convert(Gray, 1) === Gray{N0f8}(1)

    @test convert(Gray{N0f16}, 0.6) === Gray{N0f16}(0.6)
    @test convert(Gray{N0f16}, 0.6f0) === Gray{N0f16}(0.6)
    @test convert(Gray{N0f16}, 0.6N0f8) === Gray{N0f16}(0.6)

    @test convert(GrayA{N0f8}, 0.6) === GrayA{N0f8}(0.6, 1)
    @test convert(AGray{N0f8}, 0.6) === AGray{N0f8}(0.6, 1)

    @test convert(Gray24, 0.6) === Gray24(0.6)
    @test convert(Gray24, 0.6f0) === Gray24(0.6)
    @test convert(Gray24, 0.6N0f8) === Gray24(0.6)

    @test convert(AGray32, 0.6) === AGray32(0.6, 1)
    @test convert(AGray32, 0.6f0) === AGray32(0.6, 1)
    @test convert(AGray32, 0.6N0f8) === AGray32(0.6, 1)
    @test convert(AGray32, 0.6, 0.8) === AGray32(0.6, 0.8)
    @test convert(AGray32, 0, 1) === AGray32(0, 1)

    @test_throws MethodError real(Gray) # should be a concrete type
    @test_throws MethodError real(AGray(1.0))
    @test_throws MethodError real(GrayA{Float32})

    @test_throws ColorTypeResolutionError convert(Colorant, 0.6)
    @test_throws ColorTypeResolutionError convert(Color, 0.6)
    @test_throws ColorTypeResolutionError convert(Color{N0f8,1}, 0.6)

    @test_throws MethodError convert(GrayA, 0.6, 0.8)
    @test_throws MethodError convert(AGray, 0, 1)

    @test convert(Gray, 2.0) === Gray{Float64}(2.0)
    @test_throws ArgumentError convert(Gray, 2)

    @test convert(RGB24, 0.6) === RGB24(0.6, 0.6, 0.6)
    @test convert(ARGB32, 0.6) === ARGB32(0.6, 0.6, 0.6, 1)
    @test convert(ARGB32, 0.6, 0.8) === ARGB32(0.6, 0.6, 0.6, 0.8)

    @test convert(RGB, 0.6) === RGB(0.6, 0.6, 0.6)
    @test convert(BGR, 0.6N0f8) === BGR{N0f8}(0.6, 0.6, 0.6)
    @test_broken convert(ARGB, 0.6) === ARGB(0.6, 0.6, 0.6, 1)
    @test_broken convert(RGBA, 0.6N0f8) === RGBA{N0f8}(0.6, 0.6, 0.6, 1)

    @test_broken convert(ARGB, 0.6f0, 0.8f0) === ARGB{Float32}(0.6, 0.6, 0.6, 0.8)
    @test_broken convert(RGBA{Float32}, 0.6, 0.8) === RGBA{Float32}(0.6, 0.6, 0.6, 0.8)

end

@testset "conversions from rgb to rgb" begin
    Crgb = filter(T -> T <: AbstractRGB, ColorTypes.parametric3)
    Ctransparent = unique(vcat(coloralpha.(Crgb), alphacolor.(Crgb)))
    @testset "$C conversions" for C in Crgb
        @test convert(C, C{Float64}(1,0.6,0)) === C{Float64}(1,0.6,0)
        @test convert(C{N0f8}, C{Float64}(1,0.6,0)) === C{N0f8}(1,0.6,0)
        @test convert(C, RGB24(1,0.6,0)) === C{N0f8}(1,0.6,0)
        @test convert(RGB24, C(1,0.6,0)) === RGB24(1,0.6,0)
        @test convert(C, ARGB32(1,0.6,0,0.8)) === C{N0f8}(1,0.6,0)
        @test convert(ARGB32, C(1,0.6,0)) === ARGB32(1,0.6,0)
        @test convert(ARGB32, C(1,0.6,0), 0.2) === ARGB32(1,0.6,0,0.2)
    end
    @testset "$C conversions" for C in Ctransparent
        @test convert(C, C{Float64}(1,0.6,0,0.8)) === C{Float64}(1,0.6,0,0.8)
        @test convert(C{N0f8}, C{Float64}(1,0.6,0,0.8)) === C{N0f8}(1,0.6,0,0.8)
        @test convert(C, RGB24(1,0.6,0)) === C{N0f8}(1,0.6,0,1)
        @test convert(C, RGB24(1,0.6,0), 0.2) === C{N0f8}(1,0.6,0,0.2)
        @test convert(RGB24, C(1,0.6,0,0.8)) === RGB24(1,0.6,0)
        @test convert(C, ARGB32(1,0.6,0,0.8)) === C{N0f8}(1,0.6,0,0.8)
        @test convert(ARGB32, C(1,0.6,0,0.8)) === ARGB32(1,0.6,0,0.8)
    end
    @test convert(RGB24, RGB24(1,0.6,0)) === RGB24(1,0.6,0)
    @test convert(ARGB32, RGB24(1,0.6,0)) === ARGB32(1,0.6,0,1)
    @test convert(ARGB32, RGB24(1,0.6,0), 0.2) === ARGB32(1,0.6,0,0.2)
    @test convert(RGB24, ARGB32(1,0.6,0,0.8)) === RGB24(1,0.6,0)
    @test convert(ARGB32, ARGB32(1,0.6,0,0.8)) === ARGB32(1,0.6,0,0.8)
end

@testset "conversions from gray to gray" begin
    @testset "Gray conversions" begin
        @test convert(Gray, Gray{Float64}(0.4)) === Gray{Float64}(0.4)
        @test convert(Gray{N0f8}, Gray{Float64}(0.4)) === Gray{N0f8}(0.4)
        @test convert(Gray, Gray24(0.4)) === Gray{N0f8}(0.4)
        @test convert(Gray24, Gray(0.4)) === Gray24(0.4)
        @test convert(Gray, AGray32(0.4,0.8)) === Gray{N0f8}(0.4)
        @test convert(AGray32, Gray(0.4)) === AGray32(0.4)
        @test convert(AGray32, Gray(0.4), 0.2) === AGray32(0.4, 0.2)
    end
    @testset "$C conversions" for C in (AGray, GrayA)
        @test convert(C, C{Float64}(0.4,0.8)) === C{Float64}(0.4,0.8)
        @test convert(C{N0f8}, C{Float64}(0.4,0.8)) === C{N0f8}(0.4,0.8)
        @test convert(C, Gray24(0.4)) === C{N0f8}(0.4,1)
        @test convert(C, Gray24(0.4), 0.2) === C{N0f8}(0.4,0.2)
        @test convert(Gray24, C(0.4,0.8)) === Gray24(0.4)
        @test convert(C, AGray32(0.4,0.8)) === C{N0f8}(0.4,0.8)
        @test convert(AGray32, C(0.4,0.8)) === AGray32(0.4,0.8)
    end
    @test convert(Gray24, Gray24(0.4)) === Gray24(0.4)
    @test convert(AGray32, Gray24(0.4)) === AGray32(0.4,1)
    @test convert(AGray32, Gray24(0.4), 0.2) === AGray32(0.4,0.2)
    @test convert(Gray24, AGray32(0.4,0.8)) === Gray24(0.4)
    @test convert(AGray32, AGray32(0.4,0.8)) === AGray32(0.4,0.8)
end

@testset "conversions from hsv to hsv" begin
    @testset "HSV conversions" begin
        @test convert(HSV, HSV{Float64}(100,0.4,0.6)) === HSV{Float64}(100,0.4,0.6)
        @test convert(HSV{Float32}, HSV{Float64}(100,0.4,0.6)) === HSV{Float32}(100,0.4,0.6)
        @test convert(HSV, HSVA{Float64}(100,0.4,0.6,0.8)) === HSV{Float64}(100,0.4,0.6)
        @test convert(HSV, AHSV{Float32}(100,0.4,0.6,0.8)) === HSV{Float32}(100,0.4,0.6)
    end
    @testset "$C conversions" for C in (AHSV, HSVA)
        @test convert(C, HSVA{Float64}(100,0.4,0.6,0.8)) === C{Float64}(100,0.4,0.6,0.8)
        @test convert(C, AHSV{Float32}(100,0.4,0.6,0.8)) === C{Float32}(100,0.4,0.6,0.8)
        @test convert(C{Float32}, HSVA{Float64}(100,0.4,0.6,0.8)) === C{Float32}(100,0.4,0.6,0.8)
        @test convert(C{Float32}, AHSV{Float32}(100,0.4,0.6,0.8)) === C{Float32}(100,0.4,0.6,0.8)
        @test convert(C, HSV{Float64}(100,0.4,0.6)) === C{Float64}(100,0.4,0.6,1)
        @test convert(C, HSV{Float32}(100,0.4,0.6), 0.2) === C{Float32}(100,0.4,0.6,0.2)
    end
end

@testset "conversions from gray to rgb" begin
    Crgb = filter(T -> T <: AbstractRGB, ColorTypes.parametric3)
    Ctransparent = unique(vcat(coloralpha.(Crgb), alphacolor.(Crgb)))
    @testset "$C conversions" for C in Crgb
        @test convert(C,  Gray{N0f8}(0.4)    ) === C{N0f8}(0.4,0.4,0.4)
        @test convert(C, GrayA{N0f8}(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4)
        @test convert(C, AGray{N0f8}(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4)
        @test convert(C{N0f8},  Gray{N0f8}(0.4)    ) === C{N0f8}(0.4,0.4,0.4)
        @test convert(C{N0f8}, GrayA{N0f8}(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4)
        @test convert(C{N0f8}, AGray{N0f8}(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4)
        @test convert(C{Float32},  Gray{N0f8}(0.4)    ) === C{Float32}(0.4N0f8,0.4N0f8,0.4N0f8)
        @test convert(C{Float32}, GrayA{N0f8}(0.4,0.8)) === C{Float32}(0.4N0f8,0.4N0f8,0.4N0f8)
        @test convert(C{Float32}, AGray{N0f8}(0.4,0.8)) === C{Float32}(0.4N0f8,0.4N0f8,0.4N0f8)
        @test convert(C{N0f8},  Gray{Float32}(0.4)    ) === C{N0f8}(0.4,0.4,0.4)
        @test convert(C{N0f8}, GrayA{Float32}(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4)
        @test convert(C{N0f8}, AGray{Float32}(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4)
        @test convert(C,  Gray24(0.4)    ) === C{N0f8}(0.4,0.4,0.4)
        @test convert(C, AGray32(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4)
        @test convert(C{N0f8},  Gray24(0.4)    ) === C{N0f8}(0.4,0.4,0.4)
        @test convert(C{N0f8}, AGray32(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4)
        @test convert(C{Float32},  Gray24(0.4)    ) === C{Float32}(0.4N0f8,0.4N0f8,0.4N0f8)
        @test convert(C{Float32}, AGray32(0.4,0.8)) === C{Float32}(0.4N0f8,0.4N0f8,0.4N0f8)
    end
    @testset "$C conversions" for C in Ctransparent
        @test convert(C,  Gray{N0f8}(0.4)    ) === C{N0f8}(0.4,0.4,0.4,1)
        @test convert(C,  Gray{N0f8}(0.4),0.2) === C{N0f8}(0.4,0.4,0.4,0.2)
        @test convert(C, GrayA{N0f8}(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4,0.8)
        @test convert(C, AGray{N0f8}(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4,0.8)
        @test convert(C{N0f8},  Gray{N0f8}(0.4)    ) === C{N0f8}(0.4,0.4,0.4,1)
        @test convert(C{N0f8},  Gray{N0f8}(0.4),0.2) === C{N0f8}(0.4,0.4,0.4,0.2)
        @test convert(C{N0f8}, GrayA{N0f8}(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4,0.8)
        @test convert(C{N0f8}, AGray{N0f8}(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4,0.8)
        @test convert(C{Float32},  Gray{N0f8}(0.4)    ) === C{Float32}(0.4N0f8,0.4N0f8,0.4N0f8,1)
        @test convert(C{Float32},  Gray{N0f8}(0.4),0.2) === C{Float32}(0.4N0f8,0.4N0f8,0.4N0f8,0.2)
        @test convert(C{Float32}, GrayA{N0f8}(0.4,0.8)) === C{Float32}(0.4N0f8,0.4N0f8,0.4N0f8,0.8N0f8)
        @test convert(C{Float32}, AGray{N0f8}(0.4,0.8)) === C{Float32}(0.4N0f8,0.4N0f8,0.4N0f8,0.8N0f8)
        @test convert(C{N0f8},  Gray{Float32}(0.4)    ) === C{N0f8}(0.4,0.4,0.4,1)
        @test convert(C{N0f8},  Gray{Float32}(0.4),0.2) === C{N0f8}(0.4,0.4,0.4,0.2)
        @test convert(C{N0f8}, GrayA{Float32}(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4,0.8)
        @test convert(C{N0f8}, AGray{Float32}(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4,0.8)
        @test convert(C,  Gray24(0.4)    ) === C{N0f8}(0.4,0.4,0.4,1)
        @test convert(C,  Gray24(0.4),0.2) === C{N0f8}(0.4,0.4,0.4,0.2)
        @test convert(C, AGray32(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4,0.8)
        @test convert(C{N0f8},  Gray24(0.4)    ) === C{N0f8}(0.4,0.4,0.4,1)
        @test convert(C{N0f8},  Gray24(0.4),0.2) === C{N0f8}(0.4,0.4,0.4,0.2)
        @test convert(C{N0f8}, AGray32(0.4,0.8)) === C{N0f8}(0.4,0.4,0.4,0.8)
        @test convert(C{Float32},  Gray24(0.4)    ) === C{Float32}(0.4N0f8,0.4N0f8,0.4N0f8,1)
        @test convert(C{Float32},  Gray24(0.4),0.2) === C{Float32}(0.4N0f8,0.4N0f8,0.4N0f8,0.2)
        @test convert(C{Float32}, AGray32(0.4,0.8)) === C{Float32}(0.4N0f8,0.4N0f8,0.4N0f8,0.8N0f8)
    end
    @testset "RGB24 conversions" begin
        @test convert(RGB24,  Gray{N0f8}(0.4)    ) === RGB24(0.4,0.4,0.4)
        @test convert(RGB24, GrayA{N0f8}(0.4,0.8)) === RGB24(0.4,0.4,0.4)
        @test convert(RGB24, AGray{Float32}(0.4,0.8)) === RGB24(0.4,0.4,0.4)
        @test convert(RGB24,  Gray24(0.4)    ) === RGB24(0.4,0.4,0.4)
        @test convert(RGB24, AGray32(0.4,0.8)) === RGB24(0.4,0.4,0.4)
    end
    @testset "ARGB32 conversions" begin
        @test convert(ARGB32,  Gray{N0f8}(0.4)    ) === ARGB32(0.4,0.4,0.4,1)
        @test convert(ARGB32,  Gray{N0f8}(0.4),0.2) === ARGB32(0.4,0.4,0.4,0.2)
        @test convert(ARGB32, GrayA{N0f8}(0.4,0.8)) === ARGB32(0.4,0.4,0.4,0.8)
        @test convert(ARGB32, AGray{Float32}(0.4,0.8)) === ARGB32(0.4,0.4,0.4,0.8)
        @test convert(ARGB32,  Gray24(0.4)    ) === ARGB32(0.4,0.4,0.4,1)
        @test convert(ARGB32,  Gray24(0.4),0.2) === ARGB32(0.4,0.4,0.4,0.2)
        @test convert(ARGB32, AGray32(0.4,0.8)) === ARGB32(0.4,0.4,0.4,0.8)
    end
end

@testset "conversions between different spaces" begin
    @test_throws ErrorException convert(HSV, RGB(1,0,1))
    @test_throws ErrorException convert(AHSV, RGB(1,0,1), 0.5)
end

@testset "alphacolor/coloralpha for instances" begin
    Cp3 = ColorTypes.parametric3
    @testset "alphacolor/coloralpha: $C" for C in Cp3
        cf64, cf32 = C{Float64}(1, 0.6, 0), C{Float32}(1, 0.6, 0)
        for f in (alphacolor, coloralpha)
            A = f(C)
            @test f(cf64) === A{Float64}(1, 0.6, 0, 1)
            @test f(cf64, 0.8) === A{Float64}(1, 0.6, 0, 0.8)
            @test f(cf32, 0.8) === A{Float32}(1, 0.6, 0, 0.8)
            @test f(cf32, 0.8N0f8) === A{Float32}(1, 0.6, 0, 0.8N0f8)
        end
    end
    Ct3 = unique(vcat(coloralpha.(Cp3), alphacolor.(Cp3)))
    @testset "alphacolor/coloralpha: $C" for C in Ct3
        cf64, cf32 = C{Float64}(1, 0.6, 0, 0.4), C{Float32}(1, 0.6, 0, 0.4)
        for f in (alphacolor, coloralpha)
            A = f(C)
            @test f(cf64) === A{Float64}(1, 0.6, 0, 0.4)
            @test f(cf64, 0.8) === A{Float64}(1, 0.6, 0, 0.8)
            @test f(cf32, 0.8) === A{Float32}(1, 0.6, 0, 0.8)
            @test f(cf32, 0.8N0f8) === A{Float32}(1, 0.6, 0, 0.8N0f8)
        end
    end
    @testset "alphacolor/coloralpha: $C" for C in (Gray, AGray, GrayA)
        cf64 = C === Gray ? Gray(0.6)   : C{Float64}(0.6, 0.4)
        cf32 = C === Gray ? Gray(0.6f0) : C{Float32}(0.6, 0.4)
        for f in (alphacolor, coloralpha)
            A = f(C)
            @test f(cf64) === A{Float64}(0.6, C === Gray ? 1 : 0.4)
            @test f(cf64, 0.8) === A{Float64}(0.6, 0.8)
            @test f(cf32, 0.8) === A{Float32}(0.6, 0.8)
            @test f(cf32, 0.8N0f8) === A{Float32}(0.6, 0.8N0f8)
        end
    end

    @test alphacolor(RGB{N0f8}(1,0.6,0), 0.8) === ARGB{N0f8}(1,0.6,0,0.8)
    @test coloralpha(RGB{N0f8}(1,0.6,0), 0.8) === RGBA{N0f8}(1,0.6,0,0.8)

    @test alphacolor(RGB24(1,0.6,0)) === ARGB32(1,0.6,0, 1)
    @test alphacolor(RGB24(1,0.6,0), 0.8) === ARGB32(1,0.6,0, 0.8)
    @test_throws MethodError coloralpha(RGB24(1,0.6,0))
    @test_throws MethodError coloralpha(RGB24(1,0.6,0), 0.8)

    @test alphacolor(ARGB32(1,0.6,0)) === ARGB32(1,0.6,0, 1)
    @test alphacolor(ARGB32(1,0.6,0), 0.8) === ARGB32(1,0.6,0, 0.8)
    @test_throws MethodError coloralpha(ARGB32(1,0.6,0))
    @test_throws MethodError coloralpha(ARGB32(1,0.6,0), 0.8)

    @test alphacolor(Gray24(0.6)) === AGray32(0.6, 1)
    @test alphacolor(Gray24(0.6), 0.8) === AGray32(0.6, 0.8)
    @test_throws MethodError coloralpha(Gray24(0.6))
    @test_throws MethodError coloralpha(Gray24(0.6), 0.8)

    @test alphacolor(AGray32(0.6)) === AGray32(0.6, 1)
    @test alphacolor(AGray32(0.6), 0.8) === AGray32(0.6, 0.8)
    @test_throws MethodError coloralpha(AGray32(0.6))
    @test_throws MethodError coloralpha(AGray32(0.6), 0.8)
end

@testset "reinterpret" begin
    @test reinterpret(UInt32, RGB24()) === 0x00000000
    @test reinterpret(UInt32, ARGB32()) === 0xff000000
    @test reinterpret(UInt32, Gray24()) === 0x00000000
    @test reinterpret(UInt32, AGray32()) === 0xff000000

    @test reinterpret(UInt32, RGB24(0.071,0.204,0.337)) === 0x00123456
    @test reinterpret(UInt32, ARGB32(0.071,0.204,0.337,0.671)) === 0xab123456
    @test reinterpret(UInt32, Gray24(0.071)) === 0x00121212
    @test reinterpret(UInt32, AGray32(0.071, 0.671)) === 0xab121212

    @test reinterpret(RGB24, 0x00123456) === RGB24(0.071,0.204,0.337)
    @test reinterpret(RGB24, 0xdc123456) ==  RGB24(0.071,0.204,0.337)
    @test reinterpret(RGB24, 0x00123456) !== reinterpret(RGB24, 0xdc123456)
    @test reinterpret(UInt32, reinterpret(RGB24, 0xdc123456)) === 0xdc123456

    @test reinterpret(ARGB32, 0xab123456) === ARGB32(0.071,0.204,0.337,0.671)

    @test reinterpret(Gray24, 0x00121212) === Gray24(0.071)
    @test reinterpret(Gray24, 0xdc121212) ==  Gray24(0.071)
    @test reinterpret(Gray24, 0x00121212) !== reinterpret(Gray24, 0xdc121212)
    @test reinterpret(UInt32, reinterpret(Gray24, 0xdc121212)) === 0xdc121212
    # the following behavior is undefined, so this is just for the regression test.
    @test reinterpret(Gray24, 0xdc00ff12) ==  Gray24(0.071)
    @test reinterpret(UInt32, reinterpret(Gray24, 0xdc00ff12)) === 0xdc00ff12

    @test reinterpret(AGray32, 0xab121212) === AGray32(0.071, 0.671)

    @test_throws ErrorException reinterpret(UInt32, ARGB{N0f8}(1, 0, 0))
    @test_throws ErrorException reinterpret(ARGB{N0f8}, 0x12345678)
end

### Prevent ambiguous definitions

# Certain types, like Gray24, reinterpret a UInt32 as having a
# particular color meaning. The problem with defining `convert`
# methods is that a UInt32 has a value, e.g., 0 = black and 1 = white.
# Users who want to interpret a UInt32 as a bit pattern should
# explicitly use `reinterpret`.
@testset "bit pattern ambiguities" begin
    @test_throws MethodError convert(UInt32, RGB24(1,1,1))
    @test_throws MethodError convert(UInt32, ARGB32(1,1,1,0))
    @test convert(UInt32, Gray24(1)) === UInt32(1)
    @test_throws InexactError convert(UInt32, Gray24(0.5))
    @test_throws MethodError convert(UInt32, AGray32(1,0))
    @test !(RGB24(1,1,1) == 0x00ffffff)
    @test !(ARGB32(1,1,1,0) == 0x00ffffff)
    @test Gray24(1) == 1
    @test !(Gray24(1) == 0x00ffffff)
    @test !(AGray32(1,0) == 0x00ffffff)
    ret = @test_throws ArgumentError RGB24(0x00ffffff)
    @test occursin("Use `reinterpret(RGB24, 0x00ffffff)`", ret.value.msg)
    ret = @test_throws ArgumentError ARGB32(0x00ffffff)
    @test_broken occursin("Use `reinterpret(ARGB32, 0x00ffffff)`", ret.value.msg)
    ret = @test_throws ArgumentError Gray24(0x00ffffff)
    @test occursin("Use `reinterpret(Gray24, 0x00ffffff)`", ret.value.msg)
    ret = @test_throws ArgumentError AGray32(0x00ffffff)
    @test occursin("Use `reinterpret(AGray32, 0x00ffffff)`", ret.value.msg)
    c = 0x123456
    ret = @test_throws ArgumentError RGB24(c >> 16 & 0xFF, c >> 8 & 0xFF, c & 0xFF)
    @test !occursin("Use `reinterpret(RGB24", ret.value.msg)
    @test occursin("(0x00000012, 0x00000034, 0x00000056) do not lie", ret.value.msg)
    @test_throws ArgumentError RGB24[0x00000000,0x00808080]
    @test_throws ArgumentError RGB24[0x00000000,0x00808080,0x00ffffff]
    @test_throws ArgumentError RGB24[0x00000000,0x00808080,0x00ffffff,0x000000ff]
end


text:
using DataFrames, CSV, Dates

include("callGapping.jl")
include("coolOff.jl")
include("optimize.jl")
include("simParams.jl")
cd(@__DIR__)

function simulate()

    results = DataFrame(γ = Float32[], n = Int[], cFrac = Float32[],
                        gappingPolicy = Bool[], τσParam = Float32[],reward=Float32[],seed=Int[])

    lastCGparam = -1
    lastCOparam = -1
    for repetition in 1:100
        @show repetition
        println(Dates.format(now(), "HH:MM"))
        for (j,γ) in enumerate(γVals)
            for cFrac in cFracVals
                opt2 = opt2τσ(γ,cFrac)
                for (i,n) in enumerate(nVals[j])
                    @show (γ,n,cFrac)
                    upperLimit = (i == 1 ? opt2 :  lastCGparam)*1.1
                    optCG = optimize(simCallGapping,n,γ,cFrac,upperLimit,seed=repetition)
                    lastCGparam = optCG[1]
                    push!(results,[γ,n,cFrac,true,optCG[1],optCG[2],repetition]) #Call gapping is true

                    upperLimit = (i == 1 ? opt2 :  lastCOparam)*1.1
                    optCO = optimize(simCoolOff,n,γ,cFrac,upperLimit,seed=repetition)
                    lastCOparam = optCO[1]
                    push!(results,[γ,n,cFrac,false,optCO[1],optCO[2],repetition]) #Cool off is false
                end
            end
            @info "Writing results to file"
            CSV.write("results.csv",results)
        end
    end
end

simulate()


text:
#ДАНО: Робот - в произвольной клетке ограниченного прямоугольного поля,
#дополнительно на поле могут находиться внутрение перегородки прямоугольной формы,
#среди которых могут быть и вырожденые прямоугольники (отрезки),
#эти внутренние перегородки изолированы друг от друга и от внешней рамки.
#РЕЗУЛЬТАТ: Робот - в исходном положении, и все клетки поля промакированы

function ugol(r,side1,side2)
    while !isborder(r,side1) || !isborder(r,side2)
        if !isborder(r,side1)
            move!(r,side1)
        else
            move!(r,side2)
        end
    end
end

function putmarker(r,side)
    k = 1
    while k != 0
        if isborder(r,side)
            k = obhod(r,side)
            if k == 0
                break
            end
            putmarker!(r)
        end
        if !isborder(r,side)
            move!(r,side)
            putmarker!(r)
        end
    end
end

function reverse(side)
    for i=0:3
        if side == HorizonSide(i)
            return HorizonSide(mod(i+2, 4))
        end
    end
end

function next_side(side)
    for i=0:3
        if side == HorizonSide(i)
            return HorizonSide(mod(i+3,4))
        end
    end
end

function obhod(r,side)
    k = 0
    while (isborder(r,side) && !isborder(r,next_side(side)))
        move!(r,next_side(side))
        k += 1
    end
    if isborder(r,next_side(side))&&isborder(r,side)
        for i in 1:k
            move!(r,reverse(next_side(side)))
        end
        return 0
    end
    if !isborder(r,side)
        move!(r,side)
    end
    if k != 0
        while isborder(r,reverse(next_side(side)))
            move!(r,side)
        end
        for i in 1:k
            move!(r,reverse(next_side(side)))
        end
    end
    return 1
end

function up(r)
    if isborder(r,Nord) == false
        move!(r,Nord)
        putmarker!(r)
    end
end

function mpole(r)
    ugol(r, Sud, Ost)
    putmarker!(r)
    while isborder(r,Nord) == false
        putmarker(r,West)
        up(r)
        putmarker(r,Ost)
        up(r)
    end
    if isborder(r,Ost) == false
        putmarker(r,Ost)
    elseif isborder(r,West) == false
        putmarker(r,West)
    end
end

max_stars_repo_name: gardend/mirea-progs

max_issues_repo_name: gardend/mirea-progs

max_forks_repo_name: gardend/mirea-progs


text:
#-----------------------------------------------------------------------# SModel
"""
    SModel(x, y, args...)

Create a SparseRegression model with predictor AbstractMatrix `x` and response
AbstractVector `y`.  `x` must have methods:

- `mul!(::Vector{Float64}, x, ::Vector{Float64})`
- `mul!(::Vector{Float64}, x', ::Vector{Float64})`

The additional arguments can be given in any order.

# Arguments

- `loss::Loss = .5 * L2DistLoss()`
- `penalty::Penalty = L2Penalty()`
- `λ::Vector{Float64} = fill(size(x, 2), .1)`
- `w::Union{Nothing, AbstractWeights} = nothing`

# Example

    x = randn(1000, 5)
    y = x * range(-1, stop=1, length=5) + randn(1000)
    s = SModel(x, y)
    learn!(s)
    s
"""
struct SModel{ # L P X Y W
        L <: Loss, P <: Penalty, X <: AbstractMatrix, Y <: AbstractVector,
        W <: Union{Nothing, AbstractWeights}}
    x::X
    y::Y
    w::W
    β::Vector{Float64}
    λ::Vector{Float64}
    loss::L
    penalty::P

    function SModel(x::X, y::Y, w::W, λ::Vector{Float64}, loss::L, penalty::P) where
            {L<:Loss, P<:Penalty, X<:AbstractMatrix, Y<:AbstractVector,
                W<:Union{Nothing,AbstractWeights}}
        n, p = size(x)
        length(y) == n || throw(DimensionMismatch("x and y have different nobs"))
        if w != nothing
            length(w) == n || throw(DimensionMismatch("weights are incorrect length"))
        end
        all(x -> x>=0, λ) || throw(ArgumentError("Regularization parameters must be >= 0"))
        new{L,P,X,Y,W}(x, y, w, zeros(p), λ, loss, penalty)
    end
end

# hacks for type-stable arbitrary argument order
# Default args(6): weight, λfactor, loss, penalty
const LinearRegression = LossFunctions.ScaledDistanceLoss{L2DistLoss, .5}
function d(x::AbstractMatrix)
    nothing, fill(.1, size(x,2)), LinearRegression(), L2Penalty()
end

a(argu::AbstractWeights, t::Tuple)  = (argu, t[2], t[3], t[4])
a(argu::Vector{Float64}, t::Tuple)  = (t[1], argu, t[3], t[4])
a(argu::Loss, t::Tuple)             = (t[1], t[2], argu, t[4])
a(argu::Penalty, t::Tuple)          = (t[1], t[2], t[3], argu)

SModel(x::AbstractMatrix, y::AbstractVector, t::Tuple) = SModel(x, y, t...)
SModel(x::AbstractMatrix, y::AbstractVector) = SModel(x, y, d(x))
function SModel(x::AbstractMatrix, y::AbstractVector, a1)
    SModel(x, y, a(a1, d(x)))
end
function SModel(x::AbstractMatrix, y::AbstractVector, a1, a2)
    SModel(x, y, a(a2, a(a1, d(x))))
end
function SModel(x::AbstractMatrix, y::AbstractVector, a1, a2, a3)
    SModel(x, y, a(a3, a(a2, a(a1, d(x)))))
end
function SModel(x::AbstractMatrix, y::AbstractVector, a1, a2, a3, a4)
    SModel(x, y, a(a4, a(a3, a(a2, a(a1, d(x))))))
end

#-----------------------------------------------------------------------# show
const ticks = ['▁','▂','▃','▄','▅','▆','▇']
function Base.show(io::IO, o::SModel)
    println(io, "SModel")
    print(io, "  > abs.(β)  : ")
    maxβ = max(maximum(abs, o.β), 1.0)
    tickrange = (0:7) ./ 7
    for βi in o.β
        i = searchsortedfirst(tickrange, abs(βi) / maxβ) - 1
        i == 0 ? print(io, ' ') : print(io, ticks[i])
    end
    println(io)
    println(io, "  > λ factor : ", o.λ')
    println(io, "  > Loss     : ", o.loss)
    println(io, "  > Penalty  : ", o.penalty)
    println(io, "  > Data")
    println(io, "    - x : ", summary(o.x))
    println(io, "    - y : ", summary(o.y))
    print(io,   "    - w : ", summary(o.w))
end

#-----------------------------------------------------------------------# Methods
coef(o::SModel) = o.β
predict(o::SModel, x::AbstractMatrix) = x * o.β
predict(o::SModel, x::AbstractVector) = x'o.β

function value(o::SModel, x = o.x, y = o.y)
    value(o.loss, y, predict(o, x), AggMode.Mean()) + value(o.penalty, o.β, o.λ)
end

weights!(o::SModel, w::AbstractWeights) = (o.weights[:] = w)