forked from jezek/xgbutil
/
new.go
319 lines (285 loc) · 8.64 KB
/
new.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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
package xgraphics
/*
xgraphics/new.go contains a few additional constructors for creating an
xgraphics.Image.
*/
import (
"bytes"
"fmt"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"os"
"github.com/jezek/xgb/xproto"
"github.com/alex11br/xgbutil"
"github.com/alex11br/xgbutil/ewmh"
"github.com/alex11br/xgbutil/xwindow"
)
// NewConvert converts any image satisfying the image.Image interface to an
// xgraphics.Image type.
// If 'img' is an xgraphics.Image, it will be copied and a new image will
// be returned.
// Also, NewConvert attempts to optimize image conversion for some image
// formats. (i.e., *image.RGBA.)
func NewConvert(X *xgbutil.XUtil, img image.Image) *Image {
ximg := New(X, img.Bounds())
// I've attempted to optimize this loop.
// It actually takes more time to convert an image than to send the bytes
// over the wire. (I suspect 'copy' is super fast, which can be used in
// XDraw, whereas computing each pixel is super slow.)
// But how is image decoding so much faster than this? I'll have to
// investigate... Maybe the Color interface being used here is the real
// slow down.
switch concrete := img.(type) {
case *image.NRGBA:
convertNRGBA(ximg, concrete)
case *image.NRGBA64:
convertNRGBA64(ximg, concrete)
case *image.RGBA:
convertRGBA(ximg, concrete)
case *image.RGBA64:
convertRGBA64(ximg, concrete)
case *image.YCbCr:
convertYCbCr(ximg, concrete)
case *Image:
convertXImage(ximg, concrete)
default:
xgbutil.Logger.Printf("Converting image type %T the slow way. "+
"Optimization for this image type hasn't been added yet.", img)
convertImage(ximg, img)
}
return ximg
}
// NewFileName uses the image package's decoder and converts a file specified
// by fileName to an xgraphics.Image value.
// Opening a file or decoding an image can cause an error.
func NewFileName(X *xgbutil.XUtil, fileName string) (*Image, error) {
srcReader, err := os.Open(fileName)
if err != nil {
return nil, err
}
defer srcReader.Close()
img, _, err := image.Decode(srcReader)
if err != nil {
return nil, err
}
return NewConvert(X, img), nil
}
// NewBytes uses the image package's decoder to convert the bytes given to
// an xgraphics.Imag value.
// Decoding an image can cause an error.
func NewBytes(X *xgbutil.XUtil, bs []byte) (*Image, error) {
img, _, err := image.Decode(bytes.NewReader(bs))
if err != nil {
return nil, err
}
return NewConvert(X, img), nil
}
// NewEwmhIcon converts EWMH icon data (ARGB) to an xgraphics.Image type.
// You should probably use xgraphics.FindIcon instead of this directly.
func NewEwmhIcon(X *xgbutil.XUtil, icon *ewmh.WmIcon) *Image {
ximg := New(X, image.Rect(0, 0, int(icon.Width), int(icon.Height)))
r := ximg.Rect
width := r.Dx()
var argb, x, y int
for x = r.Min.X; x < r.Max.X; x++ {
for y = r.Min.Y; y < r.Max.Y; y++ {
argb = int(icon.Data[x+(y*width)])
ximg.SetBGRA(x, y, BGRA{
B: uint8(argb & 0x000000ff),
G: uint8((argb & 0x0000ff00) >> 8),
R: uint8((argb & 0x00ff0000) >> 16),
A: uint8(argb >> 24),
})
}
}
return ximg
}
// NewIcccmIcon converts two pixmap ids (icon_pixmap and icon_mask in the
// WM_HINTS properts) to a single xgraphics.Image.
// It is okay for one of iconPixmap or iconMask to be 0, but not both.
// You should probably use xgraphics.FindIcon instead of this directly.
func NewIcccmIcon(X *xgbutil.XUtil, iconPixmap,
iconMask xproto.Pixmap) (*Image, error) {
if iconPixmap == 0 && iconMask == 0 {
return nil, fmt.Errorf("NewIcccmIcon: At least one of iconPixmap or " +
"iconMask must be non-zero, but both are 0.")
}
var pximg, mximg *Image
var err error
// Get the xgraphics.Image for iconPixmap.
if iconPixmap != 0 {
pximg, err = NewDrawable(X, xproto.Drawable(iconPixmap))
if err != nil {
return nil, err
}
}
// Now get the xgraphics.Image for iconMask.
if iconMask != 0 {
mximg, err = NewDrawable(X, xproto.Drawable(iconMask))
if err != nil {
return nil, err
}
}
// Now merge them together if both were specified.
switch {
case pximg != nil && mximg != nil:
r := pximg.Bounds()
var x, y int
var bgra, maskBgra BGRA
for x = r.Min.X; x < r.Max.X; x++ {
for y = r.Min.Y; y < r.Max.Y; y++ {
maskBgra = mximg.At(x, y).(BGRA)
bgra = pximg.At(x, y).(BGRA)
if maskBgra.A == 0 {
pximg.SetBGRA(x, y, BGRA{
B: bgra.B,
G: bgra.G,
R: bgra.R,
A: 0,
})
}
}
}
return pximg, nil
case pximg != nil:
return pximg, nil
case mximg != nil:
return mximg, nil
}
panic("unreachable")
}
// NewDrawable converts an X drawable into a xgraphics.Image.
// This is used in NewIcccmIcon.
func NewDrawable(X *xgbutil.XUtil, did xproto.Drawable) (*Image, error) {
// Get the geometry of the pixmap for use in the GetImage request.
pgeom, err := xwindow.RawGeometry(X, xproto.Drawable(did))
if err != nil {
return nil, err
}
// Get the image data for each pixmap.
pixmapData, err := xproto.GetImage(X.Conn(), xproto.ImageFormatZPixmap,
did,
0, 0, uint16(pgeom.Width()), uint16(pgeom.Height()),
(1<<32)-1).Reply()
if err != nil {
return nil, err
}
// Now create the xgraphics.Image and populate it with data from
// pixmapData and maskData.
ximg := New(X, image.Rect(0, 0, pgeom.Width(), pgeom.Height()))
// We'll try to be a little flexible with the image format returned,
// but not completely flexible.
err = readDrawableData(X, ximg, did, pixmapData,
pgeom.Width(), pgeom.Height())
if err != nil {
return nil, err
}
return ximg, nil
}
// readDrawableData uses Format information to read data from an X pixmap
// into an xgraphics.Image.
// readPixmapData does not take into account all information possible to read
// X pixmaps and bitmaps. Of particular note is bit order/byte order.
func readDrawableData(X *xgbutil.XUtil, ximg *Image, did xproto.Drawable,
imgData *xproto.GetImageReply, width, height int) error {
format := GetFormat(X, imgData.Depth)
if format == nil {
return fmt.Errorf("Could not find valid format for pixmap %d "+
"with depth %d", did, imgData.Depth)
}
switch format.Depth {
case 1: // We read bitmaps in as alpha masks.
if format.BitsPerPixel != 1 {
return fmt.Errorf("The image returned for pixmap id %d with "+
"depth %d has an unsupported value for bits-per-pixel: %d",
did, format.Depth, format.BitsPerPixel)
}
// Calculate the padded width of our image data.
pad := int(X.Setup().BitmapFormatScanlinePad)
paddedWidth := width
if width%pad != 0 {
paddedWidth = width + pad - (width % pad)
}
// Process one scanline at a time. Each 'y' represents a
// single scanline.
for y := 0; y < height; y++ {
// Each scanline has length 'width' padded to
// BitmapFormatScanlinePad, which is found in the X setup info.
// 'i' is the index to the starting byte of the yth scanline.
i := y * paddedWidth / 8
for x := 0; x < width; x++ {
b := imgData.Data[i+x/8] >> uint(x%8)
if b&1 > 0 { // opaque
ximg.Set(x, y, BGRA{0x0, 0x0, 0x0, 0xff})
} else { // transparent
ximg.Set(x, y, BGRA{0xff, 0xff, 0xff, 0x0})
}
}
}
case 24, 32:
switch format.BitsPerPixel {
case 24:
bytesPer := int(format.BitsPerPixel) / 8
var i int
ximg.For(func(x, y int) BGRA {
i = y*width*bytesPer + x*bytesPer
return BGRA{
B: imgData.Data[i],
G: imgData.Data[i+1],
R: imgData.Data[i+2],
A: 0xff,
}
})
case 32:
bytesPer := int(format.BitsPerPixel) / 8
var i int
ximg.For(func(x, y int) BGRA {
i = y*width*bytesPer + x*bytesPer
return BGRA{
B: imgData.Data[i],
G: imgData.Data[i+1],
R: imgData.Data[i+2],
A: imgData.Data[i+3],
}
})
default:
return fmt.Errorf("The image returned for pixmap id %d has "+
"an unsupported value for bits-per-pixel: %d",
did, format.BitsPerPixel)
}
default:
return fmt.Errorf("The image returned for pixmap id %d has an "+
"unsupported value for depth: %d", did, format.Depth)
}
return nil
}
// GetFormat searches SetupInfo for a Format matching the depth provided.
func GetFormat(X *xgbutil.XUtil, depth byte) *xproto.Format {
for _, pixForm := range X.Setup().PixmapFormats {
if pixForm.Depth == depth {
return &pixForm
}
}
return nil
}
// getVisualInfo searches SetupInfo for a VisualInfo value matching
// the depth provided.
// XXX: This isn't used (yet).
func getVisualInfo(X *xgbutil.XUtil, depth byte,
visualid xproto.Visualid) *xproto.VisualInfo {
for _, depthInfo := range X.Screen().AllowedDepths {
fmt.Printf("%#v\n", depthInfo)
// fmt.Printf("%#v\n", depthInfo.Visuals)
fmt.Println("------------")
if depthInfo.Depth == depth {
for _, visual := range depthInfo.Visuals {
if visual.VisualId == visualid {
return &visual
}
}
}
}
return nil
}