Power Apps 色の計算いろいろ

# はじめに

::: note 
本記事は[Power Apps Advent Calendar 2024](https://qiita.com/advent-calendar/2024/powerapps) シリーズ1の6日目担当記事になります。
:::

Power Appsで色を参照した計算をいろいろとやってみようという内容です。

https://x.com/ksgiksg/status/1864674531589411043

特にクラシックボタンコントロールの塗りつぶし（Fill）プロパティを変更したとき、罫線の色が連動せず散らかってしまうことがありますので、自動的に追従するような計算ができないかと思ったのがきっかけです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/32de21aa-15c5-5d9a-cbe5-902486225482.png)


# Color型を参照して計算できない
色を指定するプロパティでは、`RGBA`関数や`Color`列挙体を使って好きな色を使用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/97b85ac4-1f2e-2f45-98a6-0355cd2da9fb.png)
これは、データ型「色（Color）」型として扱われています。

https://learn.microsoft.com/en-us/power-platform/power-fx/data-types

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/6e4f663f-92d6-58fe-3c53-fadbecdc40bf.png)

そのため、他のデータ型であるテキストや数値と合わせて加工することができず、`色`として扱う関数、例えば`ColorFade`関数でのみ扱うことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/0ae57c9e-000c-a366-effb-7619febc8a68.png)

そのため、**「このボタンの塗りつぶしの反対色」** といった計算は難しいわけです。

# Color型をTextに変換する
さて、そのままでは計算できないので、扱いやすい形にまずは変換します。
色を`JSON`関数で包むことで、JSON、つまり文字でデータを表現する形に無理やり変換することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/971737ca-4250-5d21-eaa2-c0c8ce80b63a.png)

このとき、文字として出てきたデータは16進数で表されたRGBAの値です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/bc2a6719-970c-1786-7ae9-f68d059aff84.png)

ここまでわかれば後は加工するだけです。
①JSONに変換して文字列化→②文字列を前から順にR・G・B・Aで取り出す→③16進数から10進数に変換

```powerfx:Color型からRGBA10進数を取り出し
With(
    {txt: ParseJSON(JSON(Rectangle1.Fill))},
    {
        R: Hex2Dec(
            Mid(txt,2,2)
        ),
        G: Hex2Dec(
            Mid(txt,4,2)
        ),
        B: Hex2Dec(
            Mid(txt,6,2)
        ),
        A: Hex2Dec(
            Mid(txt,8,2)
        )
    }
)
```

# 色の計算サンプル
数値にできればあとはお好きに計算してください。
サンプルとしていくつか計算例を挙げておきます。

## 背景色RGBから見やすい前景色RGB（白黒）を取得する
こちらをお借りしてPower Apps 用の式に変換しました。

https://x.com/KotorinChunChun/status/1784476629147984315

```powerfx:背景色RGBから見やすい前景色RGB（白黒）を取得する
With(
    {txt: ParseJSON(JSON(Rectangle1.Fill))},
    With(
        {
            R: Hex2Dec(
                Mid(txt,2,2)
            ),
            G: Hex2Dec(
                Mid(txt,4,2)
            ),
            B: Hex2Dec(
                Mid(txt,6,2)
            ),
            A: Hex2Dec(
                Mid(txt,8,2)
            )
        },
        With(
            { tf:((((R * 299) + (G * 587) + (B * 114)) / 1000) < 128)},
            If(tf, RGBA(255, 255, 255,1), RGBA(0, 0, 0,1))
        )
    )
)
```

## 補色と反転色
補色は、色相環（色の輪）で互いに正反対に位置する色のことを指します。例えば、赤と緑、青とオレンジ、黄色と紫などが補色の関係にあります。補色同士を組み合わせると、視覚的に強いコントラストが生まれ、互いの色を引き立て合います。
```powerfx:補色
With(
    {txt: ParseJSON(JSON(Rectangle1.Fill))},
    With(
        {
            R: Hex2Dec(
                Mid(txt,2,2)
            ),
            G: Hex2Dec(
                Mid(txt,4,2)
            ),
            B: Hex2Dec(
                Mid(txt,6,2)
            ),
            A: Hex2Dec(
                Mid(txt,8,2)
            )
        },
        With(
            {S: Max(R,G,B) + Min(R,G,B)},
            RGBA(S - R,S - G,S - B,A)
        )
    )
)
```

反転色は、ある色のRGB値を反転させた色のことです。具体的には、RGBの各値を255から引いた値が反転色になります。例えば、赤（RGB: 255, 0, 0）の反転色はシアン（RGB: 0, 255, 255）です。反転色は、元の色と視覚的に対照的な効果を持ちます。
```powerfx:反転色
With(
    {txt: ParseJSON(JSON(Rectangle1.Fill))},
    With(
        {
            R: Hex2Dec(
                Mid(txt,2,2)
            ),
            G: Hex2Dec(
                Mid(txt,4,2)
            ),
            B: Hex2Dec(
                Mid(txt,6,2)
            ),
            A: Hex2Dec(
                Mid(txt,8,2)
            )
        },
        RGBA(255 - R,255 - G,255 - B,A)
    )
)
```

## トライアド
トライアド（トライアディックカラー）は、色相環上で等間隔に配置された3つの色の組み合わせを指します。例えば、赤、青、黄色の組み合わせが典型的なトライアドです。この配色は、バランスが取れていて、視覚的に調和が取れやすいのが特徴です。

トライアドの配色を使うと、デザインにおいて鮮やかでダイナミックな効果を生み出すことができます。各色が互いに補完し合いながらも、独自の存在感を持つため、視覚的に引き立つデザインを作り出すのに適しています。

```powerfx:トライアド
With(
    {txt: ParseJSON(JSON(Rectangle1.Fill))},
    With(
        {
            R: Hex2Dec(Mid(txt,2,2)),
            G: Hex2Dec(Mid(txt,4,2)),
            B: Hex2Dec(Mid(txt,6,2)),
            A: Hex2Dec(Mid(txt,8,2))
        },
        With(
            {_max:Max(R,G,B),_min:Min(R,G,B)},
            With(
                {S:If(
                    R=G&&B=G,0,
                    _max=R,60*((G-B)/(_max-_min)),
                    _max=G,60*((B-R)/(_max-_min)) +120,
                    _max=B,60*((R-G)/(_max-_min)) +240
                )},
        		With(
	                {
                        色相: Mod(S + 360 +120,360)
		            },
                    With(
                        {
                            newR: If(
                                色相 < 60, _max,
                                色相 < 120, ((120 - 色相) / 60) * (_max - _min) + _min,
                                色相 < 180, _min,
                                色相 < 240, _min,
                                色相 < 300, ((240 - 色相) / 60) * (_max - _min) + _min,
                                _max
                            ),
                            newG: If(
                                色相 < 60, (色相/60)*(_max-_min) + _min,
                                色相 < 120,_max,
                                色相 < 180, _max,
                                色相 < 240, ((240 - 色相) / 60) * (_max - _min) + _min,
                                色相 < 300,_min,
                                _min
                            ),
                            newB: If(
                                色相 < 60, _min,
                                色相 < 120, _min,
                                色相 < 180, ((色相 - 120) / 60) * (_max - _min) + _min,
                                色相 < 240, _max,
                                色相 < 300, _max,
                                ((360 - 色相) / 60) * (_max - _min) + _min
                            )
                        },
                        RGBA(newR,newG,newB,A)
                    )
        	    )
            )
        )
    )
)
```

:::note
色相を120°回転させることで計算させています
:::

## スプリット・コンプリメンタリ
スプリット・コンプリメンタリ
スプリット・コンプリメンタリ（分割補色）は、色相環上である色の補色の両側に位置する2つの色を組み合わせた配色です。例えば、赤の補色は緑ですが、スプリット・コンプリメンタリでは緑の両側にある青緑と黄緑を選びます。この配色は、補色の強いコントラストを避けつつ、視覚的に興味深い効果を生み出すことができます。

```powerfx:スプリット・コンプリメンタリ
With(
    {txt: ParseJSON(JSON(Rectangle1.Fill))},
    With(
        {
            R: Hex2Dec(Mid(txt,2,2)),
            G: Hex2Dec(Mid(txt,4,2)),
            B: Hex2Dec(Mid(txt,6,2)),
            A: Hex2Dec(Mid(txt,8,2))
        },
        With(
            {_max:Max(R,G,B),_min:Min(R,G,B)},
            With(
                {S:If(
                    R=G&&B=G,0,
                    _max=R,60*((G-B)/(_max-_min)),
                    _max=G,60*((B-R)/(_max-_min)) +120,
                    _max=B,60*((R-G)/(_max-_min)) +240
                )},
        		With(
	                {
                        色相: Mod(S + 360 +165,360)
		            },
                    With(
                        {
                            newR: If(
                                色相 < 60, _max,
                                色相 < 120, ((120 - 色相) / 60) * (_max - _min) + _min,
                                色相 < 180, _min,
                                色相 < 240, _min,
                                色相 < 300, ((240 - 色相) / 60) * (_max - _min) + _min,
                                _max
                            ),
                            newG: If(
                                色相 < 60, (色相/60)*(_max-_min) + _min,
                                色相 < 120,_max,
                                色相 < 180, _max,
                                色相 < 240, ((240 - 色相) / 60) * (_max - _min) + _min,
                                色相 < 300,_min,
                                _min
                            ),
                            newB: If(
                                色相 < 60, _min,
                                色相 < 120, _min,
                                色相 < 180, ((色相 - 120) / 60) * (_max - _min) + _min,
                                色相 < 240, _max,
                                色相 < 300, _max,
                                ((360 - 色相) / 60) * (_max - _min) + _min
                            )
                        },
                        RGBA(newR,newG,newB,A)
                    )
        	    )
            )
        )
    )
)
```

:::note
色相を165°回転させることで計算させています
※補色：180°回転の隣の色。色相環を24分割(15°)として計算
:::

## テトラード
テトラード
テトラード（テトラディックカラー）は、色相環上で等間隔に配置された4つの色の組み合わせを指します。例えば、赤、青、緑、黄色の組み合わせが典型的なテトラードです。この配色は、バランスが取れていて、視覚的に調和が取れやすいのが特徴です。
```powerfx:テトラード
With(
    {txt: ParseJSON(JSON(Rectangle1.Fill))},
    With(
        {
            R: Hex2Dec(Mid(txt,2,2)),
            G: Hex2Dec(Mid(txt,4,2)),
            B: Hex2Dec(Mid(txt,6,2)),
            A: Hex2Dec(Mid(txt,8,2))
        },
        With(
            {_max:Max(R,G,B),_min:Min(R,G,B)},
            With(
                {S:If(
                    R=G&&B=G,0,
                    _max=R,60*((G-B)/(_max-_min)),
                    _max=G,60*((B-R)/(_max-_min)) +120,
                    _max=B,60*((R-G)/(_max-_min)) +240
                )},
        		With(
	                {
                        色相: Mod(S + 360 +90,360)
		            },
                    With(
                        {
                            newR: If(
                                色相 < 60, _max,
                                色相 < 120, ((120 - 色相) / 60) * (_max - _min) + _min,
                                色相 < 180, _min,
                                色相 < 240, _min,
                                色相 < 300, ((240 - 色相) / 60) * (_max - _min) + _min,
                                _max
                            ),
                            newG: If(
                                色相 < 60, (色相/60)*(_max-_min) + _min,
                                色相 < 120,_max,
                                色相 < 180, _max,
                                色相 < 240, ((240 - 色相) / 60) * (_max - _min) + _min,
                                色相 < 300,_min,
                                _min
                            ),
                            newB: If(
                                色相 < 60, _min,
                                色相 < 120, _min,
                                色相 < 180, ((色相 - 120) / 60) * (_max - _min) + _min,
                                色相 < 240, _max,
                                色相 < 300, _max,
                                ((360 - 色相) / 60) * (_max - _min) + _min
                            )
                        },
                        RGBA(newR,newG,newB,A)
                    )
        	    )
            )
        )
    )
)
```
:::note
色相を90°回転させることで計算させています
:::

# おわりに
最近はモダンコントロールとテーマカラーを使って、一括で色を変えたい場面がよくありますので、あらかじめ仕込んでおけばメンテナンスの手間がなくなるかもしれません。
他にも、混色計算を使ったお絵描きアプリなど、いろいろなことができそうですので、是非応用してみて下さい！

# 余談
JSON関数ってOnSelectなんかの動的な場面でしか使えなかった気がするのですが、制限緩くなりましたか？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2572227/43ac999d-0a9d-c5c6-9bf6-c48c6d5e5cba.png)


# 参考にしたサイト
[@KotorinChunChun
KotorinChunChun/kccFuncGraphics_partial.bas.vba](https://gist.github.com/KotorinChunChun/686fa1814feb628867967fd69972b596)

https://colorcode-d49c2.web.app/FrmMain

https://www.cocolor.biz/colorlesson/%e8%a3%9c%e8%89%b2%e3%82%92%e4%ba%8c%e5%88%86%e5%89%b2%e3%81%97%e3%81%9f%e3%82%b9%e3%83%97%e3%83%aa%e3%83%83%e3%83%88%e3%82%b3%e3%83%b3%e3%83%97%e3%83%aa%e3%83%a1%e3%83%b3%e3%82%bf%e3%83%aa%e3%83%bc/

https://tomari.org/main/java/color/ccal.html