-
Notifications
You must be signed in to change notification settings - Fork 8
/
write.go
174 lines (150 loc) · 4.43 KB
/
write.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package imageutil
import (
"errors"
"image/gif"
"image/jpeg"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/grokify/mogo/errors/errorsutil"
"github.com/grokify/mogo/io/ioutil"
"github.com/grokify/mogo/os/osutil"
)
const (
JPEGExt = ".jpeg"
JPEGExtJPG = ".jpg"
JPEGQualityDefault = jpeg.DefaultQuality // 75
JPEGQualityMax = 100
JPEGQualityMin = 1
JPEGMarkerPrefix = 0xff
JPEGMarkerExif = 0xe1
JPEGMarkerSOI = 0xd8
)
var rxJPEGExtension = regexp.MustCompile(`(?i)\.(jpg|jpeg)$`)
func JPEGMarker(b byte) []byte {
return []byte{JPEGMarkerPrefix, b}
}
func ReadDirJPEGFiles(dir string, rx *regexp.Regexp) (osutil.DirEntries, error) {
if rx == nil {
rx = rxJPEGExtension
}
return osutil.ReadDirMore(dir, rx, false, true, false)
}
func WriteGIFFile(filename string, img *gif.GIF) error {
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
defer f.Close()
err = gif.EncodeAll(f, img)
if err != nil {
return err
}
return f.Close()
}
var (
ErrSrcDirNotDefined = errors.New("source directory not defined")
ErrOutDirNotDefined = errors.New("output directory not defined")
ErrSrcDirNotDir = errors.New("source directory is not a directory")
ErrOutDirNotDir = errors.New("output directory is not a directory")
)
func ResizePathJPEG(src, out string, x, y uint, o *JPEGEncodeOptions) error {
if isDirSrc, err := osutil.IsDir(src); err != nil {
return err
} else if isDirSrc {
return ResizePathJPEGDir(src, out, x, y, o)
} else {
return ResizePathJPEGFile(src, out, x, y, o)
}
}
func ResizePathJPEGDir(src, out string, x, y uint, o *JPEGEncodeOptions) error {
if src == "" {
return ErrSrcDirNotDefined
} else if out == "" {
return ErrOutDirNotDefined
}
if isDirSrc, err := osutil.IsDir(src); err != nil {
return err
} else if !isDirSrc {
return errorsutil.Wrapf(ErrSrcDirNotDir, "src-dir (%s)", src)
}
if isDirOut, err := osutil.IsDir(out); err != nil {
return err
} else if !isDirOut {
return errorsutil.Wrapf(ErrOutDirNotDir, "out-dir (%s)", out)
}
files, err := osutil.ReadDirMore(src, RxFileExtensionJPG, false, true, false)
if err != nil {
return err
}
n := len(files)
for i, e := range files {
// fmt.Printf("Processing %d of %d: %s\n", i+1, n, e.Name())
srcPath := filepath.Join(src, e.Name())
outPath := filepath.Join(out, e.Name())
err := ResizePathJPEGFile(srcPath, outPath, x, y, o)
if err != nil {
return errorsutil.Wrapf(err, "failed-on-file (%s) (%d/%d)", e.Name(), i+1, n)
}
}
return nil
}
func ResizePathJPEGFile(src, out string, x, y uint, o *JPEGEncodeOptions) error {
if img, _, err := ReadImageFile(src); err != nil {
return err
} else {
img2 := Resize(x, y, img, ScalerBest())
return writeJPEGFile(out, img2, o)
}
}
type JPEGEncodeOptions struct {
Options *jpeg.Options
Exif []byte
ReadFilenameRegexp *regexp.Regexp
WriteExtension string
}
func (opts JPEGEncodeOptions) ReadFilenameRegexpOrDefault() *regexp.Regexp {
if opts.ReadFilenameRegexp != nil {
return opts.ReadFilenameRegexp
}
return rxJPEGExtension
}
func (opts JPEGEncodeOptions) WriteExtensionOrDefault() string {
if strings.TrimSpace(opts.WriteExtension) == "" {
return JPEGExtJPG // default to 3 letter extension to support Microsoft.
}
return opts.WriteExtension
}
var JPEGEncodeOptionsQualityMax = &JPEGEncodeOptions{
Options: &jpeg.Options{
Quality: JPEGQualityMax}}
// newWriterExif is used to write Exif to an `io.Writer` before calling `jpeg.Encode()`.
// It is used with `jpeg.Encode()` to remove the Start of Image (SOI) marker after adding
// SOI and Exif.
func newWriterExif(w io.Writer, exif []byte) (io.Writer, error) {
// Adapted from the following under MIT license: https://github.com/jdeng/goheif/blob/a0d6a8b3e68f9d613abd9ae1db63c72ba33abd14/heic2jpg/main.go
// See more here: https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
// https://www.codeproject.com/Articles/47486/Understanding-and-Reading-Exif-Data
wExif := ioutil.NewSkipWriter(w, 2)
if _, err := w.Write(JPEGMarker(JPEGMarkerSOI)); err != nil {
return nil, err
}
if exif == nil {
return wExif, nil
}
markerLen := 2 + len(exif)
marker := []byte{
JPEGMarkerPrefix,
JPEGMarkerExif,
uint8(markerLen >> 8),
uint8(markerLen & JPEGMarkerPrefix)}
if _, err := w.Write(marker); err != nil {
return nil, err
}
if _, err := w.Write(exif); err != nil {
return nil, err
}
return wExif, nil
}