/*****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
*****************************************************************************/
package org.apache.batik.svggen;
import java.awt.Rectangle;
import java.awt.image.BufferedImageOp;
import java.awt.image.ByteLookupTable;
import java.awt.image.LookupOp;
import java.awt.image.LookupTable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Utility class that converts a LookupOp object into
* an SVG filter descriptor. The SVG filter corresponding
* to a LookupOp is an feComponentTransfer, with a type
* set to 'table', the tableValues set to the content
* of the lookup table.
*
* @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
* @version $Id: SVGLookupOp.java,v 1.14 2003/07/04 15:25:37 vhardy Exp $
* @see org.apache.batik.svggen.SVGBufferedImageOp
*/
public class SVGLookupOp extends AbstractSVGFilterConverter {
/**
* Gamma for linear to sRGB convertion
*/
private static final double GAMMA = 1./2.4;
/**
* Lookup table for linear to sRGB value
* forward and backward mapping
*/
private static final int linearToSRGBLut[] = new int[256];
private static final int sRGBToLinear[] = new int[256];
static {
for(int i=0; i<256; i++) {
// linear to sRGB
float value = i/255f;
if (value <= 0.0031308) {
value *= 12.92f;
} else {
value = 1.055f * ((float) Math.pow(value, GAMMA)) - 0.055f;
}
linearToSRGBLut[i] = Math.round(value*255);
// sRGB to linear
value = i/255f;
if(value <= 0.04045){
value /= 12.92f;
} else {
value = (float)Math.pow((value + 0.055f)/1.055f, 1/GAMMA);
}
sRGBToLinear[i] = Math.round(value*255);
}
}
/**
* @param generatorContext used to build Elements
*/
public SVGLookupOp(SVGGeneratorContext generatorContext) {
super(generatorContext);
}
/**
* Converts a Java 2D API BufferedImageOp into
* a set of attribute/value pairs and related definitions
*
* @param op BufferedImageOp filter to be converted
* @param filterRect Rectangle, in device space, that defines the area
* to which filtering applies. May be null, meaning that the
* area is undefined.
* @return descriptor of the attributes required to represent
* the input filter
* @see org.apache.batik.svggen.SVGFilterDescriptor
*/
public SVGFilterDescriptor toSVG(BufferedImageOp filter,
Rectangle filterRect) {
if (filter instanceof LookupOp)
return toSVG((LookupOp)filter);
else
return null;
}
/**
* @param lookupOp the LookupOp to be converted
* @return a description of the SVG filter corresponding to
* lookupOp. The definition of the feComponentTransfer
* filter in put in feComponentTransferDefSet
*/
public SVGFilterDescriptor toSVG(LookupOp lookupOp) {
// Reuse definition if lookupOp has already been converted
SVGFilterDescriptor filterDesc =
(SVGFilterDescriptor)descMap.get(lookupOp);
Document domFactory = generatorContext.domFactory;
if (filterDesc == null) {
//
// First time filter is converted: create its corresponding
// SVG filter
//
Element filterDef = domFactory.createElementNS(SVG_NAMESPACE_URI,
SVG_FILTER_TAG);
Element feComponentTransferDef =
domFactory.createElementNS(SVG_NAMESPACE_URI,
SVG_FE_COMPONENT_TRANSFER_TAG);
// Append transfer function for each component, setting
// the attributes corresponding to the scale and offset.
// Because we are using a LookupOp as a BufferedImageOp,
// the number of lookup table must be:
// + 1, in which case the same lookup is applied to the
// Red, Green and Blue components,
// + 3, in which case the lookup tables apply to the
// Red, Green and Blue components
// + 4, in which case the lookup tables apply to the
// Red, Green, Blue and Alpha components
String lookupTables[] = convertLookupTables(lookupOp);
Element feFuncR = domFactory.createElementNS(SVG_NAMESPACE_URI,
SVG_FE_FUNC_R_TAG);
Element feFuncG = domFactory.createElementNS(SVG_NAMESPACE_URI,
SVG_FE_FUNC_G_TAG);
Element feFuncB = domFactory.createElementNS(SVG_NAMESPACE_URI,
SVG_FE_FUNC_B_TAG);
Element feFuncA = null;
String type = SVG_TABLE_VALUE;
if(lookupTables.length == 1){
feFuncR.setAttributeNS(null, SVG_TYPE_ATTRIBUTE, type);
feFuncG.setAttributeNS(null, SVG_TYPE_ATTRIBUTE, type);
feFuncB.setAttributeNS(null, SVG_TYPE_ATTRIBUTE, type);
feFuncR.setAttributeNS(null, SVG_TABLE_VALUES_ATTRIBUTE,
lookupTables[0]);
feFuncG.setAttributeNS(null, SVG_TABLE_VALUES_ATTRIBUTE,
lookupTables[0]);
feFuncB.setAttributeNS(null, SVG_TABLE_VALUES_ATTRIBUTE,
lookupTables[0]);
}
else if(lookupTables.length >= 3){
feFuncR.setAttributeNS(null, SVG_TYPE_ATTRIBUTE, type);
feFuncG.setAttributeNS(null, SVG_TYPE_ATTRIBUTE, type);
feFuncB.setAttributeNS(null, SVG_TYPE_ATTRIBUTE, type);
feFuncR.setAttributeNS(null, SVG_TABLE_VALUES_ATTRIBUTE,
lookupTables[0]);
feFuncG.setAttributeNS(null, SVG_TABLE_VALUES_ATTRIBUTE,
lookupTables[1]);
feFuncB.setAttributeNS(null, SVG_TABLE_VALUES_ATTRIBUTE,
lookupTables[2]);
if(lookupTables.length == 4){
feFuncA = domFactory.createElementNS(SVG_NAMESPACE_URI,
SVG_FE_FUNC_A_TAG);
feFuncA.setAttributeNS(null, SVG_TYPE_ATTRIBUTE, type);
feFuncA.setAttributeNS(null, SVG_TABLE_VALUES_ATTRIBUTE,
lookupTables[3]);
}
}
feComponentTransferDef.appendChild(feFuncR);
feComponentTransferDef.appendChild(feFuncG);
feComponentTransferDef.appendChild(feFuncB);
if(feFuncA != null)
feComponentTransferDef.appendChild(feFuncA);
filterDef.appendChild(feComponentTransferDef);
filterDef.
setAttributeNS(null, ATTR_ID,
generatorContext.idGenerator.
generateID(ID_PREFIX_FE_COMPONENT_TRANSFER));
//
// Create a filter descriptor
//
// Process filter attribute
StringBuffer filterAttrBuf = new StringBuffer(URL_PREFIX);
filterAttrBuf.append(SIGN_POUND);
filterAttrBuf.append(filterDef.getAttributeNS(null, ATTR_ID));
filterAttrBuf.append(URL_SUFFIX);
filterDesc = new SVGFilterDescriptor(filterAttrBuf.toString(),
filterDef);
defSet.add(filterDef);
descMap.put(lookupOp, filterDesc);
}
return filterDesc;
}
/**
* Converts the filter's LookupTable into an array of corresponding SVG
* table strings
*/
private String[] convertLookupTables(LookupOp lookupOp){
LookupTable lookupTable = lookupOp.getTable();
int nComponents = lookupTable.getNumComponents();
if((nComponents != 1) && (nComponents != 3) && (nComponents != 4))
throw new SVGGraphics2DRuntimeException(ERR_ILLEGAL_BUFFERED_IMAGE_LOOKUP_OP);
StringBuffer lookupTableBuf[] = new StringBuffer[nComponents];
for(int i=0; i<nComponents; i++)
lookupTableBuf[i] = new StringBuffer();
if(!(lookupTable instanceof ByteLookupTable)){
int src[] = new int[nComponents];
int dest[] = new int[nComponents];
int offset = lookupTable.getOffset();
// Offsets are used for constrained sources. Therefore,
// the lookup values should never be used under offset.
// There is no SVG equivalent for this behavior.
// These values are mapped to identity.
for(int i=0; i<offset; i++){
// Fill in string buffers
for(int j=0; j<nComponents; j++){
// lookupTableBuf[j].append(Integer.toString(i));
lookupTableBuf[j].append(doubleString(i/255.));
lookupTableBuf[j].append(SPACE);
}
}
for(int i=offset; i<=255; i++){
// Fill in source array
for(int j=0; j<nComponents; j++) src[j] = i;
// Get destination values
lookupTable.lookupPixel(src, dest);
// Fill in string buffers
for(int j=0; j<nComponents; j++){
lookupTableBuf[j].append(doubleString(dest[j]/255.));
// lookupTableBuf[j].append(Integer.toString(dest[j]));
lookupTableBuf[j].append(SPACE);
}
}
}
else{
byte src[] = new byte[nComponents];
byte dest[] = new byte[nComponents];
int offset = lookupTable.getOffset();
// Offsets are used for constrained sources. Therefore,
// the lookup values should never be used under offset.
// There is no SVG equivalent for this behavior.
// These values are mapped to identity.
for(int i=0; i<offset; i++){
// Fill in string buffers
for(int j=0; j<nComponents; j++){
// lookupTableBuf[j].append(Integer.toString(i));
lookupTableBuf[j].append(doubleString(i/255.));
lookupTableBuf[j].append(SPACE);
}
}
for(int i=0; i<=255; i++){
// Fill in source array
for(int j=0; j<nComponents; j++) {
src[j] = (byte)(0xff & i);
}
// Get destination values
((ByteLookupTable)lookupTable).lookupPixel(src, dest);
// Fill in string buffers
for(int j=0; j<nComponents; j++){
lookupTableBuf[j].append(doubleString((0xff & dest[j])/255.));
lookupTableBuf[j].append(SPACE);
}
}
}
String lookupTables[] = new String[nComponents];
for(int i=0; i<nComponents; i++)
lookupTables[i] = lookupTableBuf[i].toString().trim();
/*for(int i=0; i<lookupTables.length; i++){
System.out.println(lookupTables[i]);
}*/
return lookupTables;
}
}