Using io.Reader

I’ve met interesting piece of code at The Go image package

The standard package library supports a number of common image formats, such as GIF, JPEG and PNG. If you know the format of a source image file, you can decode from an io.Reader directly.

import (
 "image/jpeg"
 "image/png"
 "io"
)

// convertJPEGToPNG converts from JPEG to PNG.
func convertJPEGToPNG(w io.Writer, r io.Reader) error {
 img, err := jpeg.Decode(r)
 if err != nil {
  return err
 }
 return png.Encode(w, img)
}

How to use that convertJPEGToPNG function?

Look at os.Open and os.Create, they both return *os.File which implements Reader and Writer interfaces. Use return from Open as your Reader and return of Create as Writer.

https://golang.org/pkg/os/#Open
https://golang.org/pkg/os/#Create

It is probably faster to use bufio as well, but it is not necessary. And don’t forget to check the errors :slight_smile:

1 Like

Imagine you were using Java, and you wanted to open a file and get something that implements some Reader interface. You would probably have something like:

public interface Reader {
  public int Read(byte[] b)
}

public interface Writer {
  public int Write(byte[] b)
}

public class File implements Reader, Writer {
  // stuff here
  public byte[] Read() {
    // read from the file and return a byte array with the data
  }
  public int Write(byte[] b) {
    // do stuff to write to the file with the given byte array and return how many bytes were written
    return b.length()
  }
}

PS - I haven’t written Java in forever so I’m sure my syntax is wrong

Now when you go to open a file it is clear that this File type implements both the Reader and Writer interfaces.

In Go those interfaces are implemented implicitly. That is, we don’t have to say implements X, Y, Z - if it has the correct methods it automatically implements those interfaces. That means that because os.File has the Read method, it implements the io.Reader interface, and similarly because it has the Write method, it implements the io.Writer interface.

This means we can do somethign like this:

var r io.Reader = &os.File{}

That is, an os.File will implement the io.Reader interface so we can assign it to a variable that is of that type. As a result we can also do this:

png, err := os.Create("something.png")
if err != nil {
	log.Fatal(err)
}
jpg, err := os.Open("something.jpg")
if err != nil {
	log.Fatal(err)
}
convertJPEGToPNG(png, jpg)

And because jpg is a file, it implements io.Reader, and because png is an os.File as well it implements io.Writer.

Finally, all of this probably has you thinking, “Why not just write a function that takes in two files?”

The reason we don’t do this is because we only need the Read and Write methods to make our code work, and by using interfaces we can now also use this function to do things like read a jpg from the hard drive adn write the results to an http.ResponseWriter, writing the result directly to a web request response instead of having to first write it to the hard drive, then copy it back into the web request response.

If convertJPEGToPNG only took in files we would have to open the JPG, create a PNG, convert the JPG to PNG, return the PNG to the user who made the web request, then finally delete the PNG from our server so we don’t run out of hard drive space. With the current implementation we can simply do:

func httpHandler(w http.ResponseWriter, r *http.Request) {
  jpg := // get image from r, the http.Request, assuming the use uploaded it
  convertJPEGToPNG(w, jpg)
}

And that code would convert a JPEG into a PNG and return the PNG to the user without ever writing that PNG to our hard drive. Neat, right?

There are other reasons as well (like testing), but I’ll leave it at that.

Hope this helps!

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.