summaryrefslogtreecommitdiffhomepage
path: root/pkg/tui/image
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-05-18 22:30:41 -0400
committerDax Raad <[email protected]>2025-05-26 12:40:17 -0400
commit99af6146d5def31c59993636d60eb75a483a283b (patch)
treeeb57ed227c15cf9be54bc9f231c83895d812f881 /pkg/tui/image
parent020e0ca039287b73fa33041fbd1bb214e6ccb396 (diff)
downloadopencode-99af6146d5def31c59993636d60eb75a483a283b.tar.gz
opencode-99af6146d5def31c59993636d60eb75a483a283b.zip
openapi
Diffstat (limited to 'pkg/tui/image')
-rw-r--r--pkg/tui/image/clipboard_unix.go49
-rw-r--r--pkg/tui/image/clipboard_windows.go192
-rw-r--r--pkg/tui/image/images.go85
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
+}