/* ==================================================================== | |
Licensed to the Apache Software Foundation (ASF) under one or more | |
contributor license agreements. See the NOTICE file distributed with | |
this work for additional information regarding copyright ownership. | |
The ASF licenses this file to You under the Apache License, Version 2.0 | |
(the "License"); you may not use this file except in compliance with | |
the License. You may obtain a copy of the License at | |
http://www.apache.org/licenses/LICENSE-2.0 | |
Unless required by applicable law or agreed to in writing, software | |
distributed under the License is distributed on an "AS IS" BASIS, | |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
See the License for the specific language governing permissions and | |
limitations under the License. | |
==================================================================== */ | |
package org.apache.poi.sl.draw; | |
import java.awt.Dimension; | |
import java.awt.Graphics; | |
import java.awt.Graphics2D; | |
import java.awt.Insets; | |
import java.awt.Shape; | |
import java.awt.geom.AffineTransform; | |
import java.awt.geom.Rectangle2D; | |
import java.awt.image.AffineTransformOp; | |
import java.awt.image.BufferedImage; | |
import java.awt.image.RescaleOp; | |
import java.io.ByteArrayInputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.util.Iterator; | |
import javax.imageio.ImageIO; | |
import javax.imageio.ImageReadParam; | |
import javax.imageio.ImageReader; | |
import javax.imageio.ImageTypeSpecifier; | |
import javax.imageio.stream.ImageInputStream; | |
import javax.imageio.stream.MemoryCacheImageInputStream; | |
import org.apache.poi.util.POILogFactory; | |
import org.apache.poi.util.POILogger; | |
/** | |
* For now this class renders only images supported by the javax.imageio.ImageIO framework. | |
**/ | |
public class BitmapImageRenderer implements ImageRenderer { | |
private final static POILogger LOG = POILogFactory.getLogger(ImageRenderer.class); | |
protected BufferedImage img; | |
@Override | |
public void loadImage(InputStream data, String contentType) throws IOException { | |
img = readImage(data, contentType); | |
} | |
@Override | |
public void loadImage(byte data[], String contentType) throws IOException { | |
img = readImage(new ByteArrayInputStream(data), contentType); | |
} | |
/** | |
* Read the image data via ImageIO and optionally try to workaround metadata errors. | |
* The resulting image is of image image type {@link BufferedImage#TYPE_INT_ARGB} | |
* | |
* @param data the data stream | |
* @param contentType the content type | |
* @return the bufferedImage or null, if there was no image reader for this content type | |
* @throws IOException thrown if there was an error while processing the image | |
*/ | |
private static BufferedImage readImage(InputStream data, String contentType) throws IOException { | |
IOException lastException = null; | |
BufferedImage img = null; | |
// currently don't use FileCacheImageInputStream, | |
// because of the risk of filling the file handles (see #59166) | |
ImageInputStream iis = new MemoryCacheImageInputStream(data); | |
try { | |
iis = new MemoryCacheImageInputStream(data); | |
iis.mark(); | |
Iterator<ImageReader> iter = ImageIO.getImageReaders(iis); | |
while (img==null && iter.hasNext()) { | |
ImageReader reader = iter.next(); | |
ImageReadParam param = reader.getDefaultReadParam(); | |
// 0:default mode, 1:fallback mode | |
for (int mode=0; img==null && mode<2; mode++) { | |
iis.reset(); | |
iis.mark(); | |
if (mode == 1) { | |
// fallback mode for invalid image band metadata | |
// see http://stackoverflow.com/questions/10416378 | |
Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0); | |
while (imageTypes.hasNext()) { | |
ImageTypeSpecifier imageTypeSpecifier = imageTypes.next(); | |
int bufferedImageType = imageTypeSpecifier.getBufferedImageType(); | |
if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY) { | |
param.setDestinationType(imageTypeSpecifier); | |
break; | |
} | |
} | |
} | |
try { | |
reader.setInput(iis, false, true); | |
img = reader.read(0, param); | |
} catch (IOException e) { | |
lastException = e; | |
} catch (RuntimeException e) { | |
lastException = new IOException("ImageIO runtime exception - "+(mode==0 ? "normal" : "fallback"), e); | |
} | |
} | |
reader.dispose(); | |
} | |
} finally { | |
iis.close(); | |
} | |
// If you don't have an image at the end of all readers | |
if (img == null) { | |
if (lastException != null) { | |
// rethrow exception - be aware that the exception source can be in | |
// multiple locations above ... | |
throw lastException; | |
} | |
LOG.log(POILogger.WARN, "Content-type: "+contentType+" is not support. Image ignored."); | |
return null; | |
} | |
// add alpha channel | |
if (img.getType() != BufferedImage.TYPE_INT_ARGB) { | |
BufferedImage argbImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB); | |
Graphics g = argbImg.getGraphics(); | |
g.drawImage(img, 0, 0, null); | |
g.dispose(); | |
return argbImg; | |
} | |
return img; | |
} | |
@Override | |
public BufferedImage getImage() { | |
return img; | |
} | |
@Override | |
public BufferedImage getImage(Dimension dim) { | |
double w_old = img.getWidth(); | |
double h_old = img.getHeight(); | |
BufferedImage scaled = new BufferedImage((int)w_old, (int)h_old, BufferedImage.TYPE_INT_ARGB); | |
double w_new = dim.getWidth(); | |
double h_new = dim.getHeight(); | |
AffineTransform at = new AffineTransform(); | |
at.scale(w_new/w_old, h_new/h_old); | |
AffineTransformOp scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR); | |
scaleOp.filter(img, scaled); | |
return scaled; | |
} | |
@Override | |
public Dimension getDimension() { | |
return (img == null) | |
? new Dimension(0,0) | |
: new Dimension(img.getWidth(),img.getHeight()); | |
} | |
@Override | |
public void setAlpha(double alpha) { | |
if (img == null) return; | |
Dimension dim = getDimension(); | |
BufferedImage newImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB); | |
Graphics2D g = newImg.createGraphics(); | |
RescaleOp op = new RescaleOp(new float[]{1.0f, 1.0f, 1.0f, (float)alpha}, new float[]{0,0,0,0}, null); | |
g.drawImage(img, op, 0, 0); | |
g.dispose(); | |
img = newImg; | |
} | |
@Override | |
public boolean drawImage( | |
Graphics2D graphics, | |
Rectangle2D anchor) { | |
return drawImage(graphics, anchor, null); | |
} | |
@Override | |
public boolean drawImage( | |
Graphics2D graphics, | |
Rectangle2D anchor, | |
Insets clip) { | |
if (img == null) return false; | |
boolean isClipped = true; | |
if (clip == null) { | |
isClipped = false; | |
clip = new Insets(0,0,0,0); | |
} | |
int iw = img.getWidth(); | |
int ih = img.getHeight(); | |
double cw = (100000-clip.left-clip.right) / 100000.0; | |
double ch = (100000-clip.top-clip.bottom) / 100000.0; | |
double sx = anchor.getWidth()/(iw*cw); | |
double sy = anchor.getHeight()/(ih*ch); | |
double tx = anchor.getX()-(iw*sx*clip.left/100000.0); | |
double ty = anchor.getY()-(ih*sy*clip.top/100000.0); | |
AffineTransform at = new AffineTransform(sx, 0, 0, sy, tx, ty) ; | |
Shape clipOld = graphics.getClip(); | |
if (isClipped) graphics.clip(anchor.getBounds2D()); | |
graphics.drawRenderedImage(img, at); | |
graphics.setClip(clipOld); | |
return true; | |
} | |
} |