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/jezek/xgbutil" "github.com/jezek/xgbutil/ewmh" "github.com/jezek/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 }