1/*
2Copyright (c) 2014, Charlie Vieth <charlie.vieth@gmail.com>
3
4Permission to use, copy, modify, and/or distribute this software for any purpose
5with or without fee is hereby granted, provided that the above copyright notice
6and this permission notice appear in all copies.
7
8THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
10FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
12OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
13TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
14THIS SOFTWARE.
15*/
16
17package resize
18
19import (
20	"image"
21	"image/color"
22)
23
24// ycc is an in memory YCbCr image.  The Y, Cb and Cr samples are held in a
25// single slice to increase resizing performance.
26type ycc struct {
27	// Pix holds the image's pixels, in Y, Cb, Cr order. The pixel at
28	// (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3].
29	Pix []uint8
30	// Stride is the Pix stride (in bytes) between vertically adjacent pixels.
31	Stride int
32	// Rect is the image's bounds.
33	Rect image.Rectangle
34	// SubsampleRatio is the subsample ratio of the original YCbCr image.
35	SubsampleRatio image.YCbCrSubsampleRatio
36}
37
38// PixOffset returns the index of the first element of Pix that corresponds to
39// the pixel at (x, y).
40func (p *ycc) PixOffset(x, y int) int {
41	return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*3
42}
43
44func (p *ycc) Bounds() image.Rectangle {
45	return p.Rect
46}
47
48func (p *ycc) ColorModel() color.Model {
49	return color.YCbCrModel
50}
51
52func (p *ycc) At(x, y int) color.Color {
53	if !(image.Point{x, y}.In(p.Rect)) {
54		return color.YCbCr{}
55	}
56	i := p.PixOffset(x, y)
57	return color.YCbCr{
58		p.Pix[i+0],
59		p.Pix[i+1],
60		p.Pix[i+2],
61	}
62}
63
64func (p *ycc) Opaque() bool {
65	return true
66}
67
68// SubImage returns an image representing the portion of the image p visible
69// through r. The returned value shares pixels with the original image.
70func (p *ycc) SubImage(r image.Rectangle) image.Image {
71	r = r.Intersect(p.Rect)
72	if r.Empty() {
73		return &ycc{SubsampleRatio: p.SubsampleRatio}
74	}
75	i := p.PixOffset(r.Min.X, r.Min.Y)
76	return &ycc{
77		Pix:            p.Pix[i:],
78		Stride:         p.Stride,
79		Rect:           r,
80		SubsampleRatio: p.SubsampleRatio,
81	}
82}
83
84// newYCC returns a new ycc with the given bounds and subsample ratio.
85func newYCC(r image.Rectangle, s image.YCbCrSubsampleRatio) *ycc {
86	w, h := r.Dx(), r.Dy()
87	buf := make([]uint8, 3*w*h)
88	return &ycc{Pix: buf, Stride: 3 * w, Rect: r, SubsampleRatio: s}
89}
90
91// YCbCr converts ycc to a YCbCr image with the same subsample ratio
92// as the YCbCr image that ycc was generated from.
93func (p *ycc) YCbCr() *image.YCbCr {
94	ycbcr := image.NewYCbCr(p.Rect, p.SubsampleRatio)
95	var off int
96
97	switch ycbcr.SubsampleRatio {
98	case image.YCbCrSubsampleRatio422:
99		for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
100			yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
101			cy := (y - ycbcr.Rect.Min.Y) * ycbcr.CStride
102			for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
103				xx := (x - ycbcr.Rect.Min.X)
104				yi := yy + xx
105				ci := cy + xx/2
106				ycbcr.Y[yi] = p.Pix[off+0]
107				ycbcr.Cb[ci] = p.Pix[off+1]
108				ycbcr.Cr[ci] = p.Pix[off+2]
109				off += 3
110			}
111		}
112	case image.YCbCrSubsampleRatio420:
113		for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
114			yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
115			cy := (y/2 - ycbcr.Rect.Min.Y/2) * ycbcr.CStride
116			for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
117				xx := (x - ycbcr.Rect.Min.X)
118				yi := yy + xx
119				ci := cy + xx/2
120				ycbcr.Y[yi] = p.Pix[off+0]
121				ycbcr.Cb[ci] = p.Pix[off+1]
122				ycbcr.Cr[ci] = p.Pix[off+2]
123				off += 3
124			}
125		}
126	case image.YCbCrSubsampleRatio440:
127		for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
128			yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
129			cy := (y/2 - ycbcr.Rect.Min.Y/2) * ycbcr.CStride
130			for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
131				xx := (x - ycbcr.Rect.Min.X)
132				yi := yy + xx
133				ci := cy + xx
134				ycbcr.Y[yi] = p.Pix[off+0]
135				ycbcr.Cb[ci] = p.Pix[off+1]
136				ycbcr.Cr[ci] = p.Pix[off+2]
137				off += 3
138			}
139		}
140	default:
141		// Default to 4:4:4 subsampling.
142		for y := ycbcr.Rect.Min.Y; y < ycbcr.Rect.Max.Y; y++ {
143			yy := (y - ycbcr.Rect.Min.Y) * ycbcr.YStride
144			cy := (y - ycbcr.Rect.Min.Y) * ycbcr.CStride
145			for x := ycbcr.Rect.Min.X; x < ycbcr.Rect.Max.X; x++ {
146				xx := (x - ycbcr.Rect.Min.X)
147				yi := yy + xx
148				ci := cy + xx
149				ycbcr.Y[yi] = p.Pix[off+0]
150				ycbcr.Cb[ci] = p.Pix[off+1]
151				ycbcr.Cr[ci] = p.Pix[off+2]
152				off += 3
153			}
154		}
155	}
156	return ycbcr
157}
158
159// imageYCbCrToYCC converts a YCbCr image to a ycc image for resizing.
160func imageYCbCrToYCC(in *image.YCbCr) *ycc {
161	w, h := in.Rect.Dx(), in.Rect.Dy()
162	r := image.Rect(0, 0, w, h)
163	buf := make([]uint8, 3*w*h)
164	p := ycc{Pix: buf, Stride: 3 * w, Rect: r, SubsampleRatio: in.SubsampleRatio}
165	var off int
166
167	switch in.SubsampleRatio {
168	case image.YCbCrSubsampleRatio422:
169		for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
170			yy := (y - in.Rect.Min.Y) * in.YStride
171			cy := (y - in.Rect.Min.Y) * in.CStride
172			for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
173				xx := (x - in.Rect.Min.X)
174				yi := yy + xx
175				ci := cy + xx/2
176				p.Pix[off+0] = in.Y[yi]
177				p.Pix[off+1] = in.Cb[ci]
178				p.Pix[off+2] = in.Cr[ci]
179				off += 3
180			}
181		}
182	case image.YCbCrSubsampleRatio420:
183		for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
184			yy := (y - in.Rect.Min.Y) * in.YStride
185			cy := (y/2 - in.Rect.Min.Y/2) * in.CStride
186			for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
187				xx := (x - in.Rect.Min.X)
188				yi := yy + xx
189				ci := cy + xx/2
190				p.Pix[off+0] = in.Y[yi]
191				p.Pix[off+1] = in.Cb[ci]
192				p.Pix[off+2] = in.Cr[ci]
193				off += 3
194			}
195		}
196	case image.YCbCrSubsampleRatio440:
197		for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
198			yy := (y - in.Rect.Min.Y) * in.YStride
199			cy := (y/2 - in.Rect.Min.Y/2) * in.CStride
200			for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
201				xx := (x - in.Rect.Min.X)
202				yi := yy + xx
203				ci := cy + xx
204				p.Pix[off+0] = in.Y[yi]
205				p.Pix[off+1] = in.Cb[ci]
206				p.Pix[off+2] = in.Cr[ci]
207				off += 3
208			}
209		}
210	default:
211		// Default to 4:4:4 subsampling.
212		for y := in.Rect.Min.Y; y < in.Rect.Max.Y; y++ {
213			yy := (y - in.Rect.Min.Y) * in.YStride
214			cy := (y - in.Rect.Min.Y) * in.CStride
215			for x := in.Rect.Min.X; x < in.Rect.Max.X; x++ {
216				xx := (x - in.Rect.Min.X)
217				yi := yy + xx
218				ci := cy + xx
219				p.Pix[off+0] = in.Y[yi]
220				p.Pix[off+1] = in.Cb[ci]
221				p.Pix[off+2] = in.Cr[ci]
222				off += 3
223			}
224		}
225	}
226	return &p
227}
228