package no.dusken.aranea.util;
import com.sun.pdfview.PDFFile;
import com.sun.pdfview.PDFPage;
import no.dusken.aranea.model.Image;
import no.dusken.aranea.service.StoreImageService;
import no.dusken.aranea.service.StoreImageServiceImpl;
import no.dusken.common.util.ImageReader;
import no.dusken.common.util.impl.ImageReaderImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.FileImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* @author Marvin B. Lillehaug <lillehau@underdusken.no>
*/
public class ImageUtils {
/**
* The directory that images is saved relative to.
*/
private File imageDirectory;
private StoreImageService storeImageService;
private static final ImageReader imageReader = new ImageReaderImpl();
private static final Logger log = LoggerFactory.getLogger(StoreImageServiceImpl.class);
private Set<String> currentResizeOperations = Collections.synchronizedSet(new HashSet<String>());
/**
* The quality coefficient used when resizing images.
*/
private float jpegCompression = 0.75f;
/**
* Height of the pdf thumbnail. Defaults to 800
*/
private int PDF_HEIGHT = 800;
/**
* Resize an image file, always saves to png. The file is cached. Aspect ratio is preserved
*
* @param in is the image file to resize
* @param width is the maximum desired width
* @param height is the maximum desired height
* @return the resized file
* @throws IOException if somethinges goes wrong
*/
public File resizeImage(File in, int width, int height) throws IOException {
if (width < 1 && height < 1) {
// no sense to return anything
return null;
}
if (in == null || !in.exists()) {
log.warn("Input file {} is null or does not exists! ", in);
return null;
}
// the cached file
String filename = width + "x" + height + "/" + in.getName();
File cachedFile = new File(imageDirectory + "/cache/" + filename);
// has this been done before?
if (cachedFile.exists() || resizingIsStarted(filename)) {
return cachedFile;
}
// Retrieve image to be resize
log.debug("resizing image: {}", cachedFile.getAbsolutePath());
try {
currentResizeOperations.add(filename);
BufferedImage thumbImage = imageReader.readImage(in, height, width);
cachedFile.getParentFile().mkdirs();
saveImage(thumbImage, cachedFile);
} catch (IOException e) {
log.error("Failed to resize image:{}. {} ", in.getAbsolutePath(), e);
throw e;
}finally {
currentResizeOperations.remove(filename);
}
return cachedFile;
}
private boolean resizingIsStarted(String name) {
if (currentResizeOperations.contains(name)) {
log.info("resizing of image {} was already started.", name);
while (currentResizeOperations.contains(name)){
try {
log.info("sleeping, waiting for {}", name);
Thread.sleep(500);
} catch (InterruptedException e) {
log.error("Interrupted", e);
}
}
log.info("Resize done! ", name);
return true;
}
return false;
}
/** common
* This creates an image object from a pdf file, and saves the image to the
* database The image created is default 640x480, use setMaxHeight,
* setMaxWidth to change
*
* @param pdfFile is the pdf file to use (first page is used)
* @return the image object created
* @throws IOException if anything goes wrong
*/
public Image makeThumbnail(File pdfFile) throws IOException {
if (pdfFile == null || !pdfFile.exists() || !pdfFile.isFile()) {
throw new IOException("No file");
}
log.debug("making thumb for pdf: {}", pdfFile.getAbsolutePath());
RandomAccessFile raf = new RandomAccessFile(pdfFile, "r");
FileChannel channel = raf.getChannel();
ByteBuffer buf = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
PDFFile pdffile = new PDFFile(buf);
// draw the first page to an image
PDFPage page = pdffile.getPage(0);
//get the width and height for the doc at the default zoom
Rectangle rect = new Rectangle(0, 0,
(int) page.getBBox().getWidth(),
(int) page.getBBox().getHeight());
//generate the image
java.awt.Image img = page.getImage(
rect.width, rect.height, //width & height
rect, // clip rect
null, // null for the ImageObserver
true, // fill background with white
true // block until drawing is done
);
//scaling
img = img.getScaledInstance(-1, PDF_HEIGHT, BufferedImage.SCALE_SMOOTH);
//making a bufferedImage from Image
BufferedImage buffScaledPDFImage;
{
//Create the BufferedImage object with the width and height of the Image
buffScaledPDFImage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);
//Create the graphics object from the BufferedImage
Graphics g = buffScaledPDFImage.createGraphics();
//Draw the image on the graphics of the BufferedImage
g.drawImage(img, 0, 0, null);
//Dispose the Graphics
g.dispose();
}
File tmpImageFile = new File(imageDirectory + "/tmp/" + pdfFile.getName() + ".png");
tmpImageFile.getParentFile().mkdirs();
// delete if it exists:
if (tmpImageFile.exists()) {
tmpImageFile.delete();
}
ImageIO.write(buffScaledPDFImage, "png", tmpImageFile);
Image i = storeImageService.createImage(tmpImageFile);
//tmpImageFile.delete();
i.setDescription("Thumb for pdf: " + pdfFile.getName());
i.setExternalSource("From PDF");
return i;
}
/**
* @param mFile a MultipartFile that contains a pdf.
* @return Image of the first page of the pdf.
* @throws IOException
*/
public Image makeThumbnail(MultipartFile mFile) throws IOException {
File file = new File(imageDirectory + "/tmp/" + mFile.getOriginalFilename());
mFile.transferTo(file);
return makeThumbnail(file);
}
/**
* This gets the file from a image object
*
* @param image the image object
* @return the file containing the image
*/
public File getImageFile(Image image) {
return new File(imageDirectory + "/" + image.getUrl());
}
public String getBase64Image(Image image, int width, int height) throws IOException {
File f = getImageFile(image);
return getBase64Image(f, width, height);
}
public String getBase64Image(File image, int width, int height) throws IOException {
File newFile = resizeImage(image, width, height);
newFile.deleteOnExit();
return Base64.encodeFromFile(newFile.getAbsolutePath());
}
private void saveImage(BufferedImage image, File file) throws IOException {
Iterator iter = ImageIO.getImageWritersByFormatName("jpeg");
ImageWriter writer = (ImageWriter)iter.next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(jpegCompression);
FileImageOutputStream output = new FileImageOutputStream(file);
writer.setOutput(output);
IIOImage outimage = new IIOImage(image, null, null);
writer.write(null, outimage, iwp);
writer.dispose();
}
@Required
public void setImageDirectory(File imageDirectory) {
this.imageDirectory = imageDirectory;
}
public void setPDF_HEIGHT(int PDF_HEIGHT) {
this.PDF_HEIGHT = PDF_HEIGHT;
}
public void setStoreImageService(StoreImageService storeImageService) {
this.storeImageService = storeImageService;
}
public void setJpegCompression(float jpegCompression) {
this.jpegCompression = jpegCompression;
}
}