-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathiconscan.go
More file actions
105 lines (95 loc) · 2.24 KB
/
iconscan.go
File metadata and controls
105 lines (95 loc) · 2.24 KB
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
package rbxfetch
import (
"bufio"
"bytes"
"image"
"image/png"
"io"
"github.com/anaminus/iofl"
)
// FilterIconScan is an iofl.Filter that scans for an icon sheet image from the
// source.
//
// Because the source may contain multiple images, the following heuristic is
// used: the format of the image is PNG, the height of the image is Size, the
// width is a multiple of Size, and is the first widest such image.
type FilterIconScan struct {
Size int
r io.ReadCloser
buf bytes.Buffer
err error
}
// NewFilterIconScan is an iofl.NewFilter that returns a FilterIconScan.
func NewFilterIconScan(params iofl.Params, r io.ReadCloser) (f iofl.Filter, err error) {
return &FilterIconScan{r: r,
Size: params.GetInt("Size"),
}, nil
}
func (f *FilterIconScan) Source() io.ReadCloser {
return f.r
}
func (f *FilterIconScan) Close() error {
if f.err != nil {
return f.err
}
if f.err = f.r.Close(); f.err == nil {
f.err = iofl.Closed
return nil
}
return f.err
}
// readBytes scans until the given delimiter is reached.
func readBytes(r *bufio.Reader, sep []byte) error {
if len(sep) == 0 {
return nil
}
for {
if b, err := r.Peek(len(sep)); err != nil {
return err
} else if bytes.Equal(b, sep) {
break
}
if _, err := r.Discard(1); err != nil {
return err
}
}
return nil
}
// scan scans f.r for an image, writing the result to f.buf.
func (f *FilterIconScan) scan() (err error) {
header := []byte("\x89PNG\r\n\x1a\n")
var largest image.Image
for br := bufio.NewReader(f.r); ; {
// Scan for PNG headers.
if err := readBytes(br, header); err != nil {
if err == io.EOF && largest != nil {
break
}
f.r.Close()
return err
}
f.buf.Reset()
img, err := png.Decode(io.TeeReader(br, &f.buf))
// Select when height equals Size, and width is multiple of Size.
if err != nil || img.Bounds().Dy() != f.Size || img.Bounds().Dx()%f.Size != 0 {
continue
}
// Select first widest.
if largest == nil || img.Bounds().Dx() > largest.Bounds().Dx() {
largest = img
}
}
return f.r.Close()
}
func (f *FilterIconScan) Read(p []byte) (n int, err error) {
if f.err != nil {
return 0, f.err
}
if f.buf.Len() == 0 {
if err := f.scan(); err != nil {
f.err = err
return 0, err
}
}
return f.buf.Read(p)
}