kryptonian
kryptonian

Reputation: 117

Trying to convert image on demand in memory and return it converted

I'm trying to convert an image on the fly to PNG, the source can be TIFF, jpeg, or png and I want it to always be returned as png.

Currently I'm having issues with getting the image out in a http response, so any pointers would be highly appreciated as the current error from res.write is "Cannot use 'destImage' (type error) as the type []byte"

The image processing is preferred to be done in memory rather than on file system if at all possible.

import (
    "bufio"
    "bytes"
    "crypto/tls"
    "log"
    "net/http"

    "github.com/sunshineplan/imgconv"
)

var client = http.Client{}

func main() {

    http.HandleFunc("/photo", cutterHandler)
    http.ListenAndServe(":8000", nil)
}

func cutterHandler(res http.ResponseWriter, req *http.Request) {
    url := "https://proxy.local/Image.aspx?id=CAB96C9C-DA56-4BC9-8626-FC5C95FF4D95"
    http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
    reqImg, err := client.Get(url)
    if err != nil {
        log.Println(res, "Error %d", err)
        req.Response.StatusCode = http.StatusInternalServerError
        return
    }

    srcImage, err := imgconv.Decode(reqImg.Body)
    if err != nil {
        log.Println(err)
        req.Response.StatusCode = http.StatusInternalServerError
        return
    }

    convertedImage := imgconv.Resize(srcImage, imgconv.ResizeOption{Width: 128, Height: 128})
    var b bytes.Buffer
    buffer := bufio.NewWriter(&b)
    destImage := imgconv.Write(buffer, convertedImage, imgconv.FormatOption{Format: imgconv.PNG})
    res.WriteHeader(http.StatusOK)
    res.Header().Set("Content-Type", "image/png")
    res.Write(destImage)
}

Upvotes: 1

Views: 541

Answers (1)

Andrei Vasilev
Andrei Vasilev

Reputation: 121

The problem is that ResponseWriter.Write() method accepts a slice of bytes ([]byte), but you are trying to pass an error type (built-in interface type).

If I try to build your code I get the following error message:

./main.go:44:15: cannot use destImage (variable of type error) as type []byte in argument to res.Write

In the source code, I see that "destImage" variable is initialized on line 41 and has an error type (method imgconv.Write() returns an error).

 destImage := imgconv.Write(buffer, convertedImage, imgconv.FormatOption{Format: imgconv.PNG})

To fix the error you need to change imgconv.Write() method call at line 41. And it can be done in the following way:

convertedImage := imgconv.Resize(srcImage, imgconv.ResizeOption{Width: 128, Height: 128})

res.Header().Set("Content-Type", "image/png")
res.WriteHeader(http.StatusOK)

err = imgconv.Write(res, convertedImage, imgconv.FormatOption{Format: imgconv.PNG})
if err != nil {
    log.Println(err)
}

To demonstrate, I wrote another version that you may find useful.

package main

import (
    "bytes"
    "crypto/tls"
    "fmt"
    "io"
    "log"
    "net/http"
    "strconv"

    "github.com/sunshineplan/imgconv"
)

var client = http.Client{
    Transport: cloneTransport(),
}

func main() {
    imageURL := ImageURL("https://go.dev/blog/go-brand/Go-Logo/JPG/Go-Logo_Blue.jpg")
    http.Handle("/logos/golang.png", imageURL)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

type ImageURL string

func (imageURL ImageURL) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    if req.Method != http.MethodGet {
        w.WriteHeader(http.StatusMethodNotAllowed)
        return
    }

    res, err := client.Get(string(imageURL))

    if err != nil {
        log.Printf("fetch %q image: %v", imageURL, err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    if res.StatusCode != http.StatusOK {
        log.Printf("fetch %q image: %s [%d]", imageURL, res.Status, res.StatusCode)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    buffer := new(bytes.Buffer)
    err = convert(buffer, res.Body)

    defer res.Body.Close()

    if err != nil {
        log.Printf("convert %q image: %v", imageURL, err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    w.Header().Set("Content-Type", "image/png")
    w.Header().Set("Content-Length", strconv.FormatInt(int64(buffer.Len()), 10))
    w.WriteHeader(http.StatusOK)

    if _, err = buffer.WriteTo(w); err != nil {
        log.Printf("write data: %v", err)
    }
}

func convert(w io.Writer, r io.Reader) error {
    srcImage, err := imgconv.Decode(r)

    if err != nil {
        return fmt.Errorf("decode image: %w", err)
    }

    img := imgconv.Resize(srcImage, imgconv.ResizeOption{Width: 128, Height: 128})
    err = imgconv.Write(w, img, imgconv.FormatOption{Format: imgconv.PNG})

    if err != nil {
        return fmt.Errorf("encode image: %w", err)
    }

    return nil
}

func cloneTransport() *http.Transport {
    transport := http.DefaultTransport.(*http.Transport).Clone()
    transport.TLSClientConfig = &tls.Config{
        InsecureSkipVerify: true,
    }

    return transport
}

Also, another variant of convert() function without CMYK support (TIFF), with using /x/image/tiff and resize packages.

import (
    "fmt"
    "image"
    "image/png"
    "io"

    _ "golang.org/x/image/tiff"
    _ "image/jpeg"

    "github.com/nfnt/resize"
)

func convert(w io.Writer, r io.Reader) error {
    img, _, err := image.Decode(r)

    if err != nil {
        return fmt.Errorf("decode image: %w", err)
    }

    resizedImage := resize.Resize(128, 128, img, resize.Bicubic)
    err = png.Encode(w, resizedImage)

    if err != nil {
        return fmt.Errorf("encode image: %w", err)
    }

    return nil
}

Upvotes: 2

Related Questions