Concepts / Java File Dialogs and Saving User Work

Java File Dialogs and Saving User Work

ImageIO is the Java class that moves images between a running program and files, in both directions

  • Programming

Why a Painting Must Leave the Program

Imagine a painting program. The user draws on an image that exists only while the program is running. If the program cannot write that image to a file, the user's work disappears when the window closes. If the program cannot read an image from a file, the user cannot open a picture that was made earlier and keep editing it. Saving lets users keep their work, and reading lets them open and edit existing images.

A program that saves a picture has to do two things that are easy to confuse. It has to move an image out of the program and into a file, and it has to be able to bring that file back in later. Java provides a single class, javax.imageio.ImageIO, that handles both directions. This article runs the code in your head step by step, so you can see where the work happens before you read about how it happens.

Following an Image Out and Back

Think of the file as a middle point between two hand-offs. Writing moves image data out of the program and into a file. Reading moves it back into the program. Because the file sits in the middle, a problem in either direction can be traced to the hand-off where the data stopped.

BufferedImageimage in the runningprogramImageIO.writeencodes and savesImage fileencoded file on diskImageIO.readloads the fileBufferedImageimage back in the programimage, format, fileencoded filefile onlydecoded image
Where does the image data come from when saving, where does it go when loading, and how do those two paths connect?

Read the diagram from left to right. The image is handed to ImageIO along with a format and a destination. ImageIO produces an encoded file. When the program later reads that file, the path runs in reverse, and the file is decoded back into an image the program can edit. Notice that the image is never saved directly from the screen; it moves as program data, and the file is only a copy of that data in a stored form.

The Three Arguments of ImageIO.write

Suppose the variable image holds a BufferedImage. The image can be saved to a file by calling ImageIO.write with three arguments: the image, a format, and a file. Each argument answers one question: what gets saved, how it is encoded, and where it goes.

ImageIO.writemethod that saves an imageimagewhat: the BufferedImage tosave"png"how: the format to encodeinoutputFilewhere: the destination file
Which part of the call provides the image, which provides the format, and which provides the destination file?

The first argument supplies the content. The second supplies the encoding choice, which the source describes as a format string that is probably either PNG or JPEG. The third supplies the destination, which is a file. Because all three arguments are supplied by the caller, the call only works as well as the values passed in.

Saving a Canvas Step by Step

The following code is an original example written to show the save path. It assumes that image already holds a BufferedImage that the user has drawn on. Trace it line by line before you read the explanation that follows.

Code walkthrough
BufferedImage image = ...; // the canvas the user has been drawing on
File outputFile = new File("painting.png");
boolean saved = ImageIO.write(image, "png", outputFile);
if (saved) {
    System.out.println("Painting saved.");
} else {
    System.out.println("Save failed: format not available.");
}
  1. 1
    Line 1 represents the existing image. Its contents exist only in program memory at this point.
  2. 2
    Line 2 creates a File object that describes where the output should go.
  3. 3
    Line 3 calls ImageIO.write. The image is handed off with the format png and the destination file. The method returns true or false, and that value is stored in saved.
  4. 4
    Lines 4 through 8 branch on the stored value. If the format was available, the success message runs. Otherwise the failure branch runs.
  • The BufferedImage is created earlier in the program and holds the user's drawing.
  • The File object names the destination. Here the name ends in png, which is a separate detail from the format string.
  • ImageIO.write receives the three arguments: the image, the format string, and the file.
  • The boolean result is stored in saved so the program can check it on the next line.
Worked example

Tracing where the save could stop

A user clicks Save, and no file appears in the folder. Using the three arguments and the return value, find the most likely point where the process failed.

  1. 1
    Check the image argument

    The image is the first argument. If the program has no BufferedImage to pass, the question is what the program is saving, and the save step has nothing valid to write.

  2. 2
    Check the format argument

    The format is a plain string. Nothing in the call itself confirms the spelling or support, so a misspelled or unsupported format is a likely failure point.

  3. 3
    Check the file argument

    The file names the destination. A destination that the program cannot write to would prevent the file from appearing, even when the image and format are correct.

  4. 4
    Read the returned boolean

    The boolean from ImageIO.write indicates whether the format is available. If it is false, the program can report that the save did not succeed, which points directly at the format argument.

Result

The format argument is the most direct suspect when the returned value is false, because the boolean is specifically a signal about format availability. The image and file arguments should be checked next.

Reading the Boolean Result

The write call does not only perform an action. It also reports back. ImageIO.write returns a boolean that indicates whether the requested format is available. Storing that value in a variable lets the program detect a failed save, instead of assuming the file was created.

