/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.autoplot.imagedatasource; import com.drew.imaging.jpeg.JpegMetadataReader; import com.drew.metadata.Directory; import com.drew.metadata.Metadata; import com.drew.metadata.Tag; import com.drew.metadata.exif.ExifIFD0Directory; import com.drew.metadata.exif.ExifSubIFDDirectory; import com.drew.metadata.exif.GpsDirectory; import java.awt.Color; import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ConvolveOp; import java.awt.image.Kernel; import java.awt.image.WritableRaster; import java.io.File; import java.io.InputStream; import java.net.URI; import java.text.ParseException; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.metadata.IIOMetadata; import javax.imageio.stream.ImageInputStream; import org.das2.datum.Datum; import org.das2.datum.DatumRange; import org.das2.datum.DatumRangeUtil; import org.das2.datum.DatumUtil; import org.das2.datum.Units; import org.das2.datum.UnitsUtil; import org.das2.util.ImageUtil; import org.das2.util.monitor.NullProgressMonitor; import org.das2.util.monitor.ProgressMonitor; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.das2.qds.DDataSet; import org.das2.qds.DataSetUtil; import org.das2.qds.MutablePropertyDataSet; import org.das2.qds.QDataSet; import org.das2.qds.SemanticOps; import org.autoplot.datasource.AbstractDataSource; import org.autoplot.datasource.DataSetURI; import org.das2.qds.ops.Ops; import org.autoplot.metatree.MetadataUtil; import org.w3c.dom.Node; /** * * @author jbf */ public class ImageDataSource extends AbstractDataSource { public static final int CHANNEL_HUE = 1; public static final int CHANNEL_SATURATION = 2; public static final int CHANNEL_VALUE = 3; public ImageDataSource(URI uri) { super(uri); } /** * return the grayscale for each pixel, * with values between 0 and 255, using the mapping * 0.3 * r + 0.59 * g + 0.11 * b. */ public static final ImageDataSet.ColorOp GRAYSCALE_OP= new ImageDataSet.ColorOp() { @Override public double value(int rgb) { int r = rgb & 0xFF0000 >> 16; int g = rgb & 0x00FF00 >> 8; int b = rgb & 0xFF; return 0.3 * r + 0.59 * g + 0.11 * b; } }; /** * return the hue (as in Hue-Saturation-Value) for each pixel, * with values between 0 and 360. */ public static final ImageDataSet.ColorOp HUE_OP= new ImageDataSet.ColorOp() { @Override public double value(int rgb) { return toHSV(rgb, CHANNEL_HUE); } }; /** * return the saturation (as in Hue-Saturation-Value) for each pixel, * with values between 0 and 100. */ public static final ImageDataSet.ColorOp SATURATION_OP= new ImageDataSet.ColorOp() { @Override public double value(int rgb) { return toHSV(rgb, CHANNEL_SATURATION); } }; /** * return the value (as in Hue-Saturation-Value) for each pixel, with * values between 0 and 100. */ public static final ImageDataSet.ColorOp VALUE_OP=new ImageDataSet.ColorOp() { @Override public double value(int rgb) { return toHSV(rgb, CHANNEL_VALUE); } }; /** * convert the rgb value to HSV. Only one H, S, or V is returned. * @param rgb the integer rgb value. * @param channel * @return value (0-100), saturation (0-100), or hue (0-360) */ private static double toHSV(int rgb, int channel) { double r = (rgb & 0xFF0000) >> 16; double g = (rgb & 0x00FF00) >> 8; double b = (rgb & 0xFF); r = r / 255; g = g / 255; b = b / 255; // Scale to unity. double minVal = Math.min(Math.min(r, g), b); double maxVal = Math.max(Math.max(r, g), b); double delta = maxVal - minVal; double value = maxVal; if (channel == CHANNEL_VALUE) { return value * 100; } double hue = 0, sat; if (delta == 0) { hue = 0; sat = 0; } else { sat = delta / maxVal; double del_R = (((maxVal - r) / 6) + (delta / 2)) / delta; double del_G = (((maxVal - g) / 6) + (delta / 2)) / delta; double del_B = (((maxVal - b) / 6) + (delta / 2)) / delta; if (r == maxVal) { hue = del_B - del_G; } else if (g == maxVal) { hue = (1 / 3) + del_R - del_B; } else if (b == maxVal) { hue = (2 / 3) + del_G - del_R; } if (hue < 0) { hue += 1; } if (hue > 1) { hue -= 1; } hue *= 360; sat *= 100; } if (channel == CHANNEL_HUE) { return hue; } else { return sat; } } /** * rotate the image. * @param image * @param drot rotate this many degrees clockwise * @param dest the target BufferedImage or null if one should be created, the same size as the original, regardless of rotation. * @return the created bufferedImage */ public static BufferedImage rotateImage( BufferedImage image, double drot, BufferedImage dest ) { int h= image.getHeight(); int w= image.getWidth(); AffineTransform at= AffineTransform.getTranslateInstance( w/2., h/2. ); at.concatenate( AffineTransform.getRotateInstance( Math.PI*drot/180 ) ); at.concatenate( AffineTransform.getTranslateInstance( -w/2., -h/2.) ); if (dest==null ) dest= new BufferedImage( w, h, image.getType() ); ((Graphics2D)dest.getGraphics()).drawImage( image,at, null ); return dest; } @Override public QDataSet getDataSet(ProgressMonitor mon) throws Exception { mon.started(); File ff= DataSetURI.getFile( uri, mon.getSubtaskMonitor("get file") ); if ( ff.length()==0 ) { throw new IllegalArgumentException("Image file is empty: "+ff); } BufferedImage image = ImageIO.read(ff); //BufferedImage image = ImageIO.read(DataSetURI.getInputStream(uri, mon)); String rot= getParam( "rotate", "0" ); if ( !rot.equals("0") ) { double drot= Double.parseDouble(rot); BufferedImage dest= rotateImage( image, drot, null ); image= dest; } String blur= getParam( "blur", "1" ); if ( !blur.equals("1") ) { int iblur= Integer.parseInt(blur); if ( iblur<1 || iblur>51 ) throw new IllegalArgumentException("blur must be between 1 and 51"); BufferedImage dest= new BufferedImage( image.getWidth(), image.getHeight(), image.getType() ); int n= iblur*iblur; float[] matrix= new float[n]; for ( int i=0; i100 ) throw new IllegalArgumentException("fog must be between 1 and 100"); BufferedImage dest= new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB ); int color= image.getRGB(0,0); Graphics2D g= ((Graphics2D)dest.getGraphics()); g.drawImage( image, new AffineTransform(), null ); g.setColor( new Color( ( color & 0xFF0000 ) >> 16, ( color & 0x00FF00) >> 8, color & 0x0000FF, ifog*255/100 ) ); g.fillRect(0,0,image.getWidth(), image.getHeight()); image= dest; } // String transparent= getParam( "transparent", "0" ) ; // if ( !transparent.equals("0") ) { // int itransparent= Integer.parseInt(transparent); // if ( itransparent<0 || itransparent>100 ) throw new IllegalArgumentException("transparent must be between 1 and 100"); // BufferedImage dest= new BufferedImage( image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB ); // // int alpha = ( itransparent * 255 / 100 ) << 24; // int w= image.getWidth(); // int h= image.getHeight(); // for ( int i=0; icolorTable=black_white"); break; case "red": result.putProperty( QDataSet.RENDER_TYPE, "spectrogram>colorTable=black_red"); break; case "green": result.putProperty( QDataSet.RENDER_TYPE, "spectrogram>colorTable=black_green"); break; case "blue": result.putProperty( QDataSet.RENDER_TYPE, "spectrogram>colorTable=black_blue"); break; default: result.putProperty( QDataSet.RENDER_TYPE, "image" ); break; } } else { result.putProperty( QDataSet.RENDER_TYPE, "image" ); } mon.finished(); return result; } public QDataSet getRange( JSONObject axis ) throws JSONException, ParseException { String sxmin= axis.getString("min"); String sxmax= axis.getString("max"); boolean xlog= axis.has("type") && axis.get("type").equals("log"); Units units; if ( axis.has("units") ) { if ( axis.get("units").equals("UTC") ) { units= Units.us2000; } else { units= Units.lookupUnits(axis.getString("units")); } } else { units= Units.dimensionless; } DatumRange result= DatumRangeUtil.union( units.parse(sxmin), units.parse(sxmax) ); QDataSet ds= DataSetUtil.asDataSet(result); if ( xlog ) { ds= Ops.putProperty( ds, QDataSet.SCALE_TYPE, "log" ); } return ds; } private static Datum[] tryParseArray( String s ) { s= s.trim(); if ( s.startsWith("[") && s.endsWith("]") ) s= s.substring(1,s.length()-1); if ( s.startsWith("(") && s.endsWith(")") ) s= s.substring(1,s.length()-1); String[] ss= s.split(","); Datum[] result= new Datum[ss.length]; for ( int i=0; i getJpegExifMetaData(ProgressMonitor mon) throws Exception { try ( InputStream in= DataSetURI.getInputStream(uri, mon) ) { return getJpegExifMetaData( in ); } } /** * read useful JPG metadata, such as the Orientation. This also looks to see if GPS * metadata is available. * @param in inputStream from a jpeg source. * @return * @throws Exception */ public static Map getJpegExifMetaData(InputStream in) throws Exception { Metadata metadata = JpegMetadataReader.readMetadata(in); Map map = new LinkedHashMap<>(); Directory exifDirectory; exifDirectory = metadata.getDirectory(ExifSubIFDDirectory.class); if ( exifDirectory!=null ) { for (Tag t : exifDirectory.getTags()) { map.put(t.getTagName(), t.getDescription()); } } exifDirectory = metadata.getDirectory(ExifIFD0Directory.class); if ( exifDirectory!=null ) { for (Tag t : exifDirectory.getTags()) { map.put(t.getTagName(), t.getDescription()); } } exifDirectory = metadata.getDirectory(GpsDirectory.class); if ( exifDirectory!=null ) { for (Tag t : exifDirectory.getTags()) { map.put(t.getTagName(), t.getDescription()); } } return map; } @Override public Map getMetadata(ProgressMonitor mon) throws Exception { String ext = getExt(resourceURI).toLowerCase(); if (ext.equals(".jpg")) { return getJpegExifMetaData(mon); } else { File f = DataSetURI.getFile(uri, new NullProgressMonitor()); ImageReader jpegImageReader = ImageIO.getImageReadersByFormatName(ext.substring(1)).next(); ImageInputStream imageInputStream = ImageIO.createImageInputStream(f); boolean seekForwardOnly = true; boolean ignoreMetadata = false; jpegImageReader.setInput(imageInputStream, seekForwardOnly, ignoreMetadata); IIOMetadata imageMetadata = jpegImageReader.getImageMetadata(0); Node metaDataRoot = imageMetadata.getAsTree(imageMetadata.getNativeMetadataFormatName()); Map map; map = MetadataUtil.toMetaTree(metaDataRoot); return map; } } }