forked from discord/lilliput
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lilliput.go
151 lines (121 loc) · 4.34 KB
/
lilliput.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
// Package lilliput resizes and encodes images from
// compressed images
package lilliput
import (
"bytes"
"errors"
"strings"
"time"
)
var (
ErrInvalidImage = errors.New("unrecognized image format")
ErrDecodingFailed = errors.New("failed to decode image")
ErrBufTooSmall = errors.New("buffer too small to hold image")
ErrFrameBufNoPixels = errors.New("Framebuffer contains no pixels")
ErrSkipNotSupported = errors.New("skip operation not supported by this decoder")
ErrEncodeTimeout = errors.New("encode timed out")
gif87Magic = []byte("GIF87a")
gif89Magic = []byte("GIF89a")
webpMagic = []byte("RIFF")
webpFormat = []byte("WEBP")
mp42Magic = []byte("ftypmp42")
mp4IsomMagic = []byte("ftypisom")
pngMagic = []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}
)
// A Decoder decompresses compressed image data.
type Decoder interface {
// Header returns basic image metadata from the image.
// This is done lazily, reading only the first part of the image and not
// a full decode.
Header() (*ImageHeader, error)
// Close releases any resources associated with the Decoder
Close()
// Description returns a string description of the image type, such as
// "PNG"
Description() string
// Duration returns the duration of the content. This property is 0 for
// static images and animated GIFs.
Duration() time.Duration
// DecodeTo fully decodes the image pixel data into f. Generally users should
// prefer instead using the ImageOps object to decode images.
DecodeTo(f *Framebuffer) error
// SkipFrame skips a frame if the decoder supports multiple frames
// and returns io.EOF if the last frame has been reached
SkipFrame() error
// IsStreamable indicates whether the content is optimized for streaming. This is true
// for static images and animated GIFs.
IsStreamable() bool
// HasSubtitles indicates whether the content has one or more subtitle tracks.
HasSubtitles() bool
// BackgroundColor as BGRA
BackgroundColor() uint32
// ICC returns the ICC color profile, if any
ICC() []byte
// LoopCount() returns the number of loops in the image
LoopCount() int
}
// An Encoder compresses raw pixel data into a well-known image type.
type Encoder interface {
// Encode encodes the pixel data in f into the dst provided to NewEncoder. Encode quality
// options can be passed into opt, such as map[int]int{lilliput.JpegQuality: 80}
Encode(f *Framebuffer, opt map[int]int) ([]byte, error)
// Close releases any resources associated with the Encoder
Close()
}
func isGIF(maybeGIF []byte) bool {
return bytes.HasPrefix(maybeGIF, gif87Magic) || bytes.HasPrefix(maybeGIF, gif89Magic)
}
func isWebp(maybeWebp []byte) bool {
if len(maybeWebp) < 12 {
return false
}
return bytes.HasPrefix(maybeWebp, webpMagic) && bytes.Equal(maybeWebp[8:12], webpFormat)
}
func isMP4(maybeMP4 []byte) bool {
if len(maybeMP4) < 12 {
return false
}
magic := maybeMP4[4:]
return bytes.HasPrefix(magic, mp42Magic) || bytes.HasPrefix(magic, mp4IsomMagic)
}
// NewDecoder returns a Decoder which can be used to decode
// image data provided in buf. If the first few bytes of buf do not
// point to a valid magic string, an error will be returned.
func NewDecoder(buf []byte) (Decoder, error) {
// Check buffer length before accessing it
if len(buf) == 0 {
return nil, ErrInvalidImage
}
isBufGIF := isGIF(buf)
if isBufGIF {
return newGifDecoder(buf)
}
isBufWebp := isWebp(buf)
if isBufWebp {
return newWebpDecoder(buf)
}
maybeDecoder, err := newOpenCVDecoder(buf)
if err == nil {
return maybeDecoder, nil
}
return newAVCodecDecoder(buf)
}
// NewEncoder returns an Encode which can be used to encode Framebuffer
// into compressed image data. ext should be a string like ".jpeg" or
// ".png". decodedBy is optional and can be the Decoder used to make
// the Framebuffer. dst is where an encoded image will be written.
func NewEncoder(ext string, decodedBy Decoder, dst []byte) (Encoder, error) {
if strings.ToLower(ext) == ".gif" {
return newGifEncoder(decodedBy, dst)
}
if strings.ToLower(ext) == ".webp" {
return newWebpEncoder(decodedBy, dst)
}
if strings.ToLower(ext) == ".mp4" || strings.ToLower(ext) == ".webm" {
return nil, errors.New("Encoder cannot encode into video types")
}
if strings.ToLower(ext) == ".thumbhash" {
return newThumbhashEncoder(decodedBy, dst)
}
return newOpenCVEncoder(ext, decodedBy, dst)
}