image.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764
  1. package tview
  2. import (
  3. "image"
  4. "math"
  5. "github.com/gdamore/tcell/v2"
  6. )
  7. // Types of dithering applied to images.
  8. const (
  9. DitheringNone = iota // No dithering.
  10. DitheringFloydSteinberg // Floyd-Steinberg dithering (the default).
  11. )
  12. // The number of colors supported by true color terminals (R*G*B = 256*256*256).
  13. const TrueColor = 16777216
  14. // This map describes what each block element looks like. A 1 bit represents a
  15. // pixel that is drawn, a 0 bit represents a pixel that is not drawn. The least
  16. // significant bit is the top left pixel, the most significant bit is the bottom
  17. // right pixel, moving row by row from left to right, top to bottom.
  18. var blockElements = map[rune]uint64{
  19. BlockLowerOneEighthBlock: 0b1111111100000000000000000000000000000000000000000000000000000000,
  20. BlockLowerOneQuarterBlock: 0b1111111111111111000000000000000000000000000000000000000000000000,
  21. BlockLowerThreeEighthsBlock: 0b1111111111111111111111110000000000000000000000000000000000000000,
  22. BlockLowerHalfBlock: 0b1111111111111111111111111111111100000000000000000000000000000000,
  23. BlockLowerFiveEighthsBlock: 0b1111111111111111111111111111111111111111000000000000000000000000,
  24. BlockLowerThreeQuartersBlock: 0b1111111111111111111111111111111111111111111111110000000000000000,
  25. BlockLowerSevenEighthsBlock: 0b1111111111111111111111111111111111111111111111111111111100000000,
  26. BlockLeftSevenEighthsBlock: 0b0111111101111111011111110111111101111111011111110111111101111111,
  27. BlockLeftThreeQuartersBlock: 0b0011111100111111001111110011111100111111001111110011111100111111,
  28. BlockLeftFiveEighthsBlock: 0b0001111100011111000111110001111100011111000111110001111100011111,
  29. BlockLeftHalfBlock: 0b0000111100001111000011110000111100001111000011110000111100001111,
  30. BlockLeftThreeEighthsBlock: 0b0000011100000111000001110000011100000111000001110000011100000111,
  31. BlockLeftOneQuarterBlock: 0b0000001100000011000000110000001100000011000000110000001100000011,
  32. BlockLeftOneEighthBlock: 0b0000000100000001000000010000000100000001000000010000000100000001,
  33. BlockQuadrantLowerLeft: 0b0000111100001111000011110000111100000000000000000000000000000000,
  34. BlockQuadrantLowerRight: 0b1111000011110000111100001111000000000000000000000000000000000000,
  35. BlockQuadrantUpperLeft: 0b0000000000000000000000000000000000001111000011110000111100001111,
  36. BlockQuadrantUpperRight: 0b0000000000000000000000000000000011110000111100001111000011110000,
  37. BlockQuadrantUpperLeftAndLowerRight: 0b1111000011110000111100001111000000001111000011110000111100001111,
  38. }
  39. // pixel represents a character on screen used to draw part of an image.
  40. type pixel struct {
  41. style tcell.Style
  42. element rune // The block element.
  43. }
  44. // Image implements a widget that displays one image. The original image
  45. // (specified with [Image.SetImage]) is resized according to the specified size
  46. // (see [Image.SetSize]), using the specified number of colors (see
  47. // [Image.SetColors]), while applying dithering if necessary (see
  48. // [Image.SetDithering]).
  49. //
  50. // Images are approximated by graphical characters in the terminal. The
  51. // resolution is therefore limited by the number and type of characters that can
  52. // be drawn in the terminal and the colors available in the terminal. The
  53. // quality of the final image also depends on the terminal's font and spacing
  54. // settings, none of which are under the control of this package. Results may
  55. // vary.
  56. type Image struct {
  57. *Box
  58. // The image to be displayed. If nil, the widget will be empty.
  59. image image.Image
  60. // The size of the image. If a value is 0, the corresponding size is chosen
  61. // automatically based on the other size while preserving the image's aspect
  62. // ratio. If both are 0, the image uses as much space as possible. A
  63. // negative value represents a percentage, e.g. -50 means 50% of the
  64. // available space.
  65. width, height int
  66. // The number of colors to use. If 0, the number of colors is chosen based
  67. // on the terminal's capabilities.
  68. colors int
  69. // The dithering algorithm to use, one of the constants starting with
  70. // "ImageDithering".
  71. dithering int
  72. // The width of a terminal's cell divided by its height.
  73. aspectRatio float64
  74. // Horizontal and vertical alignment, one of the "Align" constants.
  75. alignHorizontal, alignVertical int
  76. // The text to be displayed before the image.
  77. label string
  78. // The label style.
  79. labelStyle tcell.Style
  80. // The screen width of the label area. A value of 0 means use the width of
  81. // the label text.
  82. labelWidth int
  83. // The actual image size (in cells) when it was drawn the last time.
  84. lastWidth, lastHeight int
  85. // The actual image (in cells) when it was drawn the last time. The size of
  86. // this slice is lastWidth * lastHeight, indexed by y*lastWidth + x.
  87. pixels []pixel
  88. // A callback function set by the Form class and called when the user leaves
  89. // this form item.
  90. finished func(tcell.Key)
  91. }
  92. // NewImage returns a new image widget with an empty image (use [Image.SetImage]
  93. // to specify the image to be displayed). The image will use the widget's entire
  94. // available space. The dithering algorithm is set to Floyd-Steinberg dithering.
  95. // The terminal's cell aspect ratio defaults to 0.5.
  96. func NewImage() *Image {
  97. return &Image{
  98. Box: NewBox(),
  99. dithering: DitheringFloydSteinberg,
  100. aspectRatio: 0.5,
  101. alignHorizontal: AlignCenter,
  102. alignVertical: AlignCenter,
  103. }
  104. }
  105. // SetImage sets the image to be displayed. If nil, the widget will be empty.
  106. func (i *Image) SetImage(image image.Image) *Image {
  107. i.image = image
  108. i.lastWidth, i.lastHeight = 0, 0
  109. return i
  110. }
  111. // SetSize sets the size of the image. Positive values refer to cells in the
  112. // terminal. Negative values refer to a percentage of the available space (e.g.
  113. // -50 means 50%). A value of 0 means that the corresponding size is chosen
  114. // automatically based on the other size while preserving the image's aspect
  115. // ratio. If both are 0, the image uses as much space as possible while still
  116. // preserving the aspect ratio.
  117. func (i *Image) SetSize(rows, columns int) *Image {
  118. i.width = columns
  119. i.height = rows
  120. return i
  121. }
  122. // SetColors sets the number of colors to use. This should be the number of
  123. // colors supported by the terminal. If 0, the number of colors is chosen based
  124. // on the TERM environment variable (which may or may not be reliable).
  125. //
  126. // Only the values 0, 2, 8, 256, and 16777216 ([TrueColor]) are supported. Other
  127. // values will be rounded up to the next supported value, to a maximum of
  128. // 16777216.
  129. //
  130. // The effect of using more colors than supported by the terminal is undefined.
  131. func (i *Image) SetColors(colors int) *Image {
  132. i.colors = colors
  133. i.lastWidth, i.lastHeight = 0, 0
  134. return i
  135. }
  136. // GetColors returns the number of colors that will be used while drawing the
  137. // image. This is one of the values listed in [Image.SetColors], except 0 which
  138. // will be replaced by the actual number of colors used.
  139. func (i *Image) GetColors() int {
  140. switch {
  141. case i.colors == 0:
  142. return availableColors
  143. case i.colors <= 2:
  144. return 2
  145. case i.colors <= 8:
  146. return 8
  147. case i.colors <= 256:
  148. return 256
  149. }
  150. return TrueColor
  151. }
  152. // SetDithering sets the dithering algorithm to use, one of the constants
  153. // starting with "Dithering", for example [DitheringFloydSteinberg] (the
  154. // default). Dithering is not applied when rendering in true-color.
  155. func (i *Image) SetDithering(dithering int) *Image {
  156. i.dithering = dithering
  157. i.lastWidth, i.lastHeight = 0, 0
  158. return i
  159. }
  160. // SetAspectRatio sets the width of a terminal's cell divided by its height.
  161. // You may change the default of 0.5 if your terminal / font has a different
  162. // aspect ratio. This is used to calculate the size of the image if the
  163. // specified width or height is 0. The function will panic if the aspect ratio
  164. // is 0 or less.
  165. func (i *Image) SetAspectRatio(aspectRatio float64) *Image {
  166. if aspectRatio <= 0 {
  167. panic("aspect ratio must be greater than 0")
  168. }
  169. i.aspectRatio = aspectRatio
  170. i.lastWidth, i.lastHeight = 0, 0
  171. return i
  172. }
  173. // SetAlign sets the vertical and horizontal alignment of the image within the
  174. // widget's space. The possible values are [AlignTop], [AlignCenter], and
  175. // [AlignBottom] for vertical alignment and [AlignLeft], [AlignCenter], and
  176. // [AlignRight] for horizontal alignment. The default is [AlignCenter] for both
  177. // (or [AlignTop] and [AlignLeft] if the image is part of a [Form]).
  178. func (i *Image) SetAlign(vertical, horizontal int) *Image {
  179. i.alignHorizontal = horizontal
  180. i.alignVertical = vertical
  181. return i
  182. }
  183. // SetLabel sets the text to be displayed before the image.
  184. func (i *Image) SetLabel(label string) *Image {
  185. i.label = label
  186. return i
  187. }
  188. // GetLabel returns the text to be displayed before the image.
  189. func (i *Image) GetLabel() string {
  190. return i.label
  191. }
  192. // SetLabelWidth sets the screen width of the label. A value of 0 will cause the
  193. // primitive to use the width of the label string.
  194. func (i *Image) SetLabelWidth(width int) *Image {
  195. i.labelWidth = width
  196. return i
  197. }
  198. // GetFieldWidth returns this primitive's field width. This is the image's width
  199. // or, if the width is 0 or less, the proportional width of the image based on
  200. // its height as returned by [Image.GetFieldHeight]. If there is no image, 0 is
  201. // returned.
  202. func (i *Image) GetFieldWidth() int {
  203. if i.width <= 0 {
  204. if i.image == nil {
  205. return 0
  206. }
  207. bounds := i.image.Bounds()
  208. height := i.GetFieldHeight()
  209. return bounds.Dx() * height / bounds.Dy()
  210. }
  211. return i.width
  212. }
  213. // GetFieldHeight returns this primitive's field height. This is the image's
  214. // height or 8 if the height is 0 or less.
  215. func (i *Image) GetFieldHeight() int {
  216. if i.height <= 0 {
  217. return 8
  218. }
  219. return i.height
  220. }
  221. // SetDisabled sets whether or not the item is disabled / read-only.
  222. func (i *Image) SetDisabled(disabled bool) FormItem {
  223. return i // Images are always read-only.
  224. }
  225. // SetFormAttributes sets attributes shared by all form items.
  226. func (i *Image) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
  227. i.labelWidth = labelWidth
  228. i.backgroundColor = bgColor
  229. i.SetLabelStyle(tcell.StyleDefault.Foreground(labelColor).Background(bgColor))
  230. i.lastWidth, i.lastHeight = 0, 0
  231. return i
  232. }
  233. // SetLabelStyle sets the style of the label.
  234. func (i *Image) SetLabelStyle(style tcell.Style) *Image {
  235. i.labelStyle = style
  236. return i
  237. }
  238. // GetLabelStyle returns the style of the label.
  239. func (i *Image) GetLabelStyle() tcell.Style {
  240. return i.labelStyle
  241. }
  242. // SetFinishedFunc sets a callback invoked when the user leaves this form item.
  243. func (i *Image) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
  244. i.finished = handler
  245. return i
  246. }
  247. // Focus is called when this primitive receives focus.
  248. func (i *Image) Focus(delegate func(p Primitive)) {
  249. // If we're part of a form, there's nothing the user can do here so we're
  250. // finished.
  251. if i.finished != nil {
  252. i.finished(-1)
  253. return
  254. }
  255. i.Box.Focus(delegate)
  256. }
  257. // render re-populates the [Image.pixels] slice besed on the current settings,
  258. // if [Image.lastWidth] and [Image.lastHeight] don't match the current image's
  259. // size. It also sets the new image size in these two variables.
  260. func (i *Image) render() {
  261. // If there is no image, there are no pixels.
  262. if i.image == nil {
  263. i.pixels = nil
  264. return
  265. }
  266. // Calculate the new (terminal-space) image size.
  267. bounds := i.image.Bounds()
  268. imageWidth, imageHeight := bounds.Dx(), bounds.Dy()
  269. if i.aspectRatio != 1.0 {
  270. imageWidth = int(float64(imageWidth) / i.aspectRatio)
  271. }
  272. width, height := i.width, i.height
  273. _, _, innerWidth, innerHeight := i.GetInnerRect()
  274. if i.labelWidth > 0 {
  275. innerWidth -= i.labelWidth
  276. } else {
  277. innerWidth -= TaggedStringWidth(i.label)
  278. }
  279. if innerWidth <= 0 {
  280. i.pixels = nil
  281. return
  282. }
  283. if width == 0 && height == 0 {
  284. // Use all available space.
  285. width, height = innerWidth, innerHeight
  286. if adjustedWidth := imageWidth * height / imageHeight; adjustedWidth < width {
  287. width = adjustedWidth
  288. } else {
  289. height = imageHeight * width / imageWidth
  290. }
  291. } else {
  292. // Turn percentages into absolute values.
  293. if width < 0 {
  294. width = innerWidth * -width / 100
  295. }
  296. if height < 0 {
  297. height = innerHeight * -height / 100
  298. }
  299. if width == 0 {
  300. // Adjust the width.
  301. width = imageWidth * height / imageHeight
  302. } else if height == 0 {
  303. // Adjust the height.
  304. height = imageHeight * width / imageWidth
  305. }
  306. }
  307. if width <= 0 || height <= 0 {
  308. i.pixels = nil
  309. return
  310. }
  311. // If nothing has changed, we're done.
  312. if i.lastWidth == width && i.lastHeight == height {
  313. return
  314. }
  315. i.lastWidth, i.lastHeight = width, height // This could still be larger than the available space but that's ok for now.
  316. // Generate the initial pixels by resizing the image (8x8 per cell).
  317. pixels := i.resize()
  318. // Turn them into block elements with background/foreground colors.
  319. i.stamp(pixels)
  320. }
  321. // resize resizes the image to the current size and returns the result as a
  322. // slice of pixels. It is assumed that [Image.lastWidth] (w) and
  323. // [Image.lastHeight] (h) are positive, non-zero values, and the slice has a
  324. // size of 64*w*h, with each pixel being represented by 3 float64 values in the
  325. // range of 0-1. The factor of 64 is due to the fact that we calculate 8x8
  326. // pixels per cell.
  327. func (i *Image) resize() [][3]float64 {
  328. // Because most of the time, we will be downsizing the image, we don't even
  329. // attempt to do any fancy interpolation. For each target pixel, we
  330. // calculate a weighted average of the source pixels using their coverage
  331. // area.
  332. bounds := i.image.Bounds()
  333. srcWidth, srcHeight := bounds.Dx(), bounds.Dy()
  334. tgtWidth, tgtHeight := i.lastWidth*8, i.lastHeight*8
  335. coverageWidth, coverageHeight := float64(tgtWidth)/float64(srcWidth), float64(tgtHeight)/float64(srcHeight)
  336. pixels := make([][3]float64, tgtWidth*tgtHeight)
  337. weights := make([]float64, tgtWidth*tgtHeight)
  338. for srcY := bounds.Min.Y; srcY < bounds.Max.Y; srcY++ {
  339. for srcX := bounds.Min.X; srcX < bounds.Max.X; srcX++ {
  340. r32, g32, b32, _ := i.image.At(srcX, srcY).RGBA()
  341. r, g, b := float64(r32)/0xffff, float64(g32)/0xffff, float64(b32)/0xffff
  342. // Iterate over all target pixels. Outer loop is Y.
  343. startY := float64(srcY-bounds.Min.Y) * coverageHeight
  344. endY := startY + coverageHeight
  345. fromY, toY := int(startY), int(endY)
  346. for tgtY := fromY; tgtY <= toY && tgtY < tgtHeight; tgtY++ {
  347. coverageY := 1.0
  348. if tgtY == fromY {
  349. coverageY -= math.Mod(startY, 1.0)
  350. }
  351. if tgtY == toY {
  352. coverageY -= 1.0 - math.Mod(endY, 1.0)
  353. }
  354. // Inner loop is X.
  355. startX := float64(srcX-bounds.Min.X) * coverageWidth
  356. endX := startX + coverageWidth
  357. fromX, toX := int(startX), int(endX)
  358. for tgtX := fromX; tgtX <= toX && tgtX < tgtWidth; tgtX++ {
  359. coverageX := 1.0
  360. if tgtX == fromX {
  361. coverageX -= math.Mod(startX, 1.0)
  362. }
  363. if tgtX == toX {
  364. coverageX -= 1.0 - math.Mod(endX, 1.0)
  365. }
  366. // Add a weighted contribution to the target pixel.
  367. index := tgtY*tgtWidth + tgtX
  368. coverage := coverageX * coverageY
  369. pixels[index][0] += r * coverage
  370. pixels[index][1] += g * coverage
  371. pixels[index][2] += b * coverage
  372. weights[index] += coverage
  373. }
  374. }
  375. }
  376. }
  377. // Normalize the pixels.
  378. for index, weight := range weights {
  379. if weight > 0 {
  380. pixels[index][0] /= weight
  381. pixels[index][1] /= weight
  382. pixels[index][2] /= weight
  383. }
  384. }
  385. return pixels
  386. }
  387. // stamp takes the pixels generated by [Image.resize] and populates the
  388. // [Image.pixels] slice accordingly.
  389. func (i *Image) stamp(resized [][3]float64) {
  390. // For each 8x8 pixel block, we find the best block element to represent it,
  391. // given the available colors.
  392. i.pixels = make([]pixel, i.lastWidth*i.lastHeight)
  393. colors := i.GetColors()
  394. for row := 0; row < i.lastHeight; row++ {
  395. for col := 0; col < i.lastWidth; col++ {
  396. // Calculate an error for each potential block element + color. Keep
  397. // the one with the lowest error.
  398. // Note that the values in "resize" may lie outside [0, 1] due to
  399. // the error distribution during dithering.
  400. minMSE := math.MaxFloat64 // Mean squared error.
  401. var final [64][3]float64 // The final pixel values.
  402. for element, bits := range blockElements {
  403. // Calculate the average color for the pixels covered by the set
  404. // bits and unset bits.
  405. var (
  406. bg, fg [3]float64
  407. setBits float64
  408. bit uint64 = 1
  409. )
  410. for y := 0; y < 8; y++ {
  411. for x := 0; x < 8; x++ {
  412. index := (row*8+y)*i.lastWidth*8 + (col*8 + x)
  413. if bits&bit != 0 {
  414. fg[0] += resized[index][0]
  415. fg[1] += resized[index][1]
  416. fg[2] += resized[index][2]
  417. setBits++
  418. } else {
  419. bg[0] += resized[index][0]
  420. bg[1] += resized[index][1]
  421. bg[2] += resized[index][2]
  422. }
  423. bit <<= 1
  424. }
  425. }
  426. for ch := 0; ch < 3; ch++ {
  427. fg[ch] /= setBits
  428. if fg[ch] < 0 {
  429. fg[ch] = 0
  430. } else if fg[ch] > 1 {
  431. fg[ch] = 1
  432. }
  433. bg[ch] /= 64 - setBits
  434. if bg[ch] < 0 {
  435. bg[ch] = 0
  436. }
  437. if bg[ch] > 1 {
  438. bg[ch] = 1
  439. }
  440. }
  441. // Quantize to the nearest acceptable color.
  442. for _, color := range []*[3]float64{&fg, &bg} {
  443. if colors == 2 {
  444. // Monochrome. The following weights correspond better
  445. // to human perception than the arithmetic mean.
  446. gray := 0.299*color[0] + 0.587*color[1] + 0.114*color[2]
  447. if gray < 0.5 {
  448. *color = [3]float64{0, 0, 0}
  449. } else {
  450. *color = [3]float64{1, 1, 1}
  451. }
  452. } else {
  453. for index, ch := range color {
  454. switch {
  455. case colors == 8:
  456. // Colors vary wildly for each terminal. Expect
  457. // suboptimal results.
  458. if ch < 0.5 {
  459. color[index] = 0
  460. } else {
  461. color[index] = 1
  462. }
  463. case colors == 256:
  464. color[index] = math.Round(ch*6) / 6
  465. }
  466. }
  467. }
  468. }
  469. // Calculate the error (and the final pixel values).
  470. var (
  471. mse float64
  472. values [64][3]float64
  473. valuesIndex int
  474. )
  475. bit = 1
  476. for y := 0; y < 8; y++ {
  477. for x := 0; x < 8; x++ {
  478. if bits&bit != 0 {
  479. values[valuesIndex] = fg
  480. } else {
  481. values[valuesIndex] = bg
  482. }
  483. index := (row*8+y)*i.lastWidth*8 + (col*8 + x)
  484. for ch := 0; ch < 3; ch++ {
  485. err := resized[index][ch] - values[valuesIndex][ch]
  486. mse += err * err
  487. }
  488. bit <<= 1
  489. valuesIndex++
  490. }
  491. }
  492. // Do we have a better match?
  493. if mse < minMSE {
  494. // Yes. Save it.
  495. minMSE = mse
  496. final = values
  497. index := row*i.lastWidth + col
  498. i.pixels[index].element = element
  499. i.pixels[index].style = tcell.StyleDefault.
  500. Foreground(tcell.NewRGBColor(int32(math.Min(255, fg[0]*255)), int32(math.Min(255, fg[1]*255)), int32(math.Min(255, fg[2]*255)))).
  501. Background(tcell.NewRGBColor(int32(math.Min(255, bg[0]*255)), int32(math.Min(255, bg[1]*255)), int32(math.Min(255, bg[2]*255))))
  502. }
  503. }
  504. // Check if there is a shade block which results in a smaller error.
  505. // What's the overall average color?
  506. var avg [3]float64
  507. for y := 0; y < 8; y++ {
  508. for x := 0; x < 8; x++ {
  509. index := (row*8+y)*i.lastWidth*8 + (col*8 + x)
  510. for ch := 0; ch < 3; ch++ {
  511. avg[ch] += resized[index][ch] / 64
  512. }
  513. }
  514. }
  515. for ch := 0; ch < 3; ch++ {
  516. if avg[ch] < 0 {
  517. avg[ch] = 0
  518. } else if avg[ch] > 1 {
  519. avg[ch] = 1
  520. }
  521. }
  522. // Quantize and choose shade element.
  523. element := BlockFullBlock
  524. var fg, bg tcell.Color
  525. shades := []rune{' ', BlockLightShade, BlockMediumShade, BlockDarkShade, BlockFullBlock}
  526. if colors == 2 {
  527. // Monochrome.
  528. gray := 0.299*avg[0] + 0.587*avg[1] + 0.114*avg[2] // See above for details.
  529. shade := int(math.Round(gray * 4))
  530. element = shades[shade]
  531. for ch := 0; ch < 3; ch++ {
  532. avg[ch] = float64(shade) / 4
  533. }
  534. bg = tcell.ColorBlack
  535. fg = tcell.ColorWhite
  536. } else if colors == TrueColor {
  537. // True color.
  538. fg = tcell.NewRGBColor(int32(math.Min(255, avg[0]*255)), int32(math.Min(255, avg[1]*255)), int32(math.Min(255, avg[2]*255)))
  539. bg = fg
  540. } else {
  541. // 8 or 256 colors.
  542. steps := 1.0
  543. if colors == 256 {
  544. steps = 6.0
  545. }
  546. var (
  547. lo, hi, pos [3]float64
  548. shade float64
  549. )
  550. for ch := 0; ch < 3; ch++ {
  551. lo[ch] = math.Floor(avg[ch]*steps) / steps
  552. hi[ch] = math.Ceil(avg[ch]*steps) / steps
  553. if r := hi[ch] - lo[ch]; r > 0 {
  554. pos[ch] = (avg[ch] - lo[ch]) / r
  555. if math.Abs(pos[ch]-0.5) < math.Abs(shade-0.5) {
  556. shade = pos[ch]
  557. }
  558. }
  559. }
  560. shade = math.Round(shade * 4)
  561. element = shades[int(shade)]
  562. shade /= 4
  563. for ch := 0; ch < 3; ch++ { // Find the closest channel value.
  564. best := math.Abs(avg[ch] - (lo[ch] + (hi[ch]-lo[ch])*shade)) // Start shade from lo to hi.
  565. if value := math.Abs(avg[ch] - (hi[ch] - (hi[ch]-lo[ch])*shade)); value < best {
  566. best = value // Swap lo and hi.
  567. lo[ch], hi[ch] = hi[ch], lo[ch]
  568. }
  569. if value := math.Abs(avg[ch] - lo[ch]); value < best {
  570. best = value // Use lo.
  571. hi[ch] = lo[ch]
  572. }
  573. if value := math.Abs(avg[ch] - hi[ch]); value < best {
  574. lo[ch] = hi[ch] // Use hi.
  575. }
  576. avg[ch] = lo[ch] + (hi[ch]-lo[ch])*shade // Quantize.
  577. }
  578. bg = tcell.NewRGBColor(int32(math.Min(255, lo[0]*255)), int32(math.Min(255, lo[1]*255)), int32(math.Min(255, lo[2]*255)))
  579. fg = tcell.NewRGBColor(int32(math.Min(255, hi[0]*255)), int32(math.Min(255, hi[1]*255)), int32(math.Min(255, hi[2]*255)))
  580. }
  581. // Calculate the error (and the final pixel values).
  582. var (
  583. mse float64
  584. values [64][3]float64
  585. valuesIndex int
  586. )
  587. for y := 0; y < 8; y++ {
  588. for x := 0; x < 8; x++ {
  589. index := (row*8+y)*i.lastWidth*8 + (col*8 + x)
  590. for ch := 0; ch < 3; ch++ {
  591. err := resized[index][ch] - avg[ch]
  592. mse += err * err
  593. }
  594. values[valuesIndex] = avg
  595. valuesIndex++
  596. }
  597. }
  598. // Is this shade element better than the block element?
  599. if mse < minMSE {
  600. // Yes. Save it.
  601. final = values
  602. index := row*i.lastWidth + col
  603. i.pixels[index].element = element
  604. i.pixels[index].style = tcell.StyleDefault.Foreground(fg).Background(bg)
  605. }
  606. // Apply dithering.
  607. if colors < TrueColor && i.dithering == DitheringFloydSteinberg {
  608. // The dithering mask determines how the error is distributed.
  609. // Each element has three values: dx, dy, and weight (in 16th).
  610. var mask = [4][3]int{
  611. {1, 0, 7},
  612. {-1, 1, 3},
  613. {0, 1, 5},
  614. {1, 1, 1},
  615. }
  616. // We dither the 8x8 block as a 2x2 block, transferring errors
  617. // to its 2x2 neighbors.
  618. for ch := 0; ch < 3; ch++ {
  619. for y := 0; y < 2; y++ {
  620. for x := 0; x < 2; x++ {
  621. // What's the error for this 4x4 block?
  622. var err float64
  623. for dy := 0; dy < 4; dy++ {
  624. for dx := 0; dx < 4; dx++ {
  625. err += (final[(y*4+dy)*8+(x*4+dx)][ch] - resized[(row*8+(y*4+dy))*i.lastWidth*8+(col*8+(x*4+dx))][ch]) / 16
  626. }
  627. }
  628. // Distribute it to the 2x2 neighbors.
  629. for _, dist := range mask {
  630. for dy := 0; dy < 4; dy++ {
  631. for dx := 0; dx < 4; dx++ {
  632. targetX, targetY := (x+dist[0])*4+dx, (y+dist[1])*4+dy
  633. if targetX < 0 || col*8+targetX >= i.lastWidth*8 || targetY < 0 || row*8+targetY >= i.lastHeight*8 {
  634. continue
  635. }
  636. resized[(row*8+targetY)*i.lastWidth*8+(col*8+targetX)][ch] -= err * float64(dist[2]) / 16
  637. }
  638. }
  639. }
  640. }
  641. }
  642. }
  643. }
  644. }
  645. }
  646. }
  647. // Draw draws this primitive onto the screen.
  648. func (i *Image) Draw(screen tcell.Screen) {
  649. i.DrawForSubclass(screen, i)
  650. // Regenerate image if necessary.
  651. i.render()
  652. // Draw label.
  653. viewX, viewY, viewWidth, viewHeight := i.GetInnerRect()
  654. _, labelBg, _ := i.labelStyle.Decompose()
  655. if i.labelWidth > 0 {
  656. labelWidth := i.labelWidth
  657. if labelWidth > viewWidth {
  658. labelWidth = viewWidth
  659. }
  660. printWithStyle(screen, i.label, viewX, viewY, 0, labelWidth, AlignLeft, i.labelStyle, labelBg == tcell.ColorDefault)
  661. viewX += labelWidth
  662. viewWidth -= labelWidth
  663. } else {
  664. _, drawnWidth, _, _ := printWithStyle(screen, i.label, viewX, viewY, 0, viewWidth, AlignLeft, i.labelStyle, labelBg == tcell.ColorDefault)
  665. viewX += drawnWidth
  666. viewWidth -= drawnWidth
  667. }
  668. // Determine image placement.
  669. x, y, width, height := viewX, viewY, i.lastWidth, i.lastHeight
  670. if i.alignHorizontal == AlignCenter {
  671. x += (viewWidth - width) / 2
  672. } else if i.alignHorizontal == AlignRight {
  673. x += viewWidth - width
  674. }
  675. if i.alignVertical == AlignCenter {
  676. y += (viewHeight - height) / 2
  677. } else if i.alignVertical == AlignBottom {
  678. y += viewHeight - height
  679. }
  680. // Draw the image.
  681. for row := 0; row < height; row++ {
  682. if y+row < viewY || y+row >= viewY+viewHeight {
  683. continue
  684. }
  685. for col := 0; col < width; col++ {
  686. if x+col < viewX || x+col >= viewX+viewWidth {
  687. continue
  688. }
  689. index := row*width + col
  690. screen.SetContent(x+col, y+row, i.pixels[index].element, nil, i.pixels[index].style)
  691. }
  692. }
  693. }