pixels.go

  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}