| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- // Based on the implementation by @trashhalo at:
- // https://github.com/trashhalo/imgcat
- package image
- import (
- "context"
- "image"
- "image/png"
- "io"
- "net/http"
- "os"
- "strings"
- tea "github.com/charmbracelet/bubbletea/v2"
- "github.com/disintegration/imageorient"
- "github.com/lucasb-eyer/go-colorful"
- "github.com/muesli/termenv"
- "github.com/nfnt/resize"
- "github.com/srwiley/oksvg"
- "github.com/srwiley/rasterx"
- )
- type loadMsg struct {
- io.ReadCloser
- }
- func loadURL(url string) tea.Cmd {
- var r io.ReadCloser
- var err error
- if strings.HasPrefix(url, "http") {
- var resp *http.Request
- resp, err = http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
- r = resp.Body
- } else {
- r, err = os.Open(url)
- }
- if err != nil {
- return func() tea.Msg {
- return errMsg{err}
- }
- }
- return load(r)
- }
- func load(r io.ReadCloser) tea.Cmd {
- return func() tea.Msg {
- return loadMsg{r}
- }
- }
- func handleLoadMsg(m Model, msg loadMsg) (Model, tea.Cmd) {
- defer msg.Close()
- img, err := readerToImage(m.width, m.height, m.url, msg)
- if err != nil {
- return m, func() tea.Msg { return errMsg{err} }
- }
- m.image = img
- return m, nil
- }
- func imageToString(width, height uint, img image.Image) (string, error) {
- img = resize.Thumbnail(width, height*2-4, img, resize.Lanczos3)
- b := img.Bounds()
- w := b.Max.X
- h := b.Max.Y
- p := termenv.ColorProfile()
- str := strings.Builder{}
- for y := 0; y < h; y += 2 {
- for x := w; x < int(width); x = x + 2 {
- str.WriteString(" ")
- }
- for x := range w {
- c1, _ := colorful.MakeColor(img.At(x, y))
- color1 := p.Color(c1.Hex())
- c2, _ := colorful.MakeColor(img.At(x, y+1))
- color2 := p.Color(c2.Hex())
- str.WriteString(termenv.String("▀").
- Foreground(color1).
- Background(color2).
- String())
- }
- str.WriteString("\n")
- }
- return str.String(), nil
- }
- func readerToImage(width uint, height uint, url string, r io.Reader) (string, error) {
- if strings.HasSuffix(strings.ToLower(url), ".svg") {
- return svgToImage(width, height, r)
- }
- img, _, err := imageorient.Decode(r)
- if err != nil {
- return "", err
- }
- return imageToString(width, height, img)
- }
- func svgToImage(width uint, height uint, r io.Reader) (string, error) {
- // Original author: https://stackoverflow.com/users/10826783/usual-human
- // https://stackoverflow.com/questions/42993407/how-to-create-and-export-svg-to-png-jpeg-in-golang
- // Adapted to use size from SVG, and to use temp file.
- tmpPngFile, err := os.CreateTemp("", "img.*.png")
- if err != nil {
- return "", err
- }
- tmpPngPath := tmpPngFile.Name()
- defer os.Remove(tmpPngPath)
- defer tmpPngFile.Close()
- // Rasterize the SVG:
- icon, err := oksvg.ReadIconStream(r)
- if err != nil {
- return "", err
- }
- w := int(icon.ViewBox.W)
- h := int(icon.ViewBox.H)
- icon.SetTarget(0, 0, float64(w), float64(h))
- rgba := image.NewRGBA(image.Rect(0, 0, w, h))
- icon.Draw(rasterx.NewDasher(w, h, rasterx.NewScannerGV(w, h, rgba, rgba.Bounds())), 1)
- // Write rasterized image as PNG:
- err = png.Encode(tmpPngFile, rgba)
- if err != nil {
- tmpPngFile.Close()
- return "", err
- }
- tmpPngFile.Close()
- rPng, err := os.Open(tmpPngPath)
- if err != nil {
- return "", err
- }
- defer rPng.Close()
- img, _, err := imageorient.Decode(rPng)
- if err != nil {
- return "", err
- }
- return imageToString(width, height, img)
- }
|