/
source.go
143 lines (116 loc) · 3.61 KB
/
source.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package image
import (
"bytes"
"errors"
"image"
"io"
"log"
"os"
"strings"
"image/jpeg"
//_ "image/jpeg"
"image/png"
//_ "image/png"
"gioui.org/op/paint"
"golang.org/x/image/draw"
)
// ImageSource wraps a local or remote image, and handle things like
// scaling and converting. Only jpeg and png format for the source
// image is supported.
type ImageSource struct {
// image data
src []byte
srcSize image.Point
// The name of the registered image format, like "jpeg" or "png".
format string
// cache the last scaled image
destImg image.Image
}
// ImageFromReader loads an image from a io.Reader.
// Image bytes buffer can be wrapped using a bytes.Reader to get an
// ImageSource.
func ImageFromReader(src []byte) (*ImageSource, error) {
imgConfig, format, err := image.DecodeConfig(bytes.NewReader(src))
if err != nil {
return nil, err
}
return &ImageSource{
src: src,
srcSize: image.Point{X: imgConfig.Width, Y: imgConfig.Height},
format: format,
}, nil
}
// ImageFromFile load an image from local filesystem or from network.
func ImageFromFile(src string) (*ImageSource, error) {
if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
// load from remote server.
return nil, errors.New("not implemented")
}
// Try to load from the file system.
imgFile, err := os.ReadFile(src)
if err != nil {
return nil, errors.New(err.Error())
}
return ImageFromReader(imgFile)
}
func (img *ImageSource) ScaleBySize(size image.Point) (image.Image, error) {
return img.scale(size)
}
func (img *ImageSource) ScaleByRatio(ratio float32) (image.Image, error) {
if ratio <= 0 {
return nil, errors.New("negative scaling ratio")
}
width, height := img.srcSize.X, img.srcSize.Y
size := image.Point{X: int(float32(width) * ratio), Y: int(float32(height) * ratio)}
return img.scale(size)
}
func (img *ImageSource) scale(size image.Point) (image.Image, error) {
if img.destImg != nil && size == img.destImg.Bounds().Size() {
return img.destImg, nil
}
srcImg, _, err := image.Decode(bytes.NewReader(img.src))
if err != nil {
log.Println("err: ", err)
return nil, err
}
dest := image.NewRGBA(image.Rectangle{Max: size})
draw.NearestNeighbor.Scale(dest, dest.Bounds(), srcImg, srcImg.Bounds(), draw.Src, nil)
img.destImg = dest
return dest, nil
}
// Save scale the image if required, and encode image.Image to PNG/JPEG image, finally write
// to the provided writer. Format must be value of "jpeg" or "png".
func (img *ImageSource) Save(out io.Writer, format string, size image.Point) error {
if img.destImg == nil && size != img.destImg.Bounds().Size() {
img.ScaleBySize(size)
}
if format == "" {
format = img.format
}
if format == "jpeg" {
return jpeg.Encode(out, img.destImg, &jpeg.Options{Quality: 100})
}
if format == "png" {
return png.Encode(out, img.destImg)
}
return errors.New("unknown image format: " + format)
}
// ImageOp scales the src image to make it fit within the constraint specified by size
// and convert it to Gio ImageOp. If size has zero value of image Point, image is not scaled.
func (img *ImageSource) ImageOp(size image.Point) (paint.ImageOp, error) {
if size == (image.Point{}) || size.X <= 0 || size.Y <= 0 {
img.ScaleBySize(size)
return paint.NewImageOp(img.destImg), nil
}
width, height := img.srcSize.X, img.srcSize.Y
ratio := min(float32(size.X)/float32(width), float32(size.Y)/float32(height))
scaledImg, err := img.ScaleByRatio(ratio)
if err != nil {
log.Println("scale image failed:", err)
return paint.ImageOp{}, err
}
return paint.NewImageOp(scaledImg), nil
}
func (img *ImageSource) Size() image.Point {
return img.srcSize
}