mirror of
https://github.com/pion/mediadevices.git
synced 2025-09-26 20:41:46 +08:00
195 lines
5.4 KiB
Go
195 lines
5.4 KiB
Go
package video
|
|
|
|
import (
|
|
"errors"
|
|
"image"
|
|
|
|
"golang.org/x/image/draw"
|
|
)
|
|
|
|
var (
|
|
scalerTestAlgos = map[string]Scaler{
|
|
"NearestNeighbor": ScalerNearestNeighbor,
|
|
}
|
|
scalerBenchAlgos = map[string]Scaler{
|
|
"NearestNeighbor": ScalerNearestNeighbor,
|
|
"ApproxBiLinear": ScalerApproxBiLinear,
|
|
"BiLinear": ScalerBiLinear,
|
|
}
|
|
)
|
|
|
|
// Scaler represents scaling algorithm
|
|
type Scaler draw.Scaler
|
|
|
|
// List of scaling algorithms
|
|
var (
|
|
ScalerNearestNeighbor = Scaler(draw.NearestNeighbor)
|
|
ScalerApproxBiLinear = Scaler(draw.ApproxBiLinear)
|
|
ScalerBiLinear = Scaler(draw.BiLinear)
|
|
ScalerCatmullRom = Scaler(draw.CatmullRom)
|
|
)
|
|
|
|
var errUnsupportedImageType = errors.New("scaling: unsupported image type")
|
|
|
|
// Scale returns video scaling transform.
|
|
// Setting scaler=nil to use default scaler. (ScalerNearestNeighbor)
|
|
// Negative width or height value will keep the aspect ratio of incoming image.
|
|
//
|
|
// Note: computation cost to scale YCbCr format is 10 times higher than RGB
|
|
// due to the implementation in x/image/draw package.
|
|
func Scale(width, height int, scaler Scaler) TransformFunc {
|
|
return func(r Reader) Reader {
|
|
scalerCached := ScalerNearestNeighbor
|
|
if scaler != nil {
|
|
scalerCached = scaler
|
|
}
|
|
|
|
// cacheScaler makes cached version of Scaler if available.
|
|
cacheScaler := func(dRect, sRect image.Rectangle) {}
|
|
if kernel, ok := scaler.(interface {
|
|
NewScaler(int, int, int, int) draw.Scaler
|
|
}); ok {
|
|
cacheScaler = func(dRect, sRect image.Rectangle) {
|
|
scalerCached = kernel.NewScaler(dRect.Dx(), dRect.Dy(), sRect.Dx(), sRect.Dy())
|
|
}
|
|
}
|
|
|
|
var rect image.Rectangle
|
|
var imgScaled image.Image
|
|
if width > 0 && height > 0 {
|
|
rect = image.Rect(0, 0, width, height)
|
|
} else if width <= 0 && height <= 0 {
|
|
panic("Both width and height are negative!")
|
|
}
|
|
|
|
// updateRect updates output image size
|
|
updateRect := func(r image.Rectangle) {}
|
|
if height <= 0 {
|
|
updateRect = func(r image.Rectangle) {
|
|
h := r.Dy() * width / r.Dx()
|
|
rect = image.Rect(0, 0, width, h)
|
|
}
|
|
} else if width <= 0 {
|
|
updateRect = func(r image.Rectangle) {
|
|
w := r.Dx() * height / r.Dy()
|
|
rect = image.Rect(0, 0, w, height)
|
|
}
|
|
}
|
|
|
|
// fixedRect returns Rectangle of chroma plane
|
|
fixedRect := func(rect image.Rectangle, sr image.YCbCrSubsampleRatio) image.Rectangle {
|
|
switch sr {
|
|
case image.YCbCrSubsampleRatio444:
|
|
case image.YCbCrSubsampleRatio422:
|
|
rect.Max.X /= 2
|
|
case image.YCbCrSubsampleRatio420:
|
|
rect.Max.X /= 2
|
|
rect.Max.Y /= 2
|
|
}
|
|
return rect
|
|
}
|
|
|
|
src := &rgbLikeYCbCr{y: &image.Gray{}, cb: &image.Gray{}, cr: &image.Gray{}}
|
|
dst := &rgbLikeYCbCr{y: &image.Gray{}, cb: &image.Gray{}, cr: &image.Gray{}}
|
|
|
|
// ycbcrRealloc reallocs image.YCbCr if needed
|
|
ycbcrRealloc := func(i1 *image.YCbCr) {
|
|
if imgScaled == nil || imgScaled.ColorModel() != i1.ColorModel() {
|
|
updateRect(i1.Rect)
|
|
cacheScaler(rect, i1.Rect)
|
|
imgScaled = image.NewYCbCr(rect, i1.SubsampleRatio)
|
|
}
|
|
imgDst := imgScaled.(*image.YCbCr)
|
|
if imgDst.Rect.Dx() != i1.Rect.Dx() || imgDst.Rect.Dy() != i1.Rect.Dy() {
|
|
updateRect(i1.Rect)
|
|
cacheScaler(rect, i1.Rect)
|
|
}
|
|
|
|
yDx := rect.Dx()
|
|
yDy := rect.Dy()
|
|
cRect := fixedRect(rect, i1.SubsampleRatio)
|
|
cDx := cRect.Dx()
|
|
cDy := cRect.Dx()
|
|
yLen := yDx * yDy
|
|
cLen := cDx * cDy
|
|
if len(imgDst.Y) < yLen {
|
|
if cap(imgDst.Y) < yLen {
|
|
imgDst.Y = make([]uint8, yLen)
|
|
}
|
|
imgDst.Y = imgDst.Y[:yLen]
|
|
}
|
|
if len(imgDst.Cr) < cLen {
|
|
if cap(imgDst.Cr) < cLen {
|
|
imgDst.Cr = make([]uint8, cLen)
|
|
}
|
|
imgDst.Cr = imgDst.Cr[:cLen]
|
|
}
|
|
if len(imgDst.Cb) < cLen {
|
|
if cap(imgDst.Cb) < cLen {
|
|
imgDst.Cb = make([]uint8, cLen)
|
|
}
|
|
imgDst.Cb = imgDst.Cb[:cLen]
|
|
}
|
|
*dst.y = image.Gray{Pix: imgDst.Y, Stride: imgDst.YStride, Rect: rect}
|
|
*dst.cb = image.Gray{Pix: imgDst.Cb, Stride: imgDst.CStride, Rect: cRect}
|
|
*dst.cr = image.Gray{Pix: imgDst.Cr, Stride: imgDst.CStride, Rect: cRect}
|
|
}
|
|
// ycbcrRealloc reallocs image.RGBA if needed
|
|
rgbaRealloc := func(i1 *image.RGBA) {
|
|
if imgScaled == nil || imgScaled.ColorModel() != i1.ColorModel() {
|
|
updateRect(i1.Rect)
|
|
cacheScaler(rect, i1.Rect)
|
|
imgScaled = image.NewRGBA(rect)
|
|
}
|
|
imgDst := imgScaled.(*image.RGBA)
|
|
if imgDst.Rect.Dx() != i1.Rect.Dx() || imgDst.Rect.Dy() != i1.Rect.Dy() {
|
|
updateRect(i1.Rect)
|
|
cacheScaler(rect, i1.Rect)
|
|
}
|
|
|
|
l := 4 * rect.Dx() * rect.Dy()
|
|
if len(imgDst.Pix) < l {
|
|
if cap(imgDst.Pix) < l {
|
|
imgDst.Pix = make([]uint8, l)
|
|
}
|
|
imgDst.Pix = imgDst.Pix[:l]
|
|
}
|
|
}
|
|
|
|
return ReaderFunc(func() (image.Image, func(), error) {
|
|
img, _, err := r.Read()
|
|
if err != nil {
|
|
return nil, func() {}, err
|
|
}
|
|
|
|
switch v := img.(type) {
|
|
case *image.RGBA:
|
|
rgbaRealloc(v)
|
|
dst := imgScaled.(*image.RGBA)
|
|
scalerCached.Scale(dst, rect, v, v.Rect, draw.Src, nil)
|
|
|
|
cloned := *dst // clone metadata
|
|
return &cloned, func() {}, nil
|
|
|
|
case *image.YCbCr:
|
|
ycbcrRealloc(v)
|
|
// Scale each plane
|
|
*src.y = image.Gray{Pix: v.Y, Stride: v.YStride, Rect: v.Rect}
|
|
*src.cb = image.Gray{
|
|
Pix: v.Cb, Stride: v.CStride, Rect: fixedRect(v.Rect, v.SubsampleRatio),
|
|
}
|
|
*src.cr = image.Gray{
|
|
Pix: v.Cr, Stride: v.CStride, Rect: fixedRect(v.Rect, v.SubsampleRatio),
|
|
}
|
|
scalerCached.Scale(dst, dst.Bounds(), src, src.Bounds(), draw.Src, nil)
|
|
|
|
cloned := *(imgScaled.(*image.YCbCr)) // clone metadata
|
|
return &cloned, func() {}, nil
|
|
|
|
default:
|
|
return nil, func() {}, errUnsupportedImageType
|
|
}
|
|
})
|
|
}
|
|
}
|