George Z.
George Z.

Reputation: 6808

Serialize Obj with 2 BufferedImage transient fields, second image won't be read

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();
            }
        });
    }
}

Upvotes: 0

Views: 385

Answers (1)

VGR
VGR

Reputation: 44292

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.

Upvotes: 1

Related Questions