Exemplo n.º 1
0
func Encode(w io.Writer, m image.Image, o *Options) error {
	quality := 75
	if o != nil {
		quality = o.Quality
	}

	var cinfo C.struct_jpeg_compress_struct
	var jerr C.struct_jpeg_error_mgr
	var workBuf *C.uchar
	var workBufLen C.ulong

	cinfo.err = C.jpeg_std_error(&jerr)
	C.jpeg_CreateCompress(&cinfo, C.JPEG_LIB_VERSION, C.size_t(unsafe.Sizeof(cinfo)))
	C.jpeg_mem_dest(&cinfo, &workBuf, &workBufLen)

	bounds := m.Bounds()
	cinfo.image_width = C.JDIMENSION(bounds.Dx())
	cinfo.image_height = C.JDIMENSION(bounds.Dy())
	cinfo.input_components = 3
	cinfo.in_color_space = C.JCS_RGB

	C.jpeg_set_defaults(&cinfo)
	C.jpeg_set_quality(&cinfo, C.int(quality), C.boolean(1))
	C.jpeg_start_compress(&cinfo, C.boolean(1))

	rowBuf := make([]byte, cinfo.image_width*3)

	for cinfo.next_scanline < cinfo.image_height {
		for x := 0; x < int(cinfo.image_width); x += 1 {
			r, g, b, _ := m.At(x, int(cinfo.next_scanline)).RGBA()
			rowBuf[x*3] = byte(r >> 8)
			rowBuf[x*3+1] = byte(g >> 8)
			rowBuf[x*3+2] = byte(b >> 8)
		}

		rowPointer := C.JSAMPROW(unsafe.Pointer(&rowBuf[0]))
		C.jpeg_write_scanlines(&cinfo, &rowPointer, 1)
	}

	C.jpeg_finish_compress(&cinfo)
	C.jpeg_destroy_compress(&cinfo)

	outBs := C.GoBytes(unsafe.Pointer(workBuf), C.int(workBufLen))
	w.Write(outBs)
	C.free(unsafe.Pointer(workBuf))

	return nil
}
Exemplo n.º 2
0
func Decode(r io.Reader) (img image.Image, er error) {
	/* Reading the whole file in may be inefficient, but libjpeg wants callbacks
	 * to functions to read in more data, and that is a nightmare to implement. We
	 * don't want to read the entire stream, however, which means pulling the header.
	 * We may be able to read enough to call jpeg_read_header with a [10]byte, but
	 * I'll change it later if need be, since this probably doesn't play nicely
	 * with a non-closing io.Reader */

	var wholeFile []byte
	if wholeFile, er = ioutil.ReadAll(r); er != nil {
		return
	}

	var cinfo C.struct_jpeg_decompress_struct
	var jerr C.struct_jpeg_error_mgr

	cinfo.err = C.jpeg_std_error(&jerr)
	C.jpeg_CreateDecompress(&cinfo, C.JPEG_LIB_VERSION, C.size_t(unsafe.Sizeof(cinfo)))

	C.jpeg_mem_src(&cinfo, (*C.uchar)(unsafe.Pointer(&wholeFile[0])), C.ulong(len(wholeFile)))

	if C.jpeg_read_header(&cinfo, C.TRUE) == C.JPEG_HEADER_OK {
		C.jpeg_start_decompress(&cinfo)

		if cinfo.num_components == 1 {
			img = decodeGrayscale(&cinfo)

		} else if cinfo.num_components == 3 {
			img = decodeRGB(&cinfo)

		} else if cinfo.num_components == 4 {
			img = decodeCMYK(&cinfo)

		} else {
			er = fmt.Errorf("Invalid number of components (%d)", cinfo.num_components)
		}

		if er == nil {
			C.jpeg_finish_decompress(&cinfo)
		}
	}

	C.jpeg_destroy_decompress(&cinfo)

	return
}
Exemplo n.º 3
0
// ReadJPEG reads a JPEG file and returns a planar YUV image.
func ReadJPEG(src io.Reader, params DecompressionParameters) (img *YUVImage, err error) {
	defer func() {
		if r := recover(); r != nil {
			img = nil
			var ok bool
			err, ok = r.(error)
			if !ok {
				err = fmt.Errorf("JPEG error: %v", r)
			}
		}
	}()

	dinfo := (*C.struct_jpeg_decompress_struct)(C.malloc(C.size_t(unsafe.Sizeof(C.struct_jpeg_decompress_struct{}))))
	if dinfo == nil {
		panic("Failed to allocate dinfo")
	}
	defer C.free(unsafe.Pointer(dinfo))
	dinfo.err = (*C.struct_jpeg_error_mgr)(C.malloc(C.size_t(unsafe.Sizeof(C.struct_jpeg_error_mgr{}))))
	if dinfo.err == nil {
		panic("Failed to allocate dinfo.err")
	}
	defer C.free(unsafe.Pointer(dinfo.err))

	img = new(YUVImage)

	// Setup error handling
	C.jpeg_std_error(dinfo.err)
	dinfo.err.error_exit = (*[0]byte)(C.error_panic)

	// Initialize decompression
	C.c_jpeg_create_decompress(dinfo)
	defer C.jpeg_destroy_decompress(dinfo)

	srcManager := makeSourceManager(src, dinfo)
	defer C.free(unsafe.Pointer(srcManager))

	C.jpeg_read_header(dinfo, C.TRUE)

	// Configure pre-scaling and request calculation of component info
	if params.TargetWidth > 0 && params.TargetHeight > 0 {
		var scaleFactor int
		for scaleFactor = 1; scaleFactor <= 8; scaleFactor++ {
			if ((scaleFactor*int(dinfo.image_width)+7)/8) >= params.TargetWidth &&
				((scaleFactor*int(dinfo.image_height)+7)/8) >= params.TargetHeight {
				break
			}
		}
		if scaleFactor < 8 {
			dinfo.scale_num = C.uint(scaleFactor)
			dinfo.scale_denom = 8
		}
	}

	// More settings
	if params.FastDCT {
		dinfo.dct_method = C.JDCT_IFAST
	} else {
		dinfo.dct_method = C.JDCT_ISLOW
	}
	C.jpeg_calc_output_dimensions(dinfo)

	// Figure out what color format we're dealing with after scaling
	compInfo := (*[3]C.jpeg_component_info)(unsafe.Pointer(dinfo.comp_info))
	colorVDiv := 1
	switch dinfo.num_components {
	case 1:
		if dinfo.jpeg_color_space != C.JCS_GRAYSCALE {
			panic("Unsupported colorspace")
		}
		img.Format = Grayscale
	case 3:
		// No support for RGB and CMYK (both rare)
		if dinfo.jpeg_color_space != C.JCS_YCbCr {
			panic("Unsupported colorspace")
		}
		dwY := compInfo[Y].downsampled_width
		dhY := compInfo[Y].downsampled_height
		dwC := compInfo[U].downsampled_width
		dhC := compInfo[U].downsampled_height
		//fmt.Printf("%d %d %d %d\n", dwY, dhY, dwC, dhC)
		if dwC != compInfo[V].downsampled_width || dhC != compInfo[V].downsampled_height {
			panic("Unsupported color subsampling (Cb and Cr differ)")
		}
		// Since the decisions about which DCT size and subsampling mode
		// to use, if any, are complex, instead just check the calculated
		// output plane sizes and infer the subsampling mode from that.
		if dwY == dwC {
			if dhY == dhC {
				img.Format = YUV444
			} else if (dhY+1)/2 == dhC {
				img.Format = YUV440
				colorVDiv = 2
			} else {
				panic("Unsupported color subsampling (vertical is not 1 or 2)")
			}
		} else if (dwY+1)/2 == dwC {
			if dhY == dhC {
				img.Format = YUV422
			} else if (dhY+1)/2 == dhC {
				img.Format = YUV420
				colorVDiv = 2
			} else {
				panic("Unsupported color subsampling (vertical is not 1 or 2)")
			}
		} else {
			panic("Unsupported color subsampling (horizontal is not 1 or 2)")
		}
	default:
		panic("Unsupported number of components")
	}

	img.Width = int(compInfo[Y].downsampled_width)
	img.Height = int(compInfo[Y].downsampled_height)
	//fmt.Printf("%dx%d (format: %d)\n", img.Width, img.Height, img.Format)
	//fmt.Printf("Output: %dx%d\n", dinfo.output_width, dinfo.output_height)

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

	// Allocate image planes
	for i := 0; i < int(dinfo.num_components); i++ {
		/*fmt.Printf("%d: %dx%d (DCT %dx%d, %dx%d blocks sf %dx%d)\n", i,
		  compInfo[i].downsampled_width, compInfo[i].downsampled_height,
		  compInfo[i].DCT_scaled_size, compInfo[i].DCT_scaled_size,
		  compInfo[i].width_in_blocks, compInfo[i].height_in_blocks,
		  compInfo[i].h_samp_factor, compInfo[i].v_samp_factor)*/
		// When downsampling, odd modes like 14x14 may be used. Pad to AlignSize
		// (16) and then add an extra AlignSize padding, to cover overflow from
		// any such modes.
		img.Stride[i] = pad(int(compInfo[i].downsampled_width), AlignSize) + AlignSize
		height := pad(int(compInfo[i].downsampled_height), AlignSize) + AlignSize
		img.Data[i] = make([]byte, img.Stride[i]*height)
	}

	// Start decompression
	C.jpeg_start_decompress(dinfo)

	// 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 yuvPtrInt [3][AlignSize]C.JSAMPROW
	yuvPtr := [3]C.JSAMPARRAY{
		C.JSAMPARRAY(unsafe.Pointer(&yuvPtrInt[0][0])),
		C.JSAMPARRAY(unsafe.Pointer(&yuvPtrInt[1][0])),
		C.JSAMPARRAY(unsafe.Pointer(&yuvPtrInt[2][0])),
	}

	// Decode the image.
	var row C.JDIMENSION

	var iMCURows int
	for i := 0; i < int(dinfo.num_components); i++ {
		compRows := int(C.DCT_v_scaled_size(dinfo, C.int(i)) * compInfo[i].v_samp_factor)
		if compRows > iMCURows {
			iMCURows = compRows
		}
	}
	//fmt.Printf("iMCU_rows: %d (div: %d)\n", iMCURows, colorVDiv)
	for row = 0; row < dinfo.output_height; {
		// First fill in the pointers into the plane data buffers
		for i := 0; i < int(dinfo.num_components); i++ {
			for j := 0; j < iMCURows; j++ {
				compRow := (int(row) + j)
				if i > 0 {
					compRow = (int(row)/colorVDiv + j)
				}
				yuvPtrInt[i][j] = C.JSAMPROW(unsafe.Pointer(&img.Data[i][img.Stride[i]*compRow]))
			}
		}
		// Get the data
		row += C.jpeg_read_raw_data(dinfo, C.JSAMPIMAGE(unsafe.Pointer(&yuvPtr[0])), C.JDIMENSION(2*iMCURows))
	}

	// Clean up
	C.jpeg_finish_decompress(dinfo)

	return
}
Exemplo n.º 4
0
// WriteJPEG writes a YUVImage as a JPEG into dest.
func WriteJPEG(img *YUVImage, dest io.Writer, params CompressionParameters) (err error) {
	defer func() {
		if r := recover(); r != nil {
			img = nil
			var ok bool
			err, ok = r.(error)
			if !ok {
				err = fmt.Errorf("JPEG error: %v", r)
			}
		}
	}()

	cinfo := (*C.struct_jpeg_compress_struct)(C.malloc(C.size_t(unsafe.Sizeof(C.struct_jpeg_compress_struct{}))))
	if cinfo == nil {
		panic("Failed to allocate cinfo")
	}
	defer C.free(unsafe.Pointer(cinfo))
	cinfo.err = (*C.struct_jpeg_error_mgr)(C.malloc(C.size_t(unsafe.Sizeof(C.struct_jpeg_error_mgr{}))))
	if cinfo.err == nil {
		panic("Failed to allocate cinfo.err")
	}
	defer C.free(unsafe.Pointer(cinfo.err))

	// No subsampling suport for now
	if img.Format != YUV444 && img.Format != Grayscale {
		panic("Unsupported colorspace")
	}

	// Setup error handling
	C.jpeg_std_error(cinfo.err)
	cinfo.err.error_exit = (*[0]byte)(C.error_panic)

	// Initialize compression object
	C.c_jpeg_create_compress(cinfo)
	defer C.jpeg_destroy_compress(cinfo)

	destManager := makeDestinationManager(dest, cinfo)
	defer C.free(unsafe.Pointer(destManager))

	// Set up compression parameters
	cinfo.image_width = C.JDIMENSION(img.Width)
	cinfo.image_height = C.JDIMENSION(img.Height)
	cinfo.input_components = 3
	cinfo.in_color_space = C.JCS_YCbCr
	if img.Format == Grayscale {
		cinfo.input_components = 1
		cinfo.in_color_space = C.JCS_GRAYSCALE
	}

	C.jpeg_set_defaults(cinfo)
	C.jpeg_set_quality(cinfo, C.int(params.Quality), C.TRUE)
	if params.Optimize {
		cinfo.optimize_coding = C.TRUE
	} else {
		cinfo.optimize_coding = C.FALSE
	}
	C.jpeg_set_colorspace(cinfo, cinfo.in_color_space)
	if params.FastDCT {
		cinfo.dct_method = C.JDCT_IFAST
	} else {
		cinfo.dct_method = C.JDCT_ISLOW
	}
	compInfo := (*[3]C.jpeg_component_info)(unsafe.Pointer(cinfo.comp_info))

	for i := 0; i < int(cinfo.input_components); i++ {
		compInfo[i].h_samp_factor = 1
		compInfo[i].v_samp_factor = 1
	}

	// 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 yuvPtrInt [3][AlignSize]C.JSAMPROW
	yuvPtr := [3]C.JSAMPARRAY{
		C.JSAMPARRAY(unsafe.Pointer(&yuvPtrInt[0][0])),
		C.JSAMPARRAY(unsafe.Pointer(&yuvPtrInt[1][0])),
		C.JSAMPARRAY(unsafe.Pointer(&yuvPtrInt[2][0])),
	}

	// Encode the image.
	var row C.JDIMENSION
	for row = 0; row < cinfo.image_height; {
		// First fill in the pointers into the plane data buffers
		for i := 0; i < int(cinfo.num_components); i++ {
			for j := 0; j < int(C.DCTSIZE*compInfo[i].v_samp_factor); j++ {
				compRow := (int(row) + j)
				yuvPtrInt[i][j] = C.JSAMPROW(unsafe.Pointer(&img.Data[i][img.Stride[i]*compRow]))
			}
		}
		// Get the data
		row += C.jpeg_write_raw_data(cinfo, C.JSAMPIMAGE(unsafe.Pointer(&yuvPtr[0])), C.JDIMENSION(C.DCTSIZE*compInfo[0].v_samp_factor))
	}

	// Clean up
	C.jpeg_finish_compress(cinfo)

	return
}