blob: f6e51824da1ff03e13e803325015630daf422afc [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003, 2008 IBM Corporation and Others
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Junji MAEDA - initial API and implementation
*******************************************************************************/
package org.eclipse.actf.visualization.engines.lowvision.operator;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
import java.util.HashMap;
import org.eclipse.actf.visualization.engines.lowvision.LowVisionException;
import org.eclipse.actf.visualization.engines.lowvision.color.ColorException;
import org.eclipse.actf.visualization.engines.lowvision.color.ColorIRGB;
import org.eclipse.actf.visualization.engines.lowvision.color.ColorSRGB;
import org.eclipse.actf.visualization.engines.lowvision.color.ColorYXY;
import org.eclipse.actf.visualization.engines.lowvision.image.Int2D;
/*
* simulate color vision deficiency
*/
public class CVDOp implements ILowVisionOperator{
// keep Y(0.299R+0.587G+0.114B) in sRGB (I in HSI) or not
// false is better
private static final boolean PRESERVE_SRGB_Y = false;
// see ColorValue.java
// these values are for (Y,x,y) (not for (x,y,z))
// (Y,x,y)space : (x,y,z)space
// Y(YY) : y
// x : x/(x+y+z)
// y : y/(x+y+z)
// sRGB->CIEXYZ->Yxy
@SuppressWarnings("unused")
private static final float R_YY = 0.21347046f,
R_X = 0.6524872f,
R_Y = 0.32519758f,
G_YY = 0.71173096f,
G_X = 0.3305722f,
G_Y = 0.5944182f,
B_YY = 0.07168579f,
B_X = 0.1482494f,
B_Y = 0.07736899f,
W_YY = 0.9963379f,
W_X = 0.34579438f,
W_Y = 0.35854465f;
private static final float C1_X = 0.747f; // Protan line
private static final float C1_Y = 0.253f; //
private static final float C2_X = 1.080f; // Deutan line
private static final float C2_Y = -0.080f; //
private static final float C3_X = 0.171f; // Tritan line
private static final float C3_Y = 0.000f; //
private static float BW_A = (B_Y-W_Y)/(B_X-W_X);// blue-white line in (x,y)
private static float BW_B = B_Y-BW_A*B_X;
// private static float BR_A = (B_Y-R_Y)/(B_X-R_X);//blue-red line in (x,y)
// private static float BR_B = B_Y-BR_A*B_X;
private static float BR_A = -0.5f; // blue-red line (cross white)
private static float BR_B = 0.525f; //
private int type = 0;
public CVDOp( int _type ){
type = _type;
}
public BufferedImage filter( BufferedImage _src, BufferedImage _dest ) throws LowVisionException{
if( type != 1 && type != 2 && type != 3 ){
throw new LowVisionException( "Invalid type: " + type );
}
WritableRaster srcRaster = _src.copyData( null );
DataBufferInt srcBufInt = (DataBufferInt)(srcRaster.getDataBuffer());
int[] srcArray = srcBufInt.getData();
int srcSize = srcArray.length;
BufferedImage destImage = _dest;
if( _dest == null ){
destImage = new BufferedImage( _src.getWidth(), _src.getHeight(), BufferedImage.TYPE_INT_RGB );
}
WritableRaster destRaster = destImage.copyData( null );
DataBufferInt destBufInt = (DataBufferInt)(destRaster.getDataBuffer());
int[] destArray = destBufInt.getData();
int destSize = destArray.length;
if( srcSize != destSize ){
throw new LowVisionException( "Sizes of src and dest images differ." );
}
HashMap<Integer, Integer> pixelMap = new HashMap<Integer, Integer>();
for( int i=0; i<srcSize; i++ ){
Integer srcPixel = new Integer( srcArray[i] );
Integer destPixel = null;
if( (destPixel=(Integer)(pixelMap.get(srcPixel))) == null ){
destPixel = new Integer( convertColor(srcArray[i],type) );
pixelMap.put( srcPixel, destPixel );
}
destArray[i] = destPixel.intValue();
}
destImage.setData( destRaster );
return( destImage );
} // filter( BufferedImage, BufferedImage )
public Int2D filter( Int2D _src, Int2D _dest ) throws LowVisionException{
if( type != 1 && type != 2 && type != 3 ){
throw new LowVisionException( "Invalid type: " + type );
}
Int2D destImage = _dest;
if( _dest == null ){
destImage = new Int2D( _src.width, _src.height );
}
HashMap<Integer, Integer> pixelMap = new HashMap<Integer, Integer>();
for( int j=0; j<_src.height; j++ ){
for( int i=0; i<_src.width; i++ ){
int srcColor =_src.data[j][i];
Integer srcPixel = new Integer( srcColor );
Integer destPixel = (Integer)(pixelMap.get(srcPixel));
if( destPixel == null ){
int destColor = convertColor( srcColor, type );
destImage.data[j][i] = destColor;
pixelMap.put( srcPixel, new Integer(destColor) );
}
else{
destImage.data[j][i] = destPixel.intValue();
}
}
}
return( destImage );
}
public static int convertColor( int _src, int _type ) throws LowVisionException{
try{
if( PRESERVE_SRGB_Y ){
ColorSRGB srgb = (new ColorIRGB( _src )).toSRGB();
// float preservedY = 0.299f*srgb.getR() + 0.587f*srgb.getG() +
// 0.114f*srgb.getB();
float preservedY = Math.max( srgb.getR(), Math.max( srgb.getG(), srgb.getB() ) );
ColorYXY yxy = srgb.toYXY();
// move (x,y) while keeping brightness (yy)
ColorYXY newYXY = moveXY( yxy, _type );
ColorSRGB newSRGB = newYXY.toSRGB();
float newR = newSRGB.getR();
float newG = newSRGB.getG();
float newB = newSRGB.getB();
// float newY = 0.299f*newR + 0.587f*newG + 0.114f*newB;
float newY = Math.max( newR, Math.max( newG, newB ) );
float ratio = 0.0f;
if( newY != 0.0f )
ratio = preservedY/newY;
else
ratio = 1.0f; // black
newSRGB.setR( newR*ratio );
newSRGB.setG( newG*ratio );
newSRGB.setB( newB*ratio );
return( newSRGB.toIRGB().toInt() );
}
else{
ColorIRGB irgb = new ColorIRGB( _src );
ColorYXY yxy = irgb.toYXY();
// move (x,y) while keeping brightness (yy)
ColorYXY newYXY = moveXY( yxy, _type );
ColorIRGB newIRGB = newYXY.toIRGB();
return( newIRGB.toInt() );
}
}catch( ColorException ce ){
ce.printStackTrace();
throw new LowVisionException( "ColorException occurred while converting color.");
}
}
// move (x,y) while keeping brightness (yy)
private static ColorYXY moveXY( ColorYXY _src, int _type ) throws LowVisionException, ColorException{
float srcYY = _src.getYY();
float srcX = _src.getX();
float srcY = _src.getY();
float confuseX = 0.0f;
float confuseY = 0.0f;
float locusA = 0.0f;
float locusB = 0.0f;
if( _type == 1 ){
confuseX = C1_X;
confuseY = C1_Y;
locusA = BW_A;
locusB = BW_B;
}
else if( _type == 2 ){
confuseX = C2_X;
confuseY = C2_Y;
locusA = BW_A;
locusB = BW_B;
}
else if( _type == 3 ){
confuseX = C3_X;
confuseY = C3_Y;
locusA = BR_A;
locusB = BR_B;
}
else{
throw new LowVisionException( "Invalid type : " + _type );
}
float destYY = srcYY;
float destX = 0.0f;
float destY = 0.0f;
if( confuseX != srcX ){
float confuseA = (confuseY-srcY)/(confuseX-srcX); // slope
float confuseB = confuseY - confuseA*confuseX; // y-intercept
if( confuseA != locusA ){
destX = (confuseB-locusB)/(locusA-confuseA);
destY = (locusA*confuseB-confuseA*locusB)/(locusA-confuseA);
}
else{
// TBD use polar coordinates, etc.
/*
* destX = srcX; destY = srcY; debugUtil.errMsg( this, "(x,y) is
* out of sRGB's range. (x,y) = " + srcX + ", " + srcY );
*/
throw new LowVisionException( "(x,y) is out of sRGB's range. (x,y) = " + srcX + ", " + srcY );
}
} else{
destX = confuseX;
destY = confuseX*locusA + locusB;
}
return( new ColorYXY( destYY, destX, destY ) );
}
}