LoginSignup
2
0

怠け者による Julia言語 におけるロゴの色を取得と表示方法

Last updated at Posted at 2023-12-06

本日は

Julia のアドベントカレンダー $N=7$ 日目です.

Qiita がダークモードを対応しておりとても快適です.うれちぃ(嬉しい)

アドベントカレンダー4日目を読みました.色々ロゴを作成されておりセンスが光ります.

さて,今日の話題はロゴの色をプログラムで取得する方法の Tips について触れます.それだけです!

image.png

(上記画像は 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進数カラーコードの相互変換ツール」を利用します.

image.png

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 で自動化しました.もっと怠けたいので今年はなんとか人生を乗り越えたいです.

以上.

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0