Files
rdplib/plugin/cliprdr/image_dib.go
T

115 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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))
}