-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmashup.go
154 lines (127 loc) · 3.93 KB
/
mashup.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
package mashup
import (
"fmt"
"image"
"image/color"
"image/draw"
"image/jpeg"
"image/png"
"io"
)
// JPEG
// NewJPEGInput creates a new Input for JPEGs
func NewJPEGInput(r io.Reader) *Input {
return &Input{
in: r,
name: "jpeg",
magic: "jpg",
decodeFunc: jpeg.Decode,
decodeConfigFunc: jpeg.DecodeConfig,
}
}
// NewJPEGOutput creates a new Output for JPEGs with the given quality
func NewJPEGOutput(w io.Writer, quality int) *Output {
return &Output{
w: w,
encodeFunc: jpegEncodeFunc(quality),
}
}
// PNG
// NewPNGInput creates a new Input for PNGs
func NewPNGInput(r io.Reader) *Input {
return &Input{
in: r,
name: "png",
magic: "png",
decodeFunc: png.Decode,
decodeConfigFunc: png.DecodeConfig,
}
}
// NewPNGOutput creates a new Output for PNGs
func NewPNGOutput(w io.Writer) *Output {
return &Output{
w: w,
encodeFunc: png.Encode,
}
}
// custom
// NewCustomInput creates a new Input for types not supported by this library
func NewCustomInput(r io.Reader, name, magic string, decodeFunc DecodeFunc, decodeConfigFunc DecodeConfigFunc) *Input {
return &Input{
in: r,
name: name,
magic: magic,
decodeFunc: decodeFunc,
decodeConfigFunc: decodeConfigFunc,
}
}
// NewCustomOutput creates a new Output for types not supported by this library
func NewCustomOutput(w io.Writer, encodeFunc EncodeFunc) *Output {
return &Output{
w: w,
encodeFunc: encodeFunc,
}
}
// magic
type Input struct {
in io.Reader
name string
magic string
decodeFunc DecodeFunc
decodeConfigFunc DecodeConfigFunc
}
type Output struct {
w io.Writer
encodeFunc EncodeFunc
}
// Mashup creates a color mashup of src and dst by computing their most prominent colors.
// Most prominent colors in dst will be replaced by the most prominent colors of src.
//
// Specify the maximum amount of colors that should be replaced using the maxColors parameter.
// If the amount of colors in either src or dst is less than the maxColors value, this amount will become the maximum.
//
// Additionally, if the src and dst are not of the same type, both formats are registered.
func Mashup(src, dst *Input, out *Output, maxColors int) error {
if maxColors <= 0 {
return fmt.Errorf("mashup: maxColors cannot be negative or zero, provided: %d", maxColors)
}
image.RegisterFormat(src.name, src.magic, src.decodeFunc, src.decodeConfigFunc)
if dst.name != src.name || dst.magic != src.magic {
image.RegisterFormat(dst.name, dst.magic, dst.decodeFunc, dst.decodeConfigFunc)
}
srcImage, _, err := image.Decode(src.in)
if err != nil {
return fmt.Errorf("mashup: could not decode src to image: %w", err)
}
_, srcSortedKeys := getProminentImageColors(srcImage)
dstImage, _, err := image.Decode(dst.in)
if err != nil {
return fmt.Errorf("mashup: could not decode dst to image: %w", err)
}
dstPixels, dstSortedKeys := getProminentImageColors(dstImage)
amount := min(maxColors, len(dstSortedKeys), len(srcSortedKeys))
dstSortedKeys = dstSortedKeys[:amount]
modifiedImage := image.NewRGBA(dstImage.Bounds())
draw.Draw(modifiedImage, dstImage.Bounds(), dstImage, image.Point{}, draw.Over)
for i, key := range dstSortedKeys {
srcColor := srcSortedKeys[i]
for _, coords := range dstPixels[key] {
modifiedImage.Set(coords.X, coords.Y, color.RGBA{R: srcColor.R, G: srcColor.G, B: srcColor.B, A: srcColor.A})
}
}
if err := out.encodeFunc(out.w, modifiedImage); err != nil {
return fmt.Errorf("mashup: could not encode image: %w", err)
}
return nil
}
// helpers
type DecodeFunc func(io.Reader) (image.Image, error)
type DecodeConfigFunc func(io.Reader) (image.Config, error)
type EncodeFunc func(io.Writer, image.Image) error
func jpegEncodeFunc(quality int) EncodeFunc {
return func(w io.Writer, img image.Image) error {
return jpeg.Encode(w, img, &jpeg.Options{
Quality: quality,
})
}
}