Beispiel #1
0
func newAtFuncYCbCr(p *image.YCbCr) AtFunc {
	return func(x, y int) (r, g, b, a uint32) {
		yi := p.YOffset(x, y)
		ci := p.COffset(x, y)
		y1 := int32(p.Y[yi]) * 0x10100
		cb1 := int32(p.Cb[ci]) - 128
		cr1 := int32(p.Cr[ci]) - 128
		r1 := (y1 + 91881*cr1) >> 8
		g1 := (y1 - 22554*cb1 - 46802*cr1) >> 8
		b1 := (y1 + 116130*cb1) >> 8
		if r1 < 0 {
			r1 = 0
		} else if r1 > 0xffff {
			r1 = 0xffff
		}
		if g1 < 0 {
			g1 = 0
		} else if g1 > 0xffff {
			g1 = 0xffff
		}
		if b1 < 0 {
			b1 = 0
		} else if b1 > 0xffff {
			b1 = 0xffff
		}
		return uint32(r1), uint32(g1), uint32(b1), 0xffff
	}
}
Beispiel #2
0
func DefaultOptionsFor(m *image.YCbCr) *Options {
	r := m.Bounds()
	return &Options{
		Whiteness:  100,
		LineWidth:  r.Dx() / 20,
		Desaturate: true,
	}
}
Beispiel #3
0
func process(m *image.YCbCr) {
	dx := float64(m.Bounds().Dx())
	opts := &cleanup.Options{
		Whiteness:  *white,
		LineWidth:  int(*lineWidth * dx),
		Desaturate: !*colored,
	}

	cleanup.ByBase(m, opts)
}
Beispiel #4
0
// encode image.YCbCr
func encodeYCbCr(cinfo *C.struct_jpeg_compress_struct, src *image.YCbCr, p *EncoderOptions) (err error) {
	// Set up compression parameters
	cinfo.image_width = C.JDIMENSION(src.Bounds().Dx())
	cinfo.image_height = C.JDIMENSION(src.Bounds().Dy())
	cinfo.input_components = 3
	cinfo.in_color_space = C.JCS_YCbCr

	C.jpeg_set_defaults(cinfo)
	setupEncoderOptions(cinfo, p)

	compInfo := (*[3]C.jpeg_component_info)(unsafe.Pointer(cinfo.comp_info))
	colorVDiv := 1
	switch src.SubsampleRatio {
	case image.YCbCrSubsampleRatio444:
		// 1x1,1x1,1x1
		compInfo[Y].h_samp_factor, compInfo[Y].v_samp_factor = 1, 1
		compInfo[Cb].h_samp_factor, compInfo[Cb].v_samp_factor = 1, 1
		compInfo[Cr].h_samp_factor, compInfo[Cr].v_samp_factor = 1, 1
	case image.YCbCrSubsampleRatio440:
		// 1x2,1x1,1x1
		compInfo[Y].h_samp_factor, compInfo[Y].v_samp_factor = 1, 2
		compInfo[Cb].h_samp_factor, compInfo[Cb].v_samp_factor = 1, 1
		compInfo[Cr].h_samp_factor, compInfo[Cr].v_samp_factor = 1, 1
		colorVDiv = 2
	case image.YCbCrSubsampleRatio422:
		// 2x1,1x1,1x1
		compInfo[Y].h_samp_factor, compInfo[Y].v_samp_factor = 2, 1
		compInfo[Cb].h_samp_factor, compInfo[Cb].v_samp_factor = 1, 1
		compInfo[Cr].h_samp_factor, compInfo[Cr].v_samp_factor = 1, 1
	case image.YCbCrSubsampleRatio420:
		// 2x2,1x1,1x1
		compInfo[Y].h_samp_factor, compInfo[Y].v_samp_factor = 2, 2
		compInfo[Cb].h_samp_factor, compInfo[Cb].v_samp_factor = 1, 1
		compInfo[Cr].h_samp_factor, compInfo[Cr].v_samp_factor = 1, 1
		colorVDiv = 2
	}

	// libjpeg raw data in is in planar format, which avoids unnecessary
	// planar->packed->planar conversions.
	cinfo.raw_data_in = C.TRUE

	// Start compression
	C.jpeg_start_compress(cinfo, C.TRUE)
	C.encode_ycbcr(
		cinfo,
		C.JSAMPROW(unsafe.Pointer(&src.Y[0])),
		C.JSAMPROW(unsafe.Pointer(&src.Cb[0])),
		C.JSAMPROW(unsafe.Pointer(&src.Cr[0])),
		C.int(src.YStride),
		C.int(src.CStride),
		C.int(colorVDiv),
	)
	C.jpeg_finish_compress(cinfo)
	return
}
func convertYCbCr(dest *Image, src *image.YCbCr) {
	var r, g, b uint8
	var x, y, i, yi, ci int

	for x = dest.Rect.Min.X; x < dest.Rect.Max.X; x++ {
		for y = dest.Rect.Min.Y; y < dest.Rect.Max.Y; y++ {
			yi, ci = src.YOffset(x, y), src.COffset(x, y)
			r, g, b = color.YCbCrToRGB(src.Y[yi], src.Cb[ci], src.Cr[ci])
			i = dest.PixOffset(x, y)
			dest.Pix[i+0] = b
			dest.Pix[i+1] = g
			dest.Pix[i+2] = r
			dest.Pix[i+3] = 0xff
		}
	}
}
Beispiel #6
0
// ByBase cleans image based on the surrounding color values
func ByBase(m *image.YCbCr, opts *Options) {
	if opts == nil {
		opts = DefaultOptionsFor(m)
	}

	white := opts.Whiteness * 255.0 / 100.0

	L := &filter.Channel{
		Data:   m.Y,
		Width:  m.Bounds().Dx(),
		Height: m.Bounds().Dy(),
		Stride: m.YStride,
	}

	// get rid of hot-pixels
	L.Median(1)

	if opts.Desaturate {
		filter.Desaturate(m)
	}

	base := L.Clone()
	base.Erode(opts.LineWidth)
	base.Blur(opts.LineWidth)

	average := base.Average()
	invspan := 1.0 / (average / white)

	for y := 0; y < L.Height; y++ {
		i := y * L.Stride
		e := i + L.Width
		for ; i < e; i++ {
			lv := float64(L.Data[i])
			bv := float64(base.Data[i])

			r := int(white + (lv-bv)*invspan)
			if r < 0x00 {
				r = 0x00
			} else if r > 0xFF {
				r = 0xFF
			}

			L.Data[i] = byte(r)
		}
	}
}
Beispiel #7
0
// encodePGM encodes gotImage in the PGM format in the IMC4 layout.
func encodePGM(gotImage image.Image) ([]byte, error) {
	var (
		m  *image.YCbCr
		ma *nycbcra.Image
	)
	switch g := gotImage.(type) {
	case *image.YCbCr:
		m = g
	case *nycbcra.Image:
		m = &g.YCbCr
		ma = g
	default:
		return nil, fmt.Errorf("lossy image did not decode to an *image.YCbCr")
	}
	if m.SubsampleRatio != image.YCbCrSubsampleRatio420 {
		return nil, fmt.Errorf("lossy image did not decode to a 4:2:0 YCbCr")
	}
	b := m.Bounds()
	w, h := b.Dx(), b.Dy()
	w2, h2 := (w+1)/2, (h+1)/2
	outW, outH := 2*w2, h+h2
	if ma != nil {
		outH += h
	}
	buf := new(bytes.Buffer)
	fmt.Fprintf(buf, "P5\n%d %d\n255\n", outW, outH)
	for y := b.Min.Y; y < b.Max.Y; y++ {
		o := m.YOffset(b.Min.X, y)
		buf.Write(m.Y[o : o+w])
		if w&1 != 0 {
			buf.WriteByte(0x00)
		}
	}
	for y := b.Min.Y; y < b.Max.Y; y += 2 {
		o := m.COffset(b.Min.X, y)
		buf.Write(m.Cb[o : o+w2])
		buf.Write(m.Cr[o : o+w2])
	}
	if ma != nil {
		for y := b.Min.Y; y < b.Max.Y; y++ {
			o := ma.AOffset(b.Min.X, y)
			buf.Write(ma.A[o : o+w])
			if w&1 != 0 {
				buf.WriteByte(0x00)
			}
		}
	}
	return buf.Bytes(), nil
}
Beispiel #8
0
func testDecodeLossy(t *testing.T, tc string, withAlpha bool) {
	webpFilename := "../testdata/" + tc + ".lossy.webp"
	pngFilename := webpFilename + ".ycbcr.png"
	if withAlpha {
		webpFilename = "../testdata/" + tc + ".lossy-with-alpha.webp"
		pngFilename = webpFilename + ".nycbcra.png"
	}

	f0, err := os.Open(webpFilename)
	if err != nil {
		t.Errorf("%s: Open WEBP: %v", tc, err)
		return
	}
	defer f0.Close()
	img0, err := Decode(f0)
	if err != nil {
		t.Errorf("%s: Decode WEBP: %v", tc, err)
		return
	}

	var (
		m0 *image.YCbCr
		a0 *image.NYCbCrA
		ok bool
	)
	if withAlpha {
		a0, ok = img0.(*image.NYCbCrA)
		if ok {
			m0 = &a0.YCbCr
		}
	} else {
		m0, ok = img0.(*image.YCbCr)
	}
	if !ok || m0.SubsampleRatio != image.YCbCrSubsampleRatio420 {
		t.Errorf("%s: decoded WEBP image is not a 4:2:0 YCbCr or 4:2:0 NYCbCrA", tc)
		return
	}
	// w2 and h2 are the half-width and half-height, rounded up.
	w, h := m0.Bounds().Dx(), m0.Bounds().Dy()
	w2, h2 := int((w+1)/2), int((h+1)/2)

	f1, err := os.Open(pngFilename)
	if err != nil {
		t.Errorf("%s: Open PNG: %v", tc, err)
		return
	}
	defer f1.Close()
	img1, err := png.Decode(f1)
	if err != nil {
		t.Errorf("%s: Open PNG: %v", tc, err)
		return
	}

	// The split-into-YCbCr-planes golden image is a 2*w2 wide and h+h2 high
	// (or 2*h+h2 high, if with Alpha) gray image arranged in IMC4 format:
	//   YYYY
	//   YYYY
	//   BBRR
	//   AAAA
	// See http://www.fourcc.org/yuv.php#IMC4
	pngW, pngH := 2*w2, h+h2
	if withAlpha {
		pngH += h
	}
	if got, want := img1.Bounds(), image.Rect(0, 0, pngW, pngH); got != want {
		t.Errorf("%s: bounds0: got %v, want %v", tc, got, want)
		return
	}
	m1, ok := img1.(*image.Gray)
	if !ok {
		t.Errorf("%s: decoded PNG image is not a Gray", tc)
		return
	}

	type plane struct {
		name     string
		m0Pix    []uint8
		m0Stride int
		m1Rect   image.Rectangle
	}
	planes := []plane{
		{"Y", m0.Y, m0.YStride, image.Rect(0, 0, w, h)},
		{"Cb", m0.Cb, m0.CStride, image.Rect(0*w2, h, 1*w2, h+h2)},
		{"Cr", m0.Cr, m0.CStride, image.Rect(1*w2, h, 2*w2, h+h2)},
	}
	if withAlpha {
		planes = append(planes, plane{
			"A", a0.A, a0.AStride, image.Rect(0, h+h2, w, 2*h+h2),
		})
	}

	for _, plane := range planes {
		dx := plane.m1Rect.Dx()
		nDiff, diff := 0, make([]byte, dx)
		for j, y := 0, plane.m1Rect.Min.Y; y < plane.m1Rect.Max.Y; j, y = j+1, y+1 {
			got := plane.m0Pix[j*plane.m0Stride:][:dx]
			want := m1.Pix[y*m1.Stride+plane.m1Rect.Min.X:][:dx]
			if bytes.Equal(got, want) {
				continue
			}
			nDiff++
			if nDiff > 10 {
				t.Errorf("%s: %s plane: more rows differ", tc, plane.name)
				break
			}
			for i := range got {
				diff[i] = got[i] - want[i]
			}
			t.Errorf("%s: %s plane: m0 row %d, m1 row %d\ngot %s\nwant%s\ndiff%s",
				tc, plane.name, j, y, hex(got), hex(want), hex(diff))
		}
	}
}
Beispiel #9
0
// resizeYCbCr returns a scaled copy of the YCbCr image slice r of m.
// The returned image has width w and height h.
func resizeYCbCr(m *image.YCbCr, r image.Rectangle, w, h int) (image.Image, bool) {
	dst := image.NewRGBA(image.Rect(0, 0, w, h))
	xdraw.ApproxBiLinear.Scale(dst, dst.Bounds(), m, m.Bounds(), xdraw.Src, nil)
	return dst, true
}
Beispiel #10
0
// encode image.YCbCr
func encodeYCbCr(cinfo *C.struct_jpeg_compress_struct, src *image.YCbCr, p *EncoderOptions) (err error) {
	// Set up compression parameters
	cinfo.image_width = C.JDIMENSION(src.Bounds().Dx())
	cinfo.image_height = C.JDIMENSION(src.Bounds().Dy())
	cinfo.input_components = 3
	cinfo.in_color_space = C.JCS_YCbCr

	C.jpeg_set_defaults(cinfo)
	setupEncoderOptions(cinfo, p)

	compInfo := (*[3]C.jpeg_component_info)(unsafe.Pointer(cinfo.comp_info))
	colorVDiv := 1
	switch src.SubsampleRatio {
	case image.YCbCrSubsampleRatio444:
		// 1x1,1x1,1x1
		compInfo[Y].h_samp_factor, compInfo[Y].v_samp_factor = 1, 1
		compInfo[Cb].h_samp_factor, compInfo[Cb].v_samp_factor = 1, 1
		compInfo[Cr].h_samp_factor, compInfo[Cr].v_samp_factor = 1, 1
	case image.YCbCrSubsampleRatio440:
		// 1x2,1x1,1x1
		compInfo[Y].h_samp_factor, compInfo[Y].v_samp_factor = 1, 2
		compInfo[Cb].h_samp_factor, compInfo[Cb].v_samp_factor = 1, 1
		compInfo[Cr].h_samp_factor, compInfo[Cr].v_samp_factor = 1, 1
		colorVDiv = 2
	case image.YCbCrSubsampleRatio422:
		// 2x1,1x1,1x1
		compInfo[Y].h_samp_factor, compInfo[Y].v_samp_factor = 2, 1
		compInfo[Cb].h_samp_factor, compInfo[Cb].v_samp_factor = 1, 1
		compInfo[Cr].h_samp_factor, compInfo[Cr].v_samp_factor = 1, 1
	case image.YCbCrSubsampleRatio420:
		// 2x2,1x1,1x1
		compInfo[Y].h_samp_factor, compInfo[Y].v_samp_factor = 2, 2
		compInfo[Cb].h_samp_factor, compInfo[Cb].v_samp_factor = 1, 1
		compInfo[Cr].h_samp_factor, compInfo[Cr].v_samp_factor = 1, 1
		colorVDiv = 2
	}

	// libjpeg raw data in is in planar format, which avoids unnecessary
	// planar->packed->planar conversions.
	cinfo.raw_data_in = C.TRUE

	// Start compression
	C.jpeg_start_compress(cinfo, C.TRUE)

	// Allocate JSAMPIMAGE to hold pointers to one iMCU worth of image data
	// this is a safe overestimate; we use the return value from
	// jpeg_read_raw_data to figure out what is the actual iMCU row count.
	var yRowPtr [AlignSize]C.JSAMPROW
	var cbRowPtr [AlignSize]C.JSAMPROW
	var crRowPtr [AlignSize]C.JSAMPROW
	yCbCrPtr := [3]C.JSAMPARRAY{
		C.JSAMPARRAY(unsafe.Pointer(&yRowPtr[0])),
		C.JSAMPARRAY(unsafe.Pointer(&cbRowPtr[0])),
		C.JSAMPARRAY(unsafe.Pointer(&crRowPtr[0])),
	}

	var rows C.JDIMENSION
	for rows = 0; rows < cinfo.image_height; {
		// First fill in the pointers into the plane data buffers
		for j := 0; j < int(C.DCTSIZE*compInfo[Y].v_samp_factor); j++ {
			yRowPtr[j] = C.JSAMPROW(unsafe.Pointer(&src.Y[src.YStride*(int(rows)+j)]))
		}
		for j := 0; j < int(C.DCTSIZE*compInfo[Cb].v_samp_factor); j++ {
			cbRowPtr[j] = C.JSAMPROW(unsafe.Pointer(&src.Cb[src.CStride*(int(rows)/colorVDiv+j)]))
			crRowPtr[j] = C.JSAMPROW(unsafe.Pointer(&src.Cr[src.CStride*(int(rows)/colorVDiv+j)]))
		}
		// Get the data
		rows += C.jpeg_write_raw_data(cinfo, C.JSAMPIMAGE(unsafe.Pointer(&yCbCrPtr[0])), C.JDIMENSION(C.DCTSIZE*compInfo[0].v_samp_factor))
	}

	C.jpeg_finish_compress(cinfo)
	return
}