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// Copy of image.YCbCrSubsampleRatio constants - this allows us to support
92// older versions of Go where these constants are not defined (i.e. Go 1.4)
93const (
94 ycbcrSubsampleRatio444 image.YCbCrSubsampleRatio = iota
95 ycbcrSubsampleRatio422
96 ycbcrSubsampleRatio420
97 ycbcrSubsampleRatio440
98 ycbcrSubsampleRatio411
99 ycbcrSubsampleRatio410
100)
101
102// YCbCr converts ycc to a YCbCr image with the same subsample ratio
103// as the YCbCr image that ycc was generated from.
104func (p *ycc) YCbCr() *image.YCbCr {
105 ycbcr := image.NewYCbCr(p.Rect, p.SubsampleRatio)
106 switch ycbcr.SubsampleRatio {
107 case ycbcrSubsampleRatio422:
108 return p.ycbcr422(ycbcr)
109 case ycbcrSubsampleRatio420:
110 return p.ycbcr420(ycbcr)
111 case ycbcrSubsampleRatio440:
112 return p.ycbcr440(ycbcr)
113 case ycbcrSubsampleRatio444:
114 return p.ycbcr444(ycbcr)
115 case ycbcrSubsampleRatio411:
116 return p.ycbcr411(ycbcr)
117 case ycbcrSubsampleRatio410:
118 return p.ycbcr410(ycbcr)
119 }
120 return ycbcr
121}
122
123// imageYCbCrToYCC converts a YCbCr image to a ycc image for resizing.
124func imageYCbCrToYCC(in *image.YCbCr) *ycc {
125 w, h := in.Rect.Dx(), in.Rect.Dy()
126 p := ycc{
127 Pix: make([]uint8, 3*w*h),
128 Stride: 3 * w,
129 Rect: image.Rect(0, 0, w, h),
130 SubsampleRatio: in.SubsampleRatio,
131 }
132 switch in.SubsampleRatio {
133 case ycbcrSubsampleRatio422:
134 return convertToYCC422(in, &p)
135 case ycbcrSubsampleRatio420:
136 return convertToYCC420(in, &p)
137 case ycbcrSubsampleRatio440:
138 return convertToYCC440(in, &p)
139 case ycbcrSubsampleRatio444:
140 return convertToYCC444(in, &p)
141 case ycbcrSubsampleRatio411:
142 return convertToYCC411(in, &p)
143 case ycbcrSubsampleRatio410:
144 return convertToYCC410(in, &p)
145 }
146 return &p
147}
148
149func (p *ycc) ycbcr422(ycbcr *image.YCbCr) *image.YCbCr {
150 var off int
151 Pix := p.Pix
152 Y := ycbcr.Y
153 Cb := ycbcr.Cb
154 Cr := ycbcr.Cr
155 for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ {
156 yy := y * ycbcr.YStride
157 cy := y * ycbcr.CStride
158 for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ {
159 ci := cy + x/2
160 Y[yy+x] = Pix[off+0]
161 Cb[ci] = Pix[off+1]
162 Cr[ci] = Pix[off+2]
163 off += 3
164 }
165 }
166 return ycbcr
167}
168
169func (p *ycc) ycbcr420(ycbcr *image.YCbCr) *image.YCbCr {
170 var off int
171 Pix := p.Pix
172 Y := ycbcr.Y
173 Cb := ycbcr.Cb
174 Cr := ycbcr.Cr
175 for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ {
176 yy := y * ycbcr.YStride
177 cy := (y / 2) * ycbcr.CStride
178 for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ {
179 ci := cy + x/2
180 Y[yy+x] = Pix[off+0]
181 Cb[ci] = Pix[off+1]
182 Cr[ci] = Pix[off+2]
183 off += 3
184 }
185 }
186 return ycbcr
187}
188
189func (p *ycc) ycbcr440(ycbcr *image.YCbCr) *image.YCbCr {
190 var off int
191 Pix := p.Pix
192 Y := ycbcr.Y
193 Cb := ycbcr.Cb
194 Cr := ycbcr.Cr
195 for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ {
196 yy := y * ycbcr.YStride
197 cy := (y / 2) * ycbcr.CStride
198 for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ {
199 ci := cy + x
200 Y[yy+x] = Pix[off+0]
201 Cb[ci] = Pix[off+1]
202 Cr[ci] = Pix[off+2]
203 off += 3
204 }
205 }
206 return ycbcr
207}
208
209func (p *ycc) ycbcr444(ycbcr *image.YCbCr) *image.YCbCr {
210 var off int
211 Pix := p.Pix
212 Y := ycbcr.Y
213 Cb := ycbcr.Cb
214 Cr := ycbcr.Cr
215 for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ {
216 yy := y * ycbcr.YStride
217 cy := y * ycbcr.CStride
218 for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ {
219 ci := cy + x
220 Y[yy+x] = Pix[off+0]
221 Cb[ci] = Pix[off+1]
222 Cr[ci] = Pix[off+2]
223 off += 3
224 }
225 }
226 return ycbcr
227}
228
229func (p *ycc) ycbcr411(ycbcr *image.YCbCr) *image.YCbCr {
230 var off int
231 Pix := p.Pix
232 Y := ycbcr.Y
233 Cb := ycbcr.Cb
234 Cr := ycbcr.Cr
235 for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ {
236 yy := y * ycbcr.YStride
237 cy := y * ycbcr.CStride
238 for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ {
239 ci := cy + x/4
240 Y[yy+x] = Pix[off+0]
241 Cb[ci] = Pix[off+1]
242 Cr[ci] = Pix[off+2]
243 off += 3
244 }
245 }
246 return ycbcr
247}
248
249func (p *ycc) ycbcr410(ycbcr *image.YCbCr) *image.YCbCr {
250 var off int
251 Pix := p.Pix
252 Y := ycbcr.Y
253 Cb := ycbcr.Cb
254 Cr := ycbcr.Cr
255 for y := 0; y < ycbcr.Rect.Max.Y-ycbcr.Rect.Min.Y; y++ {
256 yy := y * ycbcr.YStride
257 cy := (y / 2) * ycbcr.CStride
258 for x := 0; x < ycbcr.Rect.Max.X-ycbcr.Rect.Min.X; x++ {
259 ci := cy + x/4
260 Y[yy+x] = Pix[off+0]
261 Cb[ci] = Pix[off+1]
262 Cr[ci] = Pix[off+2]
263 off += 3
264 }
265 }
266 return ycbcr
267}
268
269func convertToYCC422(in *image.YCbCr, p *ycc) *ycc {
270 var off int
271 Pix := p.Pix
272 Y := in.Y
273 Cb := in.Cb
274 Cr := in.Cr
275 for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ {
276 yy := y * in.YStride
277 cy := y * in.CStride
278 for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ {
279 ci := cy + x/2
280 Pix[off+0] = Y[yy+x]
281 Pix[off+1] = Cb[ci]
282 Pix[off+2] = Cr[ci]
283 off += 3
284 }
285 }
286 return p
287}
288
289func convertToYCC420(in *image.YCbCr, p *ycc) *ycc {
290 var off int
291 Pix := p.Pix
292 Y := in.Y
293 Cb := in.Cb
294 Cr := in.Cr
295 for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ {
296 yy := y * in.YStride
297 cy := (y / 2) * in.CStride
298 for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ {
299 ci := cy + x/2
300 Pix[off+0] = Y[yy+x]
301 Pix[off+1] = Cb[ci]
302 Pix[off+2] = Cr[ci]
303 off += 3
304 }
305 }
306 return p
307}
308
309func convertToYCC440(in *image.YCbCr, p *ycc) *ycc {
310 var off int
311 Pix := p.Pix
312 Y := in.Y
313 Cb := in.Cb
314 Cr := in.Cr
315 for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ {
316 yy := y * in.YStride
317 cy := (y / 2) * in.CStride
318 for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ {
319 ci := cy + x
320 Pix[off+0] = Y[yy+x]
321 Pix[off+1] = Cb[ci]
322 Pix[off+2] = Cr[ci]
323 off += 3
324 }
325 }
326 return p
327}
328
329func convertToYCC444(in *image.YCbCr, p *ycc) *ycc {
330 var off int
331 Pix := p.Pix
332 Y := in.Y
333 Cb := in.Cb
334 Cr := in.Cr
335 for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ {
336 yy := y * in.YStride
337 cy := y * in.CStride
338 for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ {
339 ci := cy + x
340 Pix[off+0] = Y[yy+x]
341 Pix[off+1] = Cb[ci]
342 Pix[off+2] = Cr[ci]
343 off += 3
344 }
345 }
346 return p
347}
348
349func convertToYCC411(in *image.YCbCr, p *ycc) *ycc {
350 var off int
351 Pix := p.Pix
352 Y := in.Y
353 Cb := in.Cb
354 Cr := in.Cr
355 for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ {
356 yy := y * in.YStride
357 cy := y * in.CStride
358 for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ {
359 ci := cy + x/4
360 Pix[off+0] = Y[yy+x]
361 Pix[off+1] = Cb[ci]
362 Pix[off+2] = Cr[ci]
363 off += 3
364 }
365 }
366 return p
367}
368
369func convertToYCC410(in *image.YCbCr, p *ycc) *ycc {
370 var off int
371 Pix := p.Pix
372 Y := in.Y
373 Cb := in.Cb
374 Cr := in.Cr
375 for y := 0; y < in.Rect.Max.Y-in.Rect.Min.Y; y++ {
376 yy := y * in.YStride
377 cy := (y / 2) * in.CStride
378 for x := 0; x < in.Rect.Max.X-in.Rect.Min.X; x++ {
379 ci := cy + x/4
380 Pix[off+0] = Y[yy+x]
381 Pix[off+1] = Cb[ci]
382 Pix[off+2] = Cr[ci]
383 off += 3
384 }
385 }
386 return p
387}