process_images.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package icat
  3. import (
  4. "bytes"
  5. "fmt"
  6. "image"
  7. "image/color"
  8. "io"
  9. "io/fs"
  10. "net/http"
  11. "net/url"
  12. "os"
  13. "path/filepath"
  14. "strings"
  15. "kitty/tools/tty"
  16. "kitty/tools/tui/graphics"
  17. "kitty/tools/utils"
  18. "kitty/tools/utils/images"
  19. "kitty/tools/utils/shm"
  20. )
  21. var _ = fmt.Print
  22. type BytesBuf struct {
  23. data []byte
  24. pos int64
  25. }
  26. func (self *BytesBuf) Seek(offset int64, whence int) (int64, error) {
  27. switch whence {
  28. case io.SeekStart:
  29. self.pos = offset
  30. case io.SeekCurrent:
  31. self.pos += offset
  32. case io.SeekEnd:
  33. self.pos = int64(len(self.data)) + offset
  34. default:
  35. return self.pos, fmt.Errorf("Unknown value for whence: %#v", whence)
  36. }
  37. self.pos = utils.Max(0, utils.Min(self.pos, int64(len(self.data))))
  38. return self.pos, nil
  39. }
  40. func (self *BytesBuf) Read(p []byte) (n int, err error) {
  41. nb := utils.Min(int64(len(p)), int64(len(self.data))-self.pos)
  42. if nb == 0 {
  43. err = io.EOF
  44. } else {
  45. n = copy(p, self.data[self.pos:self.pos+nb])
  46. self.pos += nb
  47. }
  48. return
  49. }
  50. func (self *BytesBuf) Close() error {
  51. self.data = nil
  52. self.pos = 0
  53. return nil
  54. }
  55. type input_arg struct {
  56. arg string
  57. value string
  58. is_http_url bool
  59. }
  60. func is_http_url(arg string) bool {
  61. return strings.HasPrefix(arg, "https://") || strings.HasPrefix(arg, "http://")
  62. }
  63. func process_dirs(args ...string) (results []input_arg, err error) {
  64. results = make([]input_arg, 0, 64)
  65. if opts.Stdin != "no" && (opts.Stdin == "yes" || !tty.IsTerminal(os.Stdin.Fd())) {
  66. results = append(results, input_arg{arg: "/dev/stdin"})
  67. }
  68. for _, arg := range args {
  69. if arg != "" {
  70. if is_http_url(arg) {
  71. results = append(results, input_arg{arg: arg, value: arg, is_http_url: true})
  72. } else {
  73. if strings.HasPrefix(arg, "file://") {
  74. u, err := url.Parse(arg)
  75. if err != nil {
  76. return nil, &fs.PathError{Op: "Parse", Path: arg, Err: err}
  77. }
  78. arg = u.Path
  79. }
  80. s, err := os.Stat(arg)
  81. if err != nil {
  82. return nil, &fs.PathError{Op: "Stat", Path: arg, Err: err}
  83. }
  84. if s.IsDir() {
  85. if err = filepath.WalkDir(arg, func(path string, d fs.DirEntry, walk_err error) error {
  86. if walk_err != nil {
  87. if d == nil {
  88. err = &fs.PathError{Op: "Stat", Path: arg, Err: walk_err}
  89. }
  90. return walk_err
  91. }
  92. if !d.IsDir() {
  93. mt := utils.GuessMimeType(path)
  94. if strings.HasPrefix(mt, "image/") {
  95. results = append(results, input_arg{arg: arg, value: path})
  96. }
  97. }
  98. return nil
  99. }); err != nil {
  100. return nil, err
  101. }
  102. } else {
  103. results = append(results, input_arg{arg: arg, value: arg})
  104. }
  105. }
  106. }
  107. }
  108. return results, nil
  109. }
  110. type opened_input struct {
  111. file io.ReadSeekCloser
  112. name_to_unlink string
  113. }
  114. func (self *opened_input) Rewind() {
  115. if self.file != nil {
  116. _, _ = self.file.Seek(0, io.SeekStart)
  117. }
  118. }
  119. func (self *opened_input) Release() {
  120. if self.file != nil {
  121. self.file.Close()
  122. self.file = nil
  123. }
  124. if self.name_to_unlink != "" {
  125. os.Remove(self.name_to_unlink)
  126. self.name_to_unlink = ""
  127. }
  128. }
  129. func (self *opened_input) PutOnFilesystem() (err error) {
  130. if self.name_to_unlink != "" {
  131. return
  132. }
  133. f, err := images.CreateTempInRAM()
  134. if err != nil {
  135. return fmt.Errorf("Failed to create a temporary file to store input data with error: %w", err)
  136. }
  137. self.Rewind()
  138. _, err = io.Copy(f, self.file)
  139. if err != nil {
  140. f.Close()
  141. return fmt.Errorf("Failed to copy input data to temporary file with error: %w", err)
  142. }
  143. self.Release()
  144. self.file = f
  145. self.name_to_unlink = f.Name()
  146. return
  147. }
  148. func (self *opened_input) FileSystemName() string { return self.name_to_unlink }
  149. type image_frame struct {
  150. filename string
  151. shm shm.MMap
  152. in_memory_bytes []byte
  153. filename_is_temporary bool
  154. width, height, left, top int
  155. transmission_format graphics.GRT_f
  156. compose_onto int
  157. number int
  158. disposal_background color.NRGBA
  159. delay_ms int
  160. }
  161. type image_data struct {
  162. canvas_width, canvas_height int
  163. format_uppercase string
  164. available_width, available_height int
  165. needs_scaling, needs_conversion bool
  166. scaled_frac struct{ x, y float64 }
  167. frames []*image_frame
  168. image_number uint32
  169. image_id uint32
  170. cell_x_offset int
  171. move_x_by int
  172. move_to struct{ x, y int }
  173. width_cells, height_cells int
  174. use_unicode_placeholder bool
  175. passthrough_mode passthrough_type
  176. // for error reporting
  177. err error
  178. source_name string
  179. }
  180. func set_basic_metadata(imgd *image_data) {
  181. if imgd.frames == nil {
  182. imgd.frames = make([]*image_frame, 0, 32)
  183. }
  184. imgd.available_width = int(screen_size.Xpixel)
  185. imgd.available_height = 10 * imgd.canvas_height
  186. if place != nil {
  187. imgd.available_width = place.width * int(screen_size.Xpixel) / int(screen_size.Col)
  188. imgd.available_height = place.height * int(screen_size.Ypixel) / int(screen_size.Row)
  189. }
  190. imgd.needs_scaling = imgd.canvas_width > imgd.available_width || imgd.canvas_height > imgd.available_height || opts.ScaleUp
  191. imgd.needs_conversion = imgd.needs_scaling || remove_alpha != nil || flip || flop || imgd.format_uppercase != "PNG"
  192. }
  193. func report_error(source_name, msg string, err error) {
  194. imgd := image_data{source_name: source_name, err: fmt.Errorf("%s: %w", msg, err)}
  195. send_output(&imgd)
  196. }
  197. func make_output_from_input(imgd *image_data, f *opened_input) {
  198. bb, ok := f.file.(*BytesBuf)
  199. frame := image_frame{}
  200. imgd.frames = append(imgd.frames, &frame)
  201. frame.width = imgd.canvas_width
  202. frame.height = imgd.canvas_height
  203. if imgd.format_uppercase != "PNG" {
  204. panic(fmt.Sprintf("Unknown transmission format: %s", imgd.format_uppercase))
  205. }
  206. frame.transmission_format = graphics.GRT_format_png
  207. if ok {
  208. frame.in_memory_bytes = bb.data
  209. } else {
  210. frame.filename = f.file.(*os.File).Name()
  211. if f.name_to_unlink != "" {
  212. frame.filename_is_temporary = true
  213. f.name_to_unlink = ""
  214. }
  215. }
  216. }
  217. func process_arg(arg input_arg) {
  218. var f opened_input
  219. if arg.is_http_url {
  220. resp, err := http.Get(arg.value)
  221. if err != nil {
  222. report_error(arg.value, "Could not get", err)
  223. return
  224. }
  225. defer resp.Body.Close()
  226. if resp.StatusCode != http.StatusOK {
  227. report_error(arg.value, "Could not get", fmt.Errorf("bad status: %v", resp.Status))
  228. return
  229. }
  230. dest := bytes.Buffer{}
  231. dest.Grow(64 * 1024)
  232. _, err = io.Copy(&dest, resp.Body)
  233. if err != nil {
  234. report_error(arg.value, "Could not download", err)
  235. return
  236. }
  237. f.file = &BytesBuf{data: dest.Bytes()}
  238. } else if arg.value == "" {
  239. stdin, err := io.ReadAll(os.Stdin)
  240. if err != nil {
  241. report_error("<stdin>", "Could not read from", err)
  242. return
  243. }
  244. f.file = &BytesBuf{data: stdin}
  245. } else {
  246. q, err := os.Open(arg.value)
  247. if err != nil {
  248. report_error(arg.value, "Could not open", err)
  249. return
  250. }
  251. f.file = q
  252. }
  253. defer f.Release()
  254. can_use_go := false
  255. var c image.Config
  256. var format string
  257. var err error
  258. imgd := image_data{source_name: arg.value}
  259. if opts.Engine == "auto" || opts.Engine == "native" {
  260. c, format, err = image.DecodeConfig(f.file)
  261. f.Rewind()
  262. can_use_go = err == nil
  263. }
  264. if !keep_going.Load() {
  265. return
  266. }
  267. if can_use_go {
  268. imgd.canvas_width = c.Width
  269. imgd.canvas_height = c.Height
  270. imgd.format_uppercase = strings.ToUpper(format)
  271. set_basic_metadata(&imgd)
  272. if !imgd.needs_conversion {
  273. make_output_from_input(&imgd, &f)
  274. send_output(&imgd)
  275. return
  276. }
  277. err = render_image_with_go(&imgd, &f)
  278. if err != nil {
  279. report_error(arg.value, "Could not render image to RGB", err)
  280. return
  281. }
  282. } else {
  283. err = render_image_with_magick(&imgd, &f)
  284. if err != nil {
  285. report_error(arg.value, "ImageMagick failed", err)
  286. return
  287. }
  288. }
  289. if !keep_going.Load() {
  290. return
  291. }
  292. send_output(&imgd)
  293. }
  294. func run_worker() {
  295. for {
  296. select {
  297. case arg := <-files_channel:
  298. if !keep_going.Load() {
  299. return
  300. }
  301. process_arg(arg)
  302. default:
  303. return
  304. }
  305. }
  306. }