無限不可能性ドライブ

『ニューラルネットワーク自作入門』に刺激されてExcelVBAでニューラルネットワークを作ってみたものの、やっぱり数学やらなきゃと思い少しずつやってきたのもあって、自分の知識の整理とかそういった感じです。

【VBA】文字を隠したいならセルの色に埋め込んじゃえばいいじゃない…

!!ノンプロ研企画アドベントカレンダー!!

こちらは私が所属しているコミュニティ「ノンプログラマーのためのスキルアップ研究会」通称「ノンプロ研」の年末特別企画、ノンプロ研 Advent Calendar 2019 の17日目の記事として投稿しています。
adventar.org

なんて書いてあるの?

突然ですが、この色のついたセル、ここになんて書いてあるかわかるでしょうか…
勘のいいひとならわかりますよね?え?わかる?ほんとにわかるの?マジで!?

f:id:celaeno42:20191029201501p:plain:w500

いや、まぁ、一般的にはわかりませんよね。

でも、これをある方法で変換すると文字列がでてきます。

「The quick brown fox jumps over the lazy dog.」

今回はこの方法について説明していきます。

たねあかし

たねあかしをすると簡単です。

f:id:celaeno42:20191029203750p:plain:w500

文字コードアスキーコード)を2倍してRGBのそれぞれにあてていたんですね。2倍にしているのは、そのままだと色が暗くなってしまうからです。主要な文字のアスキーコードは 127 までで、2倍しても 254 となり色の範囲の 0 ~ 255 に収まるので大丈夫です。もし暗い色味の方がお好みの方は2倍にしなくてもいいです。

実装

やり方がわかったので実装してみましょう!
今回は標準モジュールに書いていきます。

【シートと標準モジュールの準備】

シートのオブジェクト名を変更し標準モジュールを追加しましょう。

f:id:celaeno42:20191119195348p:plain

オブジェクト オブジェクト名
Sheet1 wsMain
標準モジュール mdlMain

ついでに、Sheet1のA1セルに「埋め込む文字列:」、A2セルに「抽出した文字列:」と書いておきます。
また、セルの幅と高さは42ピクセルにしていますが、このサイズはお好みで。

f:id:celaeno42:20191119200044p:plain

【定数の宣言、シートの準備】

定数も宣言しておきましょう。

定数名 意味
RNG_TEXT 埋め込む文字列を記入するセル
RNG_DECODE_TEXT 抽出した文字列を表示するセル
RW.CLR_START 色を付けるセルの開始列
CL.CLR_START 色を付けるセルの開始行
Const RNG_TEXT As String = "D1"
Const RNG_DECODE_TEXT As String = "D2"

Enum RW
    CLR_START = 6
End Enum

Enum CL
    CLR_START = 5
End Enum

【埋め込み処理】

文字列を1文字ずつアスキーコードに変換して色として設定していきます。
RGBそれぞれに1文字を割り当てているので、3文字で1カラーになります。

Public Sub Click_埋め込む()
    Dim strText As String
    Dim strChar As String
    Dim clrR As Long, clrG As Long, clrB As Long
    Dim clrNum As Long
    Dim i As Long
    Dim r As Long
    Dim c As Long
    Dim flag As Boolean
    
    With wsMain
    
        '初期化
        .Cells.Interior.ColorIndex = xlNone
        .Range(RNG_DECODE_TEXT).ClearContents
        
        '埋め込む文字列を取得
        strText = .Range(RNG_TEXT).Value
        
        r = RW.CLR_START
        c = CL.CLR_START - 1
        
        '文字列の長さまで繰り返す
        For i = 1 To Len(strText)
        
            '[ 1 ] 文字を一文字取り出して、アスキーコードに変換して2倍する
            strChar = Mid(strText, i, 1)
            clrNum = Asc(strChar) * 2
            
            flag = False
            Select Case i Mod 3
                Case 1          '[ 2 ] G,B を初期化し、R(赤)を設定、色を付けるセルの行、列を更新
                    clrG = 255
                    clrB = 255
                    
                    clrR = clrNum
                    
                    c = c + 1
                    If c > 10 Then
                        r = r + 1
                        c = CL.CLR_START
                    End If
                
                Case 2          '[ 3 ] G(緑)を設定
                    clrG = clrNum
                
                Case Else      '[ 4 ] B(青)を設定し、セルに色を付ける
                    clrB = clrNum
                    .Cells(r, c).Interior.Color = RGB(clrR, clrG, clrB)
                    flag = True
                    
            End Select
            
        Next
        
        '[ 5 ] 文字数の関係でBまで設定できなかった場合、セルに着色されていないのでここで着色する
        If Not flag Then
            .Cells(r, c).Interior.Color = RGB(clrR, clrG, clrB)
        End If
        
    End With

