// DecodeConfig returns the color model and dimensions of a JPEG image without decoding the entire image. func DecodeConfig(r io.Reader) (config image.Config, err error) { // Recover panic defer func() { if r := recover(); r != nil { var ok bool err, ok = r.(error) if !ok { err = fmt.Errorf("JPEG error: %v", r) } } }() dinfo := C.new_decompress() defer C.destroy_decompress(dinfo) srcManager := makeSourceManager(r, dinfo) defer releaseSourceManager(srcManager) C.jpeg_read_header(dinfo, C.TRUE) config = image.Config{ ColorModel: color.YCbCrModel, Width: int(dinfo.image_width), Height: int(dinfo.image_height), } return }
// DecodeIntoRGBA reads a JPEG data stream from r and returns decoded image as an image.RGBA with RGBA colors. // This function only works with libjpeg-trubo, not libjpeg. func DecodeIntoRGBA(r io.Reader, options *DecoderOptions) (dest *image.RGBA, err error) { // Recover panic defer func() { if r := recover(); r != nil { log.Println(r) if _, ok := r.(error); !ok { err = fmt.Errorf("JPEG error: %v", r) } } }() dinfo := C.new_decompress() defer C.destroy_decompress(dinfo) srcManager := makeSourceManager(r, dinfo) defer releaseSourceManager(srcManager) C.jpeg_read_header(dinfo, C.TRUE) setupDecoderOptions(dinfo, options) C.jpeg_calc_output_dimensions(dinfo) dest = image.NewRGBA(image.Rect(0, 0, int(dinfo.output_width), int(dinfo.output_height))) colorSpace := C.getJCS_EXT_RGBA() if colorSpace == C.JCS_UNKNOWN { return nil, errors.New("JCS_EXT_RGBA is not supported (probably built without libjpeg-trubo)") } dinfo.out_color_space = colorSpace readScanLines(dinfo, dest.Pix, dest.Stride) return }
// DecodeIntoRGB reads a JPEG data stream from r and returns decoded image as an rgb.Image with RGB colors. func DecodeIntoRGB(r io.Reader, options *DecoderOptions) (dest *rgb.Image, err error) { // Recover panic defer func() { if r := recover(); r != nil { log.Println(r) if _, ok := r.(error); !ok { err = fmt.Errorf("JPEG error: %v", r) } } }() dinfo := C.new_decompress() defer C.destroy_decompress(dinfo) srcManager := makeSourceManager(r, dinfo) defer releaseSourceManager(srcManager) C.jpeg_read_header(dinfo, C.TRUE) setupDecoderOptions(dinfo, options) C.jpeg_calc_output_dimensions(dinfo) dest = rgb.NewImage(image.Rect(0, 0, int(dinfo.output_width), int(dinfo.output_height))) dinfo.out_color_space = C.JCS_RGB readScanLines(dinfo, dest.Pix, dest.Stride) return }
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 }
// Decode reads a JPEG data stream from r and returns decoded image as an image.Image. // Output image has YCbCr colors or 8bit Grayscale. func Decode(r io.Reader, options *DecoderOptions) (dest image.Image, err error) { // Recover panic defer func() { if r := recover(); r != nil { log.Println(r) if _, ok := r.(error); !ok { err = fmt.Errorf("JPEG error: %v", r) } } }() dinfo := C.new_decompress() defer C.destroy_decompress(dinfo) srcManager := makeSourceManager(r, dinfo) defer releaseSourceManager(srcManager) C.jpeg_read_header(dinfo, C.TRUE) setupDecoderOptions(dinfo, options) switch dinfo.num_components { case 1: if dinfo.jpeg_color_space != C.JCS_GRAYSCALE { return nil, errors.New("Image has unsupported colorspace") } dest, err = decodeGray(dinfo) case 3: switch dinfo.jpeg_color_space { case C.JCS_YCbCr: dest, err = decodeYCbCr(dinfo) case C.JCS_RGB: dest, err = decodeRGB(dinfo) default: return nil, errors.New("Image has unsupported colorspace") } } return }
// 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 }