/*
* 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 - 2009 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.MemoryByteArrayOutputStream;
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.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 erro occured.
*/
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)
{
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.getStyleSheet().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.getStyleSheet(), 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 StyleSheet style = box.getStyleSheet();
final String fontName = (String) style.getStyleProperty(TextStyleKeys.FONT);
final int fontSize = style.getIntStyleProperty(TextStyleKeys.FONTSIZE, 8);
final boolean bold = style.getBooleanStyleProperty(TextStyleKeys.BOLD);
final boolean italics = style.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) style.getStyleProperty(ElementStyleKeys.STROKE));
g2.setPaint((Paint) style.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;
}
public static ImageMap extractImageMap(final RenderableReplacedContentBox node, final DrawableWrapper drawable)
{
final Object backend = drawable.getBackend();
if (backend instanceof ReportDrawable)
{
final ReportDrawable rdrawable = (ReportDrawable) backend;
final int imageWidth = (int) StrictGeomUtility.toExternalValue(node.getWidth());
final int imageHeight = (int) StrictGeomUtility.toExternalValue(node.getHeight());
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();
final Object manualImageMap = attributes.getAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.IMAGE_MAP);
if (manualImageMap instanceof ImageMap)
{
return (ImageMap) manualImageMap;
}
else
{
final Object o = content.getContent().getRawObject();
if (o instanceof DrawableWrapper)
{
final DrawableWrapper drawable = (DrawableWrapper) o;
return RenderUtility.extractImageMap(content, drawable);
}
else
{
return null;
}
}
}
}