2

I am using a piece of code posted on stackover flow to write custom metadata to PNG image and read it. The write function seems to work fine but when i try to read data that i had written it throws NullPointerException. Can someone tell me what is wrong?

Here is code for writing metadata

try{
    image=ImageIO.read(new FileInputStream("input.png"));
    writeCustomData(image, "software", "FRDDC");
    ImageIO.write(image, "png", new File("output.png"));
    }
    catch(Exception e){
    e.printStackTrace();
    }

Method to write metadata

   public static byte[] writeCustomData(BufferedImage buffImg, String key, String value) throws Exception {
    ImageWriter writer = ImageIO.getImageWritersByFormatName("png").next();

    ImageWriteParam writeParam = writer.getDefaultWriteParam();
    ImageTypeSpecifier typeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);

    //adding metadata
        javax.imageio.metadata.IIOMetadata metadata = writer.getDefaultImageMetadata(typeSpecifier, writeParam);

    IIOMetadataNode textEntry = new IIOMetadataNode("tEXtEntry");
    textEntry.setAttribute("keyword", key);
    textEntry.setAttribute("value", value);

    IIOMetadataNode text = new IIOMetadataNode("tEXt");
    text.appendChild(textEntry);

    IIOMetadataNode root = new IIOMetadataNode("javax_imageio_png_1.0");
    root.appendChild(text);

    metadata.mergeTree("javax_imageio_png_1.0", root);

    //writing the data
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
        javax.imageio.stream.ImageOutputStream stream = ImageIO.createImageOutputStream(baos);
    writer.setOutput(stream);
    writer.write(metadata, new IIOImage(buffImg, null, metadata), writeParam);

    try {

            ImageIO.write(buffImg, "png", new File("new.png"));
        } catch (Exception e) {
            e.printStackTrace();
        }

    stream.close();

    return baos.toByteArray();
}

Reading metadata

try{
image=ImageIO.read(new FileInputStream("output.png"));

            ByteArrayOutputStream baos=new ByteArrayOutputStream();
            ImageIO.write(image, "png", baos );
            byte[] b=baos.toByteArray();
            String out=readCustomData(b, "software");
}
catch(Exception e){
e.printStackTrace();
}

Method to read metadata

 public static String readCustomData(byte[] imageData, String key) throws IOException{
    ImageReader imageReader = ImageIO.getImageReadersByFormatName("png").next();

    imageReader.setInput(ImageIO.createImageInputStream(new ByteArrayInputStream(imageData)), true);

    // read metadata of first image
        javax.imageio.metadata.IIOMetadata metadata = imageReader.getImageMetadata(0);

    //this cast helps getting the contents

     //Node n=metadata.getAsTree("javax_imageio_png_1.0");
     //NodeList childNodes=n.getChildNodes();
    PNGMetadata pngmeta = (PNGMetadata) metadata; 
    if(pngmeta.getStandardTextNode()==null){
        System.out.println("not found");
    }
    NodeList childNodes = pngmeta.getStandardTextNode().getChildNodes();

    for (int i = 0; i < childNodes.getLength(); i++) {
        Node node = childNodes.item(i);
        String keyword = node.getAttributes().getNamedItem("keyword").getNodeValue();
        String value = node.getAttributes().getNamedItem("value").getNodeValue();
        if(key.equals(keyword)){
            return value;
        }
    }
    return null;
}

Error Message

not found
java.lang.NullPointerException
    at PNGMeta.readCustomData(PNGMeta.java:104)
    at PNGMeta.main(PNGMeta.java:40)
BUILD SUCCESSFUL (total time: 2 seconds)
Community
  • 1
  • 1
