概要
年賀状で、みかんの汁で文字を書いて、送り先の人に炙ってもらうとそれが浮かび上がるみたいな話は聞いたことあるんじゃないかと思います。今回は、昔作った、Processingで炙り出し文字を仕込む&仕込まれた文字を浮かび上がらせるプログラムのセットについて書きます。重めの画像多めっていうえげつないラーメンみたいな記事なのでスマホで見る方は注意です。
任意の画像に任意を文字を仕込んで出力するImageConverterと、ImageConverterによって仕込まれた文字を浮かび上がらせるSecretCharacterで1つのセットとなる構成です。
githubに上げたものはココ。
実演
まずはどうなるか結果だけ。以下の画像2枚があります。
私です。
文字です。
私の画像watashi.jpgと、文字の画像moji.pngをImageConverterに通します。
結果です。
では、文字を浮かび上がらせてみましょう。結果の画像をSecretCharacterに通します。
こうなります。めでたしめでたし。
コード
画像に炙り出し文字を仕込む方がImageConverter.pdeで、炙り出し文字を浮かび上がらせる方がSecretCharacter.pdeです。まずはImageConverter.pdeから。
PImage textimg; PImage img; int pos; color c; String textimgName = "moji.png"; String imgName = "watashi.jpg"; //set text color (0, 0, 0). //and it background color must be (255, 255, 255); void setup() { size(768, 1024); //same image size background(255, 255, 255); noStroke(); } void draw() { int buffsize = width * height; int[] flag = new int[buffsize]; textPosition(flag); background(255); RePixel(flag); save("sample_out.png"); noLoop(); } void textPosition(int[] f) { textimg = loadImage(textimgName); image(textimg, 0, 0); loadPixels(); //pixel check for(int y = 0 ; y < height ; y++){ for(int x = 0; x < width ; x++){ pos = x + y * width; c = pixels[pos]; //when black(0, 0, 0) if(red(c) == 0 && blue(c) == 0 && green(c) == 0) { f[pos] = 1; } else { f[pos] = 0; } } } } void RePixel(int[] f) { img = loadImage(imgName); image(img, 0, 0); loadPixels(); for(int y = 0 ; y < height ; y++){ for(int x = 0; x < width ; x++){ pos = x + y * width; c = pixels[pos]; int r = (int)red(c); int g = (int)green(c); int b = (int)blue(c); if(f[pos] == 1) { r = r - (r % 2) + 1; g = g - (g % 2) + 1; b = b - (b % 2) + 1; } else { r = r - (r % 2); g = g - (g % 2); b = b - (b % 2); } fill(r,g,b); rect(x, y, 1, 1); } } } void mousePressed() { float r, g, b; color c = color(mouseX, mouseY); r = red(c); g = green(c); b = blue(c); println(r + ", " + g + ", " + b); } void test() { loadPixels(); for(int y = 0 ; y < height ; y++){ for(int x = 0; x < width ; x++){ int pos = x + y * width; color c = pixels[pos]; //when black(0, 0, 0) int r = (int)red(c); int g = (int)green(c); int b = (int)blue(c); int a = (int)alpha(c); if(r % 2 == 1 && g % 2 == 1 && b % 2 == 1 ) { fill(255, 255, 255); } else { fill(r,g,b,a / 2); } rect(x, y, 1, 1); } } }
ImageConverter.pdeでは、watashi.jpgに、moji.pngにおいて描かれている文字を仕込む作業をします。それぞれの画像は同サイズである必要があります。ここでいう”文字”とは、「moji.pngにおいて、画像内のRGB値が0,0,0である部分」を言います。文字の座標をtextPosition()で取得し、その情報を基にして、RePixel()でwatashi.jpgの各ピクセル情報を書き換え、sample_out.pngとしてImageConverter.pdeと同一階層の場所に吐き出します。
PImage img; String imgName = "sample_out.png"; int graylevel = 40; void setup() { size(768, 1024); background(255, 255, 255); noStroke(); } void draw() { img = loadImage(imgName); image(img, 0, 0); loadPixels(); for(int y = 0 ; y < height ; y++){ for(int x = 0; x < width ; x++){ int pos = x + y * width; color c = pixels[pos]; //when black(0, 0, 0) int r = (int)red(c); int g = (int)green(c); int b = (int)blue(c); int a = (int)alpha(c); if(r % 2 == 1 && g % 2 == 1 && b % 2 == 1 ) { fill(255, 255, 255); println("done"); } else { fill(r - graylevel, g - graylevel, b - graylevel); } rect(x, y, 1, 1); } } save("sample_result.png"); noLoop(); }
SecretCharacter.pdeでは、画像に仕込まれた炙り出し文字を表示させます。このとき、文字が仕込まれている部分以外のピクセルを少し暗くするようにしています。出力結果はsample_result.pngとして吐き出されます。
仕組み
仕組みとしては単純です。ImageConverter.pdeでは、文字を仕込む部分のピクセルのRGB値を全て奇数にします。仕込まない部分は偶数です。したがって、出力結果であるsample_out.pngの色合いは、元画像に比べて微妙に変わっているものになります。あと、出力画像のファイル形式はjpgじゃダメです。ピクセルの情報が意図したものにならないっぽい。
SecretCharacter.pdeでは、渡された画像の各ピクセルのRGB情報を読み取って、奇数の部分は白く、偶数の部分はちょっと暗く、といった感じで補正をかけます。何も仕込まれてない画像を渡したら、多分所々に白い点があいて、他の部分が暗くなっている画像が出力されるでしょう。
効果
効果…まあ一見変哲のない画像が送られたけど、SecretCharacterに通したら「I love you.」みたいな遊びには使えるんじゃないすかね(笑)。あとは、SNSとかで相手に連絡を取り合うときにこれ使ったら、誰かに端末見られても、一見何の証拠もないように見えます。まあいちいち文字仕込んで送る手間考えたらもっといい方法あると思いますが。
つまるところ、遊びで作ったプログラムですね。ちゃんといい勉強にはなってます。大丈夫です。いけます。
追記(2016/09/19)
実はこれ、焼き直し記事です。私は大学で学部の展示会を行う学生団体に所属しており、そこでの企画の一環でこの題材を扱った記事を去年に書いたりしました。まあ、元記事と比べて変わったことと言えば、githubにpushしてリンクを張ったことと、画像を差し替えたことぐらいですね。