/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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 Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.layout.output;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.AttributeNames;
import org.pentaho.reporting.engine.classic.core.DefaultImageReference;
import org.pentaho.reporting.engine.classic.core.ElementAlignment;
import org.pentaho.reporting.engine.classic.core.ImageContainer;
import org.pentaho.reporting.engine.classic.core.ReportAttributeMap;
import org.pentaho.reporting.engine.classic.core.imagemap.ImageMap;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderNode;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderableReplacedContentBox;
import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.FontSmooth;
import org.pentaho.reporting.engine.classic.core.style.StyleSheet;
import org.pentaho.reporting.engine.classic.core.style.TextStyleKeys;
import org.pentaho.reporting.engine.classic.core.util.ImageUtils;
import org.pentaho.reporting.engine.classic.core.util.ReportDrawable;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictBounds;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
import org.pentaho.reporting.libraries.base.encoder.ImageEncoder;
import org.pentaho.reporting.libraries.base.encoder.ImageEncoderRegistry;
import org.pentaho.reporting.libraries.base.encoder.UnsupportedEncoderException;
import org.pentaho.reporting.libraries.base.util.MemoryByteArrayOutputStream;
import org.pentaho.reporting.libraries.base.util.WaitingImageObserver;
import org.pentaho.reporting.libraries.resourceloader.factory.drawable.DrawableWrapper;
/**
* Creation-Date: 12.05.2007, 15:58:43
*
* @author Thomas Morgner
*/
public class RenderUtility {
private static final Log logger = LogFactory.getLog( RenderUtility.class );
private RenderUtility() {
}
public static String getEncoderType( final ReportAttributeMap attributes ) {
final Object attribute =
attributes.getAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.IMAGE_ENCODING_TYPE );
if ( attribute == null ) {
return ImageEncoderRegistry.IMAGE_PNG;
}
final String encoder = String.valueOf( attribute );
if ( ImageEncoderRegistry.getInstance().isEncoderAvailable( encoder ) ) {
return encoder;
}
return ImageEncoderRegistry.IMAGE_PNG;
}
public static float getEncoderQuality( final ReportAttributeMap attributeMap ) {
final Object attribute =
attributeMap.getAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.IMAGE_ENCODING_QUALITY );
if ( attribute == null ) {
return 0.9f;
}
if ( attribute instanceof Number ) {
final Number n = (Number) attribute;
final float v = n.floatValue();
if ( v < 0.01 ) {
return 0.01f;
}
if ( v > 0.999 ) {
return 0.999f;
}
return v;
}
return 0.9f;
}
public static boolean isFontSmooth( final StyleSheet styleSheet, final OutputProcessorMetaData metaData ) {
final double fontSize =
styleSheet.getDoubleStyleProperty( TextStyleKeys.FONTSIZE, metaData
.getNumericFeatureValue( OutputProcessorFeature.DEFAULT_FONT_SIZE ) );
final FontSmooth smoothing = (FontSmooth) styleSheet.getStyleProperty( TextStyleKeys.FONT_SMOOTH );
final boolean antiAliasing;
if ( FontSmooth.NEVER.equals( smoothing ) ) {
antiAliasing = false;
} else if ( FontSmooth.AUTO.equals( smoothing )
&& fontSize <= metaData.getNumericFeatureValue( OutputProcessorFeature.FONT_SMOOTH_THRESHOLD ) ) {
antiAliasing = false;
} else {
antiAliasing = true;
}
return antiAliasing;
}
/**
* Encodes the given image as PNG, stores the image in the generated file and returns the name of the new image file.
*
* @param image
* the image to be encoded
* @return the name of the image, never null.
* @throws IOException
* if an IO error occurred.
*/
public static byte[] encodeImage( final Image image ) throws UnsupportedEncoderException, IOException {
return encodeImage( image, ImageEncoderRegistry.IMAGE_PNG, 0.9f, true );
}
public static byte[] encodeImage( final Image image, final String mimeType, final float quality, final boolean alpha )
throws UnsupportedEncoderException, IOException {
final MemoryByteArrayOutputStream byteOut = new MemoryByteArrayOutputStream( 65536, 65536 * 2 );
encodeImage( byteOut, image, mimeType, quality, alpha );
return byteOut.toByteArray();
}
public static void encodeImage( final OutputStream outputStream, final Image image, final String mimeType,
final float quality, final boolean alpha ) throws UnsupportedEncoderException, IOException {
final WaitingImageObserver obs = new WaitingImageObserver( image );
obs.waitImageLoaded();
final ImageEncoder imageEncoder = ImageEncoderRegistry.getInstance().createEncoder( mimeType );
if ( imageEncoder == null ) {
throw new UnsupportedEncoderException( "The encoder for mime-type '" + mimeType + "' is not available" );
}
imageEncoder.encodeImage( image, outputStream, quality, alpha );
}
public static Image scaleImage( final Image img, final int targetWidth, final int targetHeight,
final Object hintValue, final boolean higherQuality ) {
final int type = BufferedImage.TYPE_INT_ARGB;
Image ret = img;
int w;
int h;
do {
if ( higherQuality ) {
final int imageWidth = ret.getWidth( null );
final int imageHeight = ret.getHeight( null );
if ( imageWidth < targetWidth ) {
// This is a up-scale operation.
w = Math.min( imageWidth << 1, targetWidth );
} else if ( imageWidth > targetWidth ) {
// downscale
w = Math.max( imageWidth >> 1, targetWidth );
} else {
w = imageWidth;
}
if ( imageHeight < targetHeight ) {
// This is a up-scale operation.
h = Math.min( imageHeight << 1, targetHeight );
} else if ( imageHeight > targetHeight ) {
// downscale
h = Math.max( imageHeight >> 1, targetHeight );
} else {
h = imageHeight;
}
} else {
w = targetWidth;
h = targetHeight;
}
final BufferedImage tmp = new BufferedImage( w, h, type );
final Graphics2D g2 = tmp.createGraphics();
g2.setRenderingHint( RenderingHints.KEY_INTERPOLATION, hintValue );
// this one scales the image ..
if ( ret instanceof BufferedImage ) {
if ( g2.drawImage( ret, 0, 0, w, h, null ) == false ) {
logger.debug( "Failed to scale the image. This should not happen." );
}
} else {
final WaitingImageObserver obs = new WaitingImageObserver( ret );
while ( g2.drawImage( ret, 0, 0, w, h, null ) == false ) {
obs.waitImageLoaded();
if ( obs.isError() ) {
logger.warn( "Error while loading the image during the rendering." );
break;
}
}
}
g2.dispose();
ret = tmp;
} while ( w != targetWidth || h != targetHeight );
return ret;
}
public static double getNormalizationScale( final OutputProcessorMetaData metaData ) {
final double devResolution = metaData.getNumericFeatureValue( OutputProcessorFeature.DEVICE_RESOLUTION );
final double scale;
if ( metaData.isFeatureSupported( OutputProcessorFeature.IMAGE_RESOLUTION_MAPPING ) && devResolution > 0 ) {
scale = devResolution / 72.0;
} else {
scale = 1;
}
return scale;
}
public static ImageContainer createImageFromDrawable( final DrawableWrapper drawable, final StrictBounds rect,
final RenderNode box, final OutputProcessorMetaData metaData ) {
return createImageFromDrawable( drawable, rect, box.getStyleSheet(), metaData );
}
public static DefaultImageReference createImageFromDrawable( final DrawableWrapper drawable, final StrictBounds rect,
final StyleSheet box, final OutputProcessorMetaData metaData ) {
final int imageWidth = (int) StrictGeomUtility.toExternalValue( rect.getWidth() );
final int imageHeight = (int) StrictGeomUtility.toExternalValue( rect.getHeight() );
if ( imageWidth == 0 || imageHeight == 0 ) {
return null;
}
final double scale = RenderUtility.getNormalizationScale( metaData );
final Image image = ImageUtils.createTransparentImage( (int) ( imageWidth * scale ), (int) ( imageHeight * scale ) );
final Graphics2D g2 = (Graphics2D) image.getGraphics();
final Object attribute = box.getStyleProperty( ElementStyleKeys.ANTI_ALIASING );
if ( attribute != null ) {
if ( Boolean.TRUE.equals( attribute ) ) {
g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
} else if ( Boolean.FALSE.equals( attribute ) ) {
g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF );
}
}
if ( RenderUtility.isFontSmooth( box, metaData ) ) {
g2.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
} else {
g2.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF );
}
g2.scale( scale, scale );
// the clipping bounds are a sub-area of the whole drawable
// we only want to print a certain area ...
final String fontName = (String) box.getStyleProperty( TextStyleKeys.FONT );
final int fontSize = box.getIntStyleProperty( TextStyleKeys.FONTSIZE, 8 );
final boolean bold = box.getBooleanStyleProperty( TextStyleKeys.BOLD );
final boolean italics = box.getBooleanStyleProperty( TextStyleKeys.ITALIC );
if ( bold && italics ) {
g2.setFont( new Font( fontName, Font.BOLD | Font.ITALIC, fontSize ) );
} else if ( bold ) {
g2.setFont( new Font( fontName, Font.BOLD, fontSize ) );
} else if ( italics ) {
g2.setFont( new Font( fontName, Font.ITALIC, fontSize ) );
} else {
g2.setFont( new Font( fontName, Font.PLAIN, fontSize ) );
}
g2.setStroke( (Stroke) box.getStyleProperty( ElementStyleKeys.STROKE ) );
g2.setPaint( (Paint) box.getStyleProperty( ElementStyleKeys.PAINT ) );
drawable.draw( g2, new Rectangle2D.Double( 0, 0, imageWidth, imageHeight ) );
g2.dispose();
try {
return new DefaultImageReference( image );
} catch ( final IOException e1 ) {
logger.warn( "Unable to fully load a given image. (It should not happen here.)", e1 );
return null;
}
}
public static long computeHorizontalAlignment( final ElementAlignment alignment, final long width,
final long imageWidth ) {
if ( ElementAlignment.RIGHT.equals( alignment ) ) {
return Math.max( 0, width - imageWidth );
}
if ( ElementAlignment.CENTER.equals( alignment ) ) {
return Math.max( 0, ( width - imageWidth ) / 2 );
}
return 0;
}
public static long computeVerticalAlignment( final ElementAlignment alignment, final long height,
final long imageHeight ) {
if ( ElementAlignment.BOTTOM.equals( alignment ) ) {
return Math.max( 0, height - imageHeight );
}
if ( ElementAlignment.MIDDLE.equals( alignment ) ) {
return Math.max( 0, ( height - imageHeight ) / 2 );
}
return 0;
}
@Deprecated
public static ImageMap extractImageMap( final RenderableReplacedContentBox node, final DrawableWrapper drawable ) {
return extractImageMap( drawable, node.getWidth(), node.getHeight() );
}
private static ImageMap extractImageMap( final DrawableWrapper drawable, final long width, final long height ) {
final Object backend = drawable.getBackend();
if ( backend instanceof ReportDrawable ) {
final ReportDrawable rdrawable = (ReportDrawable) backend;
final int imageWidth = (int) StrictGeomUtility.toExternalValue( width );
final int imageHeight = (int) StrictGeomUtility.toExternalValue( height );
if ( imageWidth == 0 || imageHeight == 0 ) {
return null;
}
return rdrawable.getImageMap( new Rectangle2D.Double( 0, 0, imageWidth, imageHeight ) );
}
return null;
}
public static ImageMap extractImageMap( final RenderableReplacedContentBox content ) {
final ReportAttributeMap attributes = content.getAttributes();
Object rawObject = content.getContent().getRawObject();
return extractImageMap( attributes, rawObject, content.getWidth(), content.getHeight() );
}
public static ImageMap extractImageMap( final ReportAttributeMap attributes, final Object rawObject,
final long width, final long height ) {
final Object manualImageMap =
attributes.getAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.IMAGE_MAP );
if ( manualImageMap instanceof ImageMap ) {
return (ImageMap) manualImageMap;
} else {
if ( rawObject instanceof DrawableWrapper ) {
final DrawableWrapper drawable = (DrawableWrapper) rawObject;
return RenderUtility.extractImageMap( drawable, width, height );
} else {
return null;
}
}
}
}