├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── save │ ├── index.html │ └── main.go ├── simple │ ├── index.html │ └── main.go └── thumbnailer │ ├── index.html │ └── main.go ├── go.mod ├── go.sum └── imageupload.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2022-09-14 (v1.0.0) 2 | 3 | * Create Go module. 4 | 5 | # 2016-05-03 6 | 7 | * Add the PNG/JPEG thumbnail methods to *Image. 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-imageupload 2 | 3 | [![GoDoc](https://godoc.org/github.com/olahol/go-imageupload?status.svg)](https://godoc.org/github.com/olahol/go-imageupload) 4 | 5 | > :white_square_button: Gracefully handle image uploading and thumbnail creation. 6 | 7 | ## Install 8 | 9 | ```bash 10 | go get github.com/olahol/go-imageupload 11 | ``` 12 | 13 | ## [Example](https://github.com/olahol/go-imageupload/tree/master/examples) 14 | 15 | Thumbnail creator. 16 | 17 | ```go 18 | package main 19 | 20 | import ( 21 | "net/http" 22 | 23 | "github.com/olahol/go-imageupload" 24 | ) 25 | 26 | func main() { 27 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 28 | http.ServeFile(w, r, "index.html") 29 | }) 30 | 31 | http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) { 32 | if r.Method != "POST" { 33 | http.NotFound(w, r) 34 | return 35 | } 36 | 37 | img, err := imageupload.Process(r, "file") 38 | 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | thumb, err := imageupload.ThumbnailPNG(img, 300, 300) 44 | 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | thumb.Write(w) 50 | }) 51 | 52 | http.ListenAndServe(":5000", nil) 53 | } 54 | ``` 55 | 56 | ```html 57 | 58 | 59 |
60 | 61 | 62 |
63 | 64 | 65 | ``` 66 | 67 | ## Contributors 68 | 69 | * Ola Holmström (@olahol) 70 | * Shintaro Kaneko (@kaneshin) 71 | 72 | 73 | ## [Documentation](https://godoc.org/github.com/olahol/go-imageupload) 74 | -------------------------------------------------------------------------------- /examples/save/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/save/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/olahol/go-imageupload" 9 | ) 10 | 11 | func main() { 12 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 13 | http.ServeFile(w, r, "index.html") 14 | }) 15 | 16 | http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) { 17 | if r.Method != "POST" { 18 | http.NotFound(w, r) 19 | return 20 | } 21 | 22 | img, err := imageupload.Process(r, "file") 23 | 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | thumb, err := imageupload.ThumbnailPNG(img, 300, 300) 29 | 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | thumb.Save(fmt.Sprintf("%d.png", time.Now().Unix())) 35 | 36 | http.Redirect(w, r, "/", http.StatusMovedPermanently) 37 | }) 38 | 39 | http.ListenAndServe(":5000", nil) 40 | } 41 | -------------------------------------------------------------------------------- /examples/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/simple/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/olahol/go-imageupload" 7 | ) 8 | 9 | var currentImage *imageupload.Image 10 | 11 | func main() { 12 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 13 | http.ServeFile(w, r, "index.html") 14 | }) 15 | 16 | http.HandleFunc("/image", func(w http.ResponseWriter, r *http.Request) { 17 | if currentImage == nil { 18 | http.NotFound(w, r) 19 | return 20 | } 21 | 22 | currentImage.Write(w) 23 | }) 24 | 25 | http.HandleFunc("/thumbnail", func(w http.ResponseWriter, r *http.Request) { 26 | if currentImage == nil { 27 | http.NotFound(w, r) 28 | return 29 | } 30 | 31 | t, err := imageupload.ThumbnailJPEG(currentImage, 300, 300, 80) 32 | 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | t.Write(w) 38 | }) 39 | 40 | http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) { 41 | if r.Method != "POST" { 42 | http.NotFound(w, r) 43 | return 44 | } 45 | 46 | img, err := imageupload.Process(r, "file") 47 | 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | currentImage = img 53 | 54 | http.Redirect(w, r, "/", http.StatusMovedPermanently) 55 | }) 56 | 57 | http.ListenAndServe(":5000", nil) 58 | } 59 | -------------------------------------------------------------------------------- /examples/thumbnailer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |
7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/thumbnailer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/olahol/go-imageupload" 7 | ) 8 | 9 | func main() { 10 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 11 | http.ServeFile(w, r, "index.html") 12 | }) 13 | 14 | http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) { 15 | if r.Method != "POST" { 16 | http.NotFound(w, r) 17 | return 18 | } 19 | 20 | img, err := imageupload.Process(r, "file") 21 | 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | thumb, err := imageupload.ThumbnailPNG(img, 300, 300) 27 | 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | thumb.Write(w) 33 | }) 34 | 35 | http.ListenAndServe(":5000", nil) 36 | } 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/olahol/go-imageupload 2 | 3 | go 1.19 4 | 5 | require github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= 2 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 3 | -------------------------------------------------------------------------------- /imageupload.go: -------------------------------------------------------------------------------- 1 | // Gracefully handle image uploading and thumbnail creation. 2 | package imageupload 3 | 4 | import ( 5 | "bytes" 6 | "encoding/base64" 7 | "errors" 8 | "fmt" 9 | "github.com/nfnt/resize" 10 | "image" 11 | _ "image/gif" 12 | "image/jpeg" 13 | "image/png" 14 | "io/ioutil" 15 | "net/http" 16 | "strconv" 17 | ) 18 | 19 | type Image struct { 20 | Filename string 21 | ContentType string 22 | Data []byte 23 | Size int 24 | } 25 | 26 | // Save image to file. 27 | func (i *Image) Save(filename string) error { 28 | return ioutil.WriteFile(filename, i.Data, 0600) 29 | } 30 | 31 | // Convert image to base64 data uri. 32 | func (i *Image) DataURI() string { 33 | return fmt.Sprintf("data:%s;base64,%s", i.ContentType, base64.StdEncoding.EncodeToString(i.Data)) 34 | } 35 | 36 | // Write image to HTTP response. 37 | func (i *Image) Write(w http.ResponseWriter) { 38 | w.Header().Set("Content-Type", i.ContentType) 39 | w.Header().Set("Content-Length", strconv.Itoa(i.Size)) 40 | w.Write(i.Data) 41 | } 42 | 43 | // Create JPEG thumbnail from image. 44 | func (i *Image) ThumbnailJPEG(width int, height int, quality int) (*Image, error) { 45 | return ThumbnailJPEG(i, width, height, quality) 46 | } 47 | 48 | // Create PNG thumbnail from image. 49 | func (i *Image) ThumbnailPNG(width int, height int) (*Image, error) { 50 | return ThumbnailPNG(i, width, height) 51 | } 52 | 53 | // Limit the size of uploaded files, put this before imageupload.Process. 54 | func LimitFileSize(maxSize int64, w http.ResponseWriter, r *http.Request) { 55 | r.Body = http.MaxBytesReader(w, r.Body, maxSize) 56 | } 57 | 58 | func okContentType(contentType string) bool { 59 | return contentType == "image/png" || contentType == "image/jpeg" || contentType == "image/gif" 60 | } 61 | 62 | // Process uploaded file into an image. 63 | func Process(r *http.Request, field string) (*Image, error) { 64 | file, info, err := r.FormFile(field) 65 | 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | contentType := info.Header.Get("Content-Type") 71 | 72 | if !okContentType(contentType) { 73 | return nil, errors.New(fmt.Sprintf("Wrong content type: %s", contentType)) 74 | } 75 | 76 | bs, err := ioutil.ReadAll(file) 77 | 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | _, _, err = image.Decode(bytes.NewReader(bs)) 83 | 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | i := &Image{ 89 | Filename: info.Filename, 90 | ContentType: contentType, 91 | Data: bs, 92 | Size: len(bs), 93 | } 94 | 95 | return i, nil 96 | } 97 | 98 | // Create JPEG thumbnail. 99 | func ThumbnailJPEG(i *Image, width int, height int, quality int) (*Image, error) { 100 | img, _, err := image.Decode(bytes.NewReader(i.Data)) 101 | 102 | thumbnail := resize.Thumbnail(uint(width), uint(height), img, resize.Lanczos3) 103 | 104 | data := new(bytes.Buffer) 105 | err = jpeg.Encode(data, thumbnail, &jpeg.Options{ 106 | Quality: quality, 107 | }) 108 | 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | bs := data.Bytes() 114 | 115 | t := &Image{ 116 | Filename: "thumbnail.jpg", 117 | ContentType: "image/jpeg", 118 | Data: bs, 119 | Size: len(bs), 120 | } 121 | 122 | return t, nil 123 | } 124 | 125 | // Create PNG thumbnail. 126 | func ThumbnailPNG(i *Image, width int, height int) (*Image, error) { 127 | img, _, err := image.Decode(bytes.NewReader(i.Data)) 128 | 129 | thumbnail := resize.Thumbnail(uint(width), uint(height), img, resize.Lanczos3) 130 | 131 | data := new(bytes.Buffer) 132 | err = png.Encode(data, thumbnail) 133 | 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | bs := data.Bytes() 139 | 140 | t := &Image{ 141 | Filename: "thumbnail.png", 142 | ContentType: "image/png", 143 | Data: bs, 144 | Size: len(bs), 145 | } 146 | 147 | return t, nil 148 | } 149 | --------------------------------------------------------------------------------