/*
* Copyright (c) 2016, Metron, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Metron, Inc. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL METRON, INC. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.metsci.glimpse.dnc.geosym;
import static com.google.common.base.Charsets.UTF_8;
import static com.metsci.glimpse.util.GeneralUtils.ints;
import static com.metsci.glimpse.util.io.StreamOpener.resourceOpener;
import static com.metsci.glimpse.util.units.Length.millimetersToInches;
import static java.awt.RenderingHints.KEY_ANTIALIASING;
import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
import static java.lang.Double.parseDouble;
import static java.lang.Integer.parseInt;
import static java.lang.Math.max;
import static java.lang.Math.round;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.io.CharStreams;
import com.kitfox.svg.SVGDiagram;
import com.kitfox.svg.SVGException;
import com.kitfox.svg.SVGUniverse;
import com.metsci.glimpse.dnc.util.AnchoredImage;
import com.metsci.glimpse.util.io.StreamOpener;
public class DncGeosymImageUtils
{
/**
* The SVG coordinates were generated in 1/100th pixel units assuming this DPI.
*/
public static final double geosymSvgDpi = 300;
public static final Color transparentWhite = new Color( 255, 255, 255, 0 );
public static AnchoredImage loadGeosymImage( String symbolId, String cgmDir, String svgDir, double screenDpi ) throws IOException, SVGException
{
String cgmLocation = cgmDir + "/" + symbolId + ".cgm";
String cgmText = loadAsString( resourceOpener, cgmLocation, UTF_8 );
int[] cgmBounds = extractCgmBounds( cgmText, cgmLocation );
double cgmScale = extractCgmScale( cgmText, cgmLocation );
String svgLocation = svgDir + "/" + symbolId + ".svg";
String svgText = removeSvgTransparency( loadAsString( resourceOpener, svgLocation, UTF_8 ) );
String svgName = "geosym-" + symbolId;
SVGUniverse svgUniverse = new SVGUniverse( );
SVGDiagram svgDiagram = svgUniverse.getDiagram( svgUniverse.loadSVG( new StringReader( svgText ), svgName ) );
svgDiagram.setIgnoringClipHeuristic( true );
// cgm-units * cgm-scale gives millimeters, and inches * screen-dpi gives screen-pixels
double cgmUnitsToScreenPixels = cgmScale * millimetersToInches * screenDpi;
double svgUnitsToScreenPixels = screenDpi / geosymSvgDpi;
int iAnchor = ( int ) round( -cgmBounds[ 0 ] * cgmUnitsToScreenPixels );
int jAnchor = ( int ) round( -cgmBounds[ 1 ] * cgmUnitsToScreenPixels );
int width = max( 1, ( int ) round( svgDiagram.getWidth( ) * svgUnitsToScreenPixels ) );
int height = max( 1, ( int ) round( svgDiagram.getHeight( ) * svgUnitsToScreenPixels ) );
BufferedImage image = new BufferedImage( width, height, TYPE_INT_ARGB );
Graphics2D g = image.createGraphics( );
try
{
g.setBackground( transparentWhite );
g.clearRect( 0, 0, width, height );
g.setRenderingHint( KEY_ANTIALIASING, VALUE_ANTIALIAS_ON );
g.scale( svgUnitsToScreenPixels, svgUnitsToScreenPixels );
svgDiagram.render( g );
return new AnchoredImage( image, iAnchor, jAnchor );
}
finally
{
g.dispose( );
}
}
public static String loadAsString( StreamOpener opener, String location, Charset charset ) throws IOException
{
InputStream in = null;
try
{
in = opener.openForRead( location );
return CharStreams.toString( new InputStreamReader( in, charset ) );
}
finally
{
if ( in != null ) in.close( );
}
}
public static final Pattern cgmBoundsPattern = Pattern.compile( "VDCEXT\\s([\\+|-]?\\d+)\\s([\\+|-]?\\d+)\\s([\\+|-]?\\d+)\\s([\\+|-]?\\d+);" );
public static int[] extractCgmBounds( String cgmText, String cgmLocation ) throws SVGException
{
Matcher m = cgmBoundsPattern.matcher( cgmText );
if ( m.find( ) )
{
return ints( parseInt( m.group( 1 ).trim( ) ),
parseInt( m.group( 2 ).trim( ) ),
parseInt( m.group( 3 ).trim( ) ),
parseInt( m.group( 4 ).trim( ) ) );
}
else
{
throw new SVGException( "Failed to parse CGM bounds: cgm-location = " + cgmLocation );
}
}
public static final Pattern cgmScalePattern = Pattern.compile( "SCALEMODE\\sMETRIC\\s(\\d*\\.?\\d*);" );
public static double extractCgmScale( String cgmText, String cgmLocation ) throws SVGException
{
Matcher m = cgmScalePattern.matcher( cgmText );
if ( m.find( ) )
{
return parseDouble( m.group( 1 ) );
}
else
{
throw new SVGException( "Failed to parse CGM scale: cgm-location = " + cgmLocation );
}
}
public static String removeSvgTransparency( String svgText )
{
return svgText.replaceAll( "fill-opacity=\"\\d*\\.?\\d*\"", "" ).replaceAll( "stroke-opacity=\"\\d*\\.?\\d*\"", "" );
}
}