-
Notifications
You must be signed in to change notification settings - Fork 123
/
webp.go
234 lines (196 loc) · 6.62 KB
/
webp.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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
package lilliput
// #include "webp.hpp"
import "C"
import (
"io"
"time"
"unsafe"
)
// webpDecoder implements the Decoder interface for WebP images.
type webpDecoder struct {
decoder C.webp_decoder
mat C.opencv_mat
buf []byte
}
// webpEncoder implements the Encoder interface for WebP images.
type webpEncoder struct {
encoder C.webp_encoder
dstBuf []byte
icc []byte
frameIndex int
hasFlushed bool
}
// newWebpDecoder creates a new WebP decoder from the provided byte buffer.
// Returns an error if the buffer is too small or contains invalid WebP data.
func newWebpDecoder(buf []byte) (*webpDecoder, error) {
mat := C.opencv_mat_create_from_data(C.int(len(buf)), 1, C.CV_8U, unsafe.Pointer(&buf[0]), C.size_t(len(buf)))
if mat == nil {
return nil, ErrBufTooSmall
}
decoder := C.webp_decoder_create(mat)
if decoder == nil {
return nil, ErrInvalidImage
}
return &webpDecoder{
decoder: decoder,
mat: mat,
buf: buf,
}, nil
}
// Header returns the image metadata including dimensions, pixel type, and frame count.
func (d *webpDecoder) Header() (*ImageHeader, error) {
return &ImageHeader{
width: int(C.webp_decoder_get_width(d.decoder)),
height: int(C.webp_decoder_get_height(d.decoder)),
pixelType: PixelType(C.webp_decoder_get_pixel_type(d.decoder)),
orientation: OrientationTopLeft,
numFrames: int(C.webp_decoder_get_num_frames(d.decoder)),
contentLength: len(d.buf),
}, nil
}
// Close releases all resources associated with the decoder.
func (d *webpDecoder) Close() {
C.webp_decoder_release(d.decoder)
C.opencv_mat_release(d.mat)
d.buf = nil
}
// Description returns the image format description ("WEBP").
func (d *webpDecoder) Description() string {
return "WEBP"
}
// Duration returns the total duration of the WebP animation.
// Returns 0 for static images.
func (d *webpDecoder) Duration() time.Duration {
return time.Duration(C.webp_decoder_get_total_duration(d.decoder)) * time.Millisecond
}
// HasSubtitles returns whether the image contains subtitle data (always false for WebP).
func (d *webpDecoder) HasSubtitles() bool {
return false
}
// IsStreamable returns whether the image format supports streaming (always false for WebP).
func (d *webpDecoder) IsStreamable() bool {
return false
}
// hasReachedEndOfFrames checks if the decoder has reached the end of all frames.
func (d *webpDecoder) hasReachedEndOfFrames() bool {
return C.webp_decoder_has_more_frames(d.decoder) == 0
}
// advanceFrameIndex advances the internal frame index for the next decoding call.
func (d *webpDecoder) advanceFrameIndex() {
C.webp_decoder_advance_frame(d.decoder)
}
// ICC returns the ICC color profile data embedded in the WebP image.
func (d *webpDecoder) ICC() []byte {
iccDst := make([]byte, 8192)
iccLength := C.webp_decoder_get_icc(d.decoder, unsafe.Pointer(&iccDst[0]), C.size_t(cap(iccDst)))
return iccDst[:iccLength]
}
// BackgroundColor returns the background color of the WebP image.
func (d *webpDecoder) BackgroundColor() uint32 {
return uint32(C.webp_decoder_get_bg_color(d.decoder))
}
// LoopCount returns the number of times the animation should loop.
func (d *webpDecoder) LoopCount() int {
return int(C.webp_decoder_get_loop_count(d.decoder))
}
// DecodeTo decodes the current frame into the provided Framebuffer.
// Returns io.EOF when all frames have been decoded.
// Returns ErrDecodingFailed if the frame cannot be decoded.
func (d *webpDecoder) DecodeTo(f *Framebuffer) error {
if f == nil {
return io.EOF
}
// Get image header information
h, err := d.Header()
if err != nil {
return err
}
// Resize the framebuffer matrix to fit the image dimensions and pixel type
err = f.resizeMat(h.Width(), h.Height(), h.PixelType())
if err != nil {
return err
}
// Decode the current frame into the framebuffer
ret := C.webp_decoder_decode(d.decoder, f.mat)
if !ret {
// Check if the decoder has reached the end of the frames
if d.hasReachedEndOfFrames() {
return io.EOF
}
return ErrDecodingFailed
}
// Set the frame properties
f.duration = time.Duration(C.webp_decoder_get_prev_frame_delay(d.decoder)) * time.Millisecond
f.xOffset = int(C.webp_decoder_get_prev_frame_x_offset(d.decoder))
f.yOffset = int(C.webp_decoder_get_prev_frame_y_offset(d.decoder))
f.dispose = DisposeMethod(C.webp_decoder_get_prev_frame_dispose(d.decoder))
f.blend = BlendMethod(C.webp_decoder_get_prev_frame_blend(d.decoder))
// Advance to the next frame
d.advanceFrameIndex()
return nil
}
// SkipFrame is not supported for WebP images and always returns ErrSkipNotSupported.
func (d *webpDecoder) SkipFrame() error {
return ErrSkipNotSupported
}
// newWebpEncoder creates a new WebP encoder using the provided decoder for metadata
// and destination buffer for the encoded output.
func newWebpEncoder(decodedBy Decoder, dstBuf []byte) (*webpEncoder, error) {
dstBuf = dstBuf[:1]
icc := decodedBy.ICC()
bgColor := decodedBy.BackgroundColor()
loopCount := decodedBy.LoopCount()
var enc C.webp_encoder
if len(icc) > 0 {
enc = C.webp_encoder_create(unsafe.Pointer(&dstBuf[0]), C.size_t(cap(dstBuf)), unsafe.Pointer(&icc[0]), C.size_t(len(icc)), C.uint32_t(bgColor), C.int(loopCount))
} else {
enc = C.webp_encoder_create(unsafe.Pointer(&dstBuf[0]), C.size_t(cap(dstBuf)), nil, 0, C.uint32_t(bgColor), C.int(loopCount))
}
if enc == nil {
return nil, ErrBufTooSmall
}
return &webpEncoder{
encoder: enc,
dstBuf: dstBuf,
icc: icc,
}, nil
}
// Encode encodes a frame into the WebP format.
// If f is nil, finalizes the WebP animation and returns the encoded data.
// Returns io.EOF after the animation has been finalized.
// The opt parameter allows specifying encoding options as key-value pairs.
func (e *webpEncoder) Encode(f *Framebuffer, opt map[int]int) ([]byte, error) {
if e.hasFlushed {
return nil, io.EOF
}
if f == nil {
// Finalize the WebP animation
length := C.webp_encoder_flush(e.encoder)
if length == 0 {
return nil, ErrInvalidImage
}
e.hasFlushed = true
return e.dstBuf[:length], nil
}
var optList []C.int
var firstOpt *C.int
for k, v := range opt {
optList = append(optList, C.int(k))
optList = append(optList, C.int(v))
}
if len(optList) > 0 {
firstOpt = (*C.int)(unsafe.Pointer(&optList[0]))
}
// Encode the current frame
frameDelay := int(f.duration.Milliseconds())
length := C.webp_encoder_write(e.encoder, f.mat, firstOpt, C.size_t(len(optList)), C.int(frameDelay), C.int(f.blend), C.int(f.dispose), 0, 0)
if length == 0 {
return nil, ErrInvalidImage
}
e.frameIndex++
return nil, nil
}
// Close releases all resources associated with the encoder.
func (e *webpEncoder) Close() {
C.webp_encoder_release(e.encoder)
}