├── .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 | [](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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------