X Tutup
package javaxt.io; import java.io.*; import javax.imageio.*; import javax.imageio.metadata.*; import java.awt.image.*; import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform; import java.awt.*; import java.util.HashSet; import java.util.HashMap; import java.util.Iterator; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.NamedNodeMap; //Imports for JPEG //import com.sun.image.codec.jpeg.*; //<-- Not always available in newer versions import javax.imageio.plugins.jpeg.JPEGImageWriteParam; //Imports for JP2 //import javax.media.jai.RenderedOp; //import com.sun.media.imageio.plugins.jpeg2000.J2KImageReadParam; //****************************************************************************** //** Image Class //****************************************************************************** /** * Used to open, resize, rotate, crop and save images. * ******************************************************************************/ public class Image { private BufferedImage bufferedImage = null; private java.util.ArrayList corners = null; private float outputQuality = 1f; //0.9f; //0.5f; private static final boolean useSunCodec = getSunCodec(); private static Class JPEGCodec; private static Class JPEGEncodeParam; private Graphics2D g2d = null; public static String[] InputFormats = getFormats(ImageIO.getReaderFormatNames()); public static String[] OutputFormats = getFormats(ImageIO.getWriterFormatNames()); private IIOMetadata metadata; private HashMap exif; private HashMap iptc; private HashMap gps; private boolean saveMetadata = false; //************************************************************************** //** Constructor //************************************************************************** /** Creates a new instance of this class using an existing image */ public Image(String PathToImageFile){ this(new java.io.File(PathToImageFile)); } public Image(java.io.File file){ if (!file.exists()) throw new IllegalArgumentException("Input file not found."); try{ createBufferedImage(new FileInputStream(file)); } catch(Exception e){} } public Image(java.io.InputStream InputStream){ createBufferedImage(InputStream); } public Image(byte[] byteArray){ this(new ByteArrayInputStream(byteArray)); } public Image(int width, int height){ this.bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); this.g2d = getGraphics(); } public Image(BufferedImage bufferedImage){ this.bufferedImage = bufferedImage; } public Image(RenderedImage img) { if (img instanceof BufferedImage) { this.bufferedImage = (BufferedImage) img; } else{ java.awt.image.ColorModel cm = img.getColorModel(); java.awt.image.WritableRaster raster = cm.createCompatibleWritableRaster(img.getWidth(), img.getHeight()); boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); java.util.Hashtable properties = new java.util.Hashtable(); String[] keys = img.getPropertyNames(); if (keys!=null) { for (int i = 0; i < keys.length; i++) { properties.put(keys[i], img.getProperty(keys[i])); } } BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties); img.copyData(raster); this.bufferedImage = result; } } //************************************************************************** //** Constructor //************************************************************************** /** Creates a new instance of this class using a block of text. * @param fontName Name of the font you with to use. Note that you can get * a list of available fonts like this:
    for (String fontName : GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()){
        System.out.println(fontName);
    }
   
