1package gift
2
3import (
4 "image"
5 "image/color"
6 "image/draw"
7)
8
9type pixel struct {
10 r, g, b, a float32
11}
12
13type imageType int
14
15const (
16 itGeneric imageType = iota
17 itNRGBA
18 itNRGBA64
19 itRGBA
20 itRGBA64
21 itYCbCr
22 itGray
23 itGray16
24 itPaletted
25)
26
27type pixelGetter struct {
28 it imageType
29 bounds image.Rectangle
30 image image.Image
31 nrgba *image.NRGBA
32 nrgba64 *image.NRGBA64
33 rgba *image.RGBA
34 rgba64 *image.RGBA64
35 gray *image.Gray
36 gray16 *image.Gray16
37 ycbcr *image.YCbCr
38 paletted *image.Paletted
39 palette []pixel
40}
41
42func newPixelGetter(img image.Image) *pixelGetter {
43 switch img := img.(type) {
44 case *image.NRGBA:
45 return &pixelGetter{
46 it: itNRGBA,
47 bounds: img.Bounds(),
48 nrgba: img,
49 }
50
51 case *image.NRGBA64:
52 return &pixelGetter{
53 it: itNRGBA64,
54 bounds: img.Bounds(),
55 nrgba64: img,
56 }
57
58 case *image.RGBA:
59 return &pixelGetter{
60 it: itRGBA,
61 bounds: img.Bounds(),
62 rgba: img,
63 }
64
65 case *image.RGBA64:
66 return &pixelGetter{
67 it: itRGBA64,
68 bounds: img.Bounds(),
69 rgba64: img,
70 }
71
72 case *image.Gray:
73 return &pixelGetter{
74 it: itGray,
75 bounds: img.Bounds(),
76 gray: img,
77 }
78
79 case *image.Gray16:
80 return &pixelGetter{
81 it: itGray16,
82 bounds: img.Bounds(),
83 gray16: img,
84 }
85
86 case *image.YCbCr:
87 return &pixelGetter{
88 it: itYCbCr,
89 bounds: img.Bounds(),
90 ycbcr: img,
91 }
92
93 case *image.Paletted:
94 return &pixelGetter{
95 it: itPaletted,
96 bounds: img.Bounds(),
97 paletted: img,
98 palette: convertPalette(img.Palette),
99 }
100
101 default:
102 return &pixelGetter{
103 it: itGeneric,
104 bounds: img.Bounds(),
105 image: img,
106 }
107 }
108}
109
110const (
111 qf8 = 1.0 / 0xff
112 qf16 = 1.0 / 0xffff
113 epal = qf16 * qf16 / 2
114)
115
116func pixelFromColor(c color.Color) (px pixel) {
117 r16, g16, b16, a16 := c.RGBA()
118 switch a16 {
119 case 0:
120 px = pixel{0, 0, 0, 0}
121 case 0xffff:
122 r := float32(r16) * qf16
123 g := float32(g16) * qf16
124 b := float32(b16) * qf16
125 px = pixel{r, g, b, 1}
126 default:
127 q := float32(1) / float32(a16)
128 r := float32(r16) * q
129 g := float32(g16) * q
130 b := float32(b16) * q
131 a := float32(a16) * qf16
132 px = pixel{r, g, b, a}
133 }
134 return px
135}
136
137func convertPalette(p []color.Color) []pixel {
138 pal := make([]pixel, len(p))
139 for i := 0; i < len(p); i++ {
140 pal[i] = pixelFromColor(p[i])
141 }
142 return pal
143}
144
145func getPaletteIndex(pal []pixel, px pixel) int {
146 var k int
147 var dmin float32 = 4
148 for i, palpx := range pal {
149 d := px.r - palpx.r
150 dcur := d * d
151 d = px.g - palpx.g
152 dcur += d * d
153 d = px.b - palpx.b
154 dcur += d * d
155 d = px.a - palpx.a
156 dcur += d * d
157 if dcur < epal {
158 return i
159 }
160 if dcur < dmin {
161 dmin = dcur
162 k = i
163 }
164 }
165 return k
166}
167
168func (p *pixelGetter) getPixel(x, y int) pixel {
169 switch p.it {
170 case itNRGBA:
171 i := p.nrgba.PixOffset(x, y)
172 r := float32(p.nrgba.Pix[i+0]) * qf8
173 g := float32(p.nrgba.Pix[i+1]) * qf8
174 b := float32(p.nrgba.Pix[i+2]) * qf8
175 a := float32(p.nrgba.Pix[i+3]) * qf8
176 return pixel{r, g, b, a}
177
178 case itNRGBA64:
179 i := p.nrgba64.PixOffset(x, y)
180 r := float32(uint16(p.nrgba64.Pix[i+0])<<8|uint16(p.nrgba64.Pix[i+1])) * qf16
181 g := float32(uint16(p.nrgba64.Pix[i+2])<<8|uint16(p.nrgba64.Pix[i+3])) * qf16
182 b := float32(uint16(p.nrgba64.Pix[i+4])<<8|uint16(p.nrgba64.Pix[i+5])) * qf16
183 a := float32(uint16(p.nrgba64.Pix[i+6])<<8|uint16(p.nrgba64.Pix[i+7])) * qf16
184 return pixel{r, g, b, a}
185
186 case itRGBA:
187 i := p.rgba.PixOffset(x, y)
188 a8 := p.rgba.Pix[i+3]
189 switch a8 {
190 case 0xff:
191 r := float32(p.rgba.Pix[i+0]) * qf8
192 g := float32(p.rgba.Pix[i+1]) * qf8
193 b := float32(p.rgba.Pix[i+2]) * qf8
194 return pixel{r, g, b, 1}
195 case 0:
196 return pixel{0, 0, 0, 0}
197 default:
198 q := float32(1) / float32(a8)
199 r := float32(p.rgba.Pix[i+0]) * q
200 g := float32(p.rgba.Pix[i+1]) * q
201 b := float32(p.rgba.Pix[i+2]) * q
202 a := float32(a8) * qf8
203 return pixel{r, g, b, a}
204 }
205
206 case itRGBA64:
207 i := p.rgba64.PixOffset(x, y)
208 a16 := uint16(p.rgba64.Pix[i+6])<<8 | uint16(p.rgba64.Pix[i+7])
209 switch a16 {
210 case 0xffff:
211 r := float32(uint16(p.rgba64.Pix[i+0])<<8|uint16(p.rgba64.Pix[i+1])) * qf16
212 g := float32(uint16(p.rgba64.Pix[i+2])<<8|uint16(p.rgba64.Pix[i+3])) * qf16
213 b := float32(uint16(p.rgba64.Pix[i+4])<<8|uint16(p.rgba64.Pix[i+5])) * qf16
214 return pixel{r, g, b, 1}
215 case 0:
216 return pixel{0, 0, 0, 0}
217 default:
218 q := float32(1) / float32(a16)
219 r := float32(uint16(p.rgba64.Pix[i+0])<<8|uint16(p.rgba64.Pix[i+1])) * q
220 g := float32(uint16(p.rgba64.Pix[i+2])<<8|uint16(p.rgba64.Pix[i+3])) * q
221 b := float32(uint16(p.rgba64.Pix[i+4])<<8|uint16(p.rgba64.Pix[i+5])) * q
222 a := float32(a16) * qf16
223 return pixel{r, g, b, a}
224 }
225
226 case itGray:
227 i := p.gray.PixOffset(x, y)
228 v := float32(p.gray.Pix[i]) * qf8
229 return pixel{v, v, v, 1}
230
231 case itGray16:
232 i := p.gray16.PixOffset(x, y)
233 v := float32(uint16(p.gray16.Pix[i+0])<<8|uint16(p.gray16.Pix[i+1])) * qf16
234 return pixel{v, v, v, 1}
235
236 case itYCbCr:
237 iy := (y-p.ycbcr.Rect.Min.Y)*p.ycbcr.YStride + (x - p.ycbcr.Rect.Min.X)
238
239 var ic int
240 switch p.ycbcr.SubsampleRatio {
241 case image.YCbCrSubsampleRatio444:
242 ic = (y-p.ycbcr.Rect.Min.Y)*p.ycbcr.CStride + (x - p.ycbcr.Rect.Min.X)
243 case image.YCbCrSubsampleRatio422:
244 ic = (y-p.ycbcr.Rect.Min.Y)*p.ycbcr.CStride + (x/2 - p.ycbcr.Rect.Min.X/2)
245 case image.YCbCrSubsampleRatio420:
246 ic = (y/2-p.ycbcr.Rect.Min.Y/2)*p.ycbcr.CStride + (x/2 - p.ycbcr.Rect.Min.X/2)
247 case image.YCbCrSubsampleRatio440:
248 ic = (y/2-p.ycbcr.Rect.Min.Y/2)*p.ycbcr.CStride + (x - p.ycbcr.Rect.Min.X)
249 default:
250 ic = p.ycbcr.COffset(x, y)
251 }
252
253 const (
254 max = 255 * 1e5
255 inv = 1.0 / max
256 )
257
258 y1 := int32(p.ycbcr.Y[iy]) * 1e5
259 cb1 := int32(p.ycbcr.Cb[ic]) - 128
260 cr1 := int32(p.ycbcr.Cr[ic]) - 128
261
262 r1 := y1 + 140200*cr1
263 g1 := y1 - 34414*cb1 - 71414*cr1
264 b1 := y1 + 177200*cb1
265
266 r := float32(clampi32(r1, 0, max)) * inv
267 g := float32(clampi32(g1, 0, max)) * inv
268 b := float32(clampi32(b1, 0, max)) * inv
269
270 return pixel{r, g, b, 1}
271
272 case itPaletted:
273 i := p.paletted.PixOffset(x, y)
274 k := p.paletted.Pix[i]
275 return p.palette[k]
276 }
277
278 return pixelFromColor(p.image.At(x, y))
279}
280
281func (p *pixelGetter) getPixelRow(y int, buf *[]pixel) {
282 *buf = (*buf)[:0]
283 for x := p.bounds.Min.X; x != p.bounds.Max.X; x++ {
284 *buf = append(*buf, p.getPixel(x, y))
285 }
286}
287
288func (p *pixelGetter) getPixelColumn(x int, buf *[]pixel) {
289 *buf = (*buf)[:0]
290 for y := p.bounds.Min.Y; y != p.bounds.Max.Y; y++ {
291 *buf = append(*buf, p.getPixel(x, y))
292 }
293}
294
295func f32u8(val float32) uint8 {
296 x := int64(val + 0.5)
297 if x > 0xff {
298 return 0xff
299 }
300 if x > 0 {
301 return uint8(x)
302 }
303 return 0
304}
305
306func f32u16(val float32) uint16 {
307 x := int64(val + 0.5)
308 if x > 0xffff {
309 return 0xffff
310 }
311 if x > 0 {
312 return uint16(x)
313 }
314 return 0
315}
316
317func clampi32(val, min, max int32) int32 {
318 if val > max {
319 return max
320 }
321 if val > min {
322 return val
323 }
324 return 0
325}
326
327type pixelSetter struct {
328 it imageType
329 bounds image.Rectangle
330 image draw.Image
331 nrgba *image.NRGBA
332 nrgba64 *image.NRGBA64
333 rgba *image.RGBA
334 rgba64 *image.RGBA64
335 gray *image.Gray
336 gray16 *image.Gray16
337 paletted *image.Paletted
338 palette []pixel
339}
340
341func newPixelSetter(img draw.Image) *pixelSetter {
342 switch img := img.(type) {
343 case *image.NRGBA:
344 return &pixelSetter{
345 it: itNRGBA,
346 bounds: img.Bounds(),
347 nrgba: img,
348 }
349
350 case *image.NRGBA64:
351 return &pixelSetter{
352 it: itNRGBA64,
353 bounds: img.Bounds(),
354 nrgba64: img,
355 }
356
357 case *image.RGBA:
358 return &pixelSetter{
359 it: itRGBA,
360 bounds: img.Bounds(),
361 rgba: img,
362 }
363
364 case *image.RGBA64:
365 return &pixelSetter{
366 it: itRGBA64,
367 bounds: img.Bounds(),
368 rgba64: img,
369 }
370
371 case *image.Gray:
372 return &pixelSetter{
373 it: itGray,
374 bounds: img.Bounds(),
375 gray: img,
376 }
377
378 case *image.Gray16:
379 return &pixelSetter{
380 it: itGray16,
381 bounds: img.Bounds(),
382 gray16: img,
383 }
384
385 case *image.Paletted:
386 return &pixelSetter{
387 it: itPaletted,
388 bounds: img.Bounds(),
389 paletted: img,
390 palette: convertPalette(img.Palette),
391 }
392
393 default:
394 return &pixelSetter{
395 it: itGeneric,
396 bounds: img.Bounds(),
397 image: img,
398 }
399 }
400}
401
402func (p *pixelSetter) setPixel(x, y int, px pixel) {
403 if !image.Pt(x, y).In(p.bounds) {
404 return
405 }
406 switch p.it {
407 case itNRGBA:
408 i := p.nrgba.PixOffset(x, y)
409 p.nrgba.Pix[i+0] = f32u8(px.r * 0xff)
410 p.nrgba.Pix[i+1] = f32u8(px.g * 0xff)
411 p.nrgba.Pix[i+2] = f32u8(px.b * 0xff)
412 p.nrgba.Pix[i+3] = f32u8(px.a * 0xff)
413
414 case itNRGBA64:
415 r16 := f32u16(px.r * 0xffff)
416 g16 := f32u16(px.g * 0xffff)
417 b16 := f32u16(px.b * 0xffff)
418 a16 := f32u16(px.a * 0xffff)
419 i := p.nrgba64.PixOffset(x, y)
420 p.nrgba64.Pix[i+0] = uint8(r16 >> 8)
421 p.nrgba64.Pix[i+1] = uint8(r16 & 0xff)
422 p.nrgba64.Pix[i+2] = uint8(g16 >> 8)
423 p.nrgba64.Pix[i+3] = uint8(g16 & 0xff)
424 p.nrgba64.Pix[i+4] = uint8(b16 >> 8)
425 p.nrgba64.Pix[i+5] = uint8(b16 & 0xff)
426 p.nrgba64.Pix[i+6] = uint8(a16 >> 8)
427 p.nrgba64.Pix[i+7] = uint8(a16 & 0xff)
428
429 case itRGBA:
430 fa := px.a * 0xff
431 i := p.rgba.PixOffset(x, y)
432 p.rgba.Pix[i+0] = f32u8(px.r * fa)
433 p.rgba.Pix[i+1] = f32u8(px.g * fa)
434 p.rgba.Pix[i+2] = f32u8(px.b * fa)
435 p.rgba.Pix[i+3] = f32u8(fa)
436
437 case itRGBA64:
438 fa := px.a * 0xffff
439 r16 := f32u16(px.r * fa)
440 g16 := f32u16(px.g * fa)
441 b16 := f32u16(px.b * fa)
442 a16 := f32u16(fa)
443 i := p.rgba64.PixOffset(x, y)
444 p.rgba64.Pix[i+0] = uint8(r16 >> 8)
445 p.rgba64.Pix[i+1] = uint8(r16 & 0xff)
446 p.rgba64.Pix[i+2] = uint8(g16 >> 8)
447 p.rgba64.Pix[i+3] = uint8(g16 & 0xff)
448 p.rgba64.Pix[i+4] = uint8(b16 >> 8)
449 p.rgba64.Pix[i+5] = uint8(b16 & 0xff)
450 p.rgba64.Pix[i+6] = uint8(a16 >> 8)
451 p.rgba64.Pix[i+7] = uint8(a16 & 0xff)
452
453 case itGray:
454 i := p.gray.PixOffset(x, y)
455 p.gray.Pix[i] = f32u8((0.299*px.r + 0.587*px.g + 0.114*px.b) * px.a * 0xff)
456
457 case itGray16:
458 i := p.gray16.PixOffset(x, y)
459 y16 := f32u16((0.299*px.r + 0.587*px.g + 0.114*px.b) * px.a * 0xffff)
460 p.gray16.Pix[i+0] = uint8(y16 >> 8)
461 p.gray16.Pix[i+1] = uint8(y16 & 0xff)
462
463 case itPaletted:
464 px1 := pixel{
465 minf32(maxf32(px.r, 0), 1),
466 minf32(maxf32(px.g, 0), 1),
467 minf32(maxf32(px.b, 0), 1),
468 minf32(maxf32(px.a, 0), 1),
469 }
470 i := p.paletted.PixOffset(x, y)
471 k := getPaletteIndex(p.palette, px1)
472 p.paletted.Pix[i] = uint8(k)
473
474 case itGeneric:
475 r16 := f32u16(px.r * 0xffff)
476 g16 := f32u16(px.g * 0xffff)
477 b16 := f32u16(px.b * 0xffff)
478 a16 := f32u16(px.a * 0xffff)
479 p.image.Set(x, y, color.NRGBA64{r16, g16, b16, a16})
480 }
481}
482
483func (p *pixelSetter) setPixelRow(y int, buf []pixel) {
484 for i, x := 0, p.bounds.Min.X; i < len(buf); i, x = i+1, x+1 {
485 p.setPixel(x, y, buf[i])
486 }
487}
488
489func (p *pixelSetter) setPixelColumn(x int, buf []pixel) {
490 for i, y := 0, p.bounds.Min.Y; i < len(buf); i, y = i+1, y+1 {
491 p.setPixel(x, y, buf[i])
492 }
493}