package display import ( "bytes" "encoding/base64" "encoding/binary" "image" "image/draw" "image/png" ) // Bitmap 表示协议层解码后的 BGRA/RGB565 小块,display 只负责合成和输出 // Logan.Li type Bitmap struct { X int Y int Width int Height int BitsPerPixel int Data []byte } // Frame 表示一次合成后的脏区域输出 // Logan.Li type Frame struct { X int Y int Width int Height int Image string RGBA []byte } // Framebuffer 借鉴 Guacamole/bring 的 display/layer 思路,在后端维护完整画布 // Logan.Li type Framebuffer struct { width int height int img *image.RGBA dirty image.Rectangle } // NewFramebuffer 创建固定尺寸远端画布,所有 RDP 更新先合成到这里 // Logan.Li func NewFramebuffer(width, height int) *Framebuffer { if width <= 0 { width = 1280 } if height <= 0 { height = 800 } return &Framebuffer{ width: width, height: height, img: image.NewRGBA(image.Rect(0, 0, width, height)), } } // Draw 合成一个 bitmap 小块,并扩大脏矩形 // Logan.Li func (f *Framebuffer) Draw(b Bitmap) { if b.Width <= 0 || b.Height <= 0 || len(b.Data) == 0 { return } dstRect := image.Rect(b.X, b.Y, b.X+b.Width, b.Y+b.Height).Intersect(f.img.Bounds()) if dstRect.Empty() { return } src := bitmapToRGBA(b) srcPoint := image.Pt(dstRect.Min.X-b.X, dstRect.Min.Y-b.Y) draw.Draw(f.img, dstRect, src, srcPoint, draw.Src) if f.dirty.Empty() { f.dirty = dstRect } else { f.dirty = f.dirty.Union(dstRect) } } // Flush 输出当前脏区域 PNG,并清空脏矩形 // Logan.Li func (f *Framebuffer) Flush() (Frame, bool) { frame, ok := f.FlushRGBA() if !ok { return Frame{}, false } img := image.NewRGBA(image.Rect(0, 0, frame.Width, frame.Height)) copy(img.Pix, frame.RGBA) var buf bytes.Buffer if err := png.Encode(&buf, img); err != nil { return Frame{}, false } frame.Image = "data:image/png;base64," + base64.StdEncoding.EncodeToString(buf.Bytes()) return frame, true } // FlushRGBA 输出当前脏区域原始 RGBA,避免实时 RDP 路径重复 PNG/base64 编解码。 // Logan.Li func (f *Framebuffer) FlushRGBA() (Frame, bool) { if f.dirty.Empty() { return Frame{}, false } rect := f.dirty.Intersect(f.img.Bounds()) f.dirty = image.Rectangle{} if rect.Empty() { return Frame{}, false } crop := image.NewRGBA(image.Rect(0, 0, rect.Dx(), rect.Dy())) draw.Draw(crop, crop.Bounds(), f.img, rect.Min, draw.Src) return Frame{ X: rect.Min.X, Y: rect.Min.Y, Width: rect.Dx(), Height: rect.Dy(), RGBA: append([]byte(nil), crop.Pix...), }, true } func bitmapToRGBA(b Bitmap) *image.RGBA { img := image.NewRGBA(image.Rect(0, 0, b.Width, b.Height)) pix := img.Pix switch b.BitsPerPixel { case 1, 2: n := b.Width * b.Height for i := 0; i < n && i*2+1 < len(b.Data); i++ { // grdp 的 RLE 输出 16bpp 使用大端 RGB565,按小端会导致蓝紫/绿色偏色。 // Logan.Li c := binary.BigEndian.Uint16(b.Data[i*2 : i*2+2]) r := uint8((c >> 11) & 0x1F) g := uint8((c >> 5) & 0x3F) bl := uint8(c & 0x1F) off := i * 4 pix[off] = (r << 3) | (r >> 2) pix[off+1] = (g << 2) | (g >> 4) pix[off+2] = (bl << 3) | (bl >> 2) pix[off+3] = 0xFF } default: stride := b.BitsPerPixel n := b.Width * b.Height for i := 0; i < n; i++ { s := i * stride if s+2 >= len(b.Data) { break } off := i * 4 pix[off] = b.Data[s+2] pix[off+1] = b.Data[s+1] pix[off+2] = b.Data[s] pix[off+3] = 0xFF } } return img }