ブレンドモードとPixel Bender
WCAN mini ActionScript Vol.12での発表したものです。
PhotoshopにあってFlashにないブレンドモード(Photoshopでは描画モード)の中で、自分がよく使うソフトライトをAS(とPixel Bender)で実装しましたというお話をさせていただきました。
まずは、紹介させていただいた本。
参加者から頂いたアンケートでPsとFlの色の扱いの違いを確認しろいわれて気づいたことがありました。
発表中に双方での結果を比較して「微妙に違う」とかありましたが、よく考えたら、Psのほうで「色の校正」にチェック入れるの忘れてました。(頂いたアンケートの指摘がこれかどうかは知りません)
Mac環境ではWebと同じ色表示にするのには必須ですよね。Flash以外でも。
よく忘れるんです。これ。
まー、でも、実際、「色の校正」にチェック入れても微妙に色違うんです。
目で比較してもわからないぐらい微妙で、ピクセルごとに調べないとわからないレベルではあります。
PsとFlで共通にあるオーバーレイで比較しても微妙に色違うので、目を瞑る方向で。
流れとしては、ASonlyで実装したものをお見せして、ちょいモーションさせるには遅いですよね。
ですからPixel Benderでやってみました。→ おお、動く!
という感じでやりたかったのに、ASonlyバージョンが動かず。
仕様を変えようかなとちょい弄ったところでやめて、そのままにしてたの忘れてた。
最下部より修正したASファイルやPixel Bender関連ファイルをダウンロードできます。
一応ASonly版は、ソフトライト以外に、覆い焼き、焼き込み、色相とあります。
色相は、Psとまったく違う結果になるので、何か間違えてるかもだけど、面白いのでそのままに。
紹介した本にはブレンドモードだけでなく、Pixel Benderの章もあるので、この先読み進んで何か修正できそうならするかも。
//AS3/////////////////////////////////////////////////////////////////////////// // // Photoshop SoftLightクラス // //////////////////////////////////////////////////////////////////////////////// package net.eternitydesign.Graphics { import flash.display.Bitmap; import flash.display.BitmapData; import flash.geom.Rectangle; import sketchbook.colors.ColorUtil; public class ExtendBlendMode { public function ExtendBlendMode() { } //--------------------------------------- // 指定されたBitmapDataを元にSoftLight処理されたBitmapを返す //--------------------------------------- public function getImage(m:String, topBitmap:BitmapData, bottomBitmap:BitmapData, rect:Rectangle = null):BitmapData { var tempColorTop:uint; var tempColorBottom:uint; var red:uint; var green:uint; var blue:uint; var resultColor:uint; var resultBitmapData = new BitmapData(bottomBitmap.width, bottomBitmap.height, true, 0xff000000); for(var i:Number = 0; i < topBitmap.width; i++) { for(var j:Number = 0; j < topBitmap.height; j++) { tempColorTop = topBitmap.getPixel(i, j); tempColorBottom = bottomBitmap.getPixel(i, j); if(rect != null) { if(rect.contains(i, j)) { red = this[m]((tempColorTop >> 16), (tempColorBottom >> 16)); green = this[m]((tempColorTop >> 8 & 0xff), (tempColorBottom >> 8 & 0xff)); blue = this[m]((tempColorTop & 0xff), (tempColorBottom & 0xff)); resultColor = (red << 16) | (green << 8) | (blue); } else { resultColor = tempColorBottom; } } else { red = this[m]((tempColorTop >> 16), (tempColorBottom >> 16)); green = this[m]((tempColorTop >> 8 & 0xff), (tempColorBottom >> 8 & 0xff)); blue = this[m]((tempColorTop & 0xff), (tempColorBottom & 0xff)); resultColor = (red << 16) | (green << 8) | (blue); } resultBitmapData.setPixel(i, j, resultColor); } } return resultBitmapData; } //--------------------------------------- // ソフトライトモード //--------------------------------------- public function softLight(topPixel:uint, bottomPixel:uint):uint { var color:uint; if(topPixel < 128) { //Pn = ( Pa / 255 ) ^ ( ( 255 - Pb ) / 128 ) * 255 color = Math.pow((bottomPixel / 255), ((255 - topPixel) / 128)) * 255; } else { //( Pa / 255 ) ^ ( 128 / Pb ) * 255 color = Math.pow((bottomPixel / 255), (128 / topPixel)) * 255; } return color; } //--------------------------------------- // 覆い焼き //--------------------------------------- public function dodge(topPixel:uint, bottomPixel:uint):uint { var color:uint; if(topPixel == 255) { color = 255; } else { // Pn = Pa * 255 / ( 255 - Pb ) color = Math.min(255, bottomPixel * 255 / (255 - topPixel)); } return color; } //--------------------------------------- // 焼き込み //--------------------------------------- public function burn(topPixel:uint, bottomPixel:uint):uint { var color:uint = 0; if(topPixel != 0) { //Pn = 255 - (( 255 - Pa ) * 255 / Pb )) color = Math.max(0, 255 - ((255 - bottomPixel) * 255 / topPixel)); } return color; } //--------------------------------------- // 色相 //--------------------------------------- public function hue(topBitmap:BitmapData, bottomBitmap:BitmapData, rect:Rectangle = null):BitmapData { var tempColorTop:uint; var tempColorBottom:uint; var topHue:int; var bottomHLS:Object; var bottomRGB:Object; var resultColor:uint; var resultBitmapData = new BitmapData(bottomBitmap.width, bottomBitmap.height, true, 0xff000000); for(var i:Number = 0; i < topBitmap.width; i++) { for(var j:Number = 0; j < topBitmap.height; j++) { tempColorTop = topBitmap.getPixel(i, j); tempColorBottom = bottomBitmap.getPixel(i, j); if(rect != null) { if(rect.contains(i, j)) { topHue = ColorUtil.RGB2HLS((tempColorTop >> 16), (tempColorTop >> 8 & 0xff), (tempColorTop & 0xff)).h; bottomHLS = ColorUtil.RGB2HLS((tempColorBottom >> 16), (tempColorBottom >> 8 & 0xff), (tempColorBottom & 0xff)); bottomRGB = ColorUtil.HLS2RGB(topHue, bottomHLS.l, bottomHLS.s); resultColor = (bottomRGB.r << 16) | (bottomRGB.g << 8) | (bottomRGB.b); } else { resultColor = tempColorBottom; } } else { topHue = ColorUtil.RGB2HLS((tempColorTop >> 16), (tempColorTop >> 8 & 0xff), (tempColorTop & 0xff)).h; bottomHLS = ColorUtil.RGB2HLS((tempColorBottom >> 16), (tempColorBottom >> 8 & 0xff), (tempColorBottom & 0xff)); bottomRGB = ColorUtil.HLS2RGB(topHue, bottomHLS.l, bottomHLS.s); resultColor = (bottomRGB.r << 16) | (bottomRGB.g << 8) | (bottomRGB.b); } resultBitmapData.setPixel(i, j, resultColor); } } return resultBitmapData; } } }
以下のサイトを参考にさせていただきました。
http://ofo.jp/osakana/cgtips/blendmode.phtml
http://www21.atwiki.jp/submarine/pages/56.html