はじめに
前回に引き続き、変なプログラムの作成方法について解説していきます。
今回は、条件によって見え方の異なる画像を作成するプログラムを紹介します。
サンプルを見てみる
Qiita上では対策されているので、対策されていないTwitterでの投稿を載せますので、確認してみてください。
人によって、「初音ミク」が見える人と、「鏡音リン」が見える人がいると思います。
仕組み
仕組みは単純で、後ろに黒背景が来るか白背景が来るかを考えて、透過度を設定しているだけです。
なので、一部のサイトでは、ライトモードかダークモードかで、表示させる画像を変えることができます。
また、ローカル上で、ファイルを開くアプリケーションによっても、表示される画像が変わります。
変数の個数の制限で、グレースケール画像しか対応できません。
計算方法
変数定義
※RGB値はbyte型だと仮定します。(割り算の結果は切り捨て)
${R_1 , G_1 , B_1 ...}$黒背景のときに表示させたい画像のある1ピクセルのRGB値
${R_2 , G_2 , B_2 ...}$白背景のときに表示させたい画像のある1ピクセルのRGB値
${A_3, R_3 , G_3 , B_3 ...}$白背景のときに表示させたい画像のある1ピクセルのARGB値
${X_1, X_2...}$一時的な変数
1.RGBの和を6で割る
2つの画像の全ピクセルにおいて、RGBの和を6で割ります。
細かく書くと、RGBの平均を求めるために3で割り、後述する作業のために、さらに2で割ります。
$${X_1=\frac{R_1+G_1+B_1}{6}}$$
$${X_2=\frac{R_2+G_2+B_2}{6}}$$
2.片方の画像の全ピクセルに128を足す
先ほど求めた各ピクセルの値のうち、白背景で表示させたい画像のピクセルに128を足します。
$${X_2 += 128}$$
これにより、全ピクセルにおいて、つねに
$${X_1<X_2}$$
とすることができ、後で計算する透過度が0以下になるのを防ぎます。
3.合成後の各ピクセルのRGBAを計算する。
以下の計算をします。
$${A_3=X_2-X_1}$$
$${R_3=G_3=B_3=\frac{255X_1}{A_3}}$$
※つねに${0<A_3}$なので、場合分けの必要なし
プログラム
Visual Studioの、フォームアプリケーション(NET.Framework)で作成してください。
namespace名を変えてコピペすれば動くと思います...
昔作ったもので、透過度をdouble型で計算していますが問題ありません。
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Forms;
namespace Sample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
RGBtoARGB("Q1A.jpg", 1, Color.Black,false);
GrayPic("img1.png", "img2.png");
}
public void GrayPic(string source, string source2)
{
Bitmap source_b = new Bitmap(source);
Bitmap source2_b = new Bitmap(new Bitmap(source2),new Size(source_b.Width,source_b.Height));
Rectangle rect = new Rectangle(0, 0, source_b.Width, source_b.Height);
BitmapData source_data = source_b.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
BitmapData source2_data = source2_b.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
try
{
Bitmap canvas = new Bitmap(source_data.Width, source_data.Height);
byte[] imgdata = new byte[source_data.Stride * source_data.Height];
byte[] imgdata2 = new byte[source2_data.Stride * source2_data.Height];
System.Runtime.InteropServices.Marshal.Copy(source_data.Scan0, imgdata, 0, imgdata.Length);
System.Runtime.InteropServices.Marshal.Copy(source2_data.Scan0, imgdata2, 0, imgdata2.Length);
int argb;
for (int y = 0; y < source_data.Height; ++y)
{
for (int x = 0; x < source_data.Width; ++x)
{
argb = 0;
byte[] gray = { 0, 0 };
int pos = y * source_data.Stride + x * 4; //Format32bppArgbは1ピクセル4バイト
gray[0] = (byte)((imgdata[pos] + imgdata[pos + 1] + imgdata[pos + 2]) / 6.0);
gray[1] = (byte)((imgdata2[pos] + imgdata2[pos + 1] + imgdata2[pos + 2]) / 6.0);
gray[1] += 127;
double alpha = 1 - Math.Abs(gray[0] - gray[1]) / 255.0;
for (int i = 0;i < 3; ++i)
{
argb += (byte)(gray[0] / alpha) << (8 * i);
}
argb += (byte)(alpha * 255) << 24;
canvas.SetPixel(x, y, Color.FromArgb(argb));
}
}
canvas.Save(AppDomain.CurrentDomain.BaseDirectory + Path.GetFileNameWithoutExtension(source) + "x" + Path.GetFileNameWithoutExtension(source2) + ".png", System.Drawing.Imaging.ImageFormat.Png);
}
finally
{
source_b.UnlockBits(source_data);
}
source_b.Dispose();
}
}
}
まとめ
今回は、条件によって見え方の異なる画像を作成について紹介してみました。
初心者で、おもしろいなと感じて、プログラミングに興味を持っていただけると幸いです。