/**
* License: GPL
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package ini.trakem2.display.graphics;
import java.awt.Composite;
import java.awt.CompositeContext;
import java.awt.RenderingHints;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import mpicbg.util.Matrix3x3;
import mpicbg.util.Util;
/**
*
* @author Stephan Saalfeld saalfeld@mpi-cbg.de
* @version 0.1b
*/
public class ColorYCbCrComposite implements Composite
{
static private interface Composer
{
public void compose( final int[] src, final int[] dst, final float alpha );
}
final static private class ARGB2ARGB implements Composer
{
final float[] srcYCbCr = new float[ 3 ];
final float[] dstYCbCr = new float[ 3 ];
final public void compose( final int[] src, final int[] dst, final float alpha )
{
rgb2ycbcr( src, srcYCbCr );
rgb2ycbcr( dst, dstYCbCr );
final float srcAlpha = src[ 3 ] / 255.0f * alpha;
final float dstAlpha = 1.0f - srcAlpha;
dstYCbCr[ 1 ] = srcYCbCr[ 1 ] * srcAlpha + dstYCbCr[ 1 ] * dstAlpha;
dstYCbCr[ 2 ] = srcYCbCr[ 2 ] * srcAlpha + dstYCbCr[ 2 ] * dstAlpha;
ycbcr2rgb( dstYCbCr, dst );
dst[ 3 ] = 255;
}
}
final static private class RGB2ARGB implements Composer
{
final float[] srcYCbCr = new float[ 3 ];
final float[] dstYCbCr = new float[ 3 ];
final public void compose( final int[] src, final int[] dst, final float alpha )
{
rgb2ycbcr( src, srcYCbCr );
rgb2ycbcr( dst, dstYCbCr );
final float dstAlpha = 1.0f - alpha;
dstYCbCr[ 1 ] = srcYCbCr[ 1 ] * alpha + dstYCbCr[ 1 ] * dstAlpha;
dstYCbCr[ 2 ] = srcYCbCr[ 2 ] * alpha + dstYCbCr[ 2 ] * dstAlpha;
ycbcr2rgb( dstYCbCr, dst );
dst[ 3 ] = 255;
}
}
final static private class Gray2ARGB implements Composer
{
final public void compose( final int[] src, final int[] dst, final float alpha )
{
final float dstAlpha = 1.0f - alpha;
final float l = 0.299f * dst[ 0 ] + 0.587f * dst[ 1 ] + 0.114f * dst[ 2 ];
dst[ 0 ] = Math.max( 0, Math.min( 255, Util.round( l * alpha + dst[ 0 ] * dstAlpha ) ) );
dst[ 1 ] = Math.max( 0, Math.min( 255, Util.round( l * alpha + dst[ 1 ] * dstAlpha ) ) );
dst[ 2 ] = Math.max( 0, Math.min( 255, Util.round( l * alpha + dst[ 2 ] * dstAlpha ) ) );
dst[ 3 ] = 255;
}
}
final static float[] rgb2ycbcr = new float[]{
0.299f, 0.587f, 0.114f,
-0.168736f, -0.331264f, 0.5f,
0.5f, -0.418688f, -0.081312f };
final static float[] ycbcr2rgb = rgb2ycbcr.clone();
static { try{ Matrix3x3.invert( ycbcr2rgb ); } catch ( Exception e ){} }
static private ColorYCbCrComposite instance = new ColorYCbCrComposite();
final private float alpha;
public static ColorYCbCrComposite getInstance( final float alpha )
{
if ( alpha == 1.0f ) { return instance; }
return new ColorYCbCrComposite( alpha );
}
private ColorYCbCrComposite()
{
this.alpha = 1.0f;
}
/**
* Transforms RGB into YCbCr
* @param rgb r[0,255], g[0,255], b[0,255], ...
* @param ycbcr y[0,1.0], cb[-0.5,0.5], cr[-0.5,0.5], ...
*/
final static private void rgb2ycbcr( final int[] rgb, final float[] ycbcr )
{
final float r = rgb[ 0 ] / 255.0f;
final float g = rgb[ 1 ] / 255.0f;
final float b = rgb[ 2 ] / 255.0f;
ycbcr[ 0 ] = rgb2ycbcr[ 0 ] * r + rgb2ycbcr[ 1 ] * g + rgb2ycbcr[ 2 ] * b;
ycbcr[ 1 ] = rgb2ycbcr[ 3 ] * r + rgb2ycbcr[ 4 ] * g + rgb2ycbcr[ 5 ] * b;
ycbcr[ 2 ] = rgb2ycbcr[ 6 ] * r + rgb2ycbcr[ 7 ] * g + rgb2ycbcr[ 8 ] * b;
}
/**
* Transforms YCbCr into RGB
* @param ycbcr y[0,1.0], cb[-0.5,0.5], cr[-0.5,0.5], ...
* @param rgb r[0,255], g[0,255], b[0,255], ...
*/
final static private void ycbcr2rgb( final float[] ycbcr, final int[] rgb )
{
final float r = ycbcr2rgb[ 0 ] * ycbcr[ 0 ] + ycbcr2rgb[ 1 ] * ycbcr[ 1 ] + ycbcr2rgb[ 2 ] * ycbcr[ 2 ];
final float g = ycbcr2rgb[ 3 ] * ycbcr[ 0 ] + ycbcr2rgb[ 4 ] * ycbcr[ 1 ] + ycbcr2rgb[ 5 ] * ycbcr[ 2 ];
final float b = ycbcr2rgb[ 6 ] * ycbcr[ 0 ] + ycbcr2rgb[ 7 ] * ycbcr[ 1 ] + ycbcr2rgb[ 8 ] * ycbcr[ 2 ];
rgb[ 0 ] = Math.max( 0, Math.min( 255, Math.round( r * 255 ) ) );
rgb[ 1 ] = Math.max( 0, Math.min( 255, Math.round( g * 255 ) ) );
rgb[ 2 ] = Math.max( 0, Math.min( 255, Math.round( b * 255 ) ) );
}
private ColorYCbCrComposite( final float alpha )
{
this.alpha = alpha;
}
public CompositeContext createContext( ColorModel srcColorModel, ColorModel dstColorModel, RenderingHints hints )
{
final Composer c;
if ( srcColorModel.getNumColorComponents() > 1 )
{
if ( srcColorModel.hasAlpha() )
c = new ARGB2ARGB();
else
c = new RGB2ARGB();
}
else
c = new Gray2ARGB();
return new CompositeContext()
{
private Composer composer = c;
public void compose( Raster src, Raster dstIn, WritableRaster dstOut )
{
final int[] srcPixel = new int[ 4 ];
final int[] dstInPixel = new int[ 4 ];
for ( int x = 0; x < dstOut.getWidth(); x++ )
{
for ( int y = 0; y < dstOut.getHeight(); y++ )
{
src.getPixel( x, y, srcPixel );
dstIn.getPixel( x, y, dstInPixel );
composer.compose( srcPixel, dstInPixel, alpha );
dstOut.setPixel( x, y, dstInPixel );
}
}
}
public void dispose()
{}
};
}
}