119 lines
2.9 KiB
Go
119 lines
2.9 KiB
Go
//go:build windows
|
|
|
|
package clipboard
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"image/png"
|
|
)
|
|
|
|
const (
|
|
biRGB = 0
|
|
cfDIB = 8
|
|
dibHeaderSize = 40
|
|
)
|
|
|
|
// pngToDIB converts a PNG image to a 32bpp bottom-up BI_RGB DIB. The DIB layout
|
|
// is BITMAPINFOHEADER (40 bytes) followed by tightly packed BGRA rows, which is
|
|
// the form Windows expects for the CF_DIB clipboard format.
|
|
func pngToDIB(pngData []byte) ([]byte, error) {
|
|
img, err := png.Decode(bytes.NewReader(pngData))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b := img.Bounds()
|
|
w, h := b.Dx(), b.Dy()
|
|
|
|
dib := make([]byte, dibHeaderSize+w*4*h)
|
|
|
|
le := binary.LittleEndian
|
|
le.PutUint32(dib[0:], dibHeaderSize) // biSize
|
|
le.PutUint32(dib[4:], uint32(w)) // biWidth
|
|
le.PutUint32(dib[8:], uint32(h)) // biHeight (positive = bottom-up)
|
|
le.PutUint16(dib[12:], 1) // biPlanes
|
|
le.PutUint16(dib[14:], 32) // biBitCount
|
|
le.PutUint32(dib[16:], biRGB) // biCompression
|
|
// biSizeImage (20) left 0 for BI_RGB; metrics/clrUsed/clrImportant left 0.
|
|
|
|
px := dib[dibHeaderSize:]
|
|
for y := 0; y < h; y++ {
|
|
srcY := h - 1 - y // bottom-up
|
|
row := px[y*w*4 : (y+1)*w*4]
|
|
for x := 0; x < w; x++ {
|
|
r, g, b_, a := img.At(b.Min.X+x, b.Min.Y+srcY).RGBA()
|
|
row[x*4+0] = uint8(b_ >> 8) // B
|
|
row[x*4+1] = uint8(g >> 8) // G
|
|
row[x*4+2] = uint8(r >> 8) // R
|
|
row[x*4+3] = uint8(a >> 8) // A
|
|
}
|
|
}
|
|
return dib, nil
|
|
}
|
|
|
|
// dibToPNG converts a BI_RGB DIB (32bpp or 24bpp) to a PNG image.
|
|
func dibToPNG(dib []byte) ([]byte, error) {
|
|
if len(dib) < dibHeaderSize {
|
|
return nil, ErrNoImage
|
|
}
|
|
le := binary.LittleEndian
|
|
w := int(int32(le.Uint32(dib[4:])))
|
|
h := int32(le.Uint32(dib[8:]))
|
|
bitCount := le.Uint16(dib[14:])
|
|
compression := le.Uint32(dib[16:])
|
|
|
|
if w <= 0 || h == 0 {
|
|
return nil, ErrNoImage
|
|
}
|
|
if compression != biRGB {
|
|
return nil, fmt.Errorf("clipboard: unsupported DIB compression %d", compression)
|
|
}
|
|
|
|
height := int(h)
|
|
if h < 0 {
|
|
height = -height
|
|
}
|
|
bottomUp := h > 0
|
|
|
|
img := image.NewRGBA(image.Rect(0, 0, w, height))
|
|
src := dib[dibHeaderSize:]
|
|
|
|
switch bitCount {
|
|
case 32:
|
|
stride := w * 4
|
|
for y := 0; y < height; y++ {
|
|
srcY := y
|
|
if bottomUp {
|
|
srcY = height - 1 - y
|
|
}
|
|
row := src[srcY*stride : (srcY+1)*stride]
|
|
for x := 0; x < w; x++ {
|
|
img.SetRGBA(x, y, color.RGBA{row[x*4+2], row[x*4+1], row[x*4+0], row[x*4+3]})
|
|
}
|
|
}
|
|
case 24:
|
|
stride := (w*3 + 3) &^ 3 // rows padded to 4-byte boundary
|
|
for y := 0; y < height; y++ {
|
|
srcY := y
|
|
if bottomUp {
|
|
srcY = height - 1 - y
|
|
}
|
|
row := src[srcY*stride : srcY*stride+w*3]
|
|
for x := 0; x < w; x++ {
|
|
img.SetRGBA(x, y, color.RGBA{row[x*3+2], row[x*3+1], row[x*3+0], 255})
|
|
}
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("clipboard: unsupported DIB bit count %d", bitCount)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, img); err != nil {
|
|
return nil, err
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|