ImageIO.write callimage, format, fileReturned true?format availableReport successfile was writtenContinue programnext stepReport failurecheck the formatboolean resulttruefalse
After the write call returns true or false, which code path does the program take next?

The flowchart shows the decision that follows the call. The program does not simply continue after ImageIO.write. It reads the stored value and chooses a path. A true result means the format was available. A false result means the save did not succeed for that reason, and the program can respond rather than silently moving on.

What do you think happens?

If the format string is misspelled, what does ImageIO.write return?

  • true

  • false

Reveal answer
Answer:

false

The boolean indicates whether the requested format is available. A misspelled format string does not name an available format, so the call returns false, and the program can detect that the save failed.

Why the Format String Matters

The format argument tells ImageIO how to encode the image before it reaches the file. The source's documentation describes the format as probably either PNG or JPEG. Those two names are the values the surrounding code expects a caller to pass in. The same pixel data written with a different format string is encoded differently, so the format is a real decision the program makes, not a label that can be chosen casually.

java
Notes on the code
  • Keeping the format in a variable means the file extension and the format argument can be set together, which reduces mismatches.
  • The boolean is still checked after the call, because the string itself is never validated by the call site.

Loading a Saved Image Back

The read direction uses ImageIO.read with a single argument, the input file. The file is the only input the read needs, because the file already records how its image was encoded. This is the asymmetry to remember: write needs a format because the program chooses how to encode, while read takes only the file.

java
Notes on the code
  • Only the file is passed in. There is no format argument.
  • The result is a BufferedImage, the same type that ImageIO.write accepted, so the loaded picture can be edited like any other image in the program.

Put the two calls together and you get a round trip. First ImageIO.write hands the image to a file. Later ImageIO.read takes the file and returns a BufferedImage. The object that passes from the save step to the load step is the file itself, not the in-memory image, which is why the user's work survives after the program closes.

Mistakes When Saving and Loading Images

  • Common mistake

    Assuming the file exists because the save line ran

    What it looks like
    ImageIO.write(image, "png", outputFile); with no check afterwards
    Why it fails

    The call returns a boolean that indicates whether the format is available. Ignoring it means a failed save goes unnoticed.

    Do this instead

    Store the result in a boolean variable and branch on it, as shown in the flowchart.

  • Common mistake

    Treating the format string as checked

    What it looks like

    Passing a format name typed by the user straight into the call

    Why it fails

    The format is a plain string, and nothing in the call confirms that it is spelled correctly or supported.

    Do this instead

    Rely on the returned boolean to detect an unavailable format, and report the failure.

  • Common mistake

    Passing a format to ImageIO.read

    What it looks like
    ImageIO.read(inputFile, "png")
    Why it fails

    The read direction takes only the input file, because the file already records its encoding.

    Do this instead

    Call ImageIO.read(inputFile) with the file as the only argument.

  • Common mistake

    Confusing the image argument with the file: the image is what gets saved, and the file is where it goes

Tracing a Failed Save

MEDIUM

A painting program calls ImageIO.write with a valid BufferedImage and a file named sketch.png, but it passes a format string that the program does not support. The program prints success whenever the call completes. Trace the program: at which line does its result first differ from what the user expects, and what should the program do instead?

Hints
  • Look at which argument carries the format, and what the returned boolean signals about it.
  • The message printed after the call is the step that should depend on the stored result, not on the call merely finishing.
EASY

Write the lines that save an image to output.png using format png, store the result, and print a message that depends on whether the save succeeded.

Hints
  • Create the File object first, then call ImageIO.write with the image, the format string, and the file.
  • Use an if statement on the stored boolean.

Summary of the Save and Load Cycle

  1. 1
    ImageIO moves images between a running program and files in both directions, and the file is the middle point of the trip.
  2. 2
    ImageIO.write(image, format, file) answers three questions: what to save, how to encode it, and where to put it.
  3. 3
    ImageIO.write returns a boolean indicating whether the format is available, and storing that value lets the program detect a failed save.
  4. 4
    ImageIO.read(inputFile) takes only the file, because the file already records its encoding.
  5. 5
    The format string is a plain value that the call does not check, so the returned boolean is how a program learns whether the format worked.

At a glance

  • ImageIO moves images between a running program and files in both directions, with the file acting as the middle point.
  • ImageIO.write(image, format, file) supplies the image, the encoding format, and the destination file.
  • The boolean returned by ImageIO.write indicates whether the format is available, so storing it lets a program detect a failed save.
  • ImageIO.read(inputFile) loads an image from a file and needs only the file as input.
  • The format string is not validated by the call itself, so the program must check the returned result.