0

before explaining my thing i want to mention that i haven't tried-search any alternate solutions (which i am pretty confident something will be found). I am just pretty curious to know why this is happening.

So...I have an object that has 2 transient buffered images fields (firstimage and second image like the following example).

Since bufferedimage does not implement serializable, one way to serialize them (i found in SO) is this. I did the exact same thing and everything worked perfect for the first image. (being saved successfully, being loaded fine aswell).

However, about the second image there is a problem which i can't explain. I serialize my object without any problems but when i try to load it, the second image is being read as null for some reason. Is there anything i am missing to my code?

I show my problem with a quick swing application.

  1. load1 button will load the first image and set it to the object.
  2. load2 button does the same thing for the second image
  3. Save button serializes the object and saves it locally
  4. Show button will show if any image is null

So, i start the app. Click load1 & load2. Click save. Click show = none of them is null. Close the app.

I start the app again and it loads the object on start. I click show and image1 is NOT null, but image 2 is null.

Why is this happening? what can i do in order to make this way work? What alternate solution would you suggest?

p.s: I would like to avoid getting images to a base64 or to byte[] and serialize them.

Person class (name makes no sense i just had it from previous testing):

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

import javax.imageio.ImageIO;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;

    private transient BufferedImage firstImage;
    private transient BufferedImage secondImage;

    public Person() {
    }

    private void writeObject(ObjectOutputStream out) {
        try {
            out.defaultWriteObject();
            if (firstImage != null)
            {
                ImageIO.write(firstImage, "png", out);
                System.out.println("First image saved.");
            }
            if (secondImage != null) {
                ImageIO.write(secondImage, "png", out);
                System.out.println("Second image saved.");
            }
            System.out.println("-------------------------");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private void readObject(ObjectInputStream in) {
        try {
            in.defaultReadObject();
            firstImage = ImageIO.read(in);
            secondImage = ImageIO.read(in);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

    public static Person loadPerson() {
        String path = "C://Users//George//Desktop//person.tmp";
        File file = new File(path);
        Person p = new Person();
        if (!file.exists())
            return p;
        try (FileInputStream fis = new FileInputStream(path); ObjectInputStream ois = new ObjectInputStream(fis);) {
            p = (Person) ois.readObject();
            ois.close();
            fis.close();
        } catch (IOException | ClassNotFoundException e1) {
            e1.printStackTrace();
        }
        return p;
    }

    public void savePerson() {
        String path = "C://Users//George//Desktop//person.tmp";
        try (FileOutputStream fos = new FileOutputStream(path); ObjectOutputStream oos = new ObjectOutputStream(fos);) {
            oos.writeObject(this);
            oos.close();
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public BufferedImage getSecondImage() {
        return secondImage;
    }

    public void setSecondImage(BufferedImage secondImage) {
        this.secondImage = secondImage;
    }

    public BufferedImage getFirstImage() {
        return firstImage;
    }

    public void setFirstImage(BufferedImage firstImage) {
        this.firstImage = firstImage;
    }
}

The app:

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UnsupportedLookAndFeelException;

@SuppressWarnings("serial")
public class Test extends JPanel {
    private JFrame f;
    private Person person;
    public Test() {
        JButton load1 = new JButton("load img1");
        load1.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    BufferedImage tmp = ImageIO.read(new File("C://Users//George//Desktop//pencil.png"));
                    person.setFirstImage(tmp);
                    tmp.flush();
                    tmp = null;
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        });
        add(load1);
        JButton load2 = new JButton("load img2");
        load2.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    BufferedImage tmp = ImageIO.read(new File("C://Users//George//Desktop//map.png"));
                    person.setSecondImage(tmp);
                    tmp.flush();
                    tmp = null;
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        });
        add(load2);

        JButton save = new JButton("save");
        save.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                person.savePerson();
            }
        });
        add(save);

        JButton show = new JButton("show which is null");
        show.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                if (person.getFirstImage() == null)
                    System.out.println("first image null");
                else
                    System.out.println("first image not null");
                if (person.getSecondImage() == null)
                    System.out.println("second image null");
                else
                    System.out.println("second image not null");
                System.out.println("---------------------");
            }
        });
        add(show);
    }
    private void display() {
        person = Person.loadPerson();
        f = new JFrame("Buffered images");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(this);
        f.setSize(new Dimension(300, 300));
//       f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException,
            IllegalAccessException, UnsupportedLookAndFeelException {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new Test().display();
            }
        });
    }
}
George Z.
  • 6,643
  • 4
  • 27
  • 47
  • `BufferedImage tmp = ImageIO.read(new File("C://...//pencil.png"));` One way to get image(s) for an example is to **hot link** to images seen in [this Q&A](http://stackoverflow.com/q/19209650/418556). – Andrew Thompson Feb 25 '18 at 13:54
  • In try-with-resources you do not need to close the resources since that's automatically done by the try-with-resources. Also how would you distinguish the cases where exactly one image is `null` from each other? You do not write anything if one of the images is null so the resulting data is the same, whether an image is stored in the first or second image. Reading would always yield an object with `firstImage!=null` and `secondImage == null`, that is if `ImageIO.read` does not advance in the stream... – fabian Feb 25 '18 at 14:00
  • @fabian . I can't understand what exactly you mean (except the try-with resources) part (i copied paste from main app, which doesn't use java 8+ and did it quickly with try-with). Could you please explain with a different way what you mean in the rest part? When i click the savebutton, i get that both images are saved. Then when app starts, it doesn't load the 2nd image. (second image stays null) Also: Do you have anything in mind to "save the day"? – George Z. Feb 25 '18 at 14:13
  • 1
    Not sure why this does not work if both images are set, so no idea how to "save the day". But about the last comment. Lets assume you set exactly one of your images != null. You do not store any info about which one is null to the outputstream. You simply write the data of the image to the stream which means when loading the data it's impossible to determine which of the images was null and which one was != null so you cannot properly restore the state. You could fix this e.g. by writing a boolean. – fabian Feb 25 '18 at 14:31
  • @fabian When i save the images, they are both not null (i test only on this scenario). I really thought this my self. I mean it makes sense that writing 2 images one after another, it will be hard to seperate them on reading since where the first ends, the second starts. So, i tested this. Write 1st img. writeboolean (false); write 2nd img. now on reading image1 = imageio.read, then read boolean then image2 = imageio.read. It doesn't work and actually, the readboolean gives me true for some reason. – George Z. Feb 25 '18 at 14:44

