diff options
| author | Dax Raad <[email protected]> | 2025-05-18 22:30:41 -0400 |
|---|---|---|
| committer | Dax Raad <[email protected]> | 2025-05-26 12:40:17 -0400 |
| commit | 99af6146d5def31c59993636d60eb75a483a283b (patch) | |
| tree | eb57ed227c15cf9be54bc9f231c83895d812f881 /pkg/tui/image | |
| parent | 020e0ca039287b73fa33041fbd1bb214e6ccb396 (diff) | |
| download | opencode-99af6146d5def31c59993636d60eb75a483a283b.tar.gz opencode-99af6146d5def31c59993636d60eb75a483a283b.zip | |
openapi
Diffstat (limited to 'pkg/tui/image')
| -rw-r--r-- | pkg/tui/image/clipboard_unix.go | 49 | ||||
| -rw-r--r-- | pkg/tui/image/clipboard_windows.go | 192 | ||||
| -rw-r--r-- | pkg/tui/image/images.go | 85 |
3 files changed, 326 insertions, 0 deletions
diff --git a/pkg/tui/image/clipboard_unix.go b/pkg/tui/image/clipboard_unix.go new file mode 100644 index 000000000..3cb590207 --- /dev/null +++ b/pkg/tui/image/clipboard_unix.go @@ -0,0 +1,49 @@ +//go:build !windows + +package image + +import ( + "bytes" + "fmt" + "image" + "github.com/atotto/clipboard" +) + +func GetImageFromClipboard() ([]byte, string, error) { + text, err := clipboard.ReadAll() + if err != nil { + return nil, "", fmt.Errorf("Error reading clipboard") + } + + if text == "" { + return nil, "", nil + } + + binaryData := []byte(text) + imageBytes, err := binaryToImage(binaryData) + if err != nil { + return nil, text, nil + } + return imageBytes, "", nil + +} + + + +func binaryToImage(data []byte) ([]byte, error) { + reader := bytes.NewReader(data) + img, _, err := image.Decode(reader) + if err != nil { + return nil, fmt.Errorf("Unable to covert bytes to image") + } + + return ImageToBytes(img) +} + + +func min(a, b int) int { + if a < b { + return a + } + return b +} diff --git a/pkg/tui/image/clipboard_windows.go b/pkg/tui/image/clipboard_windows.go new file mode 100644 index 000000000..6431ce3d4 --- /dev/null +++ b/pkg/tui/image/clipboard_windows.go @@ -0,0 +1,192 @@ +//go:build windows + +package image + +import ( + "bytes" + "fmt" + "image" + "image/color" + "log/slog" + "syscall" + "unsafe" +) + +var ( + user32 = syscall.NewLazyDLL("user32.dll") + kernel32 = syscall.NewLazyDLL("kernel32.dll") + openClipboard = user32.NewProc("OpenClipboard") + closeClipboard = user32.NewProc("CloseClipboard") + getClipboardData = user32.NewProc("GetClipboardData") + isClipboardFormatAvailable = user32.NewProc("IsClipboardFormatAvailable") + globalLock = kernel32.NewProc("GlobalLock") + globalUnlock = kernel32.NewProc("GlobalUnlock") + globalSize = kernel32.NewProc("GlobalSize") +) + +const ( + CF_TEXT = 1 + CF_UNICODETEXT = 13 + CF_DIB = 8 +) + +type BITMAPINFOHEADER struct { + BiSize uint32 + BiWidth int32 + BiHeight int32 + BiPlanes uint16 + BiBitCount uint16 + BiCompression uint32 + BiSizeImage uint32 + BiXPelsPerMeter int32 + BiYPelsPerMeter int32 + BiClrUsed uint32 + BiClrImportant uint32 +} + +func GetImageFromClipboard() ([]byte, string, error) { + ret, _, _ := openClipboard.Call(0) + if ret == 0 { + return nil, "", fmt.Errorf("failed to open clipboard") + } + defer func(closeClipboard *syscall.LazyProc, a ...uintptr) { + _, _, err := closeClipboard.Call(a...) + if err != nil { + slog.Error("close clipboard failed") + return + } + }(closeClipboard) + isTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_TEXT)) + isUnicodeTextAvailable, _, _ := isClipboardFormatAvailable.Call(uintptr(CF_UNICODETEXT)) + + if isTextAvailable != 0 || isUnicodeTextAvailable != 0 { + // Get text from clipboard + var formatToUse uintptr = CF_TEXT + if isUnicodeTextAvailable != 0 { + formatToUse = CF_UNICODETEXT + } + + hClipboardText, _, _ := getClipboardData.Call(formatToUse) + if hClipboardText != 0 { + textPtr, _, _ := globalLock.Call(hClipboardText) + if textPtr != 0 { + defer func(globalUnlock *syscall.LazyProc, a ...uintptr) { + _, _, err := globalUnlock.Call(a...) + if err != nil { + slog.Error("Global unlock failed") + return + } + }(globalUnlock, hClipboardText) + + // Get clipboard text + var clipboardText string + if formatToUse == CF_UNICODETEXT { + // Convert wide string to Go string + clipboardText = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(textPtr))[:]) + } else { + // Get size of ANSI text + size, _, _ := globalSize.Call(hClipboardText) + if size > 0 { + // Convert ANSI string to Go string + textBytes := make([]byte, size) + copy(textBytes, (*[1 << 20]byte)(unsafe.Pointer(textPtr))[:size:size]) + clipboardText = bytesToString(textBytes) + } + } + + // Check if the text is not empty + if clipboardText != "" { + return nil, clipboardText, nil + } + } + } + } + hClipboardData, _, _ := getClipboardData.Call(uintptr(CF_DIB)) + if hClipboardData == 0 { + return nil, "", fmt.Errorf("failed to get clipboard data") + } + + dataPtr, _, _ := globalLock.Call(hClipboardData) + if dataPtr == 0 { + return nil, "", fmt.Errorf("failed to lock clipboard data") + } + defer func(globalUnlock *syscall.LazyProc, a ...uintptr) { + _, _, err := globalUnlock.Call(a...) + if err != nil { + slog.Error("Global unlock failed") + return + } + }(globalUnlock, hClipboardData) + + bmiHeader := (*BITMAPINFOHEADER)(unsafe.Pointer(dataPtr)) + + width := int(bmiHeader.BiWidth) + height := int(bmiHeader.BiHeight) + if height < 0 { + height = -height + } + bitsPerPixel := int(bmiHeader.BiBitCount) + + img := image.NewRGBA(image.Rect(0, 0, width, height)) + + var bitsOffset uintptr + if bitsPerPixel <= 8 { + numColors := uint32(1) << bitsPerPixel + if bmiHeader.BiClrUsed > 0 { + numColors = bmiHeader.BiClrUsed + } + bitsOffset = unsafe.Sizeof(*bmiHeader) + uintptr(numColors*4) + } else { + bitsOffset = unsafe.Sizeof(*bmiHeader) + } + + for y := range height { + for x := range width { + + srcY := height - y - 1 + if bmiHeader.BiHeight < 0 { + srcY = y + } + + var pixelPointer unsafe.Pointer + var r, g, b, a uint8 + + switch bitsPerPixel { + case 24: + stride := (width*3 + 3) &^ 3 + pixelPointer = unsafe.Pointer(dataPtr + bitsOffset + uintptr(srcY*stride+x*3)) + b = *(*byte)(pixelPointer) + g = *(*byte)(unsafe.Add(pixelPointer, 1)) + r = *(*byte)(unsafe.Add(pixelPointer, 2)) + a = 255 + case 32: + pixelPointer = unsafe.Pointer(dataPtr + bitsOffset + uintptr(srcY*width*4+x*4)) + b = *(*byte)(pixelPointer) + g = *(*byte)(unsafe.Add(pixelPointer, 1)) + r = *(*byte)(unsafe.Add(pixelPointer, 2)) + a = *(*byte)(unsafe.Add(pixelPointer, 3)) + if a == 0 { + a = 255 + } + default: + return nil, "", fmt.Errorf("unsupported bit count: %d", bitsPerPixel) + } + + img.Set(x, y, color.RGBA{R: r, G: g, B: b, A: a}) + } + } + + imageBytes, err := ImageToBytes(img) + if err != nil { + return nil, "", err + } + return imageBytes, "", nil +} + +func bytesToString(b []byte) string { + i := bytes.IndexByte(b, 0) + if i == -1 { + return string(b) + } + return string(b[:i]) +} diff --git a/pkg/tui/image/images.go b/pkg/tui/image/images.go new file mode 100644 index 000000000..f476b201c --- /dev/null +++ b/pkg/tui/image/images.go @@ -0,0 +1,85 @@ +package image + +import ( + "bytes" + "fmt" + "image" + "image/png" + "os" + "strings" + + "github.com/charmbracelet/lipgloss" + "github.com/disintegration/imaging" + "github.com/lucasb-eyer/go-colorful" + _ "golang.org/x/image/webp" +) + +func ValidateFileSize(filePath string, sizeLimit int64) (bool, error) { + fileInfo, err := os.Stat(filePath) + if err != nil { + return false, fmt.Errorf("error getting file info: %w", err) + } + + if fileInfo.Size() > sizeLimit { + return true, nil + } + + return false, nil +} + +func ToString(width int, img image.Image) string { + img = imaging.Resize(img, width, 0, imaging.Lanczos) + b := img.Bounds() + imageWidth := b.Max.X + h := b.Max.Y + str := strings.Builder{} + + for heightCounter := 0; heightCounter < h; heightCounter += 2 { + for x := range imageWidth { + c1, _ := colorful.MakeColor(img.At(x, heightCounter)) + color1 := lipgloss.Color(c1.Hex()) + + var color2 lipgloss.Color + if heightCounter+1 < h { + c2, _ := colorful.MakeColor(img.At(x, heightCounter+1)) + color2 = lipgloss.Color(c2.Hex()) + } else { + color2 = color1 + } + + str.WriteString(lipgloss.NewStyle().Foreground(color1). + Background(color2).Render("▀")) + } + + str.WriteString("\n") + } + + return str.String() +} + +func ImagePreview(width int, filename string) (string, error) { + imageContent, err := os.Open(filename) + if err != nil { + return "", err + } + defer imageContent.Close() + + img, _, err := image.Decode(imageContent) + if err != nil { + return "", err + } + + imageString := ToString(width, img) + + return imageString, nil +} + +func ImageToBytes(image image.Image) ([]byte, error) { + buf := new(bytes.Buffer) + err := png.Encode(buf, image) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} |