End Sub

コードの解説

コードのポイントについて解説します。

[ 1 ] 文字を一文字取り出して、アスキーコードに変換して2倍する

For文でぐるぐる回しながら1文字ずつ取得していきます。文字を取得したら Asc() でアスキーコードに変換します。今回は色を明るくしたいので2倍しています。

[ 2 ] G,B を初期化し、R(赤)を設定、色を付けるセルの行、列を更新

対象とする文字の位置を3で割った余りが1の場合、つまり、1文字目、4文字目、7文字目…の処理です。[ 1 ] で得られたアスキーコードを R (赤)の色の値として変数に格納します。

RGBそれぞれが1文字に対応しているため、文字数によってはキリが悪くなることがあります(文字数が3で割り切れない場合)。その場合、GとBを初期化しておかないと前の色の値が残ったままになってしまうため、意図しない文字が埋め込まれてしまうことになります。今回は、初期値として 255 を設定し、値が 255 だった場合は文字を出力しないという設計にしています。

また、色を塗りたいセルの場所を更新しています。今回は横に10セル塗ったら行を変えるようにしていますが、この辺りはお好みで。

[ 3 ] G(緑)を設定

対象とする文字の位置を3で割った余りが2の場合、つまり、2文字目、5文字目、8文字目…の処理です。[ 1 ] で得られたアスキーコードを G (緑)の色の値として変数に格納します。

[ 4 ] B(青)を設定し、セルに色を付ける

対象とする文字の位置が3で割り切れる場合、つまり、3文字目、6文字目、9文字目…の処理です。[ 1 ] で得られたアスキーコードを B (青)の色の値として変数に格納します。

ここに到達した場合は R と G には色の値が格納されているので、セルに色を付けます。
flag は文字数の関係でここまで到達しなかった場合(R か G までで For を抜けた場合)に処理の最後で別途色を付ける必要があるため、その目安とするフラグです。

[ 5 ] 文字数の関係でBまで設定できなかった場合、セルに着色されていないのでここで着色する

flag が False の場合は、B まで到達せずに For を抜けた(着色されていない)ということを意味するので、ここで着色します。

【抽出処理】

さて、セルの色に文字を埋め込めたので、今度は抽出(復元)です。
セルの色情報から埋め込んだ文字列を抽出します。
セルの色を R, G, B に分解してそれぞれを1文字として変換すればOKです。