1 Answers1

1

The documentation for ImageIO.read states that the InputStream argument is wrapped in an ImageInputStream.

ImageInputStream is not the same as an InputStream. I don’t think you can safely assume that it only reads as far as it needs to; it may read ahead farther, for caching purposes, or perhaps in order to know what to return from its length() method.

In other words, after reading the first image, your InputStream may not be pointing to the exact byte where the second image starts.

One solution to this is to write byte arrays, rather than writing image data directly. The disadvantage is that it’s not scalable; if you have very large images, keeping them in memory will be a performance issue.

public class Person implements Serializable {

    // ...

    private void writeObject(ObjectOutputStream out)
    throws IOException {
        out.defaultWriteObject();

        if (firstImage != null) {
            ByteArrayOutputStream b = new ByteArrayOutputStream();
            ImageIO.write(firstImage, "png", b);

            out.writeInt(b.size());
            b.writeTo(out);

            System.out.println("First image saved.");
        } else {
            out.writeInt(0);
        }

        if (secondImage != null) {
            ByteArrayOutputStream b = new ByteArrayOutputStream();
            ImageIO.write(secondImage, "png", b);

            out.writeInt(b.size());
            b.writeTo(out);

            System.out.println("Second image saved.");
        } else {
            out.writeInt(0);
        }
    }

    private void readObject(ObjectInputStream in)
    throws IOException,
           ClassNotFoundException {
        in.defaultReadObject();

        int length = in.readInt();
        if (length > 0) {
            byte[] bytes = new byte[length];
            in.readFully(bytes);
            firstImage = ImageIO.read(new ByteArrayInputStream(bytes));
        }

        length = in.readInt();
        if (length > 0) {
            byte[] bytes = new byte[length];
            in.readFully(bytes);
            secondImage = ImageIO.read(new ByteArrayInputStream(bytes));
        }
    }

}

As a side note, IOException and ClassNotFoundException should not be caught inside the serialization methods. They belong in the throws clause of the method declaration. You don’t want your class pretending that serialization was successful when it didn’t actually succeed, after all. The details of the serialization methods’ signatures are described in the documentation for Serializable.

VGR
  • 40,506
  • 4
  • 48
  • 63
  • Thanks for your answer. About the exceptions, i am aware of this and thanks for writing this note. I was just curious if any exception will be thrown there since it did not make any sense to me. I just ran it and it seems to work perfectly and thanks for saving my *ss :). However, i would like to mention another way that it seems it work. I created a class DummyBufferedImage that implements serializable and had one field only, which is a bufferedimage. Then did the same thing, overriding writeObject, bla bla and it worked, but i still didnt want to apply it. Anyway, thanks a lot! – George Z. Feb 25 '18 at 19:33