package org.dynmap.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.Iterator;
import java.util.LinkedList;
import java.awt.image.BufferedImage;
import java.awt.image.DirectColorModel;
import java.awt.image.WritableRaster;
import java.io.IOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import org.dynmap.Log;
import org.dynmap.MapType.ImageFormat;
import org.dynmap.debug.Debug;
/**
* Implements soft-locks for prevent concurrency issues with file updates
*/
public class ImageIOManager {
public static String preUpdateCommand = null;
public static String postUpdateCommand = null;
private static Object imageioLock = new Object();
public static BufferOutputStream imageIOEncode(BufferedImage img, ImageFormat fmt) {
BufferOutputStream bos = new BufferOutputStream();
synchronized(imageioLock) {
try {
ImageIO.setUseCache(false); /* Don't use file cache - too small to be worth it */
if(fmt.getFileExt().equals("jpg")) {
WritableRaster raster = img.getRaster();
WritableRaster newRaster = raster.createWritableChild(0, 0, img.getWidth(),
img.getHeight(), 0, 0, new int[] {0, 1, 2});
DirectColorModel cm = (DirectColorModel)img.getColorModel();
DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(),
cm.getRedMask(), cm.getGreenMask(), cm.getBlueMask());
// now create the new buffer that is used ot write the image:
BufferedImage rgbBuffer = new BufferedImage(newCM, newRaster, false, null);
// Find a jpeg writer
ImageWriter writer = null;
Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpg");
if (iter.hasNext()) {
writer = iter.next();
}
if(writer == null) {
Log.severe("No JPEG ENCODER - Java VM does not support JPEG encoding");
return null;
}
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(fmt.getQuality());
ImageOutputStream ios;
ios = ImageIO.createImageOutputStream(bos);
writer.setOutput(ios);
writer.write(null, new IIOImage(rgbBuffer, null, null), iwp);
writer.dispose();
rgbBuffer.flush();
}
else {
ImageIO.write(img, fmt.getFileExt(), bos); /* Write to byte array stream - prevent bogus I/O errors */
}
} catch (IOException iox) {
Log.info("Error encoding image - " + iox.getMessage());
return null;
}
}
return bos;
}
private static final int MAX_WRITE_RETRIES = 6;
private static LinkedList<BufferOutputStream> baoslist = new LinkedList<BufferOutputStream>();
private static Object baos_lock = new Object();
/**
* Wrapper for IOImage.write - implements retries for busy files
* @param img - buffered image to write
* @param fmt - format to use for file
* @param fname - filename
* @throws IOException if error writing file
*/
public static void imageIOWrite(BufferedImage img, ImageFormat fmt, File fname) throws IOException {
int retrycnt = 0;
boolean done = false;
byte[] rslt;
int rsltlen;
BufferOutputStream baos;
synchronized(baos_lock) {
if(baoslist.isEmpty()) {
baos = new BufferOutputStream();
}
else {
baos = baoslist.removeFirst();
baos.reset();
}
}
synchronized(imageioLock) {
ImageIO.setUseCache(false); /* Don't use file cache - too small to be worth it */
if(fmt.getFileExt().equals("jpg")) {
WritableRaster raster = img.getRaster();
WritableRaster newRaster = raster.createWritableChild(0, 0, img.getWidth(),
img.getHeight(), 0, 0, new int[] {0, 1, 2});
DirectColorModel cm = (DirectColorModel)img.getColorModel();
DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(),
cm.getRedMask(), cm.getGreenMask(), cm.getBlueMask());
// now create the new buffer that is used ot write the image:
BufferedImage rgbBuffer = new BufferedImage(newCM, newRaster, false, null);
// Find a jpeg writer
ImageWriter writer = null;
Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("jpg");
if (iter.hasNext()) {
writer = iter.next();
}
if(writer == null) {
Log.severe("No JPEG ENCODER - Java VM does not support JPEG encoding");
return;
}
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(fmt.getQuality());
ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
writer.setOutput(ios);
writer.write(null, new IIOImage(rgbBuffer, null, null), iwp);
writer.dispose();
rgbBuffer.flush();
}
else {
ImageIO.write(img, fmt.getFileExt(), baos); /* Write to byte array stream - prevent bogus I/O errors */
}
}
// Get buffer and length
rslt = baos.buf;
rsltlen = baos.len;
File fcur = new File(fname.getPath());
File fnew = new File(fname.getPath() + ".new");
File fold = new File(fname.getPath() + ".old");
while(!done) {
RandomAccessFile f = null;
try {
f = new RandomAccessFile(fnew, "rw");
f.write(rslt, 0, rsltlen);
done = true;
} catch (IOException fnfx) {
if(retrycnt < MAX_WRITE_RETRIES) {
Debug.debug("Image file " + fname.getPath() + " - unable to write - retry #" + retrycnt);
try { Thread.sleep(50 << retrycnt); } catch (InterruptedException ix) { throw fnfx; }
retrycnt++;
}
else {
Log.info("Image file " + fname.getPath() + " - unable to write - failed");
throw fnfx;
}
} finally {
if(f != null) {
try { f.close(); } catch (IOException iox) { done = false; }
}
if(done) {
if (preUpdateCommand != null && !preUpdateCommand.isEmpty()) {
try {
new ProcessBuilder(preUpdateCommand, fnew.getAbsolutePath()).start().waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}
fcur.renameTo(fold);
fnew.renameTo(fname);
fold.delete();
if (postUpdateCommand != null && !postUpdateCommand.isEmpty()) {
try {
new ProcessBuilder(postUpdateCommand, fname.getAbsolutePath()).start().waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
// Put back in pool
synchronized(baos_lock) {
baoslist.addFirst(baos);
}
}
/**
* Wrapper for IOImage.read - implements retries for busy files
* @param fname - file to read
* @return buffered image with contents
* @throws IOException if error reading file
*/
public static BufferedImage imageIORead(File fname) throws IOException {
int retrycnt = 0;
boolean done = false;
BufferedImage img = null;
while(!done) {
FileInputStream fis = null;
try {
fis = new FileInputStream(fname);
byte[] b = new byte[(int) fname.length()];
fis.read(b);
fis.close();
fis = null;
BufferInputStream bais = new BufferInputStream(b);
synchronized(imageioLock) {
ImageIO.setUseCache(false); /* Don't use file cache - too small to be worth it */
img = ImageIO.read(bais);
}
bais.close();
done = true; /* Done if no I/O error - retries don't fix format errors */
} catch (IOException iox) {
} finally {
if(fis != null) {
try { fis.close(); } catch (IOException io) {}
fis = null;
}
}
if(!done) {
if(retrycnt < MAX_WRITE_RETRIES) {
Debug.debug("Image file " + fname.getPath() + " - unable to write - retry #" + retrycnt);
try { Thread.sleep(50 << retrycnt); } catch (InterruptedException ix) { }
retrycnt++;
}
else {
Log.info("Image file " + fname.getPath() + " - unable to read - failed");
throw new IOException("Error reading image file " + fname.getPath());
}
}
}
return img;
}
public static BufferedImage imageIODecode(InputStream str) throws IOException {
synchronized(imageioLock) {
ImageIO.setUseCache(false); /* Don't use file cache - too small to be worth it */
return ImageIO.read(str);
}
}
}