1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package image
6
7import (
8	"image/color"
9)
10
11// YCbCrSubsampleRatio is the chroma subsample ratio used in a YCbCr image.
12type YCbCrSubsampleRatio int
13
14const (
15	YCbCrSubsampleRatio444 YCbCrSubsampleRatio = iota
16	YCbCrSubsampleRatio422
17	YCbCrSubsampleRatio420
18	YCbCrSubsampleRatio440
19	YCbCrSubsampleRatio411
20	YCbCrSubsampleRatio410
21)
22
23func (s YCbCrSubsampleRatio) String() string {
24	switch s {
25	case YCbCrSubsampleRatio444:
26		return "YCbCrSubsampleRatio444"
27	case YCbCrSubsampleRatio422:
28		return "YCbCrSubsampleRatio422"
29	case YCbCrSubsampleRatio420:
30		return "YCbCrSubsampleRatio420"
31	case YCbCrSubsampleRatio440:
32		return "YCbCrSubsampleRatio440"
33	case YCbCrSubsampleRatio411:
34		return "YCbCrSubsampleRatio411"
35	case YCbCrSubsampleRatio410:
36		return "YCbCrSubsampleRatio410"
37	}
38	return "YCbCrSubsampleRatioUnknown"
39}
40
41// YCbCr is an in-memory image of Y'CbCr colors. There is one Y sample per
42// pixel, but each Cb and Cr sample can span one or more pixels.
43// YStride is the Y slice index delta between vertically adjacent pixels.
44// CStride is the Cb and Cr slice index delta between vertically adjacent pixels
45// that map to separate chroma samples.
46// It is not an absolute requirement, but YStride and len(Y) are typically
47// multiples of 8, and:
48//
49//	For 4:4:4, CStride == YStride/1 && len(Cb) == len(Cr) == len(Y)/1.
50//	For 4:2:2, CStride == YStride/2 && len(Cb) == len(Cr) == len(Y)/2.
51//	For 4:2:0, CStride == YStride/2 && len(Cb) == len(Cr) == len(Y)/4.
52//	For 4:4:0, CStride == YStride/1 && len(Cb) == len(Cr) == len(Y)/2.
53//	For 4:1:1, CStride == YStride/4 && len(Cb) == len(Cr) == len(Y)/4.
54//	For 4:1:0, CStride == YStride/4 && len(Cb) == len(Cr) == len(Y)/8.
55type YCbCr struct {
56	Y, Cb, Cr      []uint8
57	YStride        int
58	CStride        int
59	SubsampleRatio YCbCrSubsampleRatio
60	Rect           Rectangle
61}
62
63func (p *YCbCr) ColorModel() color.Model {
64	return color.YCbCrModel
65}
66
67func (p *YCbCr) Bounds() Rectangle {
68	return p.Rect
69}
70
71func (p *YCbCr) At(x, y int) color.Color {
72	return p.YCbCrAt(x, y)
73}
74
75func (p *YCbCr) RGBA64At(x, y int) color.RGBA64 {
76	r, g, b, a := p.YCbCrAt(x, y).RGBA()
77	return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
78}
79
80func (p *YCbCr) YCbCrAt(x, y int) color.YCbCr {
81	if !(Point{x, y}.In(p.Rect)) {
82		return color.YCbCr{}
83	}
84	yi := p.YOffset(x, y)
85	ci := p.COffset(x, y)
86	return color.YCbCr{
87		p.Y[yi],
88		p.Cb[ci],
89		p.Cr[ci],
90	}
91}
92
93// YOffset returns the index of the first element of Y that corresponds to
94// the pixel at (x, y).
95func (p *YCbCr) YOffset(x, y int) int {
96	return (y-p.Rect.Min.Y)*p.YStride + (x - p.Rect.Min.X)
97}
98
99// COffset returns the index of the first element of Cb or Cr that corresponds
100// to the pixel at (x, y).
101func (p *YCbCr) COffset(x, y int) int {
102	switch p.SubsampleRatio {
103	case YCbCrSubsampleRatio422:
104		return (y-p.Rect.Min.Y)*p.CStride + (x/2 - p.Rect.Min.X/2)
105	case YCbCrSubsampleRatio420:
106		return (y/2-p.Rect.Min.Y/2)*p.CStride + (x/2 - p.Rect.Min.X/2)
107	case YCbCrSubsampleRatio440:
108		return (y/2-p.Rect.Min.Y/2)*p.CStride + (x - p.Rect.Min.X)
109	case YCbCrSubsampleRatio411:
110		return (y-p.Rect.Min.Y)*p.CStride + (x/4 - p.Rect.Min.X/4)
111	case YCbCrSubsampleRatio410:
112		return (y/2-p.Rect.Min.Y/2)*p.CStride + (x/4 - p.Rect.Min.X/4)
113	}
114	// Default to 4:4:4 subsampling.
115	return (y-p.Rect.Min.Y)*p.CStride + (x - p.Rect.Min.X)
116}
117
118// SubImage returns an image representing the portion of the image p visible
119// through r. The returned value shares pixels with the original image.
120func (p *YCbCr) SubImage(r Rectangle) Image {
121	r = r.Intersect(p.Rect)
122	// If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
123	// either r1 or r2 if the intersection is empty. Without explicitly checking for
124	// this, the Pix[i:] expression below can panic.
125	if r.Empty() {
126		return &YCbCr{
127			SubsampleRatio: p.SubsampleRatio,
128		}
129	}
130	yi := p.YOffset(r.Min.X, r.Min.Y)
131	ci := p.COffset(r.Min.X, r.Min.Y)
132	return &YCbCr{
133		Y:              p.Y[yi:],
134		Cb:             p.Cb[ci:],
135		Cr:             p.Cr[ci:],
136		SubsampleRatio: p.SubsampleRatio,
137		YStride:        p.YStride,
138		CStride:        p.CStride,
139		Rect:           r,
140	}
141}
142
143func (p *YCbCr) Opaque() bool {
144	return true
145}
146
147func yCbCrSize(r Rectangle, subsampleRatio YCbCrSubsampleRatio) (w, h, cw, ch int) {
148	w, h = r.Dx(), r.Dy()
149	switch subsampleRatio {
150	case YCbCrSubsampleRatio422:
151		cw = (r.Max.X+1)/2 - r.Min.X/2
152		ch = h
153	case YCbCrSubsampleRatio420:
154		cw = (r.Max.X+1)/2 - r.Min.X/2
155		ch = (r.Max.Y+1)/2 - r.Min.Y/2
156	case YCbCrSubsampleRatio440:
157		cw = w
158		ch = (r.Max.Y+1)/2 - r.Min.Y/2
159	case YCbCrSubsampleRatio411:
160		cw = (r.Max.X+3)/4 - r.Min.X/4
161		ch = h
162	case YCbCrSubsampleRatio410:
163		cw = (r.Max.X+3)/4 - r.Min.X/4
164		ch = (r.Max.Y+1)/2 - r.Min.Y/2
165	default:
166		// Default to 4:4:4 subsampling.
167		cw = w
168		ch = h
169	}
170	return
171}
172
173// NewYCbCr returns a new YCbCr image with the given bounds and subsample
174// ratio.
175func NewYCbCr(r Rectangle, subsampleRatio YCbCrSubsampleRatio) *YCbCr {
176	w, h, cw, ch := yCbCrSize(r, subsampleRatio)
177
178	// totalLength should be the same as i2, below, for a valid Rectangle r.
179	totalLength := add2NonNeg(
180		mul3NonNeg(1, w, h),
181		mul3NonNeg(2, cw, ch),
182	)
183	if totalLength < 0 {
184		panic("image: NewYCbCr Rectangle has huge or negative dimensions")
185	}
186
187	i0 := w*h + 0*cw*ch
188	i1 := w*h + 1*cw*ch
189	i2 := w*h + 2*cw*ch
190	b := make([]byte, i2)
191	return &YCbCr{
192		Y:              b[:i0:i0],
193		Cb:             b[i0:i1:i1],
194		Cr:             b[i1:i2:i2],
195		SubsampleRatio: subsampleRatio,
196		YStride:        w,
197		CStride:        cw,
198		Rect:           r,
199	}
200}
201
202// NYCbCrA is an in-memory image of non-alpha-premultiplied Y'CbCr-with-alpha
203// colors. A and AStride are analogous to the Y and YStride fields of the
204// embedded YCbCr.
205type NYCbCrA struct {
206	YCbCr
207	A       []uint8
208	AStride int
209}
210
211func (p *NYCbCrA) ColorModel() color.Model {
212	return color.NYCbCrAModel
213}
214
215func (p *NYCbCrA) At(x, y int) color.Color {
216	return p.NYCbCrAAt(x, y)
217}
218
219func (p *NYCbCrA) RGBA64At(x, y int) color.RGBA64 {
220	r, g, b, a := p.NYCbCrAAt(x, y).RGBA()
221	return color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
222}
223
224func (p *NYCbCrA) NYCbCrAAt(x, y int) color.NYCbCrA {
225	if !(Point{X: x, Y: y}.In(p.Rect)) {
226		return color.NYCbCrA{}
227	}
228	yi := p.YOffset(x, y)
229	ci := p.COffset(x, y)
230	ai := p.AOffset(x, y)
231	return color.NYCbCrA{
232		color.YCbCr{
233			Y:  p.Y[yi],
234			Cb: p.Cb[ci],
235			Cr: p.Cr[ci],
236		},
237		p.A[ai],
238	}
239}
240
241// AOffset returns the index of the first element of A that corresponds to the
242// pixel at (x, y).
243func (p *NYCbCrA) AOffset(x, y int) int {
244	return (y-p.Rect.Min.Y)*p.AStride + (x - p.Rect.Min.X)
245}
246
247// SubImage returns an image representing the portion of the image p visible
248// through r. The returned value shares pixels with the original image.
249func (p *NYCbCrA) SubImage(r Rectangle) Image {
250	r = r.Intersect(p.Rect)
251	// If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside
252	// either r1 or r2 if the intersection is empty. Without explicitly checking for
253	// this, the Pix[i:] expression below can panic.
254	if r.Empty() {
255		return &NYCbCrA{
256			YCbCr: YCbCr{
257				SubsampleRatio: p.SubsampleRatio,
258			},
259		}
260	}
261	yi := p.YOffset(r.Min.X, r.Min.Y)
262	ci := p.COffset(r.Min.X, r.Min.Y)
263	ai := p.AOffset(r.Min.X, r.Min.Y)
264	return &NYCbCrA{
265		YCbCr: YCbCr{
266			Y:              p.Y[yi:],
267			Cb:             p.Cb[ci:],
268			Cr:             p.Cr[ci:],
269			SubsampleRatio: p.SubsampleRatio,
270			YStride:        p.YStride,
271			CStride:        p.CStride,
272			Rect:           r,
273		},
274		A:       p.A[ai:],
275		AStride: p.AStride,
276	}
277}
278
279// Opaque scans the entire image and reports whether it is fully opaque.
280func (p *NYCbCrA) Opaque() bool {
281	if p.Rect.Empty() {
282		return true
283	}
284	i0, i1 := 0, p.Rect.Dx()
285	for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ {
286		for _, a := range p.A[i0:i1] {
287			if a != 0xff {
288				return false
289			}
290		}
291		i0 += p.AStride
292		i1 += p.AStride
293	}
294	return true
295}
296
297// NewNYCbCrA returns a new [NYCbCrA] image with the given bounds and subsample
298// ratio.
299func NewNYCbCrA(r Rectangle, subsampleRatio YCbCrSubsampleRatio) *NYCbCrA {
300	w, h, cw, ch := yCbCrSize(r, subsampleRatio)
301
302	// totalLength should be the same as i3, below, for a valid Rectangle r.
303	totalLength := add2NonNeg(
304		mul3NonNeg(2, w, h),
305		mul3NonNeg(2, cw, ch),
306	)
307	if totalLength < 0 {
308		panic("image: NewNYCbCrA Rectangle has huge or negative dimension")
309	}
310
311	i0 := 1*w*h + 0*cw*ch
312	i1 := 1*w*h + 1*cw*ch
313	i2 := 1*w*h + 2*cw*ch
314	i3 := 2*w*h + 2*cw*ch
315	b := make([]byte, i3)
316	return &NYCbCrA{
317		YCbCr: YCbCr{
318			Y:              b[:i0:i0],
319			Cb:             b[i0:i1:i1],
320			Cr:             b[i1:i2:i2],
321			SubsampleRatio: subsampleRatio,
322			YStride:        w,
323			CStride:        cw,
324			Rect:           r,
325		},
326		A:       b[i2:],
327		AStride: w,
328	}
329}
330