// 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)) }