/
mage.go
248 lines (227 loc) · 6.45 KB
/
mage.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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
package mage
// #cgo pkg-config: MagickWand MagickCore
// #include <stdlib.h>
// #include <wand/MagickWand.h>
import "C"
import (
"unsafe"
"math"
)
type Mage struct {
wand *C.MagickWand
}
// Private: Convert C.MagickBooleanType to boolean type
//
// Params:
// - b: either C.MagickTrue or C.MagickFalse
//
// Examples
// mBoolean(C.MagickTrue) == true
// mBoolean(C.MagickFalse) == false
func mBoolean(b C.MagickBooleanType) bool {
return b == C.MagickTrue
}
// Private: Round up a float64 number to int
//
// Examples
// round(float64(0.5)) == 1
// round(float64(0.6)) == 1
// round(float64(0.3)) == 0
func round(x float64) int {
return int(math.Floor(x + 0.5))
}
// Private: Create a blank magick wand with size width and height
//
// Params:
// - format: format of the new image
// - width: width of the new image
// - height: height of the new image
//
// Examples
// blankWand("jpg", 100, 100)
//
// Return *C.MagickWand
func blankWand(format string, width, height int) *C.MagickWand {
wand := C.NewMagickWand()
cformat := C.CString(format)
noneBackground := C.CString("none")
defer C.free(unsafe.Pointer(cformat))
defer C.free(unsafe.Pointer(noneBackground))
C.MagickSetFormat(wand, C.CString(format))
pixel := C.NewPixelWand()
defer C.DestroyPixelWand(pixel)
C.PixelSetColor(pixel, noneBackground);
C.MagickSetSize(wand, C.size_t(width), C.size_t(height));
C.MagickNewImage(wand, C.size_t(width), C.size_t(height), pixel)
return wand
}
// Private: scale wand's image respected the initial ratio. At least one of the
// new dimension will be equal to one of the passed in width/height. This
// depends on the maximum scale between the old dimension and desired dimension
//
// Params:
// - width: probably new width of the scaled image
// - height: probably new height of the scaled image
//
// Examples: given an image with initial dimention: 1000x1000
// m.scale(300, 500) == 500, 500
// m.scale(300, 200) == 300, 300
//
// Returns a pair of scaled width and height of the new image
func (m *Mage) scale(width , height int) (scaledWidth, scaledHeight int) {
scale := float64(1.0)
imageWidth := m.Width()
imageHeight := m.Height()
if width != imageWidth || height != imageHeight {
scale = math.Max(
float64(width)/float64(imageWidth),
float64(height)/float64(imageHeight))
}
scaledWidth = round(scale * (float64(imageWidth) + 0.5))
scaledHeight = round(scale * (float64(imageHeight) + 0.5))
return scaledWidth, scaledHeight
}
// Private: center the current wand on top of the new wand, after that, we only
// keep the new wand.
//
// Params:
// - newWand: new wand, probably going to be result of blankWand
// - x: x position on the current wand that is the center of the new wand
// - y: y position on the current wand that is the center of the new wand
//
// Example:
// newWand := blankWand("jpg", width, height)
// done := m.compositeCenter(newWand, 10, 10)
//
// Return boolean result of the composition
func (m *Mage) compositeCenter(newWand *C.MagickWand, x, y int) bool{
success := C.MagickCompositeImage(
newWand,
m.wand,
C.OverCompositeOp,
C.ssize_t(x),
C.ssize_t(y))
C.DestroyMagickWand(m.wand)
m.wand = newWand
return mBoolean(success)
}
// Private: resize the current image to new width and height
//
// Params:
// - width: width of the new image
// - height: height of the new image
//
// Examples:
// m.resize(100, 100)
func (m *Mage) resize(width, height int) bool {
return mBoolean(C.MagickResizeImage(
m.wand,
C.size_t(width),
C.size_t(height),
C.LanczosFilter,
C.double(1.0)))
}
// Private: strip all comments and profiles from an image
//
// Examples:
// m.strip()
func (m *Mage) strip() bool {
return mBoolean(C.MagickStripImage(m.wand))
}
// Public: read a blob data into the current wand
//
// Examples:
// im = NewMage()
// original, err := ioutil.ReadFile("test.jpg")
// success := im.ReadBlob(original)
func (m *Mage) ReadBlob(blob []byte) bool {
return mBoolean(C.MagickReadImageBlob(
m.wand,
unsafe.Pointer(&blob[0]),
C.size_t(len(blob))))
}
// Public: export the current image into a blob. Also destroy the current wand
//
// Examples:
// im = NewMage()
// original, err := ioutil.ReadFile("test.jpg")
// success := im.ReadBlob(original)
// imageBytes := im.ExportBlob()
func (m *Mage) ExportBlob() []byte {
defer m.Destroy()
newSize := C.size_t(0)
C.MagickResetIterator(m.wand)
image := C.MagickGetImageBlob(m.wand, &newSize)
imagePointer := unsafe.Pointer(image)
defer C.MagickRelinquishMemory(imagePointer)
return C.GoBytes(imagePointer, C.int(newSize))
}
// Public: resize an image. The algorithm to resize the image is:
// - strip all comments and profiles data
// - resize the image respect to the original ratio
// - center the image with the new size, remove anything that isnt in the new
// dimension
//
// Params:
// - width: new width
// - height: new height
//
// Examples:
// im = NewMage()
// original, err := ioutil.ReadFile("test.jpg")
// success := im.ReadBlob(original)
// success = im.Resize(100, 100)
func (m *Mage) Resize(width, height int) bool {
var done bool;
scaledWidth, scaledHeight := m.scale(width, height)
done = m.strip()
done = m.resize(scaledWidth, scaledHeight)
newWand := blankWand("jpg", width, height)
done = m.compositeCenter(newWand, int((width - scaledWidth) / 2), int((height - scaledHeight) / 2))
return done
}
// Public: get current width of the image
//
// Examples:
// im = NewMage()
// original, err := ioutil.ReadFile("test.jpg")
// im.Width()
func (m *Mage) Width() int {
return int(C.MagickGetImageWidth(m.wand))
}
// Public: get current height of the image
//
// Examples:
// im = NewMage()
// original, err := ioutil.ReadFile("test.jpg")
// im.Height()
func (m *Mage) Height() int {
return int(C.MagickGetImageHeight(m.wand))
}
func (m *Mage) Destroy() {
defer C.DestroyMagickWand(m.wand)
}
// Public: initialize the magick wand environment
// This should only be called at the start of the process, before any magick
// operation
func InitWandEnv() {
C.MagickWandGenesis()
}
// Public: destroy the magick wand environment
// This should only be called at the end of the process, after all magick
// operation
func TermWandEnv() {
C.MagickWandTerminus()
}
// Public: create a new mage, associate with a new magick wand
//
// Examples:
// InitWandEnv()
// mage := NewMage()
// ...
// TermWandEnv()
func NewMage() *Mage {
mage := &Mage{}
mage.wand = C.NewMagickWand()
return mage
}