*/ public Image(String text, String fontName, int fontSize, int r, int g, int b){ this(text, new Font(fontName, Font.TRUETYPE_FONT, fontSize), r,g,b); } public Image(String text, Font font, int r, int g, int b){ //Get Font Metrics Graphics2D t = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).createGraphics(); t.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); FontMetrics fm = t.getFontMetrics(font); int width = fm.stringWidth(text); int height = fm.getHeight(); int descent = fm.getDescent(); t.dispose(); //Create Image bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); g2d = bufferedImage.createGraphics(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); //Add Text float alpha = 1.0f; //Set alpha. 0.0f is 100% transparent and 1.0f is 100% opaque. g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); g2d.setColor(new Color(r, g, b)); g2d.setFont(font); g2d.drawString(text, 0, height-descent); } //************************************************************************** //** setBackgroundColor //************************************************************************** /** Used to set the background color. Creates an image layer and inserts it * under the existing graphic. This method should only be called once. */ public void setBackgroundColor(int r, int g, int b){ /* Color org = g2d.getColor(); g2d.setColor(new Color(r,g,b)); g2d.fillRect(1,1,width-2,height-2); //g2d.fillRect(0,0,width,height); g2d.setColor(org); */ int imageType = bufferedImage.getType(); if (imageType == 0) { imageType = BufferedImage.TYPE_INT_ARGB; } int width = this.getWidth(); int height = this.getHeight(); BufferedImage bi = new BufferedImage(width, height, imageType); Graphics2D g2d = bi.createGraphics(); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f)); g2d.setColor(new Color(r,g,b)); g2d.fillRect(0,0,width,height); java.awt.Image img = bufferedImage; g2d.drawImage(img, 0, 0, null); this.bufferedImage = bi; g2d.dispose(); } //************************************************************************** //** getInputFormats //************************************************************************** /** Used to retrieve a list of supported input (read) formats. */ public String[] getInputFormats(){ return getFormats(ImageIO.getReaderFormatNames()); } //************************************************************************** //** getOutputFormats //************************************************************************** /** Used to retrieve a list of supported output (write) formats. */ public String[] getOutputFormats(){ return getFormats(ImageIO.getWriterFormatNames()); } //************************************************************************** //** getFormats //************************************************************************** /** Used to trim the list of formats. */ private static String[] getFormats(String[] inputFormats){ //Build a unique list of file formats HashSet formats = new HashSet (); for (int i=0; i> 24) & 0xff; int red = (pixel >> 16) & 0xff; int green = (pixel >> 8) & 0xff; int blue = (pixel) & 0xff; return new java.awt.Color(red, green, blue, alpha); } //************************************************************************** //** getHistogram //************************************************************************** /** Returns an array with 4 histograms: red, green, blue, and average
        ArrayList histogram = image.getHistogram();
        int[] red = histogram.get(0);
        int[] green = histogram.get(1);
        int[] blue = histogram.get(2);
        int[] average = histogram.get(3);
    
*/ public java.util.ArrayList getHistogram(){ //Create empty histograms int[] red = new int[256]; int[] green = new int[256]; int[] blue = new int[256]; int[] average = new int[256]; for (int i=0; i hist = new java.util.ArrayList(); hist.add(red); hist.add(green); hist.add(blue); hist.add(average); return hist; } //************************************************************************** //** addImage //************************************************************************** /** Used to add an image "overlay" to the existing image at a given * position. This method can also be used to create image mosiacs. */ public void addImage(BufferedImage in, int x, int y, boolean expand){ int x2 = 0; int y2 = 0; int w = bufferedImage.getWidth(); int h = bufferedImage.getHeight(); if (expand){ //Update Width and Horizontal Position of the Original Image if (x<0) { w = w + -x; if (in.getWidth()>w){ w = w + (in.getWidth()-w); } x2 = -x; x = 0; } else if (x>w) { w = (w + (x-w)) + in.getWidth(); } else{ if ((x+in.getWidth())>w){ w = w + ((x+in.getWidth())-w); } } //Update Height and Vertical Position of the Original Image if (y<0){ h = h + -y; if (in.getHeight()>h){ h = h + (in.getHeight()-h); } y2 = -y; y = 0; } else if(y>h){ h = (h + (y-h)) + in.getHeight(); } else{ if ((y+in.getHeight())>h){ h = h + ((y+in.getHeight())-h); } } } //Create new image "collage" if (w>bufferedImage.getWidth() || h>bufferedImage.getHeight()){ BufferedImage bi = new BufferedImage(w, h, getImageType()); Graphics2D g2d = bi.createGraphics(); java.awt.Image img = bufferedImage; g2d.drawImage(img, x2, y2, null); img = in; g2d.drawImage(img, x, y, null); g2d.dispose(); bufferedImage = bi; } else{ Graphics2D g2d = bufferedImage.createGraphics(); java.awt.Image img = in; g2d.drawImage(img, x, y, null); g2d.dispose(); } } //************************************************************************** //** addImage //************************************************************************** /** Used to add an image "overlay" to the existing image at a given * position. This method can also be used to create image mosiacs. */ public void addImage(javaxt.io.Image in, int x, int y, boolean expand){ addImage(in.getBufferedImage(),x,y,expand); } //************************************************************************** //** createBufferedImage //************************************************************************** /** Used to create a BufferedImage from a InputStream */ private void createBufferedImage(java.io.InputStream input) { try{ //bufferedImage = ImageIO.read(input); javax.imageio.stream.ImageInputStream stream = ImageIO.createImageInputStream(input); Iterator iter = ImageIO.getImageReaders(stream); if (!iter.hasNext()) { return; } ImageReader reader = (ImageReader)iter.next(); ImageReadParam param = reader.getDefaultReadParam(); reader.setInput(stream, true, true); try { bufferedImage = reader.read(0, param); metadata = reader.getImageMetadata(0); } finally { reader.dispose(); stream.close(); } input.close(); } catch(Exception e){ //e.printStackTrace(); } } //************************************************************************** //** Rotate //************************************************************************** /** Used to rotate the image (clockwise). Rotation angle is specified in * degrees relative to the top of the image. */ public void rotate(double Degrees){ //Define Image Center (Axis of Rotation) int width = this.getWidth(); int height = this.getHeight(); int cx = width/2; int cy = height/2; //create an array containing the corners of the image (TL,TR,BR,BL) int[] corners = { 0, 0, width, 0, width, height, 0, height }; //Define bounds of the image int minX, minY, maxX, maxY; minX = maxX = cx; minY = maxY = cy; double theta = Math.toRadians(Degrees); for (int i=0; imaxX) maxX = x; if(xmaxY) maxY = y; if(ymaxWidth){ //Set width ratio = (double)maxWidth/(double)width; width = (int)Math.round(width * ratio); height = (int)Math.round(height * ratio); } } else{ //Set width double ratio = (double)maxWidth/(double)this.getWidth(); width = (int)Math.round(this.getWidth() * ratio); height = (int)Math.round(this.getHeight() * ratio); if (height>maxHeight){ //Set height ratio = (double)maxHeight/(double)height; width = (int)Math.round(width * ratio); height = (int)Math.round(height * ratio); } } } //Resize the image (create new buffered image) java.awt.Image outputImage = bufferedImage.getScaledInstance(width, height, BufferedImage.SCALE_AREA_AVERAGING); BufferedImage bi = new BufferedImage(width, height, getImageType()); Graphics2D g2d = bi.createGraphics( ); g2d.drawImage(outputImage, 0, 0, null); g2d.dispose(); this.bufferedImage = bi; outputImage = null; bi = null; g2d = null; } //************************************************************************** //** Set/Update Corners (Skew) //************************************************************************** /** Used to skew an image by updating the corner coordinates. Coordinates * are supplied in clockwise order starting from the upper left corner. */ public void setCorners(float x0, float y0, //UL float x1, float y1, //UR float x2, float y2, //LR float x3, float y3){ //LL Skew skew = new Skew(this.bufferedImage); this.bufferedImage = skew.setCorners(x0,y0,x1,y1,x2,y2,x3,y3); if (corners==null) corners = new java.util.ArrayList(); else corners.clear(); corners.add((Float)x0); corners.add((Float)y0); corners.add((Float)x1); corners.add((Float)y1); corners.add((Float)x2); corners.add((Float)y2); corners.add((Float)x3); corners.add((Float)y3); } //************************************************************************** //** Get Corners //************************************************************************** /** Used to retrieve the corner coordinates of the image. Coordinates are * supplied in clockwise order starting from the upper left corner. This * information is particularly useful for generating drop shadows, inner * and outer glow, and reflections. * NOTE: Coordinates are not updated after resize(), rotate(), or addImage() */ public float[] getCorners(){ if (corners==null){ float w = getWidth(); float h = getHeight(); corners = new java.util.ArrayList(); corners.add((Float)0f); corners.add((Float)0f); corners.add((Float)w); corners.add((Float)0f); corners.add((Float)w); corners.add((Float)h); corners.add((Float)0f); corners.add((Float)h); } Object[] arr = corners.toArray(); float[] ret = new float[arr.length]; for (int i=0; i radius2) matrix[index] = 0; else matrix[index] = (float)Math.exp(-(distance)/sigma22) / sqrtSigmaPi2; total += matrix[index]; index++; } for (int i = 0; i < rows; i++) matrix[i] /= total; Kernel kernel = new Kernel(rows, 1, matrix); // int width = this.getWidth(); int height = this.getHeight(); int[] inPixels = new int[width*height]; int[] outPixels = new int[width*height]; bufferedImage.getRGB( 0, 0, width, height, inPixels, 0, width ); convolveAndTranspose(kernel, inPixels, outPixels, width, height, true, CLAMP_EDGES); convolveAndTranspose(kernel, outPixels, inPixels, height, width, true, CLAMP_EDGES); bufferedImage.setRGB( 0, 0, width, height, inPixels, 0, width ); } private static int CLAMP_EDGES = 1; private static int WRAP_EDGES = 2; //************************************************************************** //** convolveAndTranspose //************************************************************************** /** Applies 1D Gaussian kernel used to blur an image. This filter should be * applied twice, once horizontally and once vertically. * @author Jerry Huxtable */ private static void convolveAndTranspose(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) { float[] matrix = kernel.getKernelData( null ); int cols = kernel.getWidth(); int cols2 = cols/2; for (int y = 0; y < height; y++) { int index = y; int ioffset = y*width; for (int x = 0; x < width; x++) { float r = 0, g = 0, b = 0, a = 0; int moffset = cols2; for (int col = -cols2; col <= cols2; col++) { float f = matrix[moffset+col]; if (f != 0) { int ix = x+col; if ( ix < 0 ) { if ( edgeAction == CLAMP_EDGES ) ix = 0; else if ( edgeAction == WRAP_EDGES ) ix = (x+width) % width; } else if ( ix >= width) { if ( edgeAction == CLAMP_EDGES ) ix = width-1; else if ( edgeAction == WRAP_EDGES ) ix = (x+width) % width; } int rgb = inPixels[ioffset+ix]; a += f * ((rgb >> 24) & 0xff); r += f * ((rgb >> 16) & 0xff); g += f * ((rgb >> 8) & 0xff); b += f * (rgb & 0xff); } } int ia = alpha ? clamp((int)(a+0.5)) : 0xff; int ir = clamp((int)(r+0.5)); int ig = clamp((int)(g+0.5)); int ib = clamp((int)(b+0.5)); outPixels[index] = (ia << 24) | (ir << 16) | (ig << 8) | ib; index += height; } } } //************************************************************************** //** clamp //************************************************************************** /** Clamp a value to the range 0..255 */ private static int clamp(int c) { if (c < 0) return 0; if (c > 255) return 255; return c; } //************************************************************************** //** Desaturate //************************************************************************** /** Used to completely desaturate an image (creates a gray-scale image). */ public void desaturate(){ bufferedImage = desaturate(bufferedImage); } //************************************************************************** //** Desaturate //************************************************************************** /** Used to desaturate an image by a specified percentage (expressed as * a double or float). The larger the percentage, the greater the * desaturation and the "grayer" the image. Valid ranges are from 0-1. */ public void desaturate(double percent){ float alpha = (float) (percent); java.awt.Image overlay = desaturate(bufferedImage); Graphics2D g2d = bufferedImage.createGraphics(); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); g2d.drawImage(overlay,0,0,null); g2d.dispose(); } //************************************************************************** //** Desaturate (Private Function) //************************************************************************** /** Convenience function called by the other 2 desaturation methods. */ private BufferedImage desaturate(BufferedImage in){ BufferedImage out = new BufferedImage(in.getWidth(), in.getHeight(), getImageType(in) ); BufferedImageOp op = new ColorConvertOp( ColorSpace.getInstance(ColorSpace.CS_GRAY), null); return op.filter(in, out); } //************************************************************************** //** setOpacity //************************************************************************** /** Used to adjust the opacity of the image. */ public void setOpacity(double percent){ if (percent>1) percent=percent/100; float alpha = (float) (percent); int imageType = bufferedImage.getType(); if (imageType == 0) { imageType = BufferedImage.TYPE_INT_ARGB; } BufferedImage out = new BufferedImage(getWidth(),getHeight(),imageType); Graphics2D g2d = out.createGraphics(); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); g2d.drawImage(bufferedImage,0,0,null); g2d.dispose(); bufferedImage = out; } //************************************************************************** //** Flip (Horizonal) //************************************************************************** /** Used to flip an image along it's y-axis (horizontal). Vertical flipping * is supported via the rotate method (i.e. rotate +/-180). */ public void flip(){ BufferedImage out = new BufferedImage(getWidth(), getHeight(), getImageType()); AffineTransform tx = AffineTransform.getScaleInstance(-1, 1); tx.translate(-bufferedImage.getWidth(), 0); AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BICUBIC); bufferedImage = op.filter(bufferedImage, out); } //************************************************************************** //** Crop //************************************************************************** /** Used to crop/subset the current image. */ public void crop(int x, int y, int width, int height){ bufferedImage = getSubimage(x,y,width,height); } //************************************************************************** //** copy //************************************************************************** /** Returns a copy of the current image. */ public Image copy(){ ColorModel cm = bufferedImage.getColorModel(); boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); WritableRaster raster = bufferedImage.copyData(null); BufferedImage bi = new BufferedImage(cm, raster, isAlphaPremultiplied, null); bi = bi.getSubimage(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight()); return new Image(bi); } //************************************************************************** //** copyRect //************************************************************************** /** Returns a copy of the image at a given rectangle. */ public Image copyRect(int x, int y, int width, int height){ return new Image(getSubimage(x,y,width,height)); } //************************************************************************** //** getSubimage //************************************************************************** /** Returns a copy of the image at a given rectangle. In Java 1.6, the * BufferedImage.getSubimage() throws an exception if the rectangle falls * outside the image bounds. This method was written to overcome this * limitation. */ private BufferedImage getSubimage(int x, int y, int width, int height){ Rectangle rect1 = new Rectangle(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight()); Rectangle rect2 = new Rectangle(x, y, width, height); //If the requested rectangle falls inside the image bounds, simply return //the subimage if (rect1.contains(rect2)){ return (bufferedImage.getSubimage(x,y,width,height)); } else{ //requested rectangle falls outside the image bounds! //Create buffered image BufferedImage bi = new BufferedImage(width, height, getImageType()); //If the requested rectangle intersects the image bounds, crop the //the source image and insert it into the BufferedImage if (rect1.intersects(rect2)){ Graphics2D g2d = bi.createGraphics(); BufferedImage subImage = null; int X; int Y; if (x<0){ int x1 = 0; int y1; int h; int w; if (y<0){ y1 = 0; h = y+height; Y = height - h; } else{ y1 = y; h = height; Y = 0; } if (h+y1>bufferedImage.getHeight()) h = bufferedImage.getHeight()-y1; w = x+width; if (w>bufferedImage.getWidth()) w = bufferedImage.getWidth(); subImage = bufferedImage.getSubimage(x1,y1,w,h); X = width - w; } else{ int x1 = x; int y1; int h; int w; if (y<0){ y1 = 0; h = y+height; Y = height - h; } else{ y1 = y; h = height; Y = 0; } if (h+y1>bufferedImage.getHeight()) h = bufferedImage.getHeight()-y1; w = width; if (w+x1>bufferedImage.getWidth()) w = bufferedImage.getWidth()-x1; X = 0; subImage = bufferedImage.getSubimage(x1,y1,w,h); } g2d.drawImage(subImage, X, Y, null); g2d.dispose(); } return bi; } } //************************************************************************** //** trim //************************************************************************** /** Used to remove excess pixels around an image by cropping the image to its * "true" extents. Crop bounds are determined by finding the first non-null * or non-black pixel on each side of the image. */ public void trim(){ trim(0,0,0); } //************************************************************************** //** trim //************************************************************************** /** Used to remove excess pixels around an image by cropping the image to its * "true" extents. Crop bounds are determined by finding pixels that *don't* * match the input color. For example, you can trim off excess black pixels * around an image by specifying an rgb value of 0,0,0. Similarly, you can * trim off pure white pixels around an image by specifying an rgb value of * 255,255,255. Note that transparent pixels are considered as null values * and will be automatically trimmed from the edges. */ public void trim(int r, int g, int b){ int top = 0; int bottom = 0; int left = 0; int right = 0; for (int y=0; y-1; y--){ for (int x=0; x-1; x--){ for (int y=0; y1&&quality<=100) quality=quality/100; if (quality==1f && useSunCodec) quality = 1.2f; if (quality>=0f && quality<=1.2f) outputQuality = quality; } //************************************************************************** //** isJPEG //************************************************************************** /** Returns true if the given file extension is associated with a jpeg * compressed image. */ private boolean isJPEG(String FileExtension){ FileExtension = FileExtension.trim().toLowerCase(); if (FileExtension.equals("jpg") || FileExtension.equals("jpeg") || FileExtension.equals("jpe") || FileExtension.equals("jff") ){ return true; } return false; } //************************************************************************** //** isJPEG2000 //************************************************************************** /** Returns true if the given file extension is associated with a jpeg2000 * compressed image. */ private boolean isJPEG2000(String FileExtension){ FileExtension = FileExtension.trim().toLowerCase(); if (FileExtension.equals("jp2") || FileExtension.equals("jpc") || FileExtension.equals("j2k") || FileExtension.equals("jpx") ){ return true; } return false; } //************************************************************************** //** getJPEGByteArray //************************************************************************** /** Returns a JPEG compressed byte array. */ private byte[] getJPEGByteArray(float outputQuality) throws IOException { if (outputQuality>=0f && outputQuality<=1.2f) { ByteArrayOutputStream bas = new ByteArrayOutputStream(); BufferedImage bi = bufferedImage; int t = bufferedImage.getTransparency(); //if (t==BufferedImage.BITMASK) System.out.println("BITMASK"); //if (t==BufferedImage.OPAQUE) System.out.println("OPAQUE"); if (t==BufferedImage.TRANSLUCENT){ bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); Graphics2D biContext = bi.createGraphics(); biContext.drawImage ( bufferedImage, 0, 0, null ); } //First we will try to compress the image using the com.sun.image.codec.jpeg //package. These classes are marked as deprecated in JDK 1.7 and several //users have reported problems with this method. Instead, we are //supposed to use the JPEGImageWriteParam class. However, I have not //been able to adequatly test the compression quality or find an //anology to the setHorizontalSubsampling and setVerticalSubsampling //methods. Therefore, we will attempt to compress the image using the //com.sun.image.codec.jpeg package. If the compression fails, we will //use the JPEGImageWriteParam. if (useSunCodec){ try{ //For Java 1.7 users, we will try to invoke the Sun JPEG Codec using reflection Object encoder = JPEGCodec.getMethod("createJPEGEncoder", java.io.OutputStream.class).invoke(JPEGCodec, bas); Object params = JPEGCodec.getMethod("getDefaultJPEGEncodeParam", BufferedImage.class).invoke(JPEGCodec, bi); params.getClass().getMethod("setQuality", float.class, boolean.class).invoke(params, outputQuality, true); params.getClass().getMethod("setHorizontalSubsampling", int.class, int.class).invoke(params, 0, 2); params.getClass().getMethod("setVerticalSubsampling", int.class, int.class).invoke(params, 0, 2); //Here's the original compression code without reflection /* JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(bas); JPEGEncodeParam params = JPEGCodec.getDefaultJPEGEncodeParam(bi); params.setQuality(outputQuality, true); //true params.setHorizontalSubsampling(0,2); params.setVerticalSubsampling(0,2); params.setMarkerData(...); encoder.encode(bi, params); */ //Save metadata as needed if (saveMetadata && metadata!=null){ java.lang.reflect.Method setMarkerData = params.getClass().getMethod("setMarkerData", int.class, byte[][].class); //Parse unknown markers (similar logic to the getUnknownTags method) java.util.HashSet markers = new java.util.HashSet(); for (String name : metadata.getMetadataFormatNames()) { IIOMetadataNode node = (IIOMetadataNode) metadata.getAsTree(name); for (Node unknownNode : getElementsByTagName("unknown", node)){ String markerTag = getAttributeValue(unknownNode.getAttributes(), "MarkerTag"); try{ int marker = Integer.parseInt(markerTag); if (!markers.contains(marker)){ markers.add(marker); byte[] data = (byte[]) ((IIOMetadataNode) unknownNode).getUserObject(); if (data!=null){ byte[][] app = new byte[1][data.length]; app[0] = data; setMarkerData.invoke(params, marker, app); } } } catch(Exception e){ //e.printStackTrace(); } } } } encoder.getClass().getMethod("encode", BufferedImage.class, JPEGEncodeParam).invoke(encoder, bi, params); } catch(Exception e){ bas.reset(); } } //If the com.sun.image.codec.jpeg package is not found or if the //compression failed, we will use the JPEGImageWriteParam class. if (bas.size()==0){ if (outputQuality>1f) outputQuality = 1f; ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next(); JPEGImageWriteParam params = (JPEGImageWriteParam) writer.getDefaultWriteParam(); params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); params.setCompressionQuality(outputQuality); writer.setOutput(ImageIO.createImageOutputStream(bas)); if (saveMetadata){ writer.write(metadata, new IIOImage(bi, null, metadata), params); } else{ writer.write(null, new IIOImage(bi, null, null), params); } } bas.flush(); return bas.toByteArray(); } else{ return getByteArray(); } } //************************************************************************** //** getImageType //************************************************************************** private int getImageType(){ return getImageType(this.bufferedImage); } private int getImageType(BufferedImage bufferedImage){ int imageType = bufferedImage.getType(); if (imageType <= 0 || imageType == 12) { imageType = BufferedImage.TYPE_INT_ARGB; } return imageType; } //************************************************************************** //** getExtension //************************************************************************** /** Returns the file extension for a given file name, if one exists. */ private String getExtension(String FileName){ if (FileName.contains((CharSequence) ".")){ return FileName.substring(FileName.lastIndexOf(".")+1,FileName.length()); } else{ return ""; } } //************************************************************************** //** hasColor //************************************************************************** /** Used to determine whether a given pixel has a color value. Returns false * if the pixel matches the input color or is transparent. */ private boolean hasColor(int pixel, int red, int green, int blue){ int a = (pixel >> 24) & 0xff; int r = (pixel >> 16) & 0xff; int g = (pixel >> 8) & 0xff; int b = (pixel) & 0xff; if ((r==red && g==green && b==blue) || a==0 ){ return false; } return true; } //************************************************************************** //** equals //************************************************************************** /** Used to compare this image to another. Returns true if the all the * pixels (ARGB values) match. See isSimilarTo() to compare similar images. */ public boolean equals(Object obj){ if (obj!=null){ if (obj instanceof javaxt.io.Image){ javaxt.io.Image image = (javaxt.io.Image) obj; if (image.getWidth()==this.getWidth() && image.getHeight()==this.getHeight()) { //Iterate through all the pixels in the image and compare RGB values for (int i=0; i avg ? (hashBits << 1) | 0x01 : (hashBits << 1) & 0xFFFFFFFFFFFFFFFEl); } } return hashBits; } //************************************************************************** //** getIIOMetadata //************************************************************************** /** Returns the raw, javax.imageio.metadata.IIOMetadata associated with this * image. You can iterate through the metadata using an xml parser like this:
    IIOMetadata metadata = image.getIIOMetadata();
    for (String name : metadata.getMetadataFormatNames()) {
        System.out.println( "Format name: " + name );
        org.w3c.dom.Node metadataNode = metadata.getAsTree(name);
        System.out.println(javaxt.xml.DOM.getNodeValue(metadataNode));
    }
   
*/ public IIOMetadata getIIOMetadata(){ return metadata; } //************************************************************************** //** setIIOMetadata //************************************************************************** /** Used to set/update the raw javax.imageio.metadata.IIOMetadata associated * with this image. */ public void setIIOMetadata(IIOMetadata metadata){ this.metadata = metadata; iptc = null; exif = null; gps = null; saveMetadata = true; } //************************************************************************** //** getIptcData //************************************************************************** /** Returns the raw IPTC byte array (marker 0xED). */ public byte[] getIptcData(){ IIOMetadataNode[] tags = getUnknownTags(0xED); if (tags.length==0) return null; return (byte[]) tags[0].getUserObject(); } //************************************************************************** //** getIptcTags //************************************************************************** /** Used to parse IPTC metadata and return a list of key/value pairs found * in the metadata. You can retrieve specific IPTC metadata values like * this:
    javaxt.io.Image image = new javaxt.io.Image("/temp/image.jpg");
    java.util.HashMap iptc = image.getIptcTags();
    System.out.println("Date: " + iptc.get(0x0237));
    System.out.println("Caption: " + iptc.get(0x0278));
    System.out.println("Copyright: " + iptc.get(0x0274));
   
*/ public HashMap getIptcTags(){ if (iptc==null){ iptc = new HashMap(); for (IIOMetadataNode marker : getUnknownTags(0xED)){ byte[] iptcData = (byte[]) marker.getUserObject(); HashMap tags = new MetadataParser(iptcData, 0xED).getTags("IPTC"); iptc.putAll(tags); } } return iptc; } //************************************************************************** //** getExifData //************************************************************************** /** Returns the raw EXIF byte array (marker 0xE1). */ public byte[] getExifData(){ IIOMetadataNode[] tags = getUnknownTags(0xE1); if (tags.length==0) return null; return (byte[]) tags[0].getUserObject(); } //************************************************************************** //** getExifTags //************************************************************************** /** Used to parse EXIF metadata and return a list of key/value pairs found * in the metadata. Values can be Strings, Integers, or raw Byte Arrays. * You can retrieve specific EXIF metadata values like this:
    javaxt.io.Image image = new javaxt.io.Image("/temp/image.jpg");
    java.util.HashMap exif = image.getExifTags();
    System.out.println("Date: " + exif.get(0x0132));
    System.out.println("Camera: " + exif.get(0x0110));
    System.out.println("Focal Length: " + exif.get(0x920A));
    System.out.println("F-Stop: " + exif.get(0x829D));
    System.out.println("Shutter Speed: " + exif.get(0x829A));
   
* Note that the EXIF MakerNote is not parsed. */ public HashMap getExifTags(){ if (exif==null) parseExif(); return exif; } //************************************************************************** //** getGpsTags //************************************************************************** /** Used to parse EXIF metadata and return a list of key/value pairs * associated with GPS metadata. Values can be Strings, Integers, or raw * Byte Arrays. */ public HashMap getGpsTags(){ if (gps==null) parseExif(); return gps; } /** Private method used to initialize the exif and gps hashmaps */ private void parseExif(){ exif = new HashMap(); gps = new HashMap(); for (IIOMetadataNode marker : getUnknownTags(0xE1)){ byte[] exifData = (byte[]) marker.getUserObject(); MetadataParser metadataParser = new MetadataParser(exifData, 0xE1); HashMap exif = metadataParser.getTags("EXIF"); HashMap gps = metadataParser.getTags("GPS"); if (exif!=null) this.exif.putAll(exif); if (gps!=null) this.gps.putAll(gps); metadataParser = null; } } //************************************************************************** //** getGPSCoordinate //************************************************************************** /** Returns the x/y (lon/lat) coordinate tuple for the image. Value is * derived from EXIF GPS metadata (tags 0x0001, 0x0002, 0x0003, 0x0004). */ public double[] getGPSCoordinate(){ getExifTags(); try{ Double lat = getCoordinate((String) gps.get(0x0002)); Double lon = getCoordinate((String) gps.get(0x0004)); String latRef = (String) gps.get(0x0001); //N String lonRef = (String) gps.get(0x0003); //W if (!latRef.equalsIgnoreCase("N")) lat = -lat; if (!lonRef.equalsIgnoreCase("E")) lon = -lon; return new double[]{lon, lat}; } catch(Exception e){ return null; } } private double getCoordinate(String RationalArray) { //num + "/" + den String[] arr = RationalArray.substring(1, RationalArray.length()-1).split(","); String[] deg = arr[0].trim().split("/"); String[] min = arr[1].trim().split("/"); String[] sec = arr[2].trim().split("/"); double degNumerator = Double.parseDouble(deg[0]); double degDenominator = 1D; try{degDenominator = Double.parseDouble(deg[1]);} catch(Exception e){} double minNumerator = Double.parseDouble(min[0]); double minDenominator = 1D; try{minDenominator = Double.parseDouble(min[1]);} catch(Exception e){} double secNumerator = Double.parseDouble(sec[0]); double secDenominator = 1D; try{secDenominator = Double.parseDouble(sec[1]);} catch(Exception e){} double m = 0; if (degDenominator != 0 || degNumerator != 0){ m = (degNumerator / degDenominator); } if (minDenominator != 0 || minNumerator != 0){ m += (minNumerator / minDenominator) / 60D; } if (secDenominator != 0 || secNumerator != 0){ m += (secNumerator / secDenominator / 3600D); } return m; } //************************************************************************** //** getGPSDatum //************************************************************************** /** Returns the datum associated with the GPS coordinate. Value is * derived from EXIF GPS metadata (tag 0x0012). */ public String getGPSDatum(){ getExifTags(); return (String) gps.get(0x0012); } //************************************************************************** //** getUnknownTags //************************************************************************** /** Returns a list of "unknown" IIOMetadataNodes for a given MarkerTag. You * can use this method to retrieve EXIF, IPTC, XPM, and other format * specific metadata. Example:
    byte[] IptcData = (byte[]) metadata.getUnknownTags(0xED)[0].getUserObject();
    byte[] ExifData = (byte[]) metadata.getUnknownTags(0xE1)[0].getUserObject();
   
*/ public IIOMetadataNode[] getUnknownTags(int MarkerTag){ java.util.ArrayList markers = new java.util.ArrayList(); if (metadata!=null) for (String name : metadata.getMetadataFormatNames()) { IIOMetadataNode node=(IIOMetadataNode) metadata.getAsTree(name); Node[] unknownNodes = getElementsByTagName("unknown", node); for (Node unknownNode : unknownNodes){ try{ int marker = Integer.parseInt(getAttributeValue(unknownNode.getAttributes(), "MarkerTag")); if (marker==MarkerTag) markers.add((IIOMetadataNode) unknownNode); } catch(Exception e){ //e.printStackTrace(); } } } return markers.toArray(new IIOMetadataNode[markers.size()]); } //************************************************************************** //** getMetadataByTagName //************************************************************************** /** Returns a list of IIOMetadataNodes for a given tag name (e.g. "Chroma", * "Compression", "Data", "Dimension", "Transparency", etc).
  //Print unknown tags
    for (IIOMetadataNode unknownNode : metadata.getMetadataByTagName("unknown")){
        int marker = Integer.parseInt(javaxt.xml.DOM.getAttributeValue(unknownNode, "MarkerTag"));
        System.out.println(marker + "\t" + "0x" + Integer.toHexString(marker));
    }
   
*/ public IIOMetadataNode[] getMetadataByTagName(String tagName){ java.util.ArrayList tags = new java.util.ArrayList(); if (metadata!=null) for (String name : metadata.getMetadataFormatNames()) { IIOMetadataNode node=(IIOMetadataNode) metadata.getAsTree(name); Node[] unknownNodes = getElementsByTagName(tagName, node); for (Node unknownNode : unknownNodes){ tags.add((IIOMetadataNode) unknownNode); } } return tags.toArray(new IIOMetadataNode[tags.size()]); } //************************************************************************** //** getElementsByTagName (Copied from javaxt.xml.DOM) //************************************************************************** /** Returns an array of nodes that match a given tagName (node name). The * results will include all nodes that match, regardless of namespace. To * narrow the results to a specific namespace, simply include the namespace * prefix in the tag name (e.g. "t:Contact"). Returns an empty array if * no nodes are found. */ private static Node[] getElementsByTagName(String tagName, Node node){ java.util.ArrayList nodes = new java.util.ArrayList(); getElementsByTagName(tagName, node, nodes); return nodes.toArray(new Node[nodes.size()]); } private static void getElementsByTagName(String tagName, Node node, java.util.ArrayList nodes){ if (node!=null && node.getNodeType()==1){ String nodeName = node.getNodeName().trim(); if (nodeName.contains(":") && !tagName.contains(":")){ nodeName = nodeName.substring(nodeName.indexOf(":")+1); } if (nodeName.equalsIgnoreCase(tagName)){ nodes.add(node); } NodeList childNodes = node.getChildNodes(); for (int i=0; i> tags = new HashMap>(); public MetadataParser(byte[] data, int marker) { switch (marker) { case 0xED: parseIptc(data); break; case 0xE1: parseExif(data); break; } data = null; } //************************************************************************** //** parseIptc //************************************************************************** /** Used to parse IPTC metadata */ private void parseIptc(byte[] iptcData) { HashMap tags = new HashMap(); this.tags.put("IPTC", tags); data = iptcData; int offset = 0; while (offset < data.length) { if (data[offset] == 0x1c) { offset++; int directoryType; int tagType; int tagByteCount; try { directoryType = data[offset++]; tagType = data[offset++]; tagByteCount = get16u(offset); offset += 2; } catch (Exception e) { return; } int tagIdentifier = tagType | (directoryType << 8); String str = ""; if (tagByteCount < 1 || tagByteCount>(data.length-offset)) { } else { try { str = new String(data, offset, tagByteCount, "UTF-8"); offset += tagByteCount; } catch (Exception e) { } } tags.put(tagIdentifier, str); } else{ offset++; } } } //************************************************************************** //** parseExif //************************************************************************** /** Used to parse EXIF metadata */ public void parseExif(byte[] exifData) { HashMap tags = new HashMap(); this.tags.put("EXIF", tags); try{ String dataStr = new String(exifData, 0, 8, "UTF-8"); //new String(exifData); if (exifData.length <= 4 || !"Exif".equals(dataStr.substring(0, 4))) { //System.err.println("Not really EXIF data"); return; } String byteOrderMarker = dataStr.substring(6, 8); if ("II".equals(byteOrderMarker)) { intelOrder = true; } else if ("MM".equals(byteOrderMarker)) { intelOrder = false; } else { //System.err.println("Incorrect byte order in EXIF data."); return; } } catch(Exception e){ return; } data = exifData; int checkValue = get16u(8); if (checkValue != 0x2a) { data = null; //System.err.println("Check value fails: 0x"+ Integer.toHexString(checkValue)); return; } if (data==null) return; int firstOffset = get32u(10); processExifDir(6 + firstOffset, 6, tags); } //************************************************************************** //** getTags //************************************************************************** /** Returns key/value pairs representing the EXIF or IPTC data. */ public HashMap getTags(String dir) { return tags.get(dir); } private void processExifDir(int dirStart, int offsetBase, HashMap tags) { if (dirStart>=data.length) return; int numEntries = get16u(dirStart); for (int de = 0; de < numEntries; de++) { int dirOffset = dirStart + 2 + (12 * de); int tag = get16u(dirOffset); int format = get16u(dirOffset + 2); int components = get32u(dirOffset + 4); //System.err.println("EXIF: entry: 0x" + Integer.toHexString(tag) // + " " + format // + " " + components); if (format < 0 || format > NUM_FORMATS) { //System.err.println("Bad number of formats in EXIF dir: " + format); return; } int byteCount = components * bytesPerFormat[format]; int valueOffset = dirOffset + 8; if (byteCount > 4) { int offsetVal = get32u(dirOffset + 8); valueOffset = offsetBase + offsetVal; } if (tag == TAG_EXIF_OFFSET || tag == TAG_INTEROP_OFFSET || tag == TAG_GPS_OFFSET) { String dirName = ""; switch (tag) { case TAG_EXIF_OFFSET: dirName = "EXIF"; break; case TAG_INTEROP_OFFSET: dirName = "EXIF"; break; case TAG_GPS_OFFSET: dirName = "GPS"; break; } tags = this.tags.get(dirName); if (tags==null){ tags = new HashMap(); this.tags.put(dirName, tags); } int subdirOffset = get32u(valueOffset); processExifDir(offsetBase + subdirOffset, offsetBase, tags); } //else if (tag==0x927c){ //Maker Note //TODO: Come up with a clever way to process the Maker Note //data = java.util.Arrays.copyOfRange(data, valueOffset, byteCount); //tags = new HashMap(); //processExifDir(0, 6); //} else { switch (format) { case FMT_STRING: String value = getString(valueOffset, byteCount); if (value!=null) tags.put(tag, value); break; case FMT_SBYTE: case FMT_BYTE: case FMT_USHORT: case FMT_SSHORT: case FMT_ULONG: case FMT_SLONG: tags.put(tag, (int) getDouble(format, valueOffset)); break; case FMT_URATIONAL: case FMT_SRATIONAL: if (components>1) { //Create a string representing an array of rational numbers StringBuffer str = new StringBuffer(); str.append("["); for (int i=0; i= 0 && srcX < srcWidth1 && srcY >= 0 && srcY < srcHeight1) { // Easy case, all corners are in the image int i = srcWidth*srcY + srcX; nw = inPixels[i]; ne = inPixels[i+1]; sw = inPixels[i+srcWidth]; se = inPixels[i+srcWidth+1]; } else { // Some of the corners are off the image nw = getPixel( inPixels, srcX, srcY, srcWidth, srcHeight ); ne = getPixel( inPixels, srcX+1, srcY, srcWidth, srcHeight ); sw = getPixel( inPixels, srcX, srcY+1, srcWidth, srcHeight ); se = getPixel( inPixels, srcX+1, srcY+1, srcWidth, srcHeight ); } outPixels[x] = bilinearInterpolate(xWeight, yWeight, nw, ne, sw, se); } setRGB( dst, 0, y, transformedSpace.width, 1, outPixels ); } return dst; } final private int getPixel( int[] pixels, int x, int y, int width, int height ) { if (x < 0 || x >= width || y < 0 || y >= height) { switch (edgeAction) { case ZERO: default: return 0; case WRAP: return pixels[(mod(y, height) * width) + mod(x, width)]; case CLAMP: return pixels[(clamp(y, 0, height-1) * width) + clamp(x, 0, width-1)]; } } return pixels[ y*width+x ]; } protected BufferedImage filterPixelsNN( BufferedImage dst, int width, int height, int[] inPixels, Rectangle transformedSpace ) { int srcWidth = width; int srcHeight = height; int outWidth = transformedSpace.width; int outHeight = transformedSpace.height; int outX, outY, srcX, srcY; int[] outPixels = new int[outWidth]; outX = transformedSpace.x; outY = transformedSpace.y; int[] rgb = new int[4]; float[] out = new float[2]; for (int y = 0; y < outHeight; y++) { for (int x = 0; x < outWidth; x++) { transformInverse(outX+x, outY+y, out); srcX = (int)out[0]; srcY = (int)out[1]; // int casting rounds towards zero, so we check out[0] < 0, not srcX < 0 if (out[0] < 0 || srcX >= srcWidth || out[1] < 0 || srcY >= srcHeight) { int p; switch (edgeAction) { case ZERO: default: p = 0; break; case WRAP: p = inPixels[(mod(srcY, srcHeight) * srcWidth) + mod(srcX, srcWidth)]; break; case CLAMP: p = inPixels[(clamp(srcY, 0, srcHeight-1) * srcWidth) + clamp(srcX, 0, srcWidth-1)]; break; } outPixels[x] = p; } else { int i = srcWidth*srcY + srcX; rgb[0] = inPixels[i]; outPixels[x] = inPixels[i]; } } setRGB( dst, 0, y, transformedSpace.width, 1, outPixels ); } return dst; } protected void transformInverse(int x, int y, float[] out) { out[0] = originalSpace.width * (A*x+B*y+C)/(G*x+H*y+I); out[1] = originalSpace.height * (D*x+E*y+F)/(G*x+H*y+I); } /* public Rectangle2D getBounds2D( BufferedImage src ) { return new Rectangle(0, 0, src.getWidth(), src.getHeight()); } public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) { if ( dstPt == null ) dstPt = new Point2D.Double(); dstPt.setLocation( srcPt.getX(), srcPt.getY() ); return dstPt; } */ /** * A convenience method for getting ARGB pixels from an image. This tries to avoid the performance * penalty of BufferedImage.getRGB unmanaging the image. */ public int[] getRGB( BufferedImage image, int x, int y, int width, int height, int[] pixels ) { int type = image.getType(); if ( type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB ) return (int [])image.getRaster().getDataElements( x, y, width, height, pixels ); return image.getRGB( x, y, width, height, pixels, 0, width ); } /** * A convenience method for setting ARGB pixels in an image. This tries to avoid the performance * penalty of BufferedImage.setRGB unmanaging the image. */ public void setRGB( BufferedImage image, int x, int y, int width, int height, int[] pixels ) { int type = image.getType(); if (type == BufferedImage.TYPE_INT_ARGB || type == BufferedImage.TYPE_INT_RGB) image.getRaster().setDataElements( x, y, width, height, pixels ); else image.setRGB( x, y, width, height, pixels, 0, width ); } /** * Clamp a value to an interval. * @param a the lower clamp threshold * @param b the upper clamp threshold * @param x the input parameter * @return the clamped value */ private float clamp(float x, float a, float b) { return (x < a) ? a : (x > b) ? b : x; } /** * Clamp a value to an interval. * @param a the lower clamp threshold * @param b the upper clamp threshold * @param x the input parameter * @return the clamped value */ private int clamp(int x, int a, int b) { return (x < a) ? a : (x > b) ? b : x; } /** * Return a mod b. This differs from the % operator with respect to negative numbers. * @param a the dividend * @param b the divisor * @return a mod b */ private double mod(double a, double b) { int n = (int)(a/b); a -= n*b; if (a < 0) return a + b; return a; } /** * Return a mod b. This differs from the % operator with respect to negative numbers. * @param a the dividend * @param b the divisor * @return a mod b */ private float mod(float a, float b) { int n = (int)(a/b); a -= n*b; if (a < 0) return a + b; return a; } /** * Return a mod b. This differs from the % operator with respect to negative numbers. * @param a the dividend * @param b the divisor * @return a mod b */ private int mod(int a, int b) { int n = a/b; a -= n*b; if (a < 0) return a + b; return a; } /** * Bilinear interpolation of ARGB values. * @param x the X interpolation parameter 0..1 * @param y the y interpolation parameter 0..1 * @param rgb array of four ARGB values in the order NW, NE, SW, SE * @return the interpolated value */ private int bilinearInterpolate(float x, float y, int nw, int ne, int sw, int se) { float m0, m1; int a0 = (nw >> 24) & 0xff; int r0 = (nw >> 16) & 0xff; int g0 = (nw >> 8) & 0xff; int b0 = nw & 0xff; int a1 = (ne >> 24) & 0xff; int r1 = (ne >> 16) & 0xff; int g1 = (ne >> 8) & 0xff; int b1 = ne & 0xff; int a2 = (sw >> 24) & 0xff; int r2 = (sw >> 16) & 0xff; int g2 = (sw >> 8) & 0xff; int b2 = sw & 0xff; int a3 = (se >> 24) & 0xff; int r3 = (se >> 16) & 0xff; int g3 = (se >> 8) & 0xff; int b3 = se & 0xff; float cx = 1.0f-x; float cy = 1.0f-y; m0 = cx * a0 + x * a1; m1 = cx * a2 + x * a3; int a = (int)(cy * m0 + y * m1); m0 = cx * r0 + x * r1; m1 = cx * r2 + x * r3; int r = (int)(cy * m0 + y * m1); m0 = cx * g0 + x * g1; m1 = cx * g2 + x * g3; int g = (int)(cy * m0 + y * m1); m0 = cx * b0 + x * b1; m1 = cx * b2 + x * b3; int b = (int)(cy * m0 + y * m1); return (a << 24) | (r << 16) | (g << 8) | b; } } //end skew class } //end image class
X Tutup