/* * This file is part of Caliph & Emir. * * Caliph & Emir is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Caliph & Emir is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Caliph & Emir; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Copyright statement: * -------------------- * (c) 2002-2005 by Mathias Lux (mathias@juggle.at) * http://www.juggle.at, http://caliph-emir.sourceforge.net */ package at.lux.fotoannotation; import at.knowcenter.caliph.objectcatalog.semanticscreator.IMBeeApplicationPanel; import at.lux.components.ImageThumbPanel; import at.lux.fotoannotation.panels.*; import at.lux.imageanalysis.*; import at.lux.imaging.BmpReader; import at.lux.imaging.PpmReader; import com.drew.imaging.jpeg.JpegProcessingException; import com.drew.metadata.Directory; import com.drew.metadata.Metadata; import com.drew.metadata.MetadataException; import com.drew.metadata.Tag; import com.drew.metadata.exif.ExifDirectory; import com.drew.metadata.exif.ExifReader; import com.drew.metadata.iptc.IptcDirectory; import com.drew.metadata.iptc.IptcReader; import org.jaxen.JaxenException; import org.jaxen.XPath; import org.jaxen.jdom.JDOMXPath; import org.jdom.Attribute; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import javax.imageio.ImageIO; import javax.swing.*; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.text.DecimalFormat; import java.util.Calendar; import java.util.Date; import java.util.Iterator; import java.util.List; /** * ImageLoader * Date: 14.09.2002 * Time: 21:28:49 * * @author Mathias Lux, mathias@juggle.at */ public class ImageLoader extends Thread { public static DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance(); private JLabel status; private AnnotationFrame parent; private File img; private ImageThumbPanel imgPanel; private CreationPanel creationPanel; private TextDescriptionPanel textPanel; MetadataDescriptionPanel mdpanel; QualityPanel qPanel; IMBeeApplicationPanel bee; ColorLayoutPanel colorPanel; ShapePanel shapePanel; public ImageLoader(JLabel status, AnnotationFrame parent, File img, ImageThumbPanel imgPanel, CreationPanel creationPanel, TextDescriptionPanel textPanel, MetadataDescriptionPanel mdpanel, QualityPanel qPanel, IMBeeApplicationPanel bee, ColorLayoutPanel colorPanel, ShapePanel shapePanel) { this.status = status; this.parent = parent; this.img = img; this.imgPanel = imgPanel; this.creationPanel = creationPanel; this.textPanel = textPanel; this.mdpanel = mdpanel; this.qPanel = qPanel; this.bee = bee; this.colorPanel = colorPanel; this.shapePanel = shapePanel; } public void run() { parent.setEnabled(false); parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); imgPanel.setWait(true); status.setText("Please wait while loading image " + img.toString()); try { // loading the image BufferedImage image; if (img.getName().toLowerCase().endsWith(".ppm")) { image = PpmReader.read(new FileInputStream(img)); } else if (img.getName().toLowerCase().endsWith(".bmp")) { image = BmpReader.read(new FileInputStream(img)); } else { image = ImageIO.read(new FileInputStream(img)); } imgPanel.setImage(image); imgPanel.repaint(); // clearing previous information: if (!parent.isAutopilot()) { // check if the autopilot was the source of the call! textPanel.clearTextFields(); } // extracting information extractInformation(image); status.setText("Finished"); } catch (IOException e) { status.setText("Error: " + e.toString()); } finally { imgPanel.setWait(false); parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); parent.setEnabled(true); imgPanel.repaint(); } } private void extractInformation(BufferedImage image) { // Standardwerte wiederherstellen: mdpanel.setToCurrentTime(); creationPanel.resetTable(); debug("Generating thumbnail ..."); AnnotationToolkit.generateThumbnail(img); debug("Extracing information from the image itself"); creationPanel.setBitsPerPixel(image.getColorModel().getPixelSize() + ""); creationPanel.setImageSize(image.getWidth() + "", image.getHeight() + ""); creationPanel.setFileSize(img.length() + ""); if (img.getName().toLowerCase().endsWith(".jpg") || img.getName().toLowerCase().endsWith(".jpeg")) creationPanel.setFileFormat("JPEG"); else if (img.getName().toLowerCase().endsWith(".ppm")) { creationPanel.setFileFormat("PPM"); } else if (img.getName().toLowerCase().endsWith(".bmp")) { creationPanel.setFileFormat("BMP"); } else if (img.getName().toLowerCase().endsWith(".png")) { creationPanel.setFileFormat("PNG"); } String textPanelContent = null; try { // ---------------------------- // Reading XMP (Adobe Photoshop // specific metadata format) // ---------------------------- File xmpFile; xmpFile = new File(img.getCanonicalPath() + ".xmp"); if (xmpFile.exists()) { SAXBuilder builder; builder = new SAXBuilder(); try { Element xmpRoot; String suppCat = "", xmpDesc = "", xmpWords = ""; xmpRoot = builder.build(xmpFile).getRootElement(); // Create the whole bunch of needed namespaces: XPath path = null; try { // Supplemental Categories: path = new JDOMXPath("//photoshop:SupplementalCategories/rdf:Bag/rdf:li"); path.addNamespace("dc", "http://purl.org/dc/elements/1.1/"); path.addNamespace("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); path.addNamespace("photoshop", "http://ns.adobe.com/photoshop/1.0/"); List results = path.selectNodes(xmpRoot); if (results.size() > 0) { StringBuffer buff = new StringBuffer(); for (Iterator i = results.iterator(); i.hasNext();) { Element elem = (Element) i.next(); if (elem.getTextTrim().length() > 0) { buff.append(elem.getTextTrim()); if (i.hasNext()) buff.append(", "); } } debug(buff.toString() + " found as supplemental categories"); suppCat = buff.toString(); } else { debug("no supplementalCategories found ..."); } // Description: path = new JDOMXPath("//dc:description/rdf:Alt/rdf:li"); path.addNamespace("dc", "http://purl.org/dc/elements/1.1/"); path.addNamespace("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); results = path.selectNodes(xmpRoot); if (results.size() > 0) { StringBuffer buff = new StringBuffer(); for (Iterator i = results.iterator(); i.hasNext();) { Element elem = (Element) i.next(); if (elem.getTextTrim().length() > 0) { buff.append(elem.getTextTrim()); if (i.hasNext()) buff.append(", "); } } debug(buff.toString() + " found as dc description"); xmpDesc = buff.toString(); } else { debug("no dc description found ..."); } if (suppCat.length() > 0) { suppCat = suppCat + ", "; } if (xmpDesc.length() > 0) { xmpDesc = xmpDesc + ", "; } if (xmpWords.length() > 0) { xmpWords = xmpWords + ", "; } textPanel.setDescriptionText(suppCat + xmpDesc + xmpWords); textPanelContent = suppCat + xmpDesc + xmpWords; // Description: path = new JDOMXPath("//dc:subject/rdf:Bag/rdf:li"); path.addNamespace("dc", "http://purl.org/dc/elements/1.1/"); path.addNamespace("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); results = path.selectNodes(xmpRoot); if (results.size() > 0) { StringBuffer buff = new StringBuffer(); for (Iterator i = results.iterator(); i.hasNext();) { Element elem = (Element) i.next(); if (elem.getTextTrim().length() > 0) { buff.append(elem.getTextTrim()); if (i.hasNext()) buff.append(", "); } } debug(buff.toString() + " found as dc keywords"); xmpDesc = buff.toString(); } else { debug("no dc keywords found ..."); } } catch (JaxenException e) { debug("Error handling XPath: " + e.toString()); } } catch (JDOMException e) { // XMP not processable debug("XMP not processable: " + e.toString()); } } else { debug("XMP file " + xmpFile.getName() + "not present ... "); } // ---------------------------- // Reading EXIF // ---------------------------- debug("Extracing EXIF"); status.setText("Extracting EXIF ..."); // reading out EXIF and IPTC: Metadata metadata = new Metadata(); new ExifReader(img).extract(metadata); new IptcReader(img).extract(metadata); // getting exif tags: Directory exifDirectory = metadata.getDirectory(ExifDirectory.class); try { Date exifdate = null; if (exifDirectory.containsTag(ExifDirectory.TAG_DATETIME_ORIGINAL)) exifdate = exifDirectory.getDate(ExifDirectory.TAG_DATETIME_ORIGINAL); else if (exifDirectory.containsTag(ExifDirectory.TAG_DATETIME)) exifdate = exifDirectory.getDate(ExifDirectory.TAG_DATETIME); else if (exifDirectory.containsTag(ExifDirectory.TAG_DATETIME_DIGITIZED)) exifdate = exifDirectory.getDate(ExifDirectory.TAG_DATETIME_DIGITIZED); if (exifdate != null) { Calendar c = Calendar.getInstance(); c.setTime(exifdate); String time = c.get(Calendar.YEAR) + "-" + AnnotationToolkit.to2letters(c.get(Calendar.MONTH) + 1) + "-" + AnnotationToolkit.to2letters(c.get(Calendar.DAY_OF_MONTH)) + "T" + AnnotationToolkit.to2letters(c.get(Calendar.HOUR_OF_DAY)) + ":" + AnnotationToolkit.to2letters(c.get(Calendar.MINUTE)) + ":" + AnnotationToolkit.to2letters(c.get(Calendar.SECOND)); creationPanel.setTime(time); String dateString = AnnotationToolkit.toMonthString(c.get(Calendar.MONTH)) + ' ' + c.get(Calendar.YEAR); textPanel.setWhen(dateString); // Setting the time to some Properties in the parent frame to read them later on. parent.getCurrentFileProperties().setProperty("exif.time.short.string", dateString); parent.getCurrentFileProperties().setProperty("exif.time.full.string", time); // debug("Time set with Date-object: " + exifdate.toString()); } else { parent.getCurrentFileProperties().setProperty("exif.time.short.string", "n.a"); parent.getCurrentFileProperties().setProperty("exif.time.full.string", "n.a"); } } catch (Exception e) { e.printStackTrace(); } Iterator tagIterator = exifDirectory.getTagIterator(); while (tagIterator.hasNext()) { try { Tag currentTag = (Tag) tagIterator.next(); int tagType = exifDirectory.getInt(currentTag.getTagType()); status.setText("Extracting EXIF: " + currentTag.getTagName()); debug(exifDirectory.getTagName(tagType) + ": " + exifDirectory.getString(tagType)); creationPanel.addKeyValuePair(currentTag.getTagName(), currentTag.getDescription()); } catch (MetadataException e) { System.err.println("Exception parsing tag .. " + e.toString()); } } // ---------------------------- // Reading IPTC // ---------------------------- Directory iptcDirectory = metadata.getDirectory(IptcDirectory.class); tagIterator = iptcDirectory.getTagIterator(); StringBuffer buff = new StringBuffer(); if (iptcDirectory.containsTag(IptcDirectory.TAG_CAPTION)) { buff.append(iptcDirectory.getString(IptcDirectory.TAG_CAPTION) + "\n"); } if (iptcDirectory.containsTag(IptcDirectory.TAG_SUPPLEMENTAL_CATEGORIES)) { buff.append(iptcDirectory.getString(IptcDirectory.TAG_SUPPLEMENTAL_CATEGORIES) + "\n"); } if (iptcDirectory.containsTag(IptcDirectory.TAG_KEYWORDS)) { buff.append(iptcDirectory.getString(IptcDirectory.TAG_KEYWORDS) + "\n"); } if (textPanelContent != null) { textPanel.setDescriptionText(textPanelContent + "\n" + buff.toString()); } else { textPanel.setDescriptionText(buff.toString()); } while (tagIterator.hasNext()) { try { Tag currentTag = (Tag) tagIterator.next(); // int tagType = iptcDirectory.getInt(currentTag.getTagType()); status.setText("Extracting IPTC: " + currentTag.getTagName()); // System.out.println("Extracting IPTC: " + currentTag.getTagName()); debug(currentTag.getTagName() + ": " + currentTag.getTagName()); creationPanel.addKeyValuePair("(IPTC) " + currentTag.getTagName(), currentTag.getDescription()); } catch (MetadataException e) { System.err.println("Exception parsing tag .. " + e.toString()); } } creationPanel.updateTable(); } catch (IOException e) { debug("IOException reading EXIF: " + e.getMessage()); } catch (JpegProcessingException e) { debug("Could not read metadata: " + e.getMessage()); } /* catch (ExifProcessingException e) { // Keine EXIF Info? debug("ExifProcessingException: " + e.getMessage()); // creating std tags ... (werden manuell bef�llt) creationPanel.addKeyValuePair("Make", ""); creationPanel.addKeyValuePair("Model", ""); // Setting time from file ... weil ja keine Info da .. Calendar c = Calendar.getInstance(); c.setTime(new Date(img.lastModified())); String time = AnnotationToolkit.getMpeg7Time(c); creationPanel.setTime(time); } catch (MetadataException e) { e.printStackTrace(); } */ try { String filePath = img.getCanonicalPath(); String mp7name = filePath.substring(0, filePath.lastIndexOf('.')) + ".mp7.xml"; File mp7file = new File(mp7name); if (mp7file.exists()) { debug("Reading existing MPEG-7 information " + mp7name); status.setText("Reading existing MPEG-7 information"); // ---------------------------- // Reading from the MPEG-7 File // ---------------------------- SAXBuilder builder = new SAXBuilder(); Element root = builder.build(mp7file).getRootElement(); // in case of the shape panel the whole document is // used to set the shapes ... shapePanel.setDescriptor(root); // textual description of the File .. String text = getSingleValue(root, "//Image/TextAnnotation/FreeTextAnnotation"); if (text != null) textPanel.setDescriptionText(text); text = getSingleValue(root, "//Image/TextAnnotation/StructuredAnnotation/Who/Name"); if (text != null) textPanel.setWho(text); text = getSingleValue(root, "//Image/TextAnnotation/StructuredAnnotation/When/Name"); if (text != null) textPanel.setWhen(text); text = getSingleValue(root, "//Image/TextAnnotation/StructuredAnnotation/Where/Name"); if (text != null) textPanel.setWhere(text); text = getSingleValue(root, "//Image/TextAnnotation/StructuredAnnotation/Why/Name"); if (text != null) textPanel.setWhy(text); text = getSingleValue(root, "//Image/TextAnnotation/StructuredAnnotation/How/Name"); if (text != null) textPanel.setHow(text); text = getSingleValue(root, "//Image/TextAnnotation/StructuredAnnotation/WhatAction/Name"); if (text != null) textPanel.setWhatAction(text); text = getSingleValue(root, "//Image/TextAnnotation/StructuredAnnotation/WhatObject/Name"); if (text != null) textPanel.setWhatObject(text); // description version text = getSingleValue(root, "//DescriptionMetadata/Version"); if (text != null) { mdpanel.setVersion(Double.toString(Double.parseDouble(text) + 1d)); } // description tool text = getSingleValue(root, "//DescriptionMetadata/Instrument/Tool/Name"); if (text != null) mdpanel.setInstrument(text); // description date text = getSingleValue(root, "//DescriptionMetadata/CreationTime"); if (text != null) mdpanel.setTime(text); // description comment text = getSingleValue(root, "//DescriptionMetadata/Comment/FreeTextAnnotation"); if (text != null) mdpanel.setTextDescription(text); // quality & definition ob der Descriptor gebraucht wird ... text = getSingleValue(root, "//Image/MediaInformation/MediaProfile/MediaQuality/QualityRating/RatingValue"); if (text != null) { qPanel.setQuality(Integer.parseInt(text)); qPanel.setIncludeQuality(true); } else { qPanel.setIncludeQuality(false); } // quality agent text = getSingleValue(root, "//Image/MediaInformation/MediaProfile/MediaQuality/RatingSource/Name/GivenName"); String text2 = getSingleValue(root, "//Image/MediaInformation/MediaProfile/MediaQuality/RatingSource/Name/FamilyName"); if (text != null && text2 != null) { debug("setting quality agent: " + text2 + ", " + text); qPanel.setAgent(text2 + ", " + text); } // description metadata agent text = getSingleValue(root, "//DescriptionMetadata/Creator/Agent/Name/GivenName"); text2 = getSingleValue(root, "//DescriptionMetadata/Creator/Agent/Name/FamilyName"); if (text != null && text2 != null) { debug("setting description metadata agent: " + text2 + ", " + text); mdpanel.setAgent(text2 + ", " + text); } // creation agent text = getSingleValue(root, "//CreationInformation/Creation/Creator/Agent/Name/GivenName"); text2 = getSingleValue(root, "//CreationInformation/Creation/Creator/Agent/Name/FamilyName"); if (text != null && text2 != null) { debug("setting creation agent: " + text2 + ", " + text); creationPanel.setAgent(text2 + ", " + text); } // semantics ... java.util.List results = AnnotationToolkit.xpathQuery(root, "//Image/Semantic", null); if (results.size() > 0) { debug("setting bee semantic description ..."); bee.setSemantics((Element) ((Element) results.get(0)).detach()); } // Scalable Color boolean ccset = false, clset = false, ehset = false, dcset = false; results = AnnotationToolkit.xpathQuery(root, "//VisualDescriptor", null); if (results.size() > 0) { for (Iterator it1 = results.iterator(); it1.hasNext();) { Element elem = (Element) it1.next(); java.util.List atts = elem.getAttributes(); for (Iterator it2 = atts.iterator(); it2.hasNext();) { Attribute att = (Attribute) it2.next(); if (att.getValue().equals("ScalableColorType")) { debug("setting descriptor ScalableColor ..."); colorPanel.setScalableColor(new ScalableColor((Element) elem.detach())); ccset = true; } else if (att.getValue().equals("ColorLayoutType")) { debug("setting descriptor ColorLayout ..."); colorPanel.setColorLayout(new ColorLayout((Element) elem.detach())); clset = true; } else if (att.getValue().equals("DominantColorType")) { debug("setting descriptor DominantColor ..."); try { colorPanel.setDominantColor(new DominantColor((Element) elem.detach())); dcset = true; } catch (VisualDescriptorException e) { e.printStackTrace(); } } else if (att.getValue().equals("EdgeHistogramType")) { debug("setting descriptor EdgeHistogram ..."); try { colorPanel.setEdgeHistogram(new EdgeHistogram((Element) elem.detach())); ehset = true; } catch (Exception e) { e.printStackTrace(); } } } } } if (!ccset) { debug("Did not get the descriptor from XML: Extracing visual descriptor ScalableColor"); status.setText("Extracing visual descriptor ScalableColor ..."); colorPanel.setScalableColor(new ScalableColor(image)); } if (!clset) { debug("Did not get the descriptor from XML: Extracing visual descriptor ColorLayout"); status.setText("Extracing visual descriptor ColorLayout ..."); colorPanel.setColorLayout(new ColorLayout(image)); } // Scaling down the image to speed up the extraction thing: if (!ehset || !dcset) { if (Math.max(image.getWidth(), image.getHeight()) > 640) { status.setText("Scaling down image for extracing Descriptors ..."); // switch images image = scaleImage(image, 640); } } if (!ehset) { debug("Did not get the descriptor from XML: Extracing visual descriptor EdgeHistogram"); status.setText("Extracing visual descriptor EdgeHistogram ..."); colorPanel.setEdgeHistogram(new EdgeHistogram(image)); } if (!dcset) { debug("Did not get the descriptor from XML: Extracing visual descriptor DominantColor"); status.setText("Extracing visual descriptor DominantColor ..."); colorPanel.setDominantColor(new DominantColor(image)); } debug("Finished reading MPEG-7 description."); AnnotationFrame.setDirty(false); parent.setTitle(AnnotationFrame.TITLE_BAR + ": " + img.getName()); } else { debug("No MPEG-7 Description found, I've searched for " + mp7name); // here we check if the image is too big and scale it down: // todo: configure via properties. int maxSideLength = 640; if (Math.max(image.getWidth(), image.getHeight()) > maxSideLength) { status.setText("Scaling down image for extracing Descriptors ..."); // switch images image = scaleImage(image, maxSideLength); } debug("Extracing visual descriptor ColorLayout"); status.setText("Extracing visual descriptor ColorLayout ..."); colorPanel.setColorLayout(new ColorLayout(image)); debug("Extracing visual descriptor ScalableColor"); status.setText("Extracing visual descriptor ScalableColor ..."); colorPanel.setScalableColor(new ScalableColor(image)); status.setText("Extracing visual descriptor EdgeHistogram ..."); colorPanel.setEdgeHistogram(new EdgeHistogram(image)); status.setText("Extracing visual descriptor DominantColor ..."); colorPanel.setDominantColor(new DominantColor(image)); status.setText("Setting image in shape panel ..."); shapePanel.setImage(image); AnnotationFrame.DIRTY = true; } } catch (IOException e) { debug("IOException while searching and reading existing MPEG-7 description " + e.toString()); } catch (JDOMException e) { debug("Exception parsing existing MPEG-7 description" + e.toString()); } } public static BufferedImage scaleImage(BufferedImage image, int maxSideLength) { double ow = image.getWidth(); double oh = image.getHeight(); double scale = 0.0; if (ow > oh) { scale = ((double) maxSideLength / ow); } else { scale = ((double) maxSideLength / oh); } // create smaller image BufferedImage img = new BufferedImage((int) (ow * scale), (int) (oh * scale), BufferedImage.TYPE_INT_RGB); // fast scale Graphics g = img.getGraphics(); g.drawImage(image, 0, 0, img.getWidth(), img.getHeight(), null); return img; } private void debug(String message) { if (AnnotationFrame.DEBUG) System.out.println("[at.lux.fotoannotation.ImageLoader] " + message); } // private void collectGarbage() { // debug("Mem: " // + df.format(Runtime.getRuntime().freeMemory() / (1024.0 * 1024.0)) + "MB of " // + df.format(Runtime.getRuntime().totalMemory() / (1024.0 * 1024.0)) + "MB free"); // // debug("starting garbage collector ..."); // System.gc(); // debug("finished collecting garbage!"); // // debug("Mem: " // + df.format(Runtime.getRuntime().freeMemory() / (1024.0 * 1024.0)) + "MB of " // + df.format(Runtime.getRuntime().totalMemory() / (1024.0 * 1024.0)) + "MB free"); // status.setText("Garbage collection finished: " // + df.format(Runtime.getRuntime().freeMemory() / (1024.0 * 1024.0)) + "MB of " // + df.format(Runtime.getRuntime().totalMemory() / (1024.0 * 1024.0)) + "MB free"); // // } private String getSingleValue(Element root, String xpath) { String text = null; java.util.List results = AnnotationToolkit.xpathQuery(root, xpath, null); if (results.size() > 0) { text = ((Element) results.get(0)).getTextTrim(); } return text; } }