どうも。
いち、プログラマのyu1rowです。
以前に書いた、「
画像を色抜き合成する」なんですが、アンチエイリアスのかかった画像なんかではフチなんかが汚くなってしまいます。
まぁ、「白」と決めたら「白」のみが透明になって、「白っぽい色」はそのままになるんで 当然なんですが。
そこで、「っぽい色」も透明になるように改良します。
「っぽい色」はどう判定する?
色を「赤・緑・青」の三要素で考えるとき、ある色と「近い」かどうかは、その距離を計算することで何とかなりそう。
2つの値の距離は中学生頃に習った「ピタゴラスの定理」が使えると思う。
2次元、3次元どちらにしてもこの定理で距離が求められるので、「赤、緑、青」を3次元空間の座標とみなして、調べる色と、期待する色がその空間でどの程度の距離なのかを以下のように計算。
- 距離 = sqrt( (r1-r2)^2 + (g1-g2)^2 + (b1-b2)^2 )
※sqrt : 平方根
※r1, r2 : 画像の特定のピクセル、色抜きする色の赤成分の値(0~255)
※g1, g2 : 画像の特定のピクセル、色抜きする色の緑成分の値(0~255)
※b1, b2 : 画像の特定のピクセル、色抜きする色の青成分の値(0~255)
閾(しきい)値についてちょっと
距離が一定(閾値)以下の場合、「っぽい色」と判定して距離に応じて重ね合わせる画像に透明度を設定して完了!
これでうまくいったかな?って思って、適当な画像を重ね合わせてみたトコロ...まぁまぁうまくいってそう?
閾値を画像によってイイカンジに計算できりゃ完璧なんでしょうが、まぁメンドイので今回は以下のカンジで妥協。
- 閾値 = ( (255^2) * 3 ) * 0.5
言葉にすると、「一番遠い色」を100%としたときに、50%までの距離を閾値とする、ってカンジです。
何はともあれ、今回のソースっす!
package com.yu1row.blog;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.MemoryImageSource;
import java.awt.image.PixelGrabber;
public class Practice03 {
/**
* 画像同士を合成した {@link BufferedImage} を取得します。
* <p>
* 元画像に対し、重ね合わせ画像の特定の色を透明にし、
* 元画像に重ね合わせて合成画像を得ます。
* </p>
* @param src 元画像
* @param lay 重ね合わせ画像
* @param alphaColor 重ね合わせ画像の透明色
* @return 合成画像
*/
public static BufferedImage getOverlayedImage(
BufferedImage src, BufferedImage lay, Color alphaColor) {
if (src == null) { return null; } // 元画像がnullの場合
if (lay == null) { return src; } // 重ね合わせ画像がnullの場合
// 色抜きされたイメージを作成(近似割合50%)
Image alphaImage = getTransparentImage(lay, alphaColor, 0.5);
// 結果画像を作成
// 元画像のクローンに、色抜きイメージを描画してオーバーレイを行う
BufferedImage result = getBufferedImageClone(src);
Graphics g = result.getGraphics();
g.drawImage(alphaImage,0,0,null);
g.dispose();
return result;
}
/**
* {@code src} の {@code alphaColor} に近い色を透明色にします。
* <p>
* {@code alphaColor} に近い色が透明色となります。
* {@code transRate} に指定された割合で近似色と判断されます。
* </p>
* @param src 透明色を設定するイメージ
* @param alphaColor 透明色にする色
* @param transRate 近似割合(0.0~1.0)
* @return 透明色が設定されたイメージ
*/
private static Image getTransparentImage(
BufferedImage src, Color alphaColor, double transRate) {
if (src == null) { return null; } // 元画像がnullの場合
// 閾値
final double threshold = ( Math.pow(255.0, 2.0) * 3 ) * transRate;
// 画像のサイズ取得
int width = src.getWidth();
int height = src.getHeight();
// 画像→int[]変換
int[] pixel = new int[width*height];
PixelGrabber pg = new PixelGrabber(src,0,0,width,height,pixel,0,width);
try { pg.grabPixels(); } catch (InterruptedException e) { return null; }
// 画像の色抜き(透明度設定)
for(int i = 0; i < width * height; ++i) {
// 色の距離を計算
int r = (pixel[i] >> 16) & 0xFF;
int g = (pixel[i] >> 8) & 0xFF;
int b = (pixel[i] >> 0) & 0xFF;
double distance =
Math.pow((double)(r - alphaColor.getRed()), 2.0) +
Math.pow((double)(g - alphaColor.getGreen()), 2.0) +
Math.pow((double)(b - alphaColor.getBlue()), 2.0);
// 閾値を超えていなければ、透明度設定
if (distance < threshold) {
double ratio = (threshold - distance) / threshold;
pixel[i] ^= (int)(255.0*ratio) << 24;
}
}
// 色抜きされたイメージを作成
return Toolkit.getDefaultToolkit().createImage(
new MemoryImageSource(width, height, pixel, 0, width));
}
/**
* {@link BufferedImage} のクローンを取得します。
* @param src 元の {@link BufferedImage} オブジェクト
* @return {@link BufferedImage} のクローン
*/
private static BufferedImage getBufferedImageClone(BufferedImage src) {
if (src == null) { return null; } // 元画像がnullの場合
// 同サイズ、同タイプのBufferedImageを作成
BufferedImage dst = new BufferedImage(
src.getWidth(), src.getHeight(), src.getType());
// クローンに描画
Graphics g = dst.getGraphics();
g.drawImage(src, 0, 0, src.getWidth(), src.getHeight(), null);
g.dispose();
return dst;
}
}
※平方根を計算する Math.sqrt() は、前述の説明では出てきましたが、処理が重いので使ってません。
まぁ自身ねーんですよ、コレ
前にも書いたとおり、一応実現しましたがね、歯車の再開発してるようにしか思えないんですよ。絶対とっくに簡単に実現できるAPIとかあると思う。
もっと素晴らしく簡単に実現できる方法、誰か知ってたら教えてください。お願い。