/*** XY-STYLE COMPOSITOR ALGORITHM ***/
//--------------------------------------------------------------------------------------------------------------------------------
package{
//--------------------------------------------------------------------------------------------------------------------------------
import flash.display.*;
//--------------------------------------------------------------------------------------------------------------------------------
public class Compositor{
//--------------------------------------------------------------------------------------------------------------------------------
private static const LUM_R:Number=.299, LUM_G:Number=.587, LUM_B:Number=.114;
//--------------------------------------------------------------------------------------------------------------------------------
public static function parse(fill:BitmapData, outline:BitmapData, lineLumMin:Number=1/6, lineLumMax:Number=1/3):BitmapData{
CONFIG::debug{ Bug.expect(fill!=null, outline!=null, lineLumMin>=0, lineLumMax<=1, lineLumMin<=lineLumMax); }
var sizeX:int=fill.width, sizeY:int=fill.height, maxX:int=sizeX-1, maxY:int=sizeY-1, lineLumRange:Number=(lineLumMax-lineLumMin)/255;
CONFIG::debug{ Bug.expect(maxX>=0, maxY>=0, outline.width==sizeX, outline.height==sizeY, fill.transparent, outline.transparent); }//both images are the same size & transparent
var vFill:Vector.<uint>=fill.getVector(fill.rect), vOutline:Vector.<uint>=outline.getVector(outline.rect), n:int=vFill.length;
CONFIG::debug{ Bug.expect(n>=4, vOutline.length==n); }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - remove lone pixels
var x:int=0, y:int=0, prevX:int=-1, prevY:int=-1, nextX:int=1, nextY:int=1;
for(var i:int=0; i<n; i++){
var hasPrevX:Boolean=prevX>=0, hasPrevY:Boolean=prevY>=0, hasNextX:Boolean=nextX<=maxX, hasNextY:Boolean=nextY<=maxY;
if(vOutline[i]==0xff000000 || vOutline[i]==0xffffffff){//current pixel is opaque (black or white)
if(
hasPrevY && vOutline[x+prevY*sizeX]>=0xff000000//pixel above is opaque
|| hasNextY && vOutline[x+nextY*sizeX]>=0xff000000//pixel below is opaque
|| hasPrevX && vOutline[prevX+y*sizeX]>=0xff000000//pixel to the left is opaque
|| hasNextX && vOutline[nextX+y*sizeX]>=0xff000000//pixel to the right is opaque
){}else{//current pixel is a lone pixel
vFill[i]=0x00000000; vOutline[i]=0x00000000;//set to transparent
}
}else{//ignore transparent pixels
CONFIG::debug{ Bug.expect(vOutline[i]==0x00000000); }
}
if(hasNextX){
prevX=x++;
nextX=x+1;
}else{
x=0;
prevX=-1;
nextX=1;
prevY=y++;
nextY=y+1;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - processing
var layer:BitmapData=new BitmapData(sizeX, sizeY, true, 0x00000000);
x=0;
y=0;
prevX=-1;
prevY=-1;
nextX=1;
nextY=1;
for(i=0; i<n; i++){
hasPrevX=prevX>=0;
hasPrevY=prevY>=0;
hasNextX=nextX<=maxX;
hasNextY=nextY<=maxY;
if(vOutline[i]>=0xff000000){//current pixel is opaque
if(
vOutline[i]==0xff000000//current pixel is black (non-border outline)
|| hasPrevY && vOutline[x+prevY*sizeX]<0x01000000//pixel above is transparent (border outline)
|| hasNextY && vOutline[x+nextY*sizeX]<0x01000000//pixel below is transparent (border outline)
|| hasPrevX && vOutline[prevX+y*sizeX]<0x01000000//pixel to the left is transparent (border outline)
|| hasNextX && vOutline[nextX+y*sizeX]<0x01000000//pixel to the right is transparent (border outline)
){
var color:int=0xffffff, hex:int, lum:Number, lumMin:Number=256, lumTot:Number=0, weight:Number=0;//determine the dimmest surrounding color - clockwise from the top-left:
if(hasPrevX && hasPrevY && vFill[prevX+prevY*sizeX]>=0xff000000){ hex=vFill[prevX+prevY*sizeX]&0xffffff; lum=get$luminance(hex); lumTot+=lum*Math.SQRT1_2; weight+=Math.SQRT1_2; if(lum<lumMin){ lumMin=lum; color=hex; }}//top-left
if(hasPrevY && vFill[x +prevY*sizeX]>=0xff000000){ hex=vFill[ x+prevY*sizeX]&0xffffff; lum=get$luminance(hex); lumTot+=lum ; weight++ ; if(lum<lumMin){ lumMin=lum; color=hex; }}//above
if(hasNextX && hasPrevY && vFill[nextX+prevY*sizeX]>=0xff000000){ hex=vFill[nextX+prevY*sizeX]&0xffffff; lum=get$luminance(hex); lumTot+=lum*Math.SQRT1_2; weight+=Math.SQRT1_2; if(lum<lumMin){ lumMin=lum; color=hex; }}//top-right
if(hasNextX && vFill[nextX+y *sizeX]>=0xff000000){ hex=vFill[nextX+y *sizeX]&0xffffff; lum=get$luminance(hex); lumTot+=lum ; weight++ ; if(lum<lumMin){ lumMin=lum; color=hex; }}//right
if(hasNextX && hasNextY && vFill[nextX+nextY*sizeX]>=0xff000000){ hex=vFill[nextX+nextY*sizeX]&0xffffff; lum=get$luminance(hex); lumTot+=lum*Math.SQRT1_2; weight+=Math.SQRT1_2; if(lum<lumMin){ lumMin=lum; color=hex; }}//bottom-right
if(hasNextY && vFill[x +nextY*sizeX]>=0xff000000){ hex=vFill[ x+nextY*sizeX]&0xffffff; lum=get$luminance(hex); lumTot+=lum ; weight++ ; if(lum<lumMin){ lumMin=lum; color=hex; }}//below
if(hasPrevX && hasNextY && vFill[prevX+nextY*sizeX]>=0xff000000){ hex=vFill[prevX+nextY*sizeX]&0xffffff; lum=get$luminance(hex); lumTot+=lum*Math.SQRT1_2; weight+=Math.SQRT1_2; if(lum<lumMin){ lumMin=lum; color=hex; }}//bottom-left
if(hasPrevX && vFill[prevX+y *sizeX]>=0xff000000){ hex=vFill[prevX+y *sizeX]&0xffffff; lum=get$luminance(hex); lumTot+=lum ; weight++ ; if(lum<lumMin){ lumMin=lum; color=hex; }}//left
CONFIG::debug{ Bug.expect(lumMin<=255); }
layer.setPixel32(x, y, 0xff000000 | set$luminance(color, lineLumRange*lumTot/weight+lineLumMin));//averaged luminance calibrated to the settings
}
}
//update variables for next iteration
if(hasNextX){
prevX=x++;
nextX=x+1;
}else{
x=0;
prevX=-1;
nextX=1;
prevY=y++;
nextY=y+1;
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - final composite
fill.draw(layer);
return fill;
}
//--------------------------------------------------------------------------------------------------------------------------------
public static function parse$batch(fills:Vector.<BitmapData>, outlines:Vector.<BitmapData>, lineBrightnessMin:Number=1/6, lineBrightnessMax:Number=1/3):Vector.<BitmapData>{
CONFIG::debug{ Bug.expect(fills!=null, outlines!=null); }
var n:int=fills.length;
CONFIG::debug{ Bug.expect(n>0, outlines.length==n); }
var out:Vector.<BitmapData>=new Vector.<BitmapData>(n, true);
for(var i:int=0; i<n; i++){
out[i]=parse(fills[i], outlines[i], lineBrightnessMin, lineBrightnessMax);
}
return out;
}
//--------------------------------------------------------------------------------------------------------------------------------
public static function post(src:BitmapData, color:Vector.<uint>, map:Vector.<int>):void{//src=source image to be processed, color=new override colors, map=areas to override
CONFIG::debug{ Bug.expect(src!=null, color!=null, map!=null); }
var sizeX:int=src.width, colorN:int=color.length, mapN:int=map.length;
CONFIG::debug{ Bug.expect(sizeX>0, src.height>0, colorN==sizeX*src.height, mapN<=colorN); }//expect equal dimensions
for(var i:int=0; i<mapN; i++){
var pos:int=map[i];
CONFIG::debug{ Bug.expect(pos>=0, pos<colorN); }
src.setPixel32(pos%sizeX, pos/sizeX, color[pos]);
}
}
//--------------------------------------------------------------------------------------------------------------------------------
public static function post$batch(list$src:Vector.<BitmapData>, color:BitmapData, map:BitmapData):void{
CONFIG::debug{
Bug.expect(list$src!=null, color!=null, map!=null);
Bug.expect(color.transparent, !map.transparent);//map is black & white only
}
var n:int=list$src.length;
CONFIG::debug{ Bug.expect(n>0); }
var vColor:Vector.<uint>=color.getVector(color.rect), vMapRaw:Vector.<uint>=Vector.<uint>(map.getVector(map.rect)), vMap:Vector.<int>=new Vector.<int>();
for(var j:int=0, vMapN:int=0, vMapRawN:int=vMapRaw.length; j<vMapRawN; j++){
CONFIG::debug{ Bug.expect(vMapRaw[j]==0xff000000 || vMapRaw[j]==0xffffffff); }//expect only black or white
if(vMapRaw[j]==0xff000000){
vMap[vMapN++]=j;
}//black means override
}
for(var i:int=0; i<n; i++){
post(list$src[i], vColor, vMap);
}
}
//--------------------------------------------------------------------------------------------------------------------------------
[Inline] private static function get$luminance(hex:int):Number{//gets the luminance of the specified color
CONFIG::debug{ Bug.expect(hex>=0x000000, hex<=0xffffff); }
return (hex>>16)*LUM_R+(hex>>8&255)*LUM_G+(hex&255)*LUM_B;
}
//--------------------------------------------------------------------------------------------------------------------------------
[Inline] private static function set$luminance(hex:int, lum:Number):int{//sets the luminance of the specified color to the specified value (preserving hue)
CONFIG::debug{ Bug.expect(hex>=0x000000, hex<=0xffffff, lum>=0, lum<=1); }
var g:Number=(hex>>8&255)/(hex>>16), b:Number=(hex&255)/(hex>>16), r:Number=lum*255/(LUM_R+g*LUM_G+b*LUM_B);
return Math.round(r)<<16 | Math.round(g*r)<<8 | Math.round(b*r);
}
//--------------------------------------------------------------------------------------------------------------------------------
}
}