本日は
Julia のアドベントカレンダー $N=7$ 日目です.
Qiita がダークモードを対応しておりとても快適です.うれちぃ(嬉しい)
アドベントカレンダー4日目を読みました.色々ロゴを作成されておりセンスが光ります.
さて,今日の話題はロゴの色をプログラムで取得する方法の Tips について触れます.それだけです!
(上記画像は https://github.com/JuliaLang/julia-logo-graphics から引用)
手動で取得
例えば赤色は次のように手動で取得できます.
julia> using ColorTypes; julia_red = RGB(0.796, 0.235, 0.2)
RGB
構造体は ColorTypes パッケージにて定義されています.
"""
`RGB` is the standard Red-Green-Blue (sRGB) colorspace. Values of the
individual color channels range from 0 (black) to 1 (saturated). If
you want "Integer" storage types (e.g., 255 for full color), use `N0f8(1)`
instead (see FixedPointNumbers).
"""
struct RGB{T<:Fractional} <: AbstractRGB{T}
r::T # Red [0,1]
g::T # Green [0,1]
b::T # Blue [0,1]
RGB{T}(r::T, g::T, b::T) where {T <: Fractional} = new{T}(r, g, b)
end
ただ,0.796
とかの数字を覚えられません.もうちょっと楽をしたいです.
Colors.jl の定数を使う
Colors.jl パッケージにある JULIA_LOGO_COLORS
を使うと数値をハードコーディングせずに済みます.
"""
Colors used in the Julia logo as a `NamedTuple`.
The keys are approximate descriptions of the hue and do not include black.
Not exported, use as `JULIA_LOGO_COLORS.red` etc.
"""
const JULIA_LOGO_COLORS = (red = RGB{N0f8}(0.796, 0.235, 0.2), # colorant"#cb3c33" blocks precompilation
green = RGB{N0f8}(0.22, 0.596, 0.149),
blue = RGB{N0f8}(0.251, 0.388, 0.847),
purple = RGB{N0f8}(0.584, 0.345, 0.698))
Julia の REPL では次のようになります.
julia> using Colors
julia> julia_color = Colors.JULIA_LOGO_COLORS.red
RGB{N0f8}(0.796,0.235,0.2)
ここで N0f8
は FixedPointNumbers.jl で定義されている型であり固定小数点で格納されています.ざっくり言えば $0$ から $2^8-1=255$ の整数値を $255$ で割って $[0, 1] \subset \mathbb{R}$ へマッピングしたデータと思えば良いです.NumPy ユーザは事前に下記の計算をしておくと良いでしょう.
iPython 3.11.6 (main, Oct 31 2023, 11:54:48) [Clang 14.0.3 (clang-1403.0.22.14.1)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.18.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import numpy as np
In [2]: nv = np.array([203,60,51]).astype(np.uint8)/255
In [3]: 255 * nv
Out[3]: array([203., 60., 51.])
16進数カラーコード として取得
#CB3C33
らしいので確かめてみましょう.下記の「RGBと16進数カラーコードの相互変換ツール」を利用します.
R = 203, G = 60, B = 51
ですね.念のため下記のようにREPLでも確かめます
julia> UInt8(203)
0xcb
julia> UInt8(60)
0x3c
julia> UInt8(51)
0x33
ここで RGB{N0f8}(0.796,0.235,0.2)
との関係を見ておきましょう.
julia> using ImageCore
julia> chv = channelview([RGB{N0f8}(0.796,0.235,0.2)])
3×1 reinterpret(reshape, N0f8, ::Array{RGB{N0f8},1}) with eltype N0f8:
0.796N0f8
0.235N0f8
0.2N0f8
julia> reinterpret(UInt8, chv)
3×1 reinterpret(UInt8, reinterpret(reshape, N0f8, ::Array{RGB{N0f8},1})):
0xcb
0x3c
0x33
もっと素朴に書くと次のようになります:
julia> using FixedPointNumbers: N0f8
julia> using ColorTypes: RGB
julia> chv = reinterpret(N0f8, [RGB{N0f8}(0.796,0.235,0.2)])
3-element reinterpret(N0f8, ::Array{RGB{N0f8},1}):
0.796N0f8
0.235N0f8
0.2N0f8
julia> reinterpret(UInt8, chv)
3-element reinterpret(UInt8, ::Array{RGB{N0f8},1}):
0xcb
0x3c
0x33
Int
でもう少し直感的な表記にします.
julia> Int.(reinterpret(UInt8, chv))
3-element Vector{Int64}:
203
60
51
外部ツールで得られた R = 203, G = 60, B = 51
と一致しましたね.
逆方向に
今度は R = 203, G = 60, B = 51
から RGB{N0f8}(0.796,0.235,0.2)
を作ります.
julia> using ImageCore
julia> d = [0xcb, 0x3c, 0x33]
3-element Vector{UInt8}:
0xcb
0x3c
0x33
julia> _julia_red = colorview(RGB, normedview(N0f8, d))
0-dimensional reinterpret(reshape, RGB{N0f8}, reinterpret(N0f8, ::Vector{UInt8})) with eltype RGB{N0f8}:
RGB{N0f8}(0.796,0.235,0.2)
julia> # _julia_red は 0 次元配列として得られる.
julia> julia_red = _julia_red[begin]
RGB{N0f8}(0.796,0.235,0.2)
julia> using Colors
julia> Colors.JULIA_LOGO_COLORS.red == julia_red
true
colorview(RGB, normedview(N0f8, d))
の部分は下記のようにしても良いです.
julia> reinterpret(RGB{N0f8}, normedview(N0f8, d))[begin]
RGB{N0f8}(0.796,0.235,0.2)
あまり気にしない
これまで reinterpret
がいっぱい出てきました.バイナリデータをどう解釈するかを決めるものです.実際に機械学習などのハイレベルな画像タスクをする場合は「まぁこんなものがある」という程度の理解で良いです.あまり気にせず normedview, channelview, colorview
を組み合わせて N0f8
のフォーマットと UInt8
のフォーマットを相互に変換することが多いでしょう.normedview, channelview, colorview
のコードを追うと実際は reinterpret
を内部で使っていることがわかります.
なんにせよ.RGB{N0f8}(0.796,0.235,0.2)
と 16進数カラーコード #cb3c33
は同じだと確認はできました.
"nearest named color" の取得
nearest named color としてある brown3
を取得してみましょう.NamedColors.jl なるパッケージがあるようで,これで取得できるようです.
julia> using NamedColors
julia> colorant"brown3"
RGB{N0f8}(0.804,0.2,0.2)
ここで "brown3" の前に colorant
をつけることで @colorant_str
というマクロを使用することができます.これに関しては Julia のドキュメントに書いてあります.
@colorant_str
マクロは次のような実装になっています:
macro colorant_str(name::AbstractString)
local col
try
col = parse(Colorant, name) # この部分が大事
catch ex
col = named_color(name)
:($col)
end
:($col)
end
要するに今回の場合は下記のコードを実行していることとほぼ等価です.
julia> using NamedColors
julia> parse(NamedColors.Colorant, "brown3")
RGB{N0f8}(0.804,0.2,0.2)
上述の調査の仕方は下記のようなコードを実行することで得ることができます.興味があれば試してください.
julia> @less colorant"brown3"
今回は赤色で試しましたが,青,緑,紫に関しての色に関しても Julia のプログラムとして情報を取得することができます.
取得はできたが,もう少し直感的に表示したいぞ!
正直 RGB{N0f8}(0.796,0.235,0.2)
と言われてもよくわからないでしょう.なんとなく R が 0.79 だから赤色の成分が強いのかな?ぐらいはわかります.人間は欲張りなので「もう少しこ〜ね?あれね?いいかんじに可視化したいっすね」と思うわけです.
Sixel.jl を使う
以下は macOS + iTerm2 の環境での話になります.Julia の画像データを sixel_encode
でターミナルの上で色情報を表示させることができます.以下がそのデモの様子です.Qiita だと mov が貼れなかったので Twitter に投稿したものを共有しましょう.
手元で動かしたい方は下記を試してください.
using Dates
using ImageInTerminal
using Replay
# https://github.com/terasakisatoshi/QuartzGetWindow.jl
using QuartzGetWindow
function takemov()
x, y, w, h = getWindowGeometry(getActiveWindow())
file = "$(Dates.now()).mov"
run(`screencapture -R$(x),$(y),$(w),$(h) -v $(file)`)
end
function record()
i1 = @deparse using Colors: JULIA_LOGO_COLORS
i2 = "using NamedColors: @colorant_str" # これは deparse できなかった
i3 = @deparse using Sixel: sixel_encode
i4 = @deparse cs = JULIA_LOGO_COLORS |> collect # red, green, blue, purple
i5 = @deparse sixel_encode(hcat(fill.(cs, 100,100)...))
i6 = @deparse print("nearest named colors")
i7 = @deparse cs = [colorant"brown3", colorant"royalblue",
colorant"forestgreen", colorant"mediumorchid3"]
i8 = @deparse sixel_encode(hcat(fill.(cs, 100,100)...))
@async takemov()
replay([i1,i2,i3,i4,i5,i6,i7,i8], use_ghostwriter=true)
exit()
end
record()
ここで, QuartzGetWindow.jl は野良パッケージです.下記のリポジトリをクローンして利用してください.
一応説明
cs = JULIA_LOGO_COLORS |> collect
hcat(fill.(cs, 100,100)...)
100x100 の解像度で赤,緑,青,紫の画像データを作ります.hcat
で横方向に結合し幅が 400 = 4x100 の画像データを作ります.最後にsixel_encode
によって iTerm2 など Sixel グラフィックスに対応しているターミナルの上で画像を表示できるようにデータをエンコードしています.
Replay.jl で REPL の操作を自動化しています.@deparse
は Replay v0.5 で利用できます.(本当は Documenter.jl の仕組みを真似するべきなんだろうなぁ・・・).
まとめ
Julia のロゴ情報を Julia のプログラムを使って取得しました.表示方法も示しました.怠け者なのでデモスクリプトは Replay.jl で自動化しました.もっと怠けたいので今年はなんとか人生を乗り越えたいです.
以上.