saim2025
  • 280
  • 2
  • 5
  • 14
  • Would be interesting to see the error message. Does the PNG actually contain the meta-datum 'name'? Othewise readCustomData() returns a null, which could be a hint as you mentioned a NullPointerException – Florian Heer Dec 21 '16 at 15:01
  • Please edit your question and include the full stack trace of the exception you’re seeing. – VGR Dec 21 '16 at 15:01
  • @VGR i have added stack trace – saim2025 Dec 21 '16 at 15:09
  • @FlorianHeer PNG does not contain field 'name'. i am trying to add it myself – saim2025 Dec 21 '16 at 15:10
  • 1
    According to your code, this is the field you try to read, not write. – Florian Heer Dec 21 '16 at 15:12
  • I suggest you start by creating an [MCVE](http://stackoverflow.com/help/mcve) as described in the help center. Right now, your question is a mess. You read/write the image and metadata over and over to multiple files and streams. So while the original code you copied did work, you broke it. As an example, the way you "read metadata" effectively removes *any* metadata that might have been `output.png` (or was it `new.png`?), by using `ImageIO.write(...)`, a method that does not write meta data (and also, a `BufferedImage` does not carry meta data from the file it was read from). – Harald K Dec 22 '16 at 10:06

2 Answers2

8

Here's the most efficient way of modifying and then reading the metadata that I know of. In contrast to the code posted by the OP, this version does not fully replace the metadata in the image, but merges the new content with any existing content.

As it uses the "standard" metadata format, it should also work for any format supported by ImageIO, that allows arbitrary text comments (I only tested for PNG, though). The actual data written, should match that of the native PNG metadata format in this case.

It reads all image pixel data and metadata using a single method, to avoid excess stream open/close and seeking and memory usage. It writes all image pixel data and metadata at once for the same reason. For lossless, single image formats like PNG, this roundtrip should not lose any quality or metadata.

When reading metadata back, only the metadata is read, pixel data is ignored.

public class IIOMetadataUpdater {

    public static void main(final String[] args) throws IOException {
        File in = new File(args[0]);
        File out = new File(in.getParent(), createOutputName(in));

        System.out.println("Output path: " + out.getAbsolutePath());

        try (ImageInputStream input = ImageIO.createImageInputStream(in);
             ImageOutputStream output = ImageIO.createImageOutputStream(out)) {

            Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
            ImageReader reader = readers.next(); // TODO: Validate that there are readers

            reader.setInput(input);
            IIOImage image = reader.readAll(0, null);

            addTextEntry(image.getMetadata(), "foo", "bar");

            ImageWriter writer = ImageIO.getImageWriter(reader); // TODO: Validate that there are writers
            writer.setOutput(output);
            writer.write(image);
        }

        try (ImageInputStream input = ImageIO.createImageInputStream(out)) {
            Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
            ImageReader reader = readers.next(); // TODO: Validate that there are readers

            reader.setInput(input);
            String value = getTextEntry(reader.getImageMetadata(0), "foo");

            System.out.println("value: " + value);
        }
    }

    private static String createOutputName(final File file) {
        String name = file.getName();
        int dotIndex = name.lastIndexOf('.');

        String baseName = name.substring(0, dotIndex);
        String extension = name.substring(dotIndex);

        return baseName + "_copy" + extension;
    }

    private static void addTextEntry(final IIOMetadata metadata, final String key, final String value) throws IIOInvalidTreeException {
        IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
        textEntry.setAttribute("keyword", key);
        textEntry.setAttribute("value", value);

        IIOMetadataNode text = new IIOMetadataNode("Text");
        text.appendChild(textEntry);

        IIOMetadataNode root = new IIOMetadataNode(IIOMetadataFormatImpl.standardMetadataFormatName);
        root.appendChild(text);

        metadata.mergeTree(IIOMetadataFormatImpl.standardMetadataFormatName, root);
    }

    private static String getTextEntry(final IIOMetadata metadata, final String key) {
        IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
        NodeList entries = root.getElementsByTagName("TextEntry");

        for (int i = 0; i < entries.getLength(); i++) {
            IIOMetadataNode node = (IIOMetadataNode) entries.item(i);
            if (node.getAttribute("keyword").equals(key)) {
                return node.getAttribute("value");
            }
        }

        return null;
    }
}

Expected output of the above code is:

Output path: /path/to/yourfile_copy.png
value: bar
Harald K
  • 26,314
  • 7
  • 65
  • 111
3

Your output contains "not found", which is created by

if(pngmeta.getStandardTextNode()==null){
    System.out.println("not found");
}

Therefore

NodeList childNodes = pngmeta.getStandardTextNode().getChildNodes();

must fail with a NullPointerException. pngmeta.getStandardTextNode() results in null, so you actully call

null.getChildNodes();
Florian Heer
  • 694
  • 5
  • 17
  • I added that to check if pngmeta contained any data. Code does not work even after removing if statement – saim2025 Dec 21 '16 at 15:23
  • The if statement shows you the problem, it is not the problem itself. pngmeta.getStandardTextNode() simply contains nothing at all. – Florian Heer Dec 21 '16 at 15:29
  • But file do contain metadata. It shows up when i use some other tool to view metadata – saim2025 Dec 21 '16 at 15:33
  • 1
    call to ImageIO.write while writing metadata seems to be removing already existing metadata that i wrote using exiftool. – saim2025 Dec 21 '16 at 17:15
  • @saim2025 Yes. `BufferedImage`s does not carry the metadata from the file that it was read from, and the `ImageIO.write(..)` method does not write metadata. See my answer for how to properly add to existing metadata. – Harald K Dec 22 '16 at 12:26