/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.util;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.PixelGrabber;
import java.awt.image.RenderedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.swing.ImageIcon;
import javax.swing.text.html.CSS;
import org.w3c.dom.css.CSSPrimitiveValue;
import org.xhtmlrenderer.css.constants.CSSName;
import org.xhtmlrenderer.css.parser.PropertyValue;
import org.xhtmlrenderer.css.sheet.PropertyDeclaration;
import com.servoy.j2db.IApplication;
import com.servoy.j2db.MediaURLStreamHandler;
import com.servoy.j2db.persistence.Media;
/**
* Helper class to load image via ImageIO
*
* @author jcompagner
*/
@SuppressWarnings("nls")
public class ImageLoader
{
public static ImageIcon getIcon(byte[] array, int width, int height, boolean keepAspect)
{
return getIcon(array, width, height, keepAspect, null);
}
public static ImageIcon getIcon(byte[] array, int width, int height, boolean keepAspect, Boolean fixedWidth)
{
if (array == null || array.length == 0) return null;
ImageIcon icon = null;
if (array.length < 150000)
{
// this method is faster for smaller images
icon = new ImageIcon(array);
if (icon.getIconHeight() == -1) // icon creation failed.
{
// Try it through the imageio way
Image image = getBufferedImage(array, width, height, keepAspect);
if (image != null)
{
icon = new ImageIcon(image);
}
else return null;
}
}
else
{
Image image = getBufferedImage(array, width, height, keepAspect);
if (image != null)
{
icon = new ImageIcon(image);
}
else
{
// try the backup, slow way for large images.
icon = new ImageIcon(array);
if (icon.getIconHeight() == -1) // icon creation failed.
{
return null;
}
}
}
return resizeImageIcon(icon, width, height, keepAspect, fixedWidth);
}
public static ImageIcon resizeImageIcon(ImageIcon icon, int width, int height, boolean keepAspect)
{
return resizeImageIcon(icon, width, height, keepAspect, null);
}
public static ImageIcon resizeImageIcon(ImageIcon icon, int width, int height, boolean keepAspect, Boolean fixedWidth)
{
float widthChange = width > 0 ? icon.getIconWidth() / (float)width : 1;
float heightChange = height > 0 ? icon.getIconHeight() / (float)height : 1;
boolean resize = widthChange > 1.01 || heightChange > 1.01 || widthChange < 0.99 || heightChange < 0.99;
if (resize)
{
if (keepAspect)
{
float ratio = 0;
if (fixedWidth == null)
{
ratio = Math.max(widthChange, heightChange);
}
else if (fixedWidth.booleanValue())
{
ratio = widthChange;
}
else
{
ratio = heightChange;
}
width = (int)(icon.getIconWidth() / ratio);
height = (int)(icon.getIconHeight() / ratio);
}
if (width > 0 && height > 0)
{
icon = new ImageIcon(icon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH));
}
}
return icon;
}
public static BufferedImage getBufferedImage(byte[] array, int width, int height, boolean keepAspect)
{
BufferedImage bi = null;
ByteArrayInputStream bais = new ByteArrayInputStream(array);
ImageInputStream iis = null;
ImageReader ir = null;
try
{
iis = ImageIO.createImageInputStream(bais);
Iterator it = ImageIO.getImageReaders(iis);
if (it.hasNext())
{
ir = (ImageReader)it.next();
ir.setInput(iis, true, true);
float fWidth = 0, fHeigth = 0;
if (width != -1) fWidth = ((float)ir.getWidth(0)) / width;
if (height != -1) fHeigth = ((float)ir.getHeight(0)) / height;
ImageReadParam param = ir.getDefaultReadParam();
// Workaround for quality loss when decoding JPEG in Java 5.
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6372769 for details.
// JPEG quality is not lost in Java 1.4 and Java 6, just in Java 5.
Iterator imageTypes = ir.getImageTypes(0);
boolean looking = true;
while (looking && imageTypes.hasNext())
{
ImageTypeSpecifier type = (ImageTypeSpecifier)imageTypes.next();
ColorSpace cs = type.getColorModel().getColorSpace();
if (cs.isCS_sRGB())
{
param.setDestinationType(type);
looking = false;
}
}
if (fWidth != 0 || fHeigth != 0)
{
if (keepAspect)
{
int min = (int)Math.min(fWidth, fHeigth);
if (min == 0) min = (int)Math.max(fWidth, fHeigth);
if (min > 0)
{
param.setSourceSubsampling(min, min, 0, 0);
}
}
else
{
int iWidth = (int)fWidth;
if (iWidth == 0) iWidth = 1;
int iHeigth = (int)fHeigth;
if (iHeigth == 0) iHeigth = 1;
param.setSourceSubsampling(iWidth, iHeigth, 0, 0);
}
}
bi = ir.read(0, param);
}
}
catch (Exception e)
{
Debug.error(e);
}
finally
{
if (ir != null)
{
ir.dispose();
}
try
{
if (iis != null)
{
iis.close();
}
}
catch (Exception e1)
{
}
}
return bi;
}
/**
* @param iconArray
* @return the dimensions of the image, (0,0) if the byte array is null
*/
public static Dimension getSize(File icon)
{
if (icon == null) return new Dimension(0, 0);
ImageInputStream iis = null;
ImageReader ir = null;
try
{
iis = ImageIO.createImageInputStream(icon);
Iterator<ImageReader> it = ImageIO.getImageReaders(iis);
if (it.hasNext())
{
ir = it.next();
ir.setInput(iis, true, true);
return new Dimension(ir.getWidth(0), ir.getHeight(0));
}
}
catch (Exception e)
{
try
{
ImageIcon image = new ImageIcon(Utils.readFile(icon, -1));
return new Dimension(image.getIconWidth(), image.getIconHeight());
}
catch (Exception ex)
{
// ignore
}
Debug.error(e);
}
finally
{
if (ir != null)
{
ir.dispose();
}
try
{
if (iis != null) iis.close();
}
catch (Exception e1)
{
Debug.error(e1);
}
}
return new Dimension(0, 0);
}
/**
* @param iconArray
* @return the dimensions of the image, (0,0) if the byte array is null
*/
public static Dimension getSize(byte[] iconArray)
{
if (iconArray == null) return new Dimension(0, 0);
ByteArrayInputStream bais = new ByteArrayInputStream(iconArray);
ImageInputStream iis = null;
ImageReader ir = null;
try
{
iis = ImageIO.createImageInputStream(bais);
Iterator<ImageReader> it = ImageIO.getImageReaders(iis);
if (it.hasNext())
{
ir = it.next();
ir.setInput(iis, true, true);
return new Dimension(ir.getWidth(0), ir.getHeight(0));
}
}
catch (Exception e)
{
try
{
ImageIcon icon = new ImageIcon(iconArray);
return new Dimension(icon.getIconWidth(), icon.getIconHeight());
}
catch (Exception ex)
{
// ignore
}
Debug.error(e);
}
finally
{
if (ir != null)
{
ir.dispose();
}
try
{
if (iis != null) iis.close();
}
catch (Exception e1)
{
Debug.error(e1);
}
}
return new Dimension(0, 0);
}
public static byte[] resize(byte[] imageData, int width, int height)
{
return resize(imageData, width, height, true);
}
public static byte[] resize(byte[] imageData, int width, int height, boolean aspect)
{
return resize(imageData, width, height, aspect, null);
}
public static byte[] resize(byte[] imageData, int width, int height, boolean aspect, Boolean fixedWidth)
{
String contentType = MimeTypes.getContentType(imageData);
if (contentType == null || contentType.toLowerCase().indexOf("gif") != -1) contentType = "image/png";
ImageIcon icon = ImageLoader.getIcon(imageData, width, height, aspect, fixedWidth);
if (icon == null)
{
return null;
}
java.awt.Image image = icon.getImage();
BufferedImage bufImage = null;
if (image instanceof BufferedImage)
{
bufImage = (BufferedImage)image;
}
else
{
int type = BufferedImage.TYPE_INT_RGB;
if (contentType.equals("image/png"))
{
type = BufferedImage.TYPE_INT_ARGB_PRE;
}
else if (contentType.toLowerCase().indexOf("gif") != -1)
{
type = BufferedImage.TYPE_BYTE_INDEXED;
}
bufImage = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), type);
Graphics g2 = bufImage.createGraphics();
g2.drawImage(image, 0, 0, null);
g2.dispose();
}
try
{
return getByteArray(contentType, bufImage);
}
finally
{
bufImage.flush();
}
}
public static byte[] getByteArray(String contentType, RenderedImage bufImage)
{
byte[] resized = null;
Iterator iterator = ImageIO.getImageWritersByMIMEType(contentType);
if (iterator.hasNext())
{
ImageWriter iw = (ImageWriter)iterator.next();
resized = writeImage(iw, bufImage);
iw.dispose();
}
if (resized == null)
{
if ("image/bmp".equals(contentType))
{
iterator = ImageIO.getImageWritersByMIMEType("image/bmp, image/x-bmp, image/x-windows-bmp");
if (iterator.hasNext())
{
ImageWriter iw = (ImageWriter)iterator.next();
resized = writeImage(iw, bufImage);
iw.dispose();
}
}
if (resized == null)
{
iterator = ImageIO.getImageWritersByMIMEType("image/png");
if (iterator.hasNext())
{
ImageWriter iw = (ImageWriter)iterator.next();
resized = writeImage(iw, bufImage);
iw.dispose();
}
if (resized == null)
{
iterator = ImageIO.getImageWritersByMIMEType("image/jpeg");
if (iterator.hasNext())
{
ImageWriter iw = (ImageWriter)iterator.next();
resized = writeImage(iw, bufImage);
iw.dispose();
}
}
}
}
return resized;
}
private static byte[] writeImage(ImageWriter iw, RenderedImage bufImage)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream(4096);
try
{
ImageOutputStream os = ImageIO.createImageOutputStream(baos);
iw.setOutput(os);
iw.write(bufImage);
os.close();
}
catch (IOException e)
{
Debug.error("resizing images error", e);
}
if (baos.size() > 0)
{
return baos.toByteArray();
}
return null;
}
public static boolean imageHasAlpha(java.awt.Image image, long pixelGrabberTimeout)
{
if (image instanceof BufferedImage)
{
return ((BufferedImage)image).getColorModel().hasAlpha();
}
PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
try
{
pg.grabPixels(pixelGrabberTimeout);
}
catch (InterruptedException e)
{
return false;
}
return pg.getColorModel() != null && pg.getColorModel().hasAlpha();
}
// This method returns a buffered image with the contents of an image
public static BufferedImage imageToBufferedImage(Image image)
{
if (image instanceof BufferedImage)
{
return (BufferedImage)image;
}
// This code ensures that all the pixels in the image are loaded
image = new ImageIcon(image).getImage();
// Determine if the image has transparent pixels
boolean hasAlpha = imageHasAlpha(image, 0);
BufferedImage bimage = null;
// Create a buffered image
int width = image.getWidth(null);
width = width > 0 ? width : 1;
int height = image.getHeight(null);
height = height > 0 ? height : 1;
bimage = new BufferedImage(width, height, hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
// Copy image to buffered image
Graphics g = bimage.createGraphics();
// Paint the image onto the buffered image
g.drawImage(image, 0, 0, null);
g.dispose();
return bimage;
}
public static void paintImage(Graphics graphics, IStyleRule styleRule, IApplication application, Dimension parentSize)
{
if (styleRule != null && styleRule.hasAttribute(CSS.Attribute.BACKGROUND_IMAGE.toString()))
{
String url = styleRule.getValue(CSS.Attribute.BACKGROUND_IMAGE.toString());
int start = url.indexOf(MediaURLStreamHandler.MEDIA_URL_DEF);
if (start != -1)
{
paintBackgroundImage(graphics, styleRule, application, url, parentSize);
}
else if (url.indexOf("linear-gradient") != -1 && graphics instanceof Graphics2D)
{
paintGradientColor(graphics, styleRule, application, url, parentSize);
}
}
}
private static void paintBackgroundImage(Graphics graphics, IStyleRule styleRule, IApplication application, String url, Dimension parentSize)
{
int start = url.indexOf(MediaURLStreamHandler.MEDIA_URL_DEF);
if (start != -1)
{
String name = url.substring(start + MediaURLStreamHandler.MEDIA_URL_DEF.length());
if (name.endsWith("')") || name.endsWith("\")")) name = name.substring(0, name.length() - 2);
if (name.endsWith(")")) name = name.substring(0, name.length() - 1);
Media media = application.getFlattenedSolution().getMedia(name);
if (media != null)
{
byte[] imageData = media.getMediaData();
if (styleRule.hasAttribute(CSSName.BACKGROUND_SIZE.toString()))
{
PropertyDeclaration declaration = ((ServoyStyleRule)styleRule).getPropertyDeclaration(CSSName.BACKGROUND_SIZE.toString());
if (declaration.getValue() instanceof PropertyValue && ((PropertyValue)declaration.getValue()).getValues() != null &&
((PropertyValue)declaration.getValue()).getValues().size() == 2)
{
boolean autoWidth = "auto".equals(((CSSPrimitiveValue)((PropertyValue)declaration.getValue()).getValues().get(0)).getCssText());
boolean autoHeight = "auto".equals(((CSSPrimitiveValue)((PropertyValue)declaration.getValue()).getValues().get(1)).getCssText());
Boolean fixedWidth = null;
if (autoWidth && !autoHeight)
{
fixedWidth = Boolean.FALSE;
}
else if (autoHeight && !autoWidth)
{
fixedWidth = Boolean.TRUE;
}
int width = getImageSize(parentSize.width, ((CSSPrimitiveValue)((PropertyValue)declaration.getValue()).getValues().get(0)).getCssText());
int height = getImageSize(parentSize.height,
((CSSPrimitiveValue)((PropertyValue)declaration.getValue()).getValues().get(1)).getCssText());
imageData = resize(imageData, width, height, autoWidth || autoHeight, fixedWidth);
}
}
Image image = ImageLoader.getBufferedImage(imageData, -1, -1, false);
int offsetWidth = 0;
int offsetHeight = 0;
int imageWidth = image.getWidth(null);
int imageHeight = image.getHeight(null);
if (styleRule.hasAttribute(CSSName.BACKGROUND_POSITION.toString()))
{
PropertyDeclaration declaration = ((ServoyStyleRule)styleRule).getPropertyDeclaration(CSSName.BACKGROUND_POSITION.toString());
if (declaration.getValue() instanceof PropertyValue && ((PropertyValue)declaration.getValue()).getValues() != null &&
((PropertyValue)declaration.getValue()).getValues().size() == 2)
{
offsetWidth = getImagePosition(parentSize.width, imageWidth,
((CSSPrimitiveValue)((PropertyValue)declaration.getValue()).getValues().get(0)).getCssText());
offsetHeight = getImagePosition(parentSize.height, imageHeight,
((CSSPrimitiveValue)((PropertyValue)declaration.getValue()).getValues().get(1)).getCssText());
}
}
boolean hRepeat = true;
boolean vRepeat = true;
if (styleRule.hasAttribute(CSSName.BACKGROUND_REPEAT.toString()))
{
String repeat = styleRule.getValue(CSSName.BACKGROUND_REPEAT.toString());
hRepeat = false;
vRepeat = false;
if ("repeat".equals(repeat) || "repeat-x".equals(repeat)) hRepeat = true;
if ("repeat".equals(repeat) || "repeat-y".equals(repeat)) vRepeat = true;
}
if (hRepeat)
{
offsetWidth = adjustRepeatCoordinate(offsetWidth, imageWidth);
}
if (vRepeat)
{
offsetHeight = adjustRepeatCoordinate(offsetHeight, imageHeight);
}
if (!hRepeat && !vRepeat)
{
graphics.drawImage(image, offsetWidth, offsetHeight, null);
}
else if (hRepeat && vRepeat)
{
for (int x = offsetWidth; x < parentSize.width; x += imageWidth)
{
for (int y = offsetHeight; y < parentSize.height; y += imageHeight)
{
graphics.drawImage(image, x, y, null);
}
}
}
else if (hRepeat)
{
for (int x = offsetWidth; x < parentSize.width; x += imageWidth)
{
graphics.drawImage(image, x, offsetHeight, null);
}
}
else if (vRepeat)
{
for (int y = offsetHeight; y < parentSize.height; y += imageHeight)
{
graphics.drawImage(image, offsetWidth, y, null);
}
}
}
}
}
private static void paintGradientColor(Graphics graphics, IStyleRule styleRule, IApplication application, String url, Dimension parentSize)
{
if (url.indexOf("linear-gradient") != -1 && graphics instanceof Graphics2D)
{
List<String> tokens = PersistHelper.splitStringWithBracesOnSeparator(url.substring(url.indexOf("(") + 1, url.lastIndexOf(")")), ',');
if (tokens.size() >= 2)
{
Iterator<String> tokenizer = tokens.iterator();
String firstToken = tokenizer.next();
float startX = parentSize.width / 2;
float startY = 0;
float endX = parentSize.width / 2;
float endY = parentSize.height;
Color color1 = getColor(firstToken);
if (color1 == null)
{
if ("left".equals(firstToken))
{
startX = 0;
startY = parentSize.height;
endX = parentSize.width;
}
else if ("right".equals(firstToken))
{
startX = parentSize.width;
endX = 0;
endY = 0;
}
else if ("bottom".equals(firstToken))
{
startY = parentSize.height;
endY = 0;
}
color1 = getColor(tokenizer.next());
}
Color color2 = null;
if (color1 != null)
{
color2 = getColor(tokenizer.next());
if (color2 != null)
{
GradientPaint gradientPaint = new GradientPaint(startX, startY, color1, endX, endY, color2);
Paint tmpPaint = ((Graphics2D)graphics).getPaint();
((Graphics2D)graphics).setPaint(gradientPaint);
graphics.fillRect(0, 0, parentSize.width, parentSize.height);
((Graphics2D)graphics).setPaint(tmpPaint);
}
}
if (color1 == null || color2 == null)
{
// fallback mechanism
String[] values = styleRule.getValues(CSS.Attribute.BACKGROUND_IMAGE.toString());
if (values.length > 1)
{
for (int i = 1; i < values.length; i++)
{
if (values[i].equals(url))
{
if (values[i - 1].indexOf("linear-gradient") != -1)
{
paintGradientColor(graphics, styleRule, application, values[i - 1], parentSize);
}
else
{
paintBackgroundImage(graphics, styleRule, application, values[i - 1], parentSize);
}
}
}
}
}
}
}
}
private static int getImageSize(int containerSize, String cssText)
{
if (cssText != null)
{
if (cssText.endsWith("px"))
{
return Utils.getAsInteger(cssText.substring(0, cssText.length() - 2));
}
else if (cssText.endsWith("%"))
{
int percent = Utils.getAsInteger(cssText.substring(0, cssText.length() - 1));
return percent * containerSize / 100;
}
}
return -1;
}
private static int getImagePosition(int containerSize, int imageSize, String cssText)
{
if (cssText != null)
{
if (cssText.endsWith("px"))
{
return Utils.getAsInteger(cssText.substring(0, cssText.length() - 2));
}
else if (cssText.endsWith("%"))
{
int percent = Utils.getAsInteger(cssText.substring(0, cssText.length() - 1));
return percent * (containerSize - imageSize) / 100;
}
else if ("left".equals(cssText) || "top".equals(cssText))
{
return 0;
}
else if ("right".equals(cssText) || "bottom".equals(cssText))
{
return (containerSize - imageSize);
}
else if ("center".equals(cssText))
{
return (containerSize - imageSize) / 2;
}
}
return 0;
}
private static int adjustRepeatCoordinate(int startPosition, int imageSize)
{
int result = startPosition;
while (result > 0)
{
result = result - imageSize;
}
return result;
}
private static Color getColor(String cssDefinition)
{
if (cssDefinition != null)
{
return PersistHelper.createColorWithTransparencySupport(cssDefinition);
}
return null;
}
}