package tui
import (
"image"
"image/color"
)
const maxPreviewPixels = 1024
func fitPixels(w, h, max int) (int, int) {
if w <= max && h <= max {
return w, h
}
if w >= h {
nh := h * max / w
if nh < 1 {
nh = 1
}
return max, nh
}
nw := w * max / h
if nw < 1 {
nw = 1
}
return nw, max
}
func scaleDown(src image.Image, dw, dh int) image.Image {
// averages blocks instead of dropping pixels, which turns a photo into noise
// at preview size. dw/dh are the exact target, not a bound, so a caller that
// already worked out the aspect ratio doesn't get it recomputed from one number.
b := src.Bounds()
sw, sh := b.Dx(), b.Dy()
if dw < 1 {
dw = 1
}
if dh < 1 {
dh = 1
}
if dw >= sw && dh >= sh {
return src
}
dst := image.NewRGBA(image.Rect(0, 0, dw, dh))
switch s := src.(type) {
case *image.YCbCr:
scaleYCbCr(dst, s, b, sw, sh, dw, dh)
case *image.RGBA:
scaleRGBA(dst, s, b, sw, sh, dw, dh)
case *image.NRGBA:
scaleNRGBA(dst, s, b, sw, sh, dw, dh)
default:
scaleGeneric(dst, src, b, sw, sh, dw, dh)
}
return dst
}
func scaleYCbCr(dst *image.RGBA, s *image.YCbCr, b image.Rectangle, sw, sh, dw, dh int) {
for y := 0; y < dh; y++ {
y0 := b.Min.Y + y*sh/dh
y1 := b.Min.Y + (y+1)*sh/dh
if y1 <= y0 {
y1 = y0 + 1
}
for x := 0; x < dw; x++ {
x0 := b.Min.X + x*sw/dw
x1 := b.Min.X + (x+1)*sw/dw
if x1 <= x0 {
x1 = x0 + 1
}
var r, g, bl, n uint32
for yy := y0; yy < y1; yy++ {
for xx := x0; xx < x1; xx++ {
c := s.YCbCrAt(xx, yy)
cr, cg, cb := color.YCbCrToRGB(c.Y, c.Cb, c.Cr)
r += uint32(cr)
g += uint32(cg)
bl += uint32(cb)
n++
}
}
dst.SetRGBA(x, y, color.RGBA{uint8(r / n), uint8(g / n), uint8(bl / n), 0xff})
}
}
}
func scaleRGBA(dst *image.RGBA, s *image.RGBA, b image.Rectangle, sw, sh, dw, dh int) {
off := func(x, y int) int { return s.PixOffset(x, y) }
for y := 0; y < dh; y++ {
y0 := b.Min.Y + y*sh/dh
y1 := b.Min.Y + (y+1)*sh/dh
if y1 <= y0 {
y1 = y0 + 1
}
for x := 0; x < dw; x++ {
x0 := b.Min.X + x*sw/dw
x1 := b.Min.X + (x+1)*sw/dw
if x1 <= x0 {
x1 = x0 + 1
}
var r, g, bl, a, n uint32
for yy := y0; yy < y1; yy++ {
for xx := x0; xx < x1; xx++ {
i := off(xx, yy)
r += uint32(s.Pix[i])
g += uint32(s.Pix[i+1])
bl += uint32(s.Pix[i+2])
a += uint32(s.Pix[i+3])
n++
}
}
dst.SetRGBA(x, y, color.RGBA{uint8(r / n), uint8(g / n), uint8(bl / n), uint8(a / n)})
}
}
}
func scaleNRGBA(dst *image.RGBA, s *image.NRGBA, b image.Rectangle, sw, sh, dw, dh int) {
off := func(x, y int) int { return s.PixOffset(x, y) }
for y := 0; y < dh; y++ {
y0 := b.Min.Y + y*sh/dh
y1 := b.Min.Y + (y+1)*sh/dh
if y1 <= y0 {
y1 = y0 + 1
}
for x := 0; x < dw; x++ {
x0 := b.Min.X + x*sw/dw
x1 := b.Min.X + (x+1)*sw/dw
if x1 <= x0 {
x1 = x0 + 1
}
var r, g, bl, a, n uint32
for yy := y0; yy < y1; yy++ {
for xx := x0; xx < x1; xx++ {
i := off(xx, yy)
al := uint32(s.Pix[i+3])
r += uint32(s.Pix[i]) * al / 0xff
g += uint32(s.Pix[i+1]) * al / 0xff
bl += uint32(s.Pix[i+2]) * al / 0xff
a += al
n++
}
}
if a == 0 {
continue
}
dst.SetRGBA(x, y, color.RGBA{uint8(r * 0xff / a), uint8(g * 0xff / a), uint8(bl * 0xff / a), uint8(a / n)})
}
}
}
func scaleGeneric(dst *image.RGBA, src image.Image, b image.Rectangle, sw, sh, dw, dh int) {
for y := 0; y < dh; y++ {
y0 := b.Min.Y + y*sh/dh
y1 := b.Min.Y + (y+1)*sh/dh
if y1 <= y0 {
y1 = y0 + 1
}
for x := 0; x < dw; x++ {
x0 := b.Min.X + x*sw/dw
x1 := b.Min.X + (x+1)*sw/dw
if x1 <= x0 {
x1 = x0 + 1
}
var r, g, bl, a, n uint32
for yy := y0; yy < y1; yy++ {
for xx := x0; xx < x1; xx++ {
cr, cg, cb, ca := src.At(xx, yy).RGBA()
r += cr >> 8
g += cg >> 8
bl += cb >> 8
a += ca >> 8
n++
}
}
dst.SetRGBA(x, y, color.RGBA{uint8(r / n), uint8(g / n), uint8(bl / n), uint8(a / n)})
}
}
}
func pngExceedsPreview(data []byte) bool {
const ihdr = 16
if len(data) < ihdr+8 || string(data[12:16]) != "IHDR" {
return false
}
w := int(data[16])<<24 | int(data[17])<<16 | int(data[18])<<8 | int(data[19])
h := int(data[20])<<24 | int(data[21])<<16 | int(data[22])<<8 | int(data[23])
return w > maxPreviewPixels || h > maxPreviewPixels
}