gobytestream

bufio.Reset after bufio.Peek doesn't seem to work


I'm struggling to understand what's happening if I chain a Peek and a Reset. Here are two versions of a Thumbnail function:

func Thumbnail(r io.Reader, w io.Writer, width int) error {
    _, err := jpeg.Decode(r)

    if err != nil {
        fmt.Println("Error decoding image: ", err)
        return err
    }

    return nil
}

This works. The data is read successfully into a jpeg image. Now, this second version doesn't work:

func Thumbnail2(r io.Reader, w io.Writer, width int) error {
    var err error

    buf := bufio.NewReader(r)
    headerBytes, err := buf.Peek(512)
    if err != nil {
        fmt.Println("Error reading header bytes: ", err)
        return err
    }
    mimetype := detectContentType(headerBytes)

    fmt.Println("MIME type detected: ", mimetype)

    buf.Reset(r)  // I tried with and without, doesn't work

    _, err = jpeg.Decode(r)

    if err != nil {
        fmt.Println("Error decoding image: ", err)
        return err
    }

    return nil
}

I understand that io.Reader is an interface to read a stream of bytes. I understand that since it's a Reader, it doesn't have the Seek interface (which would have saved me some troubles). My intention was to wrap the original io.Reader into a bufio reader, so I could have a look at the first 512 bytes, then read the data as planned in the call to jpeg.Decode. But that doesn't work, with or without the call to Reset. I'm getting this error:

invalid JPEG format: missing SOI marker

I don't understand why this is happening. Could you enlighten me please?


Solution

  • You're on the right track with buf.Peek() but buf.Reset() is not the right approach, as it clears buffers but doesn't magically rewind the reader r. Instead, you can use the fact that buf.Peek() doesn't affect future normal buf.Read()'s, and pass buf directly to the JPEG decoder. The data read from r to fill the buffer during buf.Peek() will be the first data fed to the JPEG decoder. You should also allow Peek to return fewer than 512 bytes, to support small JPEG's.

    package main
    
    import (
        "bufio"
        "bytes"
        "encoding/base64"
        "fmt"
        "image/jpeg"
        "io"
    )
    
    func detectContentType(bytes []byte) string {
        return "image/jpeg"
    }
    
    func Thumbnail2(r io.Reader, w io.Writer, width int) error {
        var err error
    
        buf := bufio.NewReader(r)
        headerBytes, err := buf.Peek(512)
        if len(headerBytes) == 0 && err != nil {
            fmt.Println("Error reading header bytes: ", err)
            return err
        }
        mimetype := detectContentType(headerBytes)
    
        fmt.Println("MIME type detected: ", mimetype)
    
        _, err = jpeg.Decode(buf)
    
        if err != nil {
            fmt.Println("Error decoding image: ", err)
            return err
        }
    
        return nil
    }
    
    func main() {
        // 16x16 white JPEG
        b64 := "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAAQABADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD36iiigD//2Q=="
        body := make([]byte, base64.StdEncoding.DecodedLen(len(b64)))
        _, err := base64.StdEncoding.Decode(body, []byte(b64))
        if err != nil {
            panic(err)
        }
        fmt.Println(Thumbnail2(bytes.NewReader(body), nil, 16))
    }