/******************************************************************************* * Copyright (c) 2016 Weasis Team and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nicolas Roduit - initial API and implementation *******************************************************************************/ package org.weasis.core.api.image.util; import java.awt.Color; import java.awt.Component; import java.awt.RenderingHints; import java.awt.image.IndexColorModel; import java.awt.image.RenderedImage; import java.awt.image.renderable.ParameterBlock; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.Iterator; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.plugins.jpeg.JPEGImageWriteParam; import javax.imageio.stream.FileImageInputStream; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import javax.media.jai.ImageLayout; import javax.media.jai.JAI; import javax.media.jai.ParameterBlockJAI; import javax.media.jai.PlanarImage; import javax.media.jai.TiledImage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.core.api.gui.util.AbstractBufferHandler; import org.weasis.core.api.gui.util.AppProperties; import org.weasis.core.api.media.MimeInspector; import org.weasis.core.api.media.data.ImageElement; import org.weasis.core.api.media.data.MediaElement; import org.weasis.core.api.media.data.Thumbnail; import com.sun.media.jai.codec.FileSeekableStream; import com.sun.media.jai.codec.ImageCodec; import com.sun.media.jai.codec.ImageDecoder; import com.sun.media.jai.codec.ImageEncoder; import com.sun.media.jai.codec.JPEGEncodeParam; import com.sun.media.jai.codec.PNGEncodeParam; import com.sun.media.jai.codec.TIFFDirectory; import com.sun.media.jai.codec.TIFFEncodeParam; import com.sun.media.jai.codec.TIFFField; import com.sun.media.jai.util.ImageUtil; /** * The Class ImageFiler. * * @author Nicolas Roduit */ public class ImageFiler extends AbstractBufferHandler { private static final Logger LOGGER = LoggerFactory.getLogger(ImageFiler.class); private static final String TIFF_TAG = "tiff_directory"; //$NON-NLS-1$ public static final int TILESIZE = 512; public static final int LIMIT_TO_TILE = 768; public static final int SAVE_TILED = 0; public static final int SAVE_MULTI = 3; public static final int SAVE_CANVAS = 1; public static final int SAVE_SVG = 4; public static final int OUTPUT_BINARY = 0; public static final int OUTPUT_GRAY = 1; public static final int OUTPUT_COLOR = 2; public ImageFiler(Component win) { super(win); } @Override protected void handleNewDocument() { // Do nothing } @Override protected boolean handleSaveDocument(OutputStream outputstream) { return true; } @Override protected boolean handleOpenDocument(InputStream inputstream) { return true; } public static boolean writeTIFF(File file, RenderedImage source, boolean tiled, boolean addThumb, boolean jpegCompression) { if (file.exists() && !file.canWrite()) { return false; } try (OutputStream os = new FileOutputStream(file)) { writeTIFF(os, source, tiled, addThumb, jpegCompression); } catch (OutOfMemoryError | IOException e) { LOGGER.error("", e); //$NON-NLS-1$ return false; } return true; } public static RenderedImage loadImage(String filename) { RenderedImage src = null; if (filename != null) { try { ImageInputStream in = new FileImageInputStream(new RandomAccessFile(filename, "r")); //$NON-NLS-1$ ImageLayout layout = new ImageLayout(); layout.setTileWidth(TILESIZE); layout.setTileHeight(TILESIZE); RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout); ParameterBlockJAI pb = new ParameterBlockJAI("ImageRead"); //$NON-NLS-1$ pb.setParameter("Input", in); //$NON-NLS-1$ src = JAI.create("ImageRead", pb, hints); //$NON-NLS-1$ src = getReadableImage(src); } catch (Exception e) { LOGGER.error("Cannot load image", e); //$NON-NLS-1$ } } return src; } // public static PlanarImage loadImage2(InputStream stream) { // PlanarImage src = null; // if (stream != null) { // try { // ImageInputStream in = new FileCacheImageInputStream(stream, AppProperties.APP_TEMP_DIR); // // Tile image while reading to handle large images // ImageLayout layout = new ImageLayout(); // layout.setTileWidth(TILESIZE); // layout.setTileHeight(TILESIZE); // RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout); // ParameterBlockJAI pb = new ParameterBlockJAI("ImageRead"); //$NON-NLS-1$ // pb.setParameter("Input", in); //$NON-NLS-1$ // src = JAI.create("ImageRead", pb, hints); //$NON-NLS-1$ // } catch (Exception ex) { // src = null; // } // if (src == null) { // try { // stream.close(); // } catch (IOException ex1) { // } // // JMVisionWin.getInstance().setStatusMessage("Unable to load " + file.getName() + ".\nThis image format // // is // // not supported"); // } // return src; // } // return null; // } public static boolean writeTIFF(String fichier, RenderedImage source, boolean tiled, boolean addThumb, boolean jpegCompression) throws IOException { return writeTIFF(new File(fichier), source, tiled, addThumb, jpegCompression); } private static void writeTIFF(OutputStream os, RenderedImage source, boolean tiled, boolean addThumb, boolean jpegCompression) throws IOException { TIFFEncodeParam param = new TIFFEncodeParam(); if (tiled) { param.setWriteTiled(true); param.setTileSize(TILESIZE, TILESIZE); } boolean binary = ImageUtil.isBinary(source.getSampleModel()); if (binary) { param.setCompression(TIFFEncodeParam.COMPRESSION_GROUP4); } else if (jpegCompression) { param.setCompression(TIFFEncodeParam.COMPRESSION_JPEG_TTN2); JPEGEncodeParam wparam = new JPEGEncodeParam(); wparam.setQuality(1.0f); param.setJPEGEncodeParam(wparam); } if (addThumb) { ArrayList<TIFFField> extraFields = new ArrayList<>(6); int fileVal = getResolutionInDpi(source); if (fileVal > 0) { TIFFDirectory dir = (TIFFDirectory) source.getProperty(TIFF_TAG); // $NON-NLS-1$ TIFFField f; f = dir.getField(282); long[][] xRes = f.getAsRationals(); f = dir.getField(283); long[][] yRes = f.getAsRationals(); f = dir.getField(296); char[] resUnit = f.getAsChars(); f = dir.getField(271); if (f != null) { extraFields.add(new TIFFField(271, TIFFField.TIFF_ASCII, 1, new String[] { f.getAsString(0) })); } f = dir.getField(272); if (f != null) { extraFields.add(new TIFFField(272, TIFFField.TIFF_ASCII, 1, new String[] { f.getAsString(0) })); } extraFields.add(new TIFFField(282, TIFFField.TIFF_RATIONAL, xRes.length, xRes)); extraFields.add(new TIFFField(283, TIFFField.TIFF_RATIONAL, yRes.length, yRes)); extraFields.add(new TIFFField(296, TIFFField.TIFF_SHORT, resUnit.length, resUnit)); } extraFields.add(new TIFFField(305, TIFFField.TIFF_ASCII, 1, new String[] { AppProperties.WEASIS_NAME })); param.setExtraFields(extraFields.toArray(new TIFFField[extraFields.size()])); if (!binary) { // Doesn't support bilevel image (or binary to grayscale). ArrayList<RenderedImage> list = new ArrayList<>(); list.add(Thumbnail.createThumbnail(source)); param.setExtraImages(list.iterator()); } } ImageEncoder enc = ImageCodec.createImageEncoder("TIFF", os, param); //$NON-NLS-1$ enc.encode(source); } public static RenderedImage getThumbnailInTiff(ImageElement img) { return getThumbnailInTiff(img.getFile()); } public static RenderedImage getThumbnailInTiff(File file) { RenderedImage thumbnail = null; if (file != null) { try { String mimeType = MimeInspector.getMimeType(file); if (mimeType != null && ("image/tiff".equals(mimeType) || "image/x-tiff".equals(mimeType))) { //$NON-NLS-1$ //$NON-NLS-2$ ImageDecoder dec = ImageCodec.createImageDecoder("tiff", new FileSeekableStream(file), null); //$NON-NLS-1$ int count = dec.getNumPages(); if (count == 2) { RenderedImage src2 = dec.decodeAsRenderedImage(1); if (src2.getWidth() <= Thumbnail.MAX_SIZE) { thumbnail = src2; } } } } catch (IOException ex) { LOGGER.error("Cannot read thumbnail", ex); //$NON-NLS-1$ return null; } } return thumbnail; } private static int getResolutionInDpi(RenderedImage source) { if (source.getProperty(TIFF_TAG) instanceof TIFFDirectory) { // $NON-NLS-1$ TIFFDirectory dir = (TIFFDirectory) source.getProperty(TIFF_TAG); // $NON-NLS-1$ TIFFField fieldx = dir.getField(282); // 282 is X_resolution TIFFField fieldy = dir.getField(283); // 283 is Y_resolution TIFFField fieldUnit = dir.getField(296); // 296 is unit if (fieldx != null && fieldUnit != null) { char c = fieldUnit.getAsChars()[0]; if (c == '2' || c == '\u0002') { int resolutionx = (int) fieldx.getAsDouble(0); // this is the magic step, no idea why it is needed, // but numbers are wrong otherwise int resolutiony = (int) fieldy.getAsDouble(0); if (resolutionx == resolutiony) { return resolutionx; } } } } return 0; } public static boolean writePNG(File file, RenderedImage source) { if (file.exists() && !file.canWrite()) { return false; } try (OutputStream os = new FileOutputStream(file)) { writePNG(os, source); } catch (OutOfMemoryError | IOException e) { LOGGER.error("", e); //$NON-NLS-1$ return false; } return true; } public static void writePNG(OutputStream os, RenderedImage source) throws IOException { PNGEncodeParam param = new PNGEncodeParam.Palette(); ImageEncoder enc = ImageCodec.createImageEncoder("PNG", os, param); //$NON-NLS-1$ enc.encode(source); } public static boolean writeJPG(File file, RenderedImage source, float quality) { if (file.exists() && !file.canWrite()) { return false; } try (OutputStream os = new FileOutputStream(file)) { writeJPG(os, source, quality); } catch (OutOfMemoryError | IOException e) { LOGGER.error("", e); //$NON-NLS-1$ return false; } return true; } public static boolean writeJPG(OutputStream outputStream, RenderedImage source, float quality) { ImageWriter writer = null; try { Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("JPEG"); //$NON-NLS-1$ if (iter.hasNext()) { writer = iter.next(); try (ImageOutputStream os = ImageIO.createImageOutputStream(outputStream)) { writer.setOutput(os); JPEGImageWriteParam iwp = new JPEGImageWriteParam(null); iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); iwp.setCompressionQuality(quality); writer.write(null, new IIOImage(source, null, null), iwp); } } } catch (OutOfMemoryError | IOException e) { LOGGER.error("", e); //$NON-NLS-1$ return false; } finally { if (writer != null) { writer.dispose(); } } return true; } public static RenderedImage getReadableImage(RenderedImage source) { RenderedImage dst = null; if (source != null && source.getSampleModel() != null) { if (ImageUtil.isBinary(source.getSampleModel())) { dst = source; if (source.getColorModel() instanceof IndexColorModel) { IndexColorModel icm = (IndexColorModel) source.getColorModel(); byte[] tableData = new byte[icm.getMapSize()]; icm.getReds(tableData); if (tableData[0] != (byte) 0x00) { ImageLayout layout = new ImageLayout(); layout.setSampleModel( LayoutUtil.createBinarySampelModel(source.getTileWidth(), source.getTileHeight())); layout.setColorModel(LayoutUtil.createBinaryIndexColorModel()); RenderingHints hints = new RenderingHints(JAI.KEY_TRANSFORM_ON_COLORMAP, Boolean.FALSE); hints.add(new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout)); ParameterBlock pb = new ParameterBlock(); pb.addSource(source); return JAI.create("NotBinary", pb, hints); //$NON-NLS-1$ } } } else if (source.getColorModel() instanceof IndexColorModel) { dst = PlanarImage.wrapRenderedImage(ImageToolkit.convertIndexColorToRGBColor(source)); } else { dst = source; } String bdSel = "bandSelect"; //$NON-NLS-1$ int numBands = dst.getSampleModel().getNumBands(); if (numBands == 2) { ParameterBlockJAI pb = new ParameterBlockJAI(bdSel); // $NON-NLS-1$ pb.addSource(dst); pb.setParameter("bandIndices", new int[] { 0, 1, 0 }); //$NON-NLS-1$ dst = JAI.create(bdSel, pb, null); // $NON-NLS-1$ } // for image with alpha channel else if (numBands > 3) { ParameterBlockJAI pb = new ParameterBlockJAI(bdSel); // $NON-NLS-1$ pb.addSource(dst); pb.setParameter("bandIndices", new int[] { 0, 1, 2 }); //$NON-NLS-1$ dst = JAI.create(bdSel, pb, null); // $NON-NLS-1$ } } return dst; } public static String changeExtension(String filename, String ext) { if (filename == null) { return ""; //$NON-NLS-1$ } // replace extension after the last point int pointPos = filename.lastIndexOf("."); //$NON-NLS-1$ if (pointPos == -1) { pointPos = filename.length(); } return filename.substring(0, pointPos) + ext; } public static PlanarImage getEmptyImage(Byte[] bandValues, float width, float height) { ParameterBlock pb = new ParameterBlock(); pb.add(width); pb.add(height); pb.add(bandValues); return JAI.create("constant", pb, null); //$NON-NLS-1$ } public static PlanarImage getEmptyImage(Color color, float width, float height) { Byte[] bandValues = { (byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue() }; return getEmptyImage(bandValues, width, height); } public static TiledImage getEmptyTiledImage(Color color, float width, float height) { return new TiledImage(getEmptyImage(color, width, height), ImageFiler.TILESIZE, ImageFiler.TILESIZE); } public static TiledImage getEmptyTiledImage(Byte[] bandValues, float width, float height) { return new TiledImage(getEmptyImage(bandValues, width, height), ImageFiler.TILESIZE, ImageFiler.TILESIZE); } public static void encodeImagePng(RenderedImage image, FileOutputStream fileStream) throws IOException { PNGEncodeParam param = new PNGEncodeParam.Palette(); ImageEncoder enc = ImageCodec.createImageEncoder("PNG", fileStream, param); //$NON-NLS-1$ enc.encode(image); } public static PlanarImage tileImage(RenderedImage img) { // Tile image while reading to handle large images ImageLayout layout = new ImageLayout(); layout.setTileWidth(TILESIZE); layout.setTileHeight(TILESIZE); RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout); ParameterBlock pb = new ParameterBlock(); pb.addSource(img); pb.add(img.getSampleModel().getDataType()); return JAI.create("format", pb, hints); //$NON-NLS-1$ } public static File cacheTiledImage(RenderedImage img, MediaElement media) { if (img.getWidth() > 2 * ImageFiler.TILESIZE || img.getHeight() > 2 * ImageFiler.TILESIZE) { File imgCacheFile = null; try { imgCacheFile = File.createTempFile("tiled_", ".tif", AppProperties.FILE_CACHE_DIR); //$NON-NLS-1$ //$NON-NLS-2$ } catch (IOException e) { LOGGER.error("Creating cache image", e); //$NON-NLS-1$ } if (ImageFiler.writeTIFF(imgCacheFile, img, true, false, false)) { return imgCacheFile; } } return null; } public static RenderedImage readTiledCacheImage(File file) { try { ImageDecoder dec = ImageCodec.createImageDecoder("tiff", new FileSeekableStream(file), null); //$NON-NLS-1$ return dec.decodeAsRenderedImage(); } catch (IOException e) { LOGGER.error("", e); //$NON-NLS-1$ } return null; } public static RenderedImage readThumbnailCacheImage(File file) { try { ImageDecoder dec = ImageCodec.createImageDecoder("tiff", new FileSeekableStream(file), null); //$NON-NLS-1$ int count = dec.getNumPages(); if (count == 2) { RenderedImage src2 = dec.decodeAsRenderedImage(1); if (src2.getWidth() <= Thumbnail.MAX_SIZE) { return src2; } } } catch (IOException e) { LOGGER.error("", e); //$NON-NLS-1$ } return null; } }