added image rendering and resizing

This commit is contained in:
foglar 2025-06-02 16:53:32 +02:00
parent 658aac8704
commit 5588d95957
8 changed files with 296 additions and 27 deletions

View File

@ -4,14 +4,12 @@ import (
"fmt"
_ "image/jpeg"
_ "image/png"
"log"
"strings"
"github.com/jroimartin/gocui"
//"github.com/dolmen-go/kittyimg"
//"whspbrd/pkg/systray"
"whspbrd/pkg/term_image"
"whspbrd/pkg/cell_size"
"whspbrd/pkg/render_image"
)
var messages []string
@ -85,7 +83,7 @@ func updateChatView(v *gocui.View) {
//}
// Print image directly to terminal (stdout)
term_image.RenderImage("kogami-rounded.png", i*3+2, 23)
render_image.RenderImage("kogami-rounded.png", i*3+2, 23, 50, 50)
//err = kittyimg.Fprintln(os.Stdout, img)
//if err != nil {
// log.Println("Error rendering image:", err)
@ -196,24 +194,39 @@ func quit(g *gocui.Gui, v *gocui.View) error {
}
func main() {
fmt.Print("\033")
//systray.Systray()
g, err := gocui.NewGui(gocui.OutputNormal)
//g, err := gocui.NewGui(gocui.OutputNormal)
//if err != nil {
// log.Panicln(err)
//}
//defer g.Close()
//
//g.SetManagerFunc(layout)
//
//if err := keybindings(g); err != nil {
// log.Panicln(err)
//}
//
//if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
// log.Panicln(err)
//}
// and here is agrid making a grid of images
//for i := 0; i < 10; i++ {
// for j := 0; j < 10; j++ {
// render_image.RenderImage("kogami-pf-edit.jpg", i*3+2, j*7+23, 64, 64)
// }
//}
//render_image.RenderImage("kogami-pf-edit.jpg", 0, 3, 750, 0)
w, h, err := cell_size.GetTerminalCellSizePixels()
if err != nil {
log.Panicln(err)
fmt.Println("Error getting terminal cell size:", err)
return
}
defer g.Close()
width, height := cell_size.GetConsoleSize()
g.SetManagerFunc(layout)
fmt.Println("Terminal cell size in pixels:", w, "x", h)
fmt.Println("Console size in characters:", width, "x", height)
if err := keybindings(g); err != nil {
log.Panicln(err)
}
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
log.Panicln(err)
}
term_image.RenderImage("kogami-rounded.png", 20, 20)
}

View File

@ -0,0 +1,48 @@
//go:build !windows
package cell_size
import (
"fmt"
"os"
"syscall"
"unsafe"
)
type winsize struct {
Rows uint16
Cols uint16
Xpixels uint16
Ypixels uint16
}
func GetTerminalCellSizePixels() (widthPx int, heightPx int, err error) {
ws := &winsize{}
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
os.Stdout.Fd(),
uintptr(syscall.TIOCGWINSZ),
uintptr(unsafe.Pointer(ws)),
)
if errno != 0 {
return 0, 0, errno
}
if ws.Cols == 0 || ws.Rows == 0 {
return 0, 0, fmt.Errorf("terminal rows or columns is zero")
}
widthPx = int(ws.Xpixels) / int(ws.Cols)
heightPx = int(ws.Ypixels) / int(ws.Rows)
return
}
func GetConsoleSize() (int, int) {
var sz struct {
rows uint16
cols uint16
xpixels uint16
ypixels uint16
}
_, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
uintptr(syscall.Stdout), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
return int(sz.cols), int(sz.rows)
}

View File

@ -0,0 +1,107 @@
//go:build windows
package cell_size
import (
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
type coord struct {
X int16
Y int16
}
type consoleFontInfoEx struct {
cbSize uint32
nFont uint32
dwFontSize coord
fontFamily uint32
fontWeight uint32
faceName [32]uint16
}
var (
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
procGetCurrentConsoleFontEx = kernel32.NewProc("GetCurrentConsoleFontEx")
)
func GetTerminalCellSizePixels() (widthPx int, heightPx int, err error) {
var fontInfo consoleFontInfoEx
fontInfo.cbSize = uint32(unsafe.Sizeof(fontInfo))
stdOutHandle := windows.Handle(syscall.Stdout)
ret, _, err := procGetCurrentConsoleFontEx.Call(
uintptr(stdOutHandle),
uintptr(0), // bMaximumWindow = false
uintptr(unsafe.Pointer(&fontInfo)),
)
if ret == 0 {
return 0, 0, err
}
return int(fontInfo.dwFontSize.X), int(fontInfo.dwFontSize.Y), nil
}
type (
short int16
word uint16
smallRect struct {
Left short
Top short
Right short
Bottom short
}
consoleScreenBufferInfo struct {
Size coord
CursorPosition coord
Attributes word
Window smallRect
MaximumWindowSize coord
}
)
var kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
var getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
// GetConsoleSize returns the current number of columns and rows in the active console window.
// The return value of this function is in the order of cols, rows.
func GetConsoleSize() (int, int) {
stdoutHandle := getStdHandle(syscall.STD_OUTPUT_HANDLE)
var info, err = getConsoleScreenBufferInfo(stdoutHandle)
if err != nil {
return 0, 0
}
return int(info.Window.Right - info.Window.Left + 1), int(info.Window.Bottom - info.Window.Top + 1)
}
func getError(r1, r2 uintptr, lastErr error) error {
// If the function fails, the return value is zero.
if r1 == 0 {
if lastErr != nil {
return lastErr
}
return syscall.EINVAL
}
return nil
}
func getStdHandle(stdhandle int) uintptr {
handle, err := syscall.GetStdHandle(stdhandle)
if err != nil {
panic(fmt.Errorf("could not get standard io handle %d", stdhandle))
}
return uintptr(handle)
}
func getConsoleScreenBufferInfo(handle uintptr) (*consoleScreenBufferInfo, error) {
var info consoleScreenBufferInfo
if err := getError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0)); err != nil {
return nil, err
}
return &info, nil
}