Public Sub Click_抽出する()
    Dim r As Long
    Dim c As Long
    Dim strText As String
    Dim clrNum As Long
    Dim hexR As String, hexG As String, hexB As String
    Dim lngR As Long, lngG As Long, lngB As Long
    Dim chrR As String, chrG As String, chrB As String
    
    With wsMain
        
        strText = ""
        r = RW.CLR_START
        c = CL.CLR_START
        '未着色セルが出現するまで繰り返す
        Do
            Do
                '[ 1 ] セルの色を取得し、16進数でRGBに変換する
                clrNum = .Cells(r, c).Interior.Color
                hexR = "&H" & Right(Hex(clrNum), 2)
                hexG = "&H" & Mid(Hex(clrNum), 3, 2)
                hexB = "&H" & Left(Hex(clrNum), 2)
                
                '[ 2 ] 16進数を10進数に変換する
                lngR = CLng(hexR)
                lngG = CLng(hexG)
                lngB = CLng(hexB)
                
                '[ 3 ] 色の値が255でなかったら2で割って文字に変換する
                If lngR <> 255 Then
                    lngR = lngR / 2
                    strText = strText & Chr(lngR)
                End If
                
                If lngG <> 255 Then
                    lngG = lngG / 2
                    strText = strText & Chr(lngG)
                End If
                
                If lngB <> 255 Then
                    lngB = lngB / 2
                    strText = strText & Chr(lngB)
                End If
                
                c = c + 1
            Loop Until .Cells(r, c).Interior.ColorIndex = xlNone
            r = r + 1
            c = CL.CLR_START
        Loop Until .Cells(r, c).Interior.ColorIndex = xlNone
        
        '[ 4 ] 取得した文字列を表示する
        .Range(RNG_DECODE_TEXT).Value = strText
        
    End With
    
End Sub

コードの解説

コードのポイントについて解説します。

[ 1 ] セルの色を取得し、16進数でRGBに変換する

Do - Loop でぐるぐる繰り返しながら1セルずつ処理していきます。

clrNum にセルの色を格納します。RGBに分解しやすいように、一度16進数に変換してから、右側2文字(R)、真ん中2文字(G)、左側2文字(B)に分け、「&H」を付加することで16進数に戻しています。
※ 取得された色の16進表記は「BGR」の順になっているので注意してください。(「RGB」の順ではないです。)

例えば、1つめのセルの色は10進数で「13291688」ですが、これを16進数に変換すると「CAD0A8」となります。
右側の2文字「A8」を取り出し文字列「&HA8」として、hexR に格納します。hexG、hexB も同様です。

[ 2 ] 16進数(文字列)を10進数に変換する

[ 1 ] で hexR、hexG、hexB に格納された文字列は「&H○○」の形式なので、16進数とみなすことができます。そこで、それぞれを CLng() を使って 10進数に変換します。
例えば、上で hexR に格納されているのは「&HA8」ですが、これを10進数に変換すると「168」になります。
同様に、hexG に格納されているのは「&HD0」なので「208」、hexB は「&HCA」なので「202」となります。

[ 3 ] 色の値が255でなかったら2で割って文字に変換する

さて、埋め込み処理の際に 255 にした場合は文字として出力しないことにするとしていたので、[ 2 ] で変換された数値が 255 でない場合のみ処理を行います。今回は、元の文字のアスキーコードを 2倍にして埋め込んでいるので、ここでは 2で割って元のアスキーコードに戻しています(lngR = lngR / 2)。
求めたアスキーコードを Chr() で文字に変換して、抽出された文字列(strText)に順次追加していきます。

[ 4 ] 取得した文字列を表示する

すべての色付きセルについて上記処理が終了したら、表示先セルに抽出された文字列を表示します。

【初期化処理】

「埋め込む文字列」、「抽出した文字列」、「着色されたセル」を初期化(クリア)する処理です。特に解説は不要でしょう。

Public Sub Click_クリア()
    With wsMain
        .Range(RNG_TEXT).ClearContents
        .Range(RNG_DECODE_TEXT).ClearContents
        .Cells.Interior.ColorIndex = xlNone
    End With
End Sub

ボタンに書く処理を割り当てて完成

あとは適当にボタンを作って、「Click_埋め込む()」、「Click_抽出する()」、「Click_クリア()」の処理を割り当てれば完成です。
いろいろな文字列でどんな色になるか試してみましょう!

これの用途

まぁ、これが何に使えるのかっていうのは…私もよくわかりません。。暗号?
でも、おもしろいからいいよね!


f:id:celaeno42:20181212233850p:plain