115 lines
3.1 KiB
Go
115 lines
3.1 KiB
Go
// image_dib.go 将 Windows 剪贴板的 CF_DIB(BITMAPINFO + 像素)转换为 PNG。
|
||
//
|
||
// 剪贴板截图类内容常见两种来源:现代应用直接提供 "PNG" 注册格式;老应用
|
||
// (如 Server 上的画图)只提供 CF_DIB。本转换器覆盖最常见子集:
|
||
// BI_RGB / BI_BITFIELDS 压缩、24/32bpp、底行优先(默认)与 top-down。
|
||
package cliprdr
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/binary"
|
||
"fmt"
|
||
"image"
|
||
"image/png"
|
||
)
|
||
|
||
// DIBToPNG 把 CF_DIB 数据解码并编码为 PNG;不支持的子集返回错误。
|
||
func DIBToPNG(dib []byte) ([]byte, error) {
|
||
if len(dib) < 40 {
|
||
return nil, fmt.Errorf("dib too short: %d", len(dib))
|
||
}
|
||
biSize := binary.LittleEndian.Uint32(dib[0:4])
|
||
w := int(int32(binary.LittleEndian.Uint32(dib[4:8])))
|
||
hRaw := int32(binary.LittleEndian.Uint32(dib[8:12]))
|
||
topDown := hRaw < 0
|
||
h := int(hRaw)
|
||
if h < 0 {
|
||
h = -h
|
||
}
|
||
bpp := binary.LittleEndian.Uint16(dib[14:16])
|
||
compression := binary.LittleEndian.Uint32(dib[16:20])
|
||
if w <= 0 || h <= 0 || w > 16384 || h > 16384 {
|
||
return nil, fmt.Errorf("bad dimensions %dx%d", w, h)
|
||
}
|
||
|
||
var rMask, gMask, bMask uint32
|
||
pixOff := int(biSize)
|
||
switch compression {
|
||
case 0: // BI_RGB
|
||
rMask, gMask, bMask = 0x00FF0000, 0x0000FF00, 0x000000FF
|
||
case 3: // BI_BITFIELDS
|
||
if biSize >= 108 { // BITMAPV4HEADER/V5HEADER 内含掩码
|
||
rMask = binary.LittleEndian.Uint32(dib[40:44])
|
||
gMask = binary.LittleEndian.Uint32(dib[44:48])
|
||
bMask = binary.LittleEndian.Uint32(dib[48:52])
|
||
} else { // 三个 DWORD 掩码紧跟标准头
|
||
if pixOff+12 > len(dib) {
|
||
return nil, fmt.Errorf("missing bitfields masks")
|
||
}
|
||
rMask = binary.LittleEndian.Uint32(dib[pixOff:])
|
||
gMask = binary.LittleEndian.Uint32(dib[pixOff+4:])
|
||
bMask = binary.LittleEndian.Uint32(dib[pixOff+8:])
|
||
pixOff += 12
|
||
}
|
||
default:
|
||
return nil, fmt.Errorf("unsupported compression %d", compression)
|
||
}
|
||
if bpp != 24 && bpp != 32 {
|
||
return nil, fmt.Errorf("unsupported bpp %d", bpp)
|
||
}
|
||
|
||
stride := (w*int(bpp) + 31) / 32 * 4
|
||
if pixOff+stride*h > len(dib) {
|
||
return nil, fmt.Errorf("dib truncated: need %d, have %d", pixOff+stride*h, len(dib))
|
||
}
|
||
|
||
img := image.NewRGBA(image.Rect(0, 0, w, h))
|
||
for y := 0; y < h; y++ {
|
||
srcY := y
|
||
if !topDown {
|
||
srcY = h - 1 - y
|
||
}
|
||
row := dib[pixOff+srcY*stride:]
|
||
for x := 0; x < w; x++ {
|
||
var r8, g8, b8 uint8
|
||
if bpp == 32 {
|
||
v := binary.LittleEndian.Uint32(row[x*4:])
|
||
r8, g8, b8 = maskTo8(v, rMask), maskTo8(v, gMask), maskTo8(v, bMask)
|
||
} else {
|
||
o := x * 3
|
||
b8, g8, r8 = row[o], row[o+1], row[o+2]
|
||
}
|
||
i := img.PixOffset(x, y)
|
||
img.Pix[i], img.Pix[i+1], img.Pix[i+2], img.Pix[i+3] = r8, g8, b8, 255
|
||
}
|
||
}
|
||
|
||
var buf bytes.Buffer
|
||
if err := png.Encode(&buf, img); err != nil {
|
||
return nil, err
|
||
}
|
||
return buf.Bytes(), nil
|
||
}
|
||
|
||
// maskTo8 把 mask 提取出的位域缩放为 8 位
|
||
func maskTo8(v, mask uint32) uint8 {
|
||
if mask == 0 {
|
||
return 0
|
||
}
|
||
shift := 0
|
||
for mask&1 == 0 {
|
||
mask >>= 1
|
||
shift++
|
||
}
|
||
bits := 0
|
||
for mask != 0 {
|
||
mask >>= 1
|
||
bits++
|
||
}
|
||
val := (v >> uint(shift)) & ((1 << uint(bits)) - 1)
|
||
if bits < 8 {
|
||
return uint8(val<<(8-bits) | val>>(2*uint(bits)-8))
|
||
}
|
||
return uint8(val >> uint(bits-8))
|
||
}
|