View File

@ -1,4 +1,4 @@
package term_image
package render_image
import (
"encoding/base64"
@ -6,6 +6,8 @@ import (
"image"
"image/draw"
"os"
"whspbrd/pkg/resize_image"
)
func LoadImage(filePath string) (image.Image, error) {
@ -18,25 +20,26 @@ func LoadImage(filePath string) (image.Image, error) {
return img, err
}
func ConvertToRGBA(img image.Image) *image.RGBA {
func convertToRGBA(img image.Image) *image.RGBA {
rgba := image.NewRGBA(img.Bounds())
draw.Draw(rgba, rgba.Bounds(), img, image.Point{0, 0}, draw.Src)
return rgba
}
func EncodeImageToBase64RGBA(rgba *image.RGBA) string {
func encodeImageToBase64RGBA(rgba *image.RGBA) string {
return base64.StdEncoding.EncodeToString(rgba.Pix)
}
func RenderImage(filepath string, row int, col int) {
func RenderImage(filepath string, row int, col int, width_ int, height_ int) {
img, err := LoadImage(filepath)
if err != nil {
fmt.Printf("Error loading image: %v\n", err)
return
}
rgba := ConvertToRGBA(img)
encoded := EncodeImageToBase64RGBA(rgba)
rgba := convertToRGBA(img)
rgba, _ = resize_image.Resize(*rgba, width_, height_)
encoded := encodeImageToBase64RGBA(rgba)
width := rgba.Rect.Dx()
height := rgba.Rect.Dy()
@ -64,4 +67,3 @@ func RenderImage(filepath string, row int, col int) {
}
fmt.Print("\033[u")
}

View File

@ -0,0 +1,33 @@
package resize_image
import (
"image"
"image/color"
)
func Resize(img image.RGBA, width int, height int) (*image.RGBA, error) {
if width <= 0 || height <= 0 {
return nil, nil
}
newImg := image.NewRGBA(image.Rect(0, 0, width, height))
scaleX := float64(img.Bounds().Dx()) / float64(width)
scaleY := float64(img.Bounds().Dy()) / float64(height)
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
srcX := int(float64(x) * scaleX)
srcY := int(float64(y) * scaleY)
r, g, b, a := getPixel(img, srcX, srcY)
newImg.Set(x, y, color.RGBA{r, g, b, a})
}
}
return newImg, nil
}
func getPixel(img image.RGBA, x int, y int) (uint8, uint8, uint8, uint8) {
index := img.PixOffset(x, y)
return img.Pix[index], img.Pix[index+1], img.Pix[index+2], img.Pix[index+3]
}

View File

@ -0,0 +1,66 @@
package term_image
import (
"encoding/base64"
"fmt"
"image"
"image/draw"
"os"
)
func LoadImage(filePath string) (image.Image, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer f.Close()
img, _, err := image.Decode(f)
return img, err
}
func convertToRGBA(img image.Image) *image.RGBA {
rgba := image.NewRGBA(img.Bounds())
draw.Draw(rgba, rgba.Bounds(), img, image.Point{0, 0}, draw.Src)
return rgba
}
func encodeImageToBase64RGBA(rgba *image.RGBA) string {
return base64.StdEncoding.EncodeToString(rgba.Pix)
}
func RenderImage(filepath string, row int, col int) {
img, err := LoadImage(filepath)
if err != nil {
fmt.Printf("Error loading image: %v\n", err)
return
}
rgba := convertToRGBA(img)
encoded := encodeImageToBase64RGBA(rgba)
width := rgba.Rect.Dx()
height := rgba.Rect.Dy()
fmt.Printf("\033[s\033[%d;%dH", row, col)
chunk_size := 4096
pos := 0
first := true
for pos < len(encoded) {
fmt.Print("\033_G")
if first {
fmt.Printf("q=2,a=T,f=32,s=%d,v=%d,", width, height)
first = false
}
chunk_len := len(encoded) - pos
if chunk_len > chunk_size {
chunk_len = chunk_size
}
if pos+chunk_len < len(encoded) {
fmt.Print("m=1")
}
fmt.Printf(";%s\033\\", encoded[pos:pos+chunk_len])
pos += chunk_len
}
fmt.Print("\033[u")
}