All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.identityx.clientSDK.piiSupport.PNGUtility Maven / Gradle / Ivy

There is a newer version: 5.6.0.2
Show newest version
package com.identityx.clientSDK.piiSupport;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.imageio.ImageIO;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/*
 * Utility class for image resizing
 * 
 */
public class PNGUtility {

	private static final Log logger = LogFactory.getLog(PNGUtility.class);
	private List fontSizes = Arrays.asList( 60, 48, 36, 28, 26, 24, 22,20, 18, 16, 14, 12, 11, 10, 9, 8);

	class TextDetails {
		private List lines = new ArrayList();
		private int fontSize;
		private boolean overrun;

		public TextDetails(int fontSize, String firstLine, boolean overrun) {
			
			this.lines.add(firstLine);
			this.fontSize = fontSize;
			this.overrun = overrun;
		}

		public TextDetails(int fontSize, boolean overrun) {
			
			this.fontSize = fontSize;
			this.overrun = overrun;
		}

		public TextDetails(int fontSize) {
			
			this.fontSize = fontSize;
		}
		
		public List getLines() {
			return lines;
		}
		public void addLine(String line) {
			this.lines.add(line);
		}
		public int getFontSize() {
			return fontSize;
		}
		public void setFontSize(int fontSize) {
			this.fontSize = fontSize;
		}
		public boolean isOverrun() {
			return overrun;
		}
		public void setOverrun(boolean overrun) {
			this.overrun = overrun;
		}
	}
	
	public BufferedImage createPNGFromBase64Data(String content) throws IOException 
	{
		BufferedImage bufferedImage = null;
		byte[] imageContent = Base64.decodeBase64(content);
		try {
			bufferedImage = ImageIO.read(new ByteArrayInputStream(imageContent));
		} catch (IOException e) {
			logger.error("The PNG Data supplied for the transaction was not valid");
			throw e;
		}
		return bufferedImage;
	}
	
	public BufferedImage resizePNGFromBase64Data(DisplayPNGCharacteristics descriptor, String content) throws IOException
	{
		BufferedImage bufferedImage = null;
		byte[] imageContent = Base64.decodeBase64(content);
		BufferedImage scaledImage = null;
		if (descriptor != null)
		{
			try {
				bufferedImage = ImageIO.read(new ByteArrayInputStream(imageContent));
				scaledImage = this.getScaledInstance(bufferedImage, (int)descriptor.getWidth(), (int)descriptor.getHeight(), RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR, false);
				
			} catch (IOException e) {
				logger.error("The PNG Data supplied for the transaction was not valid");
				throw e;
			}
			
			return scaledImage;
		}
		else
		{
			logger.error("Null DisplayPNGCharacteristicsDescriptor supplied so just returning the original image");
		}
		
		return bufferedImage;
	}
	
	public String createBase64DatafromPNG(BufferedImage image) throws IOException {

		ByteArrayOutputStream baos  = new ByteArrayOutputStream();
		
		try {
			ImageIO.write(image, "png", baos);
		} catch (IOException e) {
			logger.error("Failed to write the scaled PNG image");
			throw e;
		}
		
		return Base64.encodeBase64URLSafeString(baos.toByteArray());
	}
		
	public DisplayPNGCharacteristics getDisplayCharacteristicsOfImage(String content) throws IOException 
	{
		BufferedImage image = createPNGFromBase64Data(content);
		
		DisplayPNGCharacteristics pngDescriptor = new DisplayPNGCharacteristics();
		int bitDepth = image.getColorModel().getPixelSize();
		pngDescriptor.setBitDepth(bitDepth);
		pngDescriptor.setWidth(image.getWidth());
		pngDescriptor.setHeight(image.getHeight());
		return pngDescriptor;
	}	
	
	public BufferedImage getScaledInstance(BufferedImage img, int targetWidth, int targetHeight, Object hint, boolean higherQuality) 
	{
		int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
		BufferedImage ret = (BufferedImage)img;
		int w, h;
		
		h = img.getHeight();
		w = img.getWidth();
		
		if (higherQuality) 
		{ 
			// Use multi-step technique: start with original size, then 
			// scale down in multiple passes with drawImage() 
			// until the target size is reached w = img.getWidth();
			double scaleFactor = determineImageScale(w, h, targetWidth, targetHeight);
			w = (int) (w * scaleFactor);
			h = (int) (h * scaleFactor);
		} 
		else 
		{ 
			// Use one-step technique: scale directly from original 
			// size to target size with a single drawImage() call w = targetWidth;
			h = targetHeight;
			w = targetWidth;
		}   
		do 
		{ 
			if (higherQuality && w > targetWidth) 
			{ 
				w /= 2;
				if (w < targetWidth) 
				{ 
					w = targetWidth;
				} 
			} 
			if (higherQuality && h > targetHeight) 
			{ 
				h /= 2;
				if (h < targetHeight) 
				{ 
					h = targetHeight;
				} 
			} 
			BufferedImage tmp = new BufferedImage(w, h, type);
			Graphics2D g2 = tmp.createGraphics();
			g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
			g2.drawImage(ret, 0, 0, w, h, null);
			g2.dispose();
			ret = tmp;
		} 
		while (w > targetWidth || h > targetHeight);
		return ret;
	}
	
	public double determineImageScale(int sourceWidth, int sourceHeight, int targetWidth, int targetHeight) 
	{
		double scalex = (double) targetWidth / sourceWidth;
		double scaley = (double) targetHeight / sourceHeight;
		return Math.min(scalex, scaley);		
	}
	
	public String createBase64ImageFromText(String text, int width, int height) throws IOException {
		
		int leftPadding = 2;
		int rightPadding = 10;
		
		TextDetails details = getFontSize(text, width - leftPadding - rightPadding, height);
		if (logger.isDebugEnabled()) {
			logger.debug("Creating PNG image from text, using font size: " + String.valueOf(details.getFontSize()) + " width: " + String.valueOf(width) + " and text: " + text);
		}
       	
       	BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
       	Graphics2D g2d = img.createGraphics();
	    Font font = new Font("Arial", Font.PLAIN, details.getFontSize());
       	g2d.setFont(font);
		g2d.setBackground(Color.WHITE);
		g2d.clearRect(0, 0, width, height);

       	g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
       	g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
       	g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
       	g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
       	g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
       	g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
       	g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
       	g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
       	g2d.setFont(font);
       	FontMetrics fm = g2d.getFontMetrics();
       	g2d.setColor(Color.BLACK);
       	int counter = 1;
       	for (String line : details.getLines()) {
           	g2d.drawString(line, leftPadding, fm.getAscent() * counter);
           	counter++;
       	}
       	g2d.dispose();

		ByteArrayOutputStream baos  = new ByteArrayOutputStream();
		try {
			ImageIO.write(img, "png", baos);
		} catch (IOException e) {
			logger.error("Failed to write the text to PNG image");
			throw e;
		}
		return Base64.encodeBase64URLSafeString(baos.toByteArray());
	}
	
	public BufferedImage addTransparentBackground(BufferedImage sourceImage, int requiredWidth, int requiredHeight, boolean isCentred) {
		
		int imageWidth = 0;
		int imageHeight = 0;
		BufferedImage combinedImage = null;

		imageWidth = sourceImage.getWidth();
		imageHeight = sourceImage.getHeight();
		
		if (imageWidth > requiredWidth || imageHeight > requiredHeight) {
			// TODO problem
		}
		
		combinedImage = new BufferedImage(requiredWidth, requiredHeight, BufferedImage.TYPE_INT_ARGB);

		Graphics2D g2d = combinedImage.createGraphics();
		if (isCentred) {
			g2d.drawImage(sourceImage, (requiredWidth - imageWidth) / 2, (requiredHeight - imageHeight) / 2, null);
		}
		else {
			g2d.drawImage(sourceImage, 0, 0, null);
		}
		
		return combinedImage;
	}
	
	private TextDetails getFontSize(String text, int width, int height) {

	    BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
	    Graphics2D g2d = img.createGraphics();
	    Font font = null;
	    int testWidth = 0;
	    int testHeight = 0;
	    int numPossibleLines = 1;
	    TextDetails details = null;

		// Use as large a font size as possible so start with largest
		// 1. Can it fit on one line?
		// 2. If not, can we fit it on multiple lines
		// 3. If not, move down to next largest font
		// 4. If on smallest font and still can't fit, make best effort to keep overrun to minimum
		for (int size : fontSizes) {

		    font = new Font("Arial", Font.PLAIN, size);
	       	g2d.setFont(font);
	       	FontMetrics fm = g2d.getFontMetrics();
	       	testWidth = fm.stringWidth(text);
	       	if (testWidth <= width) {
	       		// Fits on one line
	           	g2d.dispose();
	       		return new TextDetails(size, text, false);
	       	}
	       	else {
		       	testHeight = fm.getHeight();
		       	numPossibleLines = height / testHeight;

	       		// Try to fit with multiple lines
		       	if (testWidth <= width * numPossibleLines) {
		       		// Theoretically possible to fit the text, depending on how line breaks work out
		       		details = new TextDetails(size);
		       		details = splitToMultiLine(text, width, numPossibleLines, fm, details);
		       		
		       		if (!details.isOverrun()) {
			           	g2d.dispose();
			           	return details;
		       		}
		       	}
	       	}
		}
		
   		// Last chance saloon, it is going to overrun anyway so make the best effort possible at it
       	g2d.dispose();
       	return details;
	}
	
	private TextDetails splitToMultiLine(String text, int width, int numPossibleLines, FontMetrics fm, TextDetails details) {

		// Recursive method to split main text string into a number of lines that will fit into the
		
		if (fm.stringWidth(text) < width) {
			// Text fits as is, add line and return with no overrun 
			details.addLine(text);
			details.setOverrun(false);
			return details;
		}
		else if (numPossibleLines == 1) {
			// Overrun on last line, add line and return but with overrun 
			details.addLine(text);
			details.setOverrun(true);
			return details;
		}
		else {
			// More than one line left to deal with, recursively measure and split
			String thisLine = "";
			String testLine = "";
			String remainingText = null;
			int index;
			int prevIndex = 0;
			
			while (prevIndex < text.length()) {
				index = text.indexOf(" ", prevIndex);
				if (index != -1) {
					testLine = text.substring(0, index);
					if (fm.stringWidth(testLine) < width) {
						thisLine = testLine;
						prevIndex = index+1;
					}
					else {
						break;
					}
				}
				else {
					break;
				}
			}
			
			details.addLine(thisLine);
			
			remainingText = text.substring(thisLine.length() + 1);
			return splitToMultiLine(remainingText, width, numPossibleLines - 1, fm, details);
		}
	}	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy