package eu.europa.esig.dss.pades.signature.visible;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Iterator;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import eu.europa.esig.dss.DSSDocument;
import eu.europa.esig.dss.DSSException;
import eu.europa.esig.dss.MimeType;
import eu.europa.esig.dss.pades.SignatureImageParameters;
import eu.europa.esig.dss.pades.SignatureImageTextParameters;
import eu.europa.esig.dss.utils.Utils;
/**
* A static utilities that helps in creating ImageAndResolution
*
* @author pakeyser
*
*/
public class ImageUtils {
private static final Logger LOG = LoggerFactory.getLogger(ImageUtils.class);
private static final int DPI = 300;
public static ImageAndResolution create(final SignatureImageParameters imageParameters) throws IOException {
SignatureImageTextParameters textParamaters = imageParameters.getTextParameters();
DSSDocument image = imageParameters.getImage();
if ((textParamaters != null) && Utils.isStringNotEmpty(textParamaters.getText())) {
BufferedImage buffImg = ImageTextWriter.createTextImage(textParamaters.getText(), textParamaters.getFont(), textParamaters.getTextColor(),
textParamaters.getBackgroundColor(), DPI);
if (image != null) {
InputStream is = null;
try {
is = image.openStream();
switch (textParamaters.getSignerNamePosition()) {
case LEFT:
buffImg = ImagesMerger.mergeOnRight(ImageIO.read(is), buffImg, textParamaters.getBackgroundColor());
break;
case RIGHT:
buffImg = ImagesMerger.mergeOnRight(buffImg, ImageIO.read(is), textParamaters.getBackgroundColor());
break;
case TOP:
buffImg = ImagesMerger.mergeOnTop(ImageIO.read(is), buffImg, textParamaters.getBackgroundColor());
break;
case BOTTOM:
buffImg = ImagesMerger.mergeOnTop(buffImg, ImageIO.read(is), textParamaters.getBackgroundColor());
break;
default:
break;
}
} finally {
Utils.closeQuietly(is);
}
}
return convertToInputStream(image, buffImg, DPI);
}
// Image only
return readAndDisplayMetadata(image);
}
private static ImageAndResolution readAndDisplayMetadata(DSSDocument image) throws IOException {
if (isImageWithContentType(image, MimeType.JPEG)) {
return readAndDisplayMetadataJPEG(image);
} else if (isImageWithContentType(image, MimeType.PNG)) {
return readAndDisplayMetadataPNG(image);
}
throw new DSSException("Unsupported image type");
}
private static boolean isImageWithContentType(DSSDocument image, MimeType expectedContentType) {
if (image.getMimeType() != null) {
return expectedContentType == image.getMimeType();
} else {
String contentType = null;
try {
contentType = Files.probeContentType(Paths.get(image.getName()));
} catch (IOException e) {
LOG.warn("Unable to retrieve the content-type : " + e.getMessage());
}
return Utils.areStringsEqual(expectedContentType.getMimeTypeString(), contentType);
}
}
public static ImageAndResolution readAndDisplayMetadataJPEG(DSSDocument image) throws IOException {
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("jpeg");
if (!readers.hasNext()) {
throw new DSSException("No writer for JPEG found");
}
// pick the first available ImageReader
ImageReader reader = readers.next();
ImageInputStream iis = null;
try {
iis = ImageIO.createImageInputStream(image.openStream());
// attach source to the reader
reader.setInput(iis, true);
// read metadata of first image
IIOMetadata metadata = reader.getImageMetadata(0);
Element root = (Element) metadata.getAsTree("javax_imageio_jpeg_image_1.0");
NodeList elements = root.getElementsByTagName("app0JFIF");
Element e = (Element) elements.item(0);
int x = Integer.parseInt(e.getAttribute("Xdensity"));
int y = Integer.parseInt(e.getAttribute("Ydensity"));
return new ImageAndResolution(image.openStream(), x, y);
} finally {
Utils.closeQuietly(iis);
}
}
public static ImageAndResolution readAndDisplayMetadataPNG(DSSDocument image) throws IOException {
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("png");
if (!readers.hasNext()) {
throw new DSSException("No writer for PNG found");
}
// pick the first available ImageReader
ImageReader reader = readers.next();
ImageInputStream iis = null;
try {
iis = ImageIO.createImageInputStream(image.openStream());
// attach source to the reader
reader.setInput(iis, true);
// read metadata of first image
IIOMetadata metadata = reader.getImageMetadata(0);
int hdpi = 96, vdpi = 96;
double mm2inch = 25.4;
Element node = (Element) metadata.getAsTree("javax_imageio_1.0");
NodeList lst = node.getElementsByTagName("HorizontalPixelSize");
if (lst != null && lst.getLength() == 1) {
hdpi = (int) (mm2inch / Float.parseFloat(((Element) lst.item(0)).getAttribute("value")));
}
lst = node.getElementsByTagName("VerticalPixelSize");
if (lst != null && lst.getLength() == 1) {
vdpi = (int) (mm2inch / Float.parseFloat(((Element) lst.item(0)).getAttribute("value")));
}
return new ImageAndResolution(image.openStream(), hdpi, vdpi);
} finally {
Utils.closeQuietly(iis);
}
}
private static ImageAndResolution convertToInputStream(DSSDocument imageParam, BufferedImage buffImage, int dpi) throws IOException {
if (imageParam == null || isImageWithContentType(imageParam, MimeType.JPEG)) {
return convertToInputStreamJPG(buffImage, dpi);
} else if (isImageWithContentType(imageParam, MimeType.PNG)) {
return convertToInputStreamPNG(buffImage, dpi);
}
throw new DSSException("Unsupported image type");
}
private static ImageAndResolution convertToInputStreamJPG(BufferedImage buffImage, int dpi) throws IOException {
Iterator<ImageWriter> it = ImageIO.getImageWritersByFormatName("jpeg");
if (!it.hasNext()) {
throw new DSSException("No writer for JPEG found");
}
ImageWriter writer = it.next();
JPEGImageWriteParam jpegParams = (JPEGImageWriteParam) writer.getDefaultWriteParam();
jpegParams.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT);
jpegParams.setCompressionQuality(1);
ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, jpegParams);
initDpiJPG(metadata, dpi);
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageOutputStream imageOs = ImageIO.createImageOutputStream(os);
writer.setOutput(imageOs);
writer.write(metadata, new IIOImage(buffImage, null, metadata), jpegParams);
InputStream is = new ByteArrayInputStream(os.toByteArray());
return new ImageAndResolution(is, dpi, dpi);
}
private static void initDpiJPG(IIOMetadata metadata, int dpi) throws IIOInvalidTreeException {
Element tree = (Element) metadata.getAsTree("javax_imageio_jpeg_image_1.0");
Element jfif = (Element) tree.getElementsByTagName("app0JFIF").item(0);
jfif.setAttribute("Xdensity", Integer.toString(dpi));
jfif.setAttribute("Ydensity", Integer.toString(dpi));
jfif.setAttribute("resUnits", "1"); // density is dots per inch
metadata.setFromTree("javax_imageio_jpeg_image_1.0", tree);
}
private static ImageAndResolution convertToInputStreamPNG(BufferedImage buffImage, int dpi) throws IOException {
Iterator<ImageWriter> it = ImageIO.getImageWritersByFormatName("png");
if (!it.hasNext()) {
throw new DSSException("No writer for PNG found");
}
ImageWriter writer = it.next();
ImageWriteParam imageWriterParams = writer.getDefaultWriteParam();
ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, imageWriterParams);
initDpiPNG(metadata, dpi);
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageOutputStream imageOs = ImageIO.createImageOutputStream(os);
writer.setOutput(imageOs);
writer.write(metadata, new IIOImage(buffImage, null, metadata), imageWriterParams);
InputStream is = new ByteArrayInputStream(os.toByteArray());
return new ImageAndResolution(is, dpi, dpi);
}
private static void initDpiPNG(IIOMetadata metadata, int dpi) throws IIOInvalidTreeException {
// for PNG, it's dots per millimeter
double dotsPerMilli = 1.0 * dpi / 25.4;
IIOMetadataNode horiz = new IIOMetadataNode("HorizontalPixelSize");
horiz.setAttribute("value", Double.toString(dotsPerMilli));
IIOMetadataNode vert = new IIOMetadataNode("VerticalPixelSize");
vert.setAttribute("value", Double.toString(dotsPerMilli));
IIOMetadataNode dim = new IIOMetadataNode("Dimension");
dim.appendChild(horiz);
dim.appendChild(vert);
IIOMetadataNode root = new IIOMetadataNode("javax_imageio_1.0");
root.appendChild(dim);
metadata.mergeTree("javax_imageio_1.0", root);
}
}