Real-image posters: match the box to the poster's shape and crop the remainder
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
+56
-9
@@ -294,17 +294,47 @@ func (c *coverView) aspect() (w, h int) {
|
||||
return b.Dx(), b.Dy()
|
||||
}
|
||||
|
||||
// coverCells returns how many columns and rows a poster of the given aspect
|
||||
// coverFitTolerance is how far the shape of the cell box may differ from the
|
||||
// poster's, as a fraction. The box is chosen to match the poster's shape
|
||||
// closely; the small remainder is cropped away from the poster before it is
|
||||
// shown as a real image, because a real image placed in a box of another shape
|
||||
// is letterboxed (visible bars beside or above it).
|
||||
const coverFitTolerance = 0.03
|
||||
|
||||
// coverCells returns how many columns and rows a poster of the given shape
|
||||
// needs inside w×h cells. cellAspect is the height of a cell divided by its
|
||||
// width, about 2 in most terminals.
|
||||
// width, about 2 in most terminals. Cell counts are whole numbers, so the
|
||||
// largest size whose box is within coverFitTolerance of the poster's shape is
|
||||
// chosen (or the closest one if none is).
|
||||
func coverCells(imgW, imgH, w, h int, cellAspect float64) (cols, rows int) {
|
||||
cols = w
|
||||
rows = int(float64(cols) * float64(imgH) / float64(imgW) / cellAspect)
|
||||
if rows > h {
|
||||
rows = h
|
||||
cols = int(float64(rows) * cellAspect * float64(imgW) / float64(imgH))
|
||||
if imgW <= 0 || imgH <= 0 || w <= 0 || h <= 0 || cellAspect <= 0 {
|
||||
return 0, 0
|
||||
}
|
||||
return max(cols, 0), max(rows, 0)
|
||||
shape := float64(imgW) / float64(imgH) // width / height of the poster
|
||||
// A box of r rows is r*cellAspect*shape columns wide for the same shape.
|
||||
maxRows := min(h, int(float64(w)/(cellAspect*shape)))
|
||||
if maxRows < 1 {
|
||||
return w, 0
|
||||
}
|
||||
bestErr := 2.0
|
||||
for r := maxRows; r >= max(1, maxRows-6); r-- {
|
||||
c := int(float64(r)*cellAspect*shape + 0.5)
|
||||
if c < 1 || c > w {
|
||||
continue
|
||||
}
|
||||
boxShape := float64(c) / (float64(r) * cellAspect)
|
||||
err := boxShape/shape - 1
|
||||
if err < 0 {
|
||||
err = -err
|
||||
}
|
||||
if err <= coverFitTolerance {
|
||||
return c, r
|
||||
}
|
||||
if err < bestErr {
|
||||
bestErr, cols, rows = err, c, r
|
||||
}
|
||||
}
|
||||
return cols, rows
|
||||
}
|
||||
|
||||
func (c *coverView) Draw(screen tcell.Screen) {
|
||||
@@ -341,6 +371,23 @@ func (c *coverView) Draw(screen tcell.Screen) {
|
||||
}
|
||||
}
|
||||
|
||||
// cropToBox returns the part of an image of the given bounds that has the same
|
||||
// shape as a box of cols×rows cells: a few pixels are trimmed from both sides
|
||||
// of the longer dimension (at most the fit tolerance), so the image fills the
|
||||
// box exactly and the terminal does not letterbox it.
|
||||
func cropToBox(b image.Rectangle, cols, rows int, cellAspect float64) image.Rectangle {
|
||||
box := float64(cols) / (float64(rows) * cellAspect) // width / height of the box
|
||||
shape := float64(b.Dx()) / float64(b.Dy())
|
||||
if box < shape { // box is narrower: trim the sides
|
||||
w := int(float64(b.Dy())*box + 0.5)
|
||||
trim := (b.Dx() - w) / 2
|
||||
return image.Rect(b.Min.X+trim, b.Min.Y, b.Min.X+trim+w, b.Max.Y)
|
||||
}
|
||||
h := int(float64(b.Dx())/box + 0.5) // box is wider: trim top and bottom
|
||||
trim := (b.Dy() - h) / 2
|
||||
return image.Rect(b.Min.X, b.Min.Y+trim, b.Max.X, b.Min.Y+trim+h)
|
||||
}
|
||||
|
||||
// drawGraphics shows the poster as a real image through the Kitty graphics
|
||||
// protocol: the image is uploaded once and the cells get placeholder
|
||||
// characters. It reports false when that is not possible (no protocol, no true
|
||||
@@ -350,7 +397,7 @@ func (c *coverView) drawGraphics(screen tcell.Screen, x, y, cols, rows int) bool
|
||||
if c.gfx == nil || screen.Colors() < 1<<24 || cols > maxPlaceholders || rows > maxPlaceholders {
|
||||
return false
|
||||
}
|
||||
id, err := c.gfx.imageID(c.img)
|
||||
id, err := c.gfx.imageID(c.img, cropToBox(c.img.Bounds(), cols, rows, c.aspectOfCells()))
|
||||
if err == nil {
|
||||
err = c.gfx.place(id, cols, rows)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user