319 lines
8.4 KiB
Go
319 lines
8.4 KiB
Go
// Copyright 2011 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package image
|
|
|
|
import (
|
|
"image/color"
|
|
)
|
|
|
|
// YCbCrSubsampleRatio is the chroma subsample ratio used in a YCbCr image.
|
|
type YCbCrSubsampleRatio int
|
|
|
|
const (
|
|
YCbCrSubsampleRatio444 YCbCrSubsampleRatio = iota
|
|
YCbCrSubsampleRatio422
|
|
YCbCrSubsampleRatio420
|
|
YCbCrSubsampleRatio440
|
|
YCbCrSubsampleRatio411
|
|
YCbCrSubsampleRatio410
|
|
)
|
|
|
|
func (s YCbCrSubsampleRatio) String() string {
|
|
switch s {
|
|
case YCbCrSubsampleRatio444:
|
|
return "YCbCrSubsampleRatio444"
|
|
case YCbCrSubsampleRatio422:
|
|
return "YCbCrSubsampleRatio422"
|
|
case YCbCrSubsampleRatio420:
|
|
return "YCbCrSubsampleRatio420"
|
|
case YCbCrSubsampleRatio440:
|
|
return "YCbCrSubsampleRatio440"
|
|
case YCbCrSubsampleRatio411:
|
|
return "YCbCrSubsampleRatio411"
|
|
case YCbCrSubsampleRatio410:
|
|
return "YCbCrSubsampleRatio410"
|
|
}
|
|
return "YCbCrSubsampleRatioUnknown"
|
|
}
|
|
|
|
// YCbCr is an in-memory image of Y'CbCr colors. There is one Y sample per
|
|
// pixel, but each Cb and Cr sample can span one or more pixels.
|
|
// YStride is the Y slice index delta between vertically adjacent pixels.
|
|
// CStride is the Cb and Cr slice index delta between vertically adjacent pixels
|
|
// that map to separate chroma samples.
|
|
// It is not an absolute requirement, but YStride and len(Y) are typically
|
|
// multiples of 8, and:
|
|
// For 4:4:4, CStride == YStride/1 && len(Cb) == len(Cr) == len(Y)/1.
|
|
// For 4:2:2, CStride == YStride/2 && len(Cb) == len(Cr) == len(Y)/2.
|
|
// For 4:2:0, CStride == YStride/2 && len(Cb) == len(Cr) == len(Y)/4.
|
|
// For 4:4:0, CStride == YStride/1 && len(Cb) == len(Cr) == len(Y)/2.
|
|
// For 4:1:1, CStride == YStride/4 && len(Cb) == len(Cr) == len(Y)/4.
|
|
// For 4:1:0, CStride == YStride/4 && len(Cb) == len(Cr) == len(Y)/8.
|
|
type YCbCr struct {
|
|
Y, Cb, Cr []uint8
|
|
YStride int
|
|
CStride int
|
|
SubsampleRatio YCbCrSubsampleRatio
|
|
Rect Rectangle
|
|
}
|
|
|
|
func (p *YCbCr) ColorModel() color.Model {
|
|
return color.YCbCrModel
|
|
}
|
|
|
|
func (p *YCbCr) Bounds() Rectangle {
|
|
return p.Rect
|
|
}
|
|
|
|
func (p *YCbCr) At(x, y int) color.Color {
|
|
return p.YCbCrAt(x, y)
|
|
}
|
|
|
|
func (p *YCbCr) YCbCrAt(x, y int) color.YCbCr {
|
|
if !(Point{x, y}.In(p.Rect)) {
|
|
return color.YCbCr{}
|
|
}
|
|
yi := p.YOffset(x, y)
|
|
ci := p.COffset(x, y)
|
|
return color.YCbCr{
|
|
p.Y[yi],
|
|
p.Cb[ci],
|
|
p.Cr[ci],
|
|
}
|
|
}
|
|
|
|
// YOffset returns the index of the first element of Y that corresponds to
|
|
// the pixel at (x, y).
|
|
func (p *YCbCr) YOffset(x, y int) int {
|
|
return (y-p.Rect.Min.Y)*p.YStride + (x - p.Rect.Min.X)
|
|
}
|
|
|
|
// COffset returns the index of the first element of Cb or Cr that corresponds
|
|
// to the pixel at (x, y).
|
|
func (p *YCbCr) COffset(x, y int) int {
|
|
switch p.SubsampleRatio {
|
|
case YCbCrSubsampleRatio422:
|
|
return (y-p.Rect.Min.Y)*p.CStride + (x/2 - p.Rect.Min.X/2)
|
|
case YCbCrSubsampleRatio420:
|
|
return (y/2-p.Rect.Min.Y/2)*p.CStride + (x/2 - p.Rect.Min.X/2)
|
|
case YCbCrSubsampleRatio440:
|
|
return (y/2-p.Rect.Min.Y/2)*p.CStride + (x - p.Rect.Min.X)
|
|
case YCbCrSubsampleRatio411:
|
|
return (y-p.Rect.Min.Y)*p.CStride + (x/4 - p.Rect.Min.X/4)
|
|
case YCbCrSubsampleRatio410:
|
|
return (y/2-p.Rect.Min.Y/2)*p.CStride + (x/4 - p.Rect.Min.X/4)
|
|
}
|
|
// Default to 4:4:4 subsampling.
|
|
return (y-p.Rect.Min.Y)*p.CStride + (x - p.Rect.Min.X)
|
|
}
|
|
|
|
// SubImage returns an image representing the portion of the image p visible
|
|
// through r. The returned value shares pixels with the original image.
|
|
func (p *YCbCr) SubImage(r Rectangle) Image {
|
|
r = r.Intersect(p.Rect)
|
|
// If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
|
|
// either r1 or r2 if the intersection is empty. Without explicitly checking for
|
|
// this, the Pix[i:] expression below can panic.
|
|
if r.Empty() {
|
|
return &YCbCr{
|
|
SubsampleRatio: p.SubsampleRatio,
|
|
}
|
|
}
|
|
yi := p.YOffset(r.Min.X, r.Min.Y)
|
|
ci := p.COffset(r.Min.X, r.Min.Y)
|
|
return &YCbCr{
|
|
Y: p.Y[yi:],
|
|
Cb: p.Cb[ci:],
|
|
Cr: p.Cr[ci:],
|
|
SubsampleRatio: p.SubsampleRatio,
|
|
YStride: p.YStride,
|
|
CStride: p.CStride,
|
|
Rect: r,
|
|
}
|
|
}
|
|
|
|
func (p *YCbCr) Opaque() bool {
|
|
return true
|
|
}
|
|
|
|
func yCbCrSize(r Rectangle, subsampleRatio YCbCrSubsampleRatio) (w, h, cw, ch int) {
|
|
w, h = r.Dx(), r.Dy()
|
|
switch subsampleRatio {
|
|
case YCbCrSubsampleRatio422:
|
|
cw = (r.Max.X+1)/2 - r.Min.X/2
|
|
ch = h
|
|
case YCbCrSubsampleRatio420:
|
|
cw = (r.Max.X+1)/2 - r.Min.X/2
|
|
ch = (r.Max.Y+1)/2 - r.Min.Y/2
|
|
case YCbCrSubsampleRatio440:
|
|
cw = w
|
|
ch = (r.Max.Y+1)/2 - r.Min.Y/2
|
|
case YCbCrSubsampleRatio411:
|
|
cw = (r.Max.X+3)/4 - r.Min.X/4
|
|
ch = h
|
|
case YCbCrSubsampleRatio410:
|
|
cw = (r.Max.X+3)/4 - r.Min.X/4
|
|
ch = (r.Max.Y+1)/2 - r.Min.Y/2
|
|
default:
|
|
// Default to 4:4:4 subsampling.
|
|
cw = w
|
|
ch = h
|
|
}
|
|
return
|
|
}
|
|
|
|
// NewYCbCr returns a new YCbCr image with the given bounds and subsample
|
|
// ratio.
|
|
func NewYCbCr(r Rectangle, subsampleRatio YCbCrSubsampleRatio) *YCbCr {
|
|
w, h, cw, ch := yCbCrSize(r, subsampleRatio)
|
|
|
|
// totalLength should be the same as i2, below, for a valid Rectangle r.
|
|
totalLength := add2NonNeg(
|
|
mul3NonNeg(1, w, h),
|
|
mul3NonNeg(2, cw, ch),
|
|
)
|
|
if totalLength < 0 {
|
|
panic("image: NewYCbCr Rectangle has huge or negative dimensions")
|
|
}
|
|
|
|
i0 := w*h + 0*cw*ch
|
|
i1 := w*h + 1*cw*ch
|
|
i2 := w*h + 2*cw*ch
|
|
b := make([]byte, i2)
|
|
return &YCbCr{
|
|
Y: b[:i0:i0],
|
|
Cb: b[i0:i1:i1],
|
|
Cr: b[i1:i2:i2],
|
|
SubsampleRatio: subsampleRatio,
|
|
YStride: w,
|
|
CStride: cw,
|
|
Rect: r,
|
|
}
|
|
}
|
|
|
|
// NYCbCrA is an in-memory image of non-alpha-premultiplied Y'CbCr-with-alpha
|
|
// colors. A and AStride are analogous to the Y and YStride fields of the
|
|
// embedded YCbCr.
|
|
type NYCbCrA struct {
|
|
YCbCr
|
|
A []uint8
|
|
AStride int
|
|
}
|
|
|
|
func (p *NYCbCrA) ColorModel() color.Model {
|
|
return color.NYCbCrAModel
|
|
}
|
|
|
|
func (p *NYCbCrA) At(x, y int) color.Color {
|
|
return p.NYCbCrAAt(x, y)
|
|
}
|
|
|
|
func (p *NYCbCrA) NYCbCrAAt(x, y int) color.NYCbCrA {
|
|
if !(Point{X: x, Y: y}.In(p.Rect)) {
|
|
return color.NYCbCrA{}
|
|
}
|
|
yi := p.YOffset(x, y)
|
|
ci := p.COffset(x, y)
|
|
ai := p.AOffset(x, y)
|
|
return color.NYCbCrA{
|
|
color.YCbCr{
|
|
Y: p.Y[yi],
|
|
Cb: p.Cb[ci],
|
|
Cr: p.Cr[ci],
|
|
},
|
|
p.A[ai],
|
|
}
|
|
}
|
|
|
|
// AOffset returns the index of the first element of A that corresponds to the
|
|
// pixel at (x, y).
|
|
func (p *NYCbCrA) AOffset(x, y int) int {
|
|
return (y-p.Rect.Min.Y)*p.AStride + (x - p.Rect.Min.X)
|
|
}
|
|
|
|
// SubImage returns an image representing the portion of the image p visible
|
|
// through r. The returned value shares pixels with the original image.
|
|
func (p *NYCbCrA) SubImage(r Rectangle) Image {
|
|
r = r.Intersect(p.Rect)
|
|
// If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
|
|
// either r1 or r2 if the intersection is empty. Without explicitly checking for
|
|
// this, the Pix[i:] expression below can panic.
|
|
if r.Empty() {
|
|
return &NYCbCrA{
|
|
YCbCr: YCbCr{
|
|
SubsampleRatio: p.SubsampleRatio,
|
|
},
|
|
}
|
|
}
|
|
yi := p.YOffset(r.Min.X, r.Min.Y)
|
|
ci := p.COffset(r.Min.X, r.Min.Y)
|
|
ai := p.AOffset(r.Min.X, r.Min.Y)
|
|
return &NYCbCrA{
|
|
YCbCr: YCbCr{
|
|
Y: p.Y[yi:],
|
|
Cb: p.Cb[ci:],
|
|
Cr: p.Cr[ci:],
|
|
SubsampleRatio: p.SubsampleRatio,
|
|
YStride: p.YStride,
|
|
CStride: p.CStride,
|
|
Rect: r,
|
|
},
|
|
A: p.A[ai:],
|
|
AStride: p.AStride,
|
|
}
|
|
}
|
|
|
|
// Opaque scans the entire image and reports whether it is fully opaque.
|
|
func (p *NYCbCrA) Opaque() bool {
|
|
if p.Rect.Empty() {
|
|
return true
|
|
}
|
|
i0, i1 := 0, p.Rect.Dx()
|
|
for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ {
|
|
for _, a := range p.A[i0:i1] {
|
|
if a != 0xff {
|
|
return false
|
|
}
|
|
}
|
|
i0 += p.AStride
|
|
i1 += p.AStride
|
|
}
|
|
return true
|
|
}
|
|
|
|
// NewNYCbCrA returns a new NYCbCrA image with the given bounds and subsample
|
|
// ratio.
|
|
func NewNYCbCrA(r Rectangle, subsampleRatio YCbCrSubsampleRatio) *NYCbCrA {
|
|
w, h, cw, ch := yCbCrSize(r, subsampleRatio)
|
|
|
|
// totalLength should be the same as i3, below, for a valid Rectangle r.
|
|
totalLength := add2NonNeg(
|
|
mul3NonNeg(2, w, h),
|
|
mul3NonNeg(2, cw, ch),
|
|
)
|
|
if totalLength < 0 {
|
|
panic("image: NewNYCbCrA Rectangle has huge or negative dimension")
|
|
}
|
|
|
|
i0 := 1*w*h + 0*cw*ch
|
|
i1 := 1*w*h + 1*cw*ch
|
|
i2 := 1*w*h + 2*cw*ch
|
|
i3 := 2*w*h + 2*cw*ch
|
|
b := make([]byte, i3)
|
|
return &NYCbCrA{
|
|
YCbCr: YCbCr{
|
|
Y: b[:i0:i0],
|
|
Cb: b[i0:i1:i1],
|
|
Cr: b[i1:i2:i2],
|
|
SubsampleRatio: subsampleRatio,
|
|
YStride: w,
|
|
CStride: cw,
|
|
Rect: r,
|
|
},
|
|
A: b[i2:],
|
|
AStride: w,
|
|
}
|
|
}
|