This repository has been archived by the owner on Oct 12, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
/
guetzli.go
163 lines (145 loc) · 4.15 KB
/
guetzli.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
// Copyright 2017 <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Guetzli perceptual JPEG encoder for Go.
package guetzli // import "github.com/chai2010/guetzli-go"
import (
"bytes"
"errors"
"image"
"image/color"
"io"
"io/ioutil"
"reflect"
)
// Guetzli algorithm is beneficial only for this quality or higher.
// If you work around it and lower the quality, you'll only
// waste your time generating average JPEGs, without Guetzli improvement.
//
// If you need even smaller files and can tolerate bigger distortions,
// MozJPEG is a better choice for quality < 84.
const (
MinQuality = 84
MaxQuality = 110
// Equal to google/guetzli's default quality
DefaultQuality = 95
)
var (
errEncodeFailed = errors.New("guetzli: encode failed!")
errInvalidQuality = errors.New("guetzli: invalid quality (must >=84 and <= 110)")
)
func isInvalidQuality(quality int) bool {
return quality < MinQuality || quality > MaxQuality
}
type Options struct {
Quality int // 84 <= quality <= 110
}
// MemP Image Spec (Native Endian), see https://github.com/chai2010/image.
type MemP interface {
MemPMagic() string
Bounds() image.Rectangle
Channels() int
DataType() reflect.Kind
Pix() []byte // PixSlice type
// Stride is the Pix stride (in bytes, must align with SizeofKind(p.DataType))
// between vertically adjacent pixels.
Stride() int
}
func EncodeImage(m image.Image, quality int) (data []byte, ok bool) {
if isInvalidQuality(quality) {
return nil, false
}
return encodeImage(m, quality)
}
func EncodeGray(m *image.Gray, quality int) (data []byte, ok bool) {
if isInvalidQuality(quality) {
return nil, false
}
return encodeGray(m.Pix, m.Bounds().Dx(), m.Bounds().Dy(), m.Stride, quality)
}
func EncodeRGBA(m *image.RGBA, quality int) (data []byte, ok bool) {
if isInvalidQuality(quality) {
return nil, false
}
return encodeRGBA(m.Pix, m.Bounds().Dx(), m.Bounds().Dy(), m.Stride, quality)
}
func EncodeRGB(pix []byte, w, h, stride int, quality int) (data []byte, ok bool) {
if isInvalidQuality(quality) {
return nil, false
}
return encodeRGB(pix, w, h, stride, quality)
}
func Encode(w io.Writer, m image.Image, o *Options) error {
var quality = DefaultQuality
if o != nil {
quality = o.Quality
}
if isInvalidQuality(quality) {
return errInvalidQuality
}
data, ok := encodeImage(m, quality)
if !ok {
return errEncodeFailed
}
_, err := io.Copy(w, bytes.NewReader(data))
if err != nil {
return err
}
return nil
}
func Save(name string, m image.Image, o *Options) error {
var quality = DefaultQuality
if o != nil {
quality = o.Quality
}
if isInvalidQuality(quality) {
return errInvalidQuality
}
data, ok := encodeImage(m, quality)
if !ok {
return errEncodeFailed
}
return ioutil.WriteFile(name, data, 0666)
}
func encodeImage(m image.Image, quality int) (data []byte, ok bool) {
b := m.Bounds()
if memp, ok := m.(MemP); ok {
switch {
case memp.Channels() == 1 && memp.DataType() == reflect.Uint8:
return encodeGray(memp.Pix(), b.Dx(), b.Dy(), memp.Stride(), quality)
case memp.Channels() == 3 && memp.DataType() == reflect.Uint8:
return encodeRGB(memp.Pix(), b.Dx(), b.Dy(), memp.Stride(), quality)
case memp.Channels() == 4 && memp.DataType() == reflect.Uint8:
return encodeRGBA(memp.Pix(), b.Dx(), b.Dy(), memp.Stride(), quality)
}
}
switch m := m.(type) {
case *image.Gray:
return encodeGray(m.Pix, b.Dx(), b.Dy(), m.Stride, quality)
case *image.RGBA:
return encodeRGBA(m.Pix, b.Dx(), b.Dy(), m.Stride, quality)
default:
rgba := toRGBAImage(m)
return encodeRGBA(rgba.Pix, b.Dx(), b.Dy(), rgba.Stride, quality)
}
}
func toRGBAImage(m image.Image) *image.RGBA {
if m, ok := m.(*image.RGBA); ok {
return m
}
b := m.Bounds()
rgba := image.NewRGBA(b)
dstColorRGBA64 := &color.RGBA64{}
dstColor := color.Color(dstColorRGBA64)
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
pr, pg, pb, pa := m.At(x, y).RGBA()
dstColorRGBA64.R = uint16(pr)
dstColorRGBA64.G = uint16(pg)
dstColorRGBA64.B = uint16(pb)
dstColorRGBA64.A = uint16(pa)
rgba.Set(x, y, dstColor)
}
}
return rgba
}