diff --git a/jpeg/compress.go b/jpeg/compress.go index 2fed1e7..87cabea 100644 --- a/jpeg/compress.go +++ b/jpeg/compress.go @@ -6,16 +6,26 @@ package jpeg #include "jpeglib.h" #include "jpeg.h" -void error_panic(j_common_ptr cinfo); - static struct jpeg_compress_struct *new_compress(void) { - struct jpeg_compress_struct *cinfo = (struct jpeg_compress_struct *)calloc(sizeof(struct jpeg_compress_struct), 1); - struct jpeg_error_mgr *jerr = (struct jpeg_error_mgr *)calloc(sizeof(struct jpeg_error_mgr), 1); + struct jpeg_compress_struct *cinfo = (struct jpeg_compress_struct *) calloc(sizeof(struct jpeg_compress_struct), 1); + if (!cinfo) { + return NULL; + } + + struct my_error_mgr *jerr = (struct my_error_mgr *)calloc(sizeof(struct my_error_mgr), 1); + if (!jerr) { + free(cinfo); + return NULL; + } - jpeg_std_error(jerr); - jerr->error_exit = (void *)error_panic; + cinfo->err = jpeg_std_error(&jerr->pub); + jerr->pub.error_exit = (void *)error_longjmp; + if (setjmp(jerr->jmpbuf) != 0) { + free(jerr); + free(cinfo); + return NULL; + } jpeg_create_compress(cinfo); - cinfo->err = jerr; return cinfo; } @@ -26,58 +36,104 @@ static void destroy_compress(struct jpeg_compress_struct *cinfo) { free(cinfo); } -static void encode_gray(j_compress_ptr cinfo, JSAMPROW pix, int stride) { - // 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. - JSAMPROW *rows = alloca(sizeof(JSAMPROW) * ALIGN_SIZE); - - int v = 0; - for (v = 0; v < cinfo->image_height; ) { - // First fill in the pointers into the plane data buffers - int h = 0; - for (h = 0; h < DCTSIZE * cinfo->comp_info[0].v_samp_factor; h++) { - rows[h] = &pix[stride * (v + h)]; - } - // Get the data - v += jpeg_write_raw_data(cinfo, &rows, DCTSIZE * cinfo->comp_info[0].v_samp_factor); +static JDIMENSION write_scanlines(j_compress_ptr cinfo, JSAMPROW row, JDIMENSION max_lines, int *msg_code) { + // handle error + struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err; + if (setjmp(err->jmpbuf) != 0) { + *msg_code = err->pub.msg_code; + return 0; } + + *msg_code = 0; + return jpeg_write_scanlines(cinfo, &row, max_lines); } -static void encode_rgba(j_compress_ptr cinfo, JSAMPROW pix, int stride) { - JSAMPROW rows[1]; +static JDIMENSION write_mcu_gray(struct jpeg_compress_struct *cinfo, JSAMPROW pix, int stride, int *msg_code) { + // handle error + struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err; + if (setjmp(err->jmpbuf) != 0) { + *msg_code = err->pub.msg_code; + return 0; + } + + // Set height to one MCU size + // because jpeg_write_raw_data processes just one MCU row per call. + int height = DCTSIZE * cinfo->comp_info[0].v_samp_factor; - int v; - for (v = 0; v < cinfo->image_height; ) { - rows[0] = &pix[v * stride]; - v += jpeg_write_scanlines(cinfo, rows, 1); + // 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. + JSAMPROW *rows = alloca(sizeof(JSAMPROW *) * height); + + // First fill in the pointers into the plane data buffers + int h = 0; + for (h = 0; h < height; h++) { + rows[h] = &pix[stride * h]; } + + // Get the data + *msg_code = 0; + return jpeg_write_raw_data(cinfo, &rows, height); } +static JDIMENSION write_mcu_ycbcr(struct jpeg_compress_struct *cinfo, JSAMPROW y_row, JSAMPROW cb_row, JSAMPROW cr_row, int y_stride, int c_stride, int *msg_code) { + // handle error + struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err; + if (setjmp(err->jmpbuf) != 0) { + *msg_code = err->pub.msg_code; + return 0; + } -static void encode_ycbcr(j_compress_ptr cinfo, JSAMPROW y_row, JSAMPROW cb_row, JSAMPROW cr_row, int y_stride, int c_stride, int color_v_div) { // 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. - JSAMPROW *y_rows = alloca(sizeof(JSAMPROW) * ALIGN_SIZE); - JSAMPROW *cb_rows = alloca(sizeof(JSAMPROW) * ALIGN_SIZE); - JSAMPROW *cr_rows = alloca(sizeof(JSAMPROW) * ALIGN_SIZE); - JSAMPARRAY image[] = { y_rows, cb_rows, cr_rows }; - - int v = 0; - for (v = 0; v < cinfo->image_height; ) { - int h = 0; - // First fill in the pointers into the plane data buffers - for (h = 0; h < DCTSIZE * cinfo->comp_info[0].v_samp_factor; h++) { - y_rows[h] = &y_row[y_stride * (v + h)]; - } - for (h = 0; h < DCTSIZE * cinfo->comp_info[1].v_samp_factor; h++) { - cb_rows[h] = &cb_row[c_stride * (v / color_v_div + h)]; - cr_rows[h] = &cr_row[c_stride * (v / color_v_div + h)]; - } - // Get the data - v += jpeg_write_raw_data(cinfo, image, DCTSIZE * cinfo->comp_info[0].v_samp_factor); + int y_h = DCTSIZE * cinfo->comp_info[0].v_samp_factor; + int c_h = DCTSIZE * cinfo->comp_info[1].v_samp_factor; + JSAMPROW *y_rows = alloca(sizeof(JSAMPROW) * y_h); + JSAMPROW *cb_rows = alloca(sizeof(JSAMPROW) * c_h); + JSAMPROW *cr_rows = alloca(sizeof(JSAMPROW) * c_h); + JSAMPARRAY image[] = {y_rows, cb_rows, cr_rows}; + int h = 0; + + // First fill in the pointers into the plane data buffers + for (h = 0; h < y_h; h++) { + y_rows[h] = &y_row[y_stride * h]; + } + + for (h = 0; h < c_h; h++) { + cb_rows[h] = &cb_row[c_stride * h]; + cr_rows[h] = &cr_row[c_stride * h]; + } + + // Get the data + *msg_code = 0; + return jpeg_write_raw_data(cinfo, image, y_h); +} + +static int start_compress(j_compress_ptr cinfo, boolean write_all_tables) +{ + // handle error + struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err; + if (setjmp(err->jmpbuf) != 0) { + return err->pub.msg_code; + } + + jpeg_start_compress(cinfo, write_all_tables); + + return 0; +} + +static int finish_compress(j_compress_ptr cinfo) +{ + // handle error + struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err; + if (setjmp(err->jmpbuf) != 0) { + return err->pub.msg_code; } + + jpeg_finish_compress(cinfo); + + return 0; } */ @@ -85,7 +141,6 @@ import "C" import ( "errors" - "fmt" "image" "io" "unsafe" @@ -99,24 +154,79 @@ type EncoderOptions struct { DCTMethod DCTMethod } -// Encode encodes src image and writes into w as JPEG format data. -func Encode(w io.Writer, src image.Image, opt *EncoderOptions) (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) - } - } - }() +func newCompress(w io.Writer) (cinfo *C.struct_jpeg_compress_struct, err error) { + cinfo = C.new_compress() + if cinfo == nil { + err = errors.New("failed to allocate jpeg encoder") + return + } - cinfo := C.new_compress() - defer C.destroy_compress(cinfo) + _, err = makeDestinationManager(w, cinfo) + return +} - dstManager := makeDestinationManager(w, cinfo) - defer releaseDestinationManager(dstManager) +func startCompress(cinfo *C.struct_jpeg_compress_struct) error { + code := C.start_compress(cinfo, C.TRUE) + if code != 0 { + return errors.New(jpegErrorMessage(unsafe.Pointer(cinfo))) + } + return nil +} + +func destroyCompress(cinfo *C.struct_jpeg_compress_struct) { + if cinfo == nil { + return + } + destinationManager := getDestinationManager(cinfo) + if destinationManager != nil { + releaseDestinationManager(destinationManager) + } + C.destroy_compress(cinfo) +} + +func finishCompress(cinfo *C.struct_jpeg_compress_struct) error { + code := C.finish_compress(cinfo) + if code != 0 { + return errors.New(jpegErrorMessage(unsafe.Pointer(cinfo))) + } + return nil +} + +func writeScanline(cinfo *C.struct_jpeg_compress_struct, row C.JSAMPROW, maxLines C.JDIMENSION) (line int, err error) { + code := C.int(0) + line = int(C.write_scanlines(cinfo, row, maxLines, &code)) + if code != 0 { + err = errors.New(jpegErrorMessage(unsafe.Pointer(cinfo))) + } + return +} + +func writeMCUGray(cinfo *C.struct_jpeg_compress_struct, row C.JSAMPROW, stride int) (line int, err error) { + code := C.int(0) + line = int(C.write_mcu_gray(cinfo, row, C.int(stride), &code)) + if code != 0 { + err = errors.New(jpegErrorMessage(unsafe.Pointer(cinfo))) + } + return +} + +func writeMCUYCbCr(cinfo *C.struct_jpeg_compress_struct, y, cb, cr C.JSAMPROW, yStride, cStride int) (line int, err error) { + code := C.int(0) + line = int(C.write_mcu_ycbcr(cinfo, y, cb, cr, C.int(yStride), C.int(cStride), &code)) + if code != 0 { + err = errors.New(jpegErrorMessage(unsafe.Pointer(cinfo))) + } + return +} + +// Encode encodes src image and writes into w as JPEG format data. +func Encode(w io.Writer, src image.Image, opt *EncoderOptions) (err error) { + var cinfo *C.struct_jpeg_compress_struct + cinfo, err = newCompress(w) + if err != nil { + return + } + defer destroyCompress(cinfo) switch s := src.(type) { case *image.YCbCr: @@ -135,16 +245,16 @@ func Encode(w io.Writer, src image.Image, opt *EncoderOptions) (err error) { // 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()) + w, h := src.Bounds().Dx(), src.Bounds().Dy() + cinfo.image_width = C.JDIMENSION(w) + cinfo.image_height = C.JDIMENSION(h) 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 + cVDiv := 1 switch src.SubsampleRatio { case image.YCbCrSubsampleRatio444: // 1x1,1x1,1x1 @@ -156,7 +266,7 @@ func encodeYCbCr(cinfo *C.struct_jpeg_compress_struct, src *image.YCbCr, p *Enco 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 + cVDiv = 2 case image.YCbCrSubsampleRatio422: // 2x1,1x1,1x1 compInfo[Y].h_samp_factor, compInfo[Y].v_samp_factor = 2, 1 @@ -167,7 +277,7 @@ func encodeYCbCr(cinfo *C.struct_jpeg_compress_struct, src *image.YCbCr, p *Enco 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 + cVDiv = 2 } // libjpeg raw data in is in planar format, which avoids unnecessary @@ -175,50 +285,80 @@ func encodeYCbCr(cinfo *C.struct_jpeg_compress_struct, src *image.YCbCr, p *Enco 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) + err = startCompress(cinfo) + if err != nil { + return + } + defer func() { + ferr := finishCompress(cinfo) + if ferr != nil && err == nil { + err = ferr + } + }() + + for v := 0; v < h; { + yOff, cOff := v*src.YStride, v/cVDiv*src.CStride + line, err := writeMCUYCbCr( + cinfo, + C.JSAMPROW(unsafe.Pointer(&src.Y[yOff])), + C.JSAMPROW(unsafe.Pointer(&src.Cb[cOff])), + C.JSAMPROW(unsafe.Pointer(&src.Cr[cOff])), + src.YStride, + src.CStride, + ) + if err != nil { + return err + } + v += line + } return } // encode image.RGBA func encodeRGBA(cinfo *C.struct_jpeg_compress_struct, src *image.RGBA, p *EncoderOptions) (err error) { // Set up compression parameters - cinfo.image_width = C.JDIMENSION(src.Bounds().Dx()) - cinfo.image_height = C.JDIMENSION(src.Bounds().Dy()) + w, h := src.Bounds().Dx(), src.Bounds().Dy() + cinfo.image_width = C.JDIMENSION(w) + cinfo.image_height = C.JDIMENSION(h) cinfo.input_components = 4 cinfo.in_color_space = getJCS_EXT_RGBA() if cinfo.in_color_space == C.JCS_UNKNOWN { return errors.New("JCS_EXT_RGBA is not supported (probably built without libjpeg-turbo)") } - C.jpeg_set_defaults(cinfo) setupEncoderOptions(cinfo, p) // Start compression - C.jpeg_start_compress(cinfo, C.TRUE) - C.encode_rgba(cinfo, C.JSAMPROW(unsafe.Pointer(&src.Pix[0])), C.int(src.Stride)) - C.jpeg_finish_compress(cinfo) + err = startCompress(cinfo) + if err != nil { + return + } + defer func() { + ferr := finishCompress(cinfo) + if ferr != nil && err == nil { + err = ferr + } + }() + + for v := 0; v < h; { + line, err := writeScanline(cinfo, C.JSAMPROW(unsafe.Pointer(&src.Pix[v*src.Stride])), C.JDIMENSION(1)) + if err != nil { + return err + } + v += line + } return } // encode image.Gray func encodeGray(cinfo *C.struct_jpeg_compress_struct, src *image.Gray, p *EncoderOptions) (err error) { // Set up compression parameters - cinfo.image_width = C.JDIMENSION(src.Bounds().Dx()) - cinfo.image_height = C.JDIMENSION(src.Bounds().Dy()) + w, h := src.Bounds().Dx(), src.Bounds().Dy() + cinfo.image_width = C.JDIMENSION(w) + cinfo.image_height = C.JDIMENSION(h) cinfo.input_components = 1 cinfo.in_color_space = C.JCS_GRAYSCALE - C.jpeg_set_defaults(cinfo) setupEncoderOptions(cinfo, p) compInfo := (*C.jpeg_component_info)(unsafe.Pointer(cinfo.comp_info)) @@ -229,13 +369,29 @@ func encodeGray(cinfo *C.struct_jpeg_compress_struct, src *image.Gray, p *Encode cinfo.raw_data_in = C.TRUE // Start compression - C.jpeg_start_compress(cinfo, C.TRUE) - C.encode_gray(cinfo, C.JSAMPROW(unsafe.Pointer(&src.Pix[0])), C.int(src.Stride)) - C.jpeg_finish_compress(cinfo) + err = startCompress(cinfo) + if err != nil { + return + } + defer func() { + ferr := finishCompress(cinfo) + if ferr != nil && err == nil { + err = ferr + } + }() + + for v := 0; v < h; { + line, err := writeMCUGray(cinfo, C.JSAMPROW(unsafe.Pointer(&src.Pix[v*src.Stride])), src.Stride) + if err != nil { + return err + } + v += line + } return } func setupEncoderOptions(cinfo *C.struct_jpeg_compress_struct, opt *EncoderOptions) { + C.jpeg_set_defaults(cinfo) C.jpeg_set_quality(cinfo, C.int(opt.Quality), C.TRUE) if opt.OptimizeCoding { cinfo.optimize_coding = C.TRUE diff --git a/jpeg/decompress.go b/jpeg/decompress.go index 8143a4b..5be1754 100644 --- a/jpeg/decompress.go +++ b/jpeg/decompress.go @@ -15,31 +15,80 @@ static struct jpeg_decompress_struct *new_decompress(void) { return NULL; } - struct jpeg_error_mgr *jerr = (struct jpeg_error_mgr *)calloc(sizeof(struct jpeg_error_mgr), 1); + struct my_error_mgr *jerr = (struct my_error_mgr *)calloc(sizeof(struct my_error_mgr), 1); if (!jerr) { free(dinfo); return NULL; } - dinfo->err = jpeg_std_error(jerr); - jerr->error_exit = (void *)error_panic; + dinfo->err = jpeg_std_error(&jerr->pub); + jerr->pub.error_exit = (void *)error_longjmp; + if (setjmp(jerr->jmpbuf) != 0) { + free(jerr); + free(dinfo); + return NULL; + } jpeg_create_decompress(dinfo); return dinfo; } +static int start_decompress(j_decompress_ptr dinfo) +{ + // handle error + struct my_error_mgr *err = (struct my_error_mgr *)dinfo->err; + if (setjmp(err->jmpbuf) != 0) { + return err->pub.msg_code; + } + + jpeg_start_decompress(dinfo); + return 0; +} + +static int finish_decompress(j_decompress_ptr dinfo) +{ + // handle error + struct my_error_mgr *err = (struct my_error_mgr *)dinfo->err; + if (setjmp(err->jmpbuf) != 0) { + return err->pub.msg_code; + } + + jpeg_finish_decompress(dinfo); + return 0; +} + static void destroy_decompress(struct jpeg_decompress_struct *dinfo) { free(dinfo->err); jpeg_destroy_decompress(dinfo); free(dinfo); } -static JDIMENSION read_scanlines(j_decompress_ptr dinfo, unsigned char *buf, int stride, int height) { +static int read_header(struct jpeg_decompress_struct *dinfo, int req_img) +{ + // handle error + struct my_error_mgr *err = (struct my_error_mgr *)dinfo->err; + if (setjmp(err->jmpbuf) != 0) { + return err->pub.msg_code; + } + + jpeg_read_header(dinfo, req_img); + return 0; +} + +static JDIMENSION read_scanlines(j_decompress_ptr dinfo, unsigned char *buf, int stride, int height, int *msg_code) { + // handle error + struct my_error_mgr *err = (struct my_error_mgr *)dinfo->err; + if (setjmp(err->jmpbuf) != 0) { + *msg_code = err->pub.msg_code; + return 0; + } + JSAMPROW *rows = alloca(sizeof(JSAMPROW) * height); int i; for (i = 0; i < height; i++) { rows[i] = &buf[i * stride]; } + *msg_code = 0; return jpeg_read_scanlines(dinfo, rows, height); } @@ -51,38 +100,53 @@ static int DCT_v_scaled_size(j_decompress_ptr dinfo, int component) { #endif } -static void decode_gray(j_decompress_ptr dinfo, JSAMPROW pix, int stride, int imcu_rows) { - JSAMPROW *rows = alloca(sizeof(JSAMPROW) * ALIGN_SIZE); - while (dinfo->output_scanline < dinfo->output_height) { - int h = 0; - for (h = 0; h < imcu_rows; h++) { - rows[h] = &pix[stride*(dinfo->output_scanline + h)]; - } - // Get the data - jpeg_read_raw_data(dinfo, &rows, 2 * imcu_rows); +static JDIMENSION read_mcu_gray(struct jpeg_decompress_struct *dinfo, JSAMPROW pix, int stride, int imcu_rows, int *msg_code) { + // handle error + struct my_error_mgr *err = (struct my_error_mgr *)dinfo->err; + if (setjmp(err->jmpbuf) != 0) { + *msg_code = err->pub.msg_code; + return 0; + } + + JSAMPROW *rows = alloca(sizeof(JSAMPROW) * imcu_rows); + int h = 0; + for (h = 0; h < imcu_rows; h++) { + rows[h] = &pix[stride * h]; } + + // Get the data + *msg_code = 0; + return jpeg_read_raw_data(dinfo, &rows, imcu_rows); } -static void decode_ycbcr(j_decompress_ptr dinfo, JSAMPROW y_row, JSAMPROW cb_row, JSAMPROW cr_row, int y_stride, int c_stride, int color_v_div, int imcu_rows) { +static JDIMENSION read_mcu_ycbcr(struct jpeg_decompress_struct *dinfo, JSAMPROW y_row, JSAMPROW cb_row, JSAMPROW cr_row, int y_stride, int c_stride, int imcu_rows, int *msg_code) { + // handle error + struct my_error_mgr *err = (struct my_error_mgr *)dinfo->err; + if (setjmp(err->jmpbuf) != 0) { + *msg_code = err->pub.msg_code; + return 0; + } + // 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. - JSAMPROW *y_rows = alloca(sizeof(JSAMPROW) * ALIGN_SIZE); - JSAMPROW *cb_rows = alloca(sizeof(JSAMPROW) * ALIGN_SIZE); - JSAMPROW *cr_rows = alloca(sizeof(JSAMPROW) * ALIGN_SIZE); - JSAMPARRAY image[] = { y_rows, cb_rows, cr_rows }; - - while (dinfo->output_scanline < dinfo->output_height) { - // First fill in the pointers into the plane data buffers - int h = 0; - for (h = 0; h < imcu_rows; h++) { - y_rows[h] = &y_row[y_stride*(dinfo->output_scanline+h)]; - cb_rows[h] = &cb_row[c_stride*(dinfo->output_scanline/color_v_div+h)]; - cr_rows[h] = &cr_row[c_stride*(dinfo->output_scanline/color_v_div+h)]; - } - // Get the data - jpeg_read_raw_data(dinfo, image, 2 * imcu_rows); + JSAMPROW *y_rows = alloca(sizeof(JSAMPROW) * imcu_rows); + JSAMPROW *cb_rows = alloca(sizeof(JSAMPROW) * imcu_rows); + JSAMPROW *cr_rows = alloca(sizeof(JSAMPROW) * imcu_rows); + JSAMPARRAY image[] = {y_rows, cb_rows, cr_rows}; + int x = 0; + + // First fill in the pointers into the plane data buffers + int h = 0; + for (h = 0; h < imcu_rows; h++) { + y_rows[h] = &y_row[y_stride * h]; + cb_rows[h] = &cb_row[c_stride * h]; + cr_rows[h] = &cr_row[c_stride * h]; } + + // Get the data + *msg_code = 0; + return jpeg_read_raw_data(dinfo, image, imcu_rows); } */ @@ -119,6 +183,56 @@ func destroyDecompress(dinfo *C.struct_jpeg_decompress_struct) { C.destroy_decompress(dinfo) } +func readHeader(dinfo *C.struct_jpeg_decompress_struct) error { + if C.read_header(dinfo, C.TRUE) != 0 { + return errors.New(jpegErrorMessage(unsafe.Pointer(dinfo))) + } + return nil +} + +func startDecompress(dinfo *C.struct_jpeg_decompress_struct) error { + if C.start_decompress(dinfo) != 0 { + return errors.New(jpegErrorMessage(unsafe.Pointer(dinfo))) + } + return nil +} + +func finishDecompress(dinfo *C.struct_jpeg_decompress_struct) error { + if C.finish_decompress(dinfo) != 0 { + return errors.New(jpegErrorMessage(unsafe.Pointer(dinfo))) + } + return nil +} + +func readScanlines(dinfo *C.struct_jpeg_decompress_struct, row *C.uchar, stride, height C.int) (lines C.JDIMENSION, err error) { + code := C.int(0) + lines = C.read_scanlines(dinfo, row, stride, height, &code) + if code != 0 { + err = errors.New(jpegErrorMessage(unsafe.Pointer(dinfo))) + } else if lines == 0 { + err = errors.New("unexpected EOF") + } + return +} + +func readMCUGray(dinfo *C.struct_jpeg_decompress_struct, pix C.JSAMPROW, stride, iMCURows int) (line C.JDIMENSION, err error) { + code := C.int(0) + line = C.read_mcu_gray(dinfo, pix, C.int(stride), C.int(iMCURows), &code) + if code != 0 { + err = errors.New(jpegErrorMessage(unsafe.Pointer(dinfo))) + } + return +} + +func readMCUYCbCr(dinfo *C.struct_jpeg_decompress_struct, y, cb, cr C.JSAMPROW, yStride, cStride int, iMCURows int) (line C.JDIMENSION, err error) { + code := C.int(0) + line = C.read_mcu_ycbcr(dinfo, y, cb, cr, C.int(yStride), C.int(cStride), C.int(iMCURows), &code) + if code != 0 { + err = errors.New(jpegErrorMessage(unsafe.Pointer(dinfo))) + } + return +} + // DecoderOptions specifies JPEG decoding parameters. type DecoderOptions struct { ScaleTarget image.Rectangle // ScaleTarget is the target size to scale image. @@ -144,22 +258,17 @@ func Decode(r io.Reader, options *DecoderOptions) (dest image.Image, err error) } defer destroyDecompress(dinfo) - // Recover panic - defer func() { - if r := recover(); r != nil { - if _, ok := r.(error); !ok { - err = fmt.Errorf("JPEG error: %v", r) - } - } - }() + err = readHeader(dinfo) + if err != nil { + return nil, err + } - 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") + return nil, errors.New("unsupported colorspace") } dest, err = decodeGray(dinfo) case 3: @@ -169,8 +278,10 @@ func Decode(r io.Reader, options *DecoderOptions) (dest image.Image, err error) case C.JCS_RGB: dest, err = decodeRGB(dinfo) default: - return nil, errors.New("Image has unsupported colorspace") + return nil, errors.New("unsupported colorspace") } + default: + return nil, fmt.Errorf("unsupported number of components: %d", dinfo.num_components) } return } @@ -179,16 +290,28 @@ func decodeGray(dinfo *C.struct_jpeg_decompress_struct) (dest *image.Gray, err e // output dawnsampled raw data before starting decompress dinfo.raw_data_out = C.TRUE - C.jpeg_start_decompress(dinfo) + err = startDecompress(dinfo) + if err != nil { + return nil, err + } + defer func() { + ferr := finishDecompress(dinfo) + if ferr != nil && err == nil { + err = ferr + } + }() compInfo := (*[1]C.jpeg_component_info)(unsafe.Pointer(dinfo.comp_info)) dest = NewGrayAligned(image.Rect(0, 0, int(compInfo[0].downsampled_width), int(compInfo[0].downsampled_height))) iMCURows := int(C.DCT_v_scaled_size(dinfo, C.int(0)) * compInfo[0].v_samp_factor) - C.decode_gray(dinfo, C.JSAMPROW(unsafe.Pointer(&dest.Pix[0])), C.int(dest.Stride), C.int(iMCURows)) - - C.jpeg_finish_decompress(dinfo) + for dinfo.output_scanline < dinfo.output_height { + _, err = readMCUGray(dinfo, C.JSAMPROW(unsafe.Pointer(&dest.Pix[dest.Stride*int(dinfo.output_scanline)])), dest.Stride, iMCURows) + if err != nil { + return + } + } return } @@ -196,7 +319,10 @@ func decodeYCbCr(dinfo *C.struct_jpeg_decompress_struct) (dest *image.YCbCr, err // output dawnsampled raw data before starting decompress dinfo.raw_data_out = C.TRUE - C.jpeg_start_decompress(dinfo) + err = startDecompress(dinfo) + if err != nil { + return nil, err + } compInfo := (*[3]C.jpeg_component_info)(unsafe.Pointer(dinfo.comp_info)) @@ -213,18 +339,18 @@ func decodeYCbCr(dinfo *C.struct_jpeg_decompress_struct) (dest *image.YCbCr, err // to use, if any, are complex, instead just check the calculated // output plane sizes and infer the subsampling mode from that. var subsampleRatio image.YCbCrSubsampleRatio - colorVDiv := 1 + cVDiv := 1 switch { case dwY == dwC && dhY == dhC: subsampleRatio = image.YCbCrSubsampleRatio444 case dwY == dwC && (dhY+1)/2 == dhC: subsampleRatio = image.YCbCrSubsampleRatio440 - colorVDiv = 2 + cVDiv = 2 case (dwY+1)/2 == dwC && dhY == dhC: subsampleRatio = image.YCbCrSubsampleRatio422 case (dwY+1)/2 == dwC && (dhY+1)/2 == dhC: subsampleRatio = image.YCbCrSubsampleRatio420 - colorVDiv = 2 + cVDiv = 2 default: return nil, errors.New("Unsupported color subsampling") } @@ -239,19 +365,39 @@ func decodeYCbCr(dinfo *C.struct_jpeg_decompress_struct) (dest *image.YCbCr, err iMCURows = compRows } } - //fmt.Printf("iMCU_rows: %d (div: %d)\n", iMCURows, colorVDiv) + yStride, cStride := dest.YStride, dest.CStride - C.decode_ycbcr(dinfo, - C.JSAMPROW(unsafe.Pointer(&dest.Y[0])), - C.JSAMPROW(unsafe.Pointer(&dest.Cb[0])), - C.JSAMPROW(unsafe.Pointer(&dest.Cr[0])), - C.int(dest.YStride), - C.int(dest.CStride), - C.int(colorVDiv), - C.int(iMCURows), - ) + for dinfo.output_scanline < dinfo.output_height { + y := C.JSAMPROW(unsafe.Pointer(&dest.Y[yStride*int(dinfo.output_scanline)])) + cb := C.JSAMPROW(unsafe.Pointer(&dest.Cb[cStride*int(dinfo.output_scanline)/cVDiv])) + cr := C.JSAMPROW(unsafe.Pointer(&dest.Cr[cStride*int(dinfo.output_scanline)/cVDiv])) + _, err = readMCUYCbCr(dinfo, y, cb, cr, yStride, cStride, iMCURows) + if err != nil { + return + } + } + return +} - C.jpeg_finish_decompress(dinfo) +func readRGBScanlines(dinfo *C.struct_jpeg_decompress_struct, pix []uint8, stride int) (err error) { + err = startDecompress(dinfo) + if err != nil { + return + } + defer func() { + ferr := finishDecompress(dinfo) + if ferr != nil && err == nil { + err = ferr + } + }() + + for dinfo.output_scanline < dinfo.output_height { + pbuf := (*C.uchar)(unsafe.Pointer(&pix[stride*int(dinfo.output_scanline)])) + _, err = readScanlines(dinfo, pbuf, C.int(stride), dinfo.rec_outbuf_height) + if err != nil { + return + } + } return } @@ -261,7 +407,7 @@ func decodeRGB(dinfo *C.struct_jpeg_decompress_struct) (dest *rgb.Image, err err 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) + err = readRGBScanlines(dinfo, dest.Pix, dest.Stride) return } @@ -273,24 +419,13 @@ func DecodeIntoRGB(r io.Reader, options *DecoderOptions) (dest *rgb.Image, err e } defer destroyDecompress(dinfo) - // Recover panic - defer func() { - if r := recover(); r != nil { - if _, ok := r.(error); !ok { - err = fmt.Errorf("JPEG error: %v", r) - } - } - }() + err = readHeader(dinfo) + if err != nil { + return nil, err + } - 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 + return decodeRGB(dinfo) } // DecodeIntoRGBA reads a JPEG data stream from r and returns decoded image as an image.RGBA with RGBA colors. @@ -311,7 +446,11 @@ func DecodeIntoRGBA(r io.Reader, options *DecoderOptions) (dest *image.RGBA, err } }() - C.jpeg_read_header(dinfo, C.TRUE) + err = readHeader(dinfo) + if err != nil { + return nil, err + } + setupDecoderOptions(dinfo, options) C.jpeg_calc_output_dimensions(dinfo) @@ -321,19 +460,10 @@ func DecodeIntoRGBA(r io.Reader, options *DecoderOptions) (dest *image.RGBA, err if colorSpace == C.JCS_UNKNOWN { return nil, errors.New("JCS_EXT_RGBA is not supported (probably built without libjpeg-turbo)") } - dinfo.out_color_space = colorSpace - readScanLines(dinfo, dest.Pix, dest.Stride) - return -} + err = readRGBScanlines(dinfo, dest.Pix, dest.Stride) -func readScanLines(dinfo *C.struct_jpeg_decompress_struct, buf []uint8, stride int) { - C.jpeg_start_decompress(dinfo) - for dinfo.output_scanline < dinfo.output_height { - pbuf := (*C.uchar)(unsafe.Pointer(&buf[stride*int(dinfo.output_scanline)])) - C.read_scanlines(dinfo, pbuf, C.int(stride), dinfo.rec_outbuf_height) - } - C.jpeg_finish_decompress(dinfo) + return } // DecodeConfig returns the color model and dimensions of a JPEG image without decoding the entire image. @@ -354,7 +484,10 @@ func DecodeConfig(r io.Reader) (config image.Config, err error) { } }() - C.jpeg_read_header(dinfo, C.TRUE) + err = readHeader(dinfo) + if err != nil { + return + } config = image.Config{ ColorModel: color.YCbCrModel, diff --git a/jpeg/destinationManager.go b/jpeg/destinationManager.go index ead87f1..e09a3c7 100644 --- a/jpeg/destinationManager.go +++ b/jpeg/destinationManager.go @@ -29,6 +29,7 @@ static void free_jpeg_destination_mgr(struct jpeg_destination_mgr *p) { import "C" import ( + "errors" "io" "sync" "unsafe" @@ -61,26 +62,29 @@ func destinationInit(cinfo *C.struct_jpeg_compress_struct) { // do nothing } -func flushBuffer(mgr *destinationManager, inBuffer int) { +func flushBuffer(mgr *destinationManager, inBuffer int) error { wrote := 0 for wrote != inBuffer { slice := C.GoBytes(unsafe.Pointer(uintptr(mgr.buffer)+uintptr(wrote)), C.int(inBuffer-wrote)) bytes, err := mgr.dest.Write(slice) if err != nil { - releaseDestinationManager(mgr) - panic(err) + return err } - wrote += int(bytes) + wrote += bytes } mgr.pub.free_in_buffer = writeBufferSize mgr.pub.next_output_byte = (*C.JOCTET)(mgr.buffer) + return nil } //export destinationEmpty func destinationEmpty(cinfo *C.struct_jpeg_compress_struct) C.boolean { // need to write *entire* buffer, not subtracting free_in_buffer mgr := getDestinationManager(cinfo) - flushBuffer(mgr, writeBufferSize) + err := flushBuffer(mgr, writeBufferSize) + if err != nil { + return C.FALSE + } return C.TRUE } @@ -89,19 +93,22 @@ func destinationTerm(cinfo *C.struct_jpeg_compress_struct) { // just empty buffer mgr := getDestinationManager(cinfo) inBuffer := int(writeBufferSize - mgr.pub.free_in_buffer) - flushBuffer(mgr, inBuffer) + flushBuffer(mgr, inBuffer) // can ignore error here } -func makeDestinationManager(dest io.Writer, cinfo *C.struct_jpeg_compress_struct) (mgr *destinationManager) { +func makeDestinationManager(dest io.Writer, cinfo *C.struct_jpeg_compress_struct) (mgr *destinationManager, err error) { mgr = new(destinationManager) mgr.dest = dest mgr.pub = C.calloc_jpeg_destination_mgr() if mgr.pub == nil { - panic("Failed to allocate C.struct_jpeg_destination_mgr") + err = errors.New("failed to allocate C.struct_jpeg_destination_mgr") + return } mgr.buffer = C.calloc(writeBufferSize, 1) if mgr.buffer == nil { - panic("Failed to allocate buffer") + C.free_jpeg_destination_mgr(mgr.pub) + err = errors.New("failed to allocate buffer") + return } mgr.pub.init_destination = (*[0]byte)(C.destinationInit) mgr.pub.empty_output_buffer = (*[0]byte)(C.destinationEmpty) diff --git a/jpeg/error.go b/jpeg/error.go new file mode 100644 index 0000000..c49c8b5 --- /dev/null +++ b/jpeg/error.go @@ -0,0 +1,37 @@ +package jpeg + +/* +#include +#include +#include "jpeglib.h" + +static void error_message(j_common_ptr cinfo, char *buf, size_t capa) { + if (cinfo != NULL && cinfo->err != NULL) { + if (cinfo->err->format_message != NULL) { + (*cinfo->err->format_message)(cinfo, buf); + } + else { + snprintf(buf, capa, "JPEG error code %d", cinfo->err->msg_code); + } + } + else { + snprintf(buf, capa, "JPEG unknown error"); + } +} +*/ +import "C" + +import ( + "unsafe" +) + +func jpegErrorMessage(cinfo unsafe.Pointer) string { + buf := C.calloc(1, C.JMSG_LENGTH_MAX) + if buf == nil { + return "cannot allocate memory" + } + defer C.free(buf) + msg := (*C.char)(buf) + C.error_message(C.j_common_ptr(cinfo), msg, C.JMSG_LENGTH_MAX) + return C.GoString(msg) +} diff --git a/jpeg/error_c.c b/jpeg/error_c.c new file mode 100644 index 0000000..5fad056 --- /dev/null +++ b/jpeg/error_c.c @@ -0,0 +1,10 @@ +#ifndef __INTELLISENSE__ +#include "_cgo_export.h" +#endif +#include "jpeg.h" + +/* must not return */ +void error_longjmp(j_common_ptr cinfo) { + struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err; + longjmp(err->jmpbuf, err->pub.msg_code); +} diff --git a/jpeg/issue51_test.go b/jpeg/issue51_test.go new file mode 100644 index 0000000..1165a63 --- /dev/null +++ b/jpeg/issue51_test.go @@ -0,0 +1,19 @@ +package jpeg_test + +import ( + "bytes" + "testing" + + "github.com/pixiv/go-libjpeg/jpeg" +) + +var data = []byte("\xff\xd8\xff\xdb\x00C\x000000000000000" + + "00000000000000000000" + + "00000000000000000000" + + "00000000000\xff\xc9\x00\v\b00\x000" + + "\x01\x01\x14\x00\xff\xda\x00\b\x01\x010\x00?\x0000") + +// https://github.com/pixiv/go-libjpeg/issues/51 +func TestIssue51(t *testing.T) { + jpeg.Decode(bytes.NewReader(data), &jpeg.DecoderOptions{}) +} diff --git a/jpeg/jpeg.h b/jpeg/jpeg.h index e174f7e..1c01b9a 100644 --- a/jpeg/jpeg.h +++ b/jpeg/jpeg.h @@ -1,4 +1,27 @@ #pragma once +#include +#include +#include +#include "jpeglib.h" +#include "jerror.h" + // the dimension multiple to which data buffers should be aligned. #define ALIGN_SIZE 16 + +struct my_error_mgr { + struct jpeg_error_mgr pub; + jmp_buf jmpbuf; +}; + +#if defined(_WIN32) && !defined(__CYGWIN__) +// setjmp/longjmp occasionally crashes on Windows +// see https://github.com/golang/go/issues/13672 +// use __builtin_setjmp/longjmp for workaround +#undef setjmp +#define setjmp(b) __builtin_setjmp(b) +#undef longjmp +#define longjmp(b, c) __builtin_longjmp((b), 1) // __builtin_longjmp accepts only `1` as the secound argument +#endif + +void error_longjmp(j_common_ptr cinfo); diff --git a/jpeg/jpeg_test.go b/jpeg/jpeg_test.go index 7e02860..0522961 100644 --- a/jpeg/jpeg_test.go +++ b/jpeg/jpeg_test.go @@ -227,7 +227,7 @@ func TestDecodeAndEncode(t *testing.T) { }() if err := jpeg.Encode(w, img, &jpeg.EncoderOptions{Quality: 90}); err != nil { - t.Errorf("Encode returns error: %v", err) + t.Errorf("%s: Encode returns error: %v", file, err) } } } diff --git a/jpeg/sourceManager.go b/jpeg/sourceManager.go index 78ed674..b1ccc47 100644 --- a/jpeg/sourceManager.go +++ b/jpeg/sourceManager.go @@ -37,11 +37,22 @@ static void free_jpeg_source_mgr(struct jpeg_source_mgr *p) { import "C" import ( + "errors" "io" + "reflect" "sync" "unsafe" ) +func makePseudoSlice(ptr unsafe.Pointer) []byte { + var buffer []byte + slice := (*reflect.SliceHeader)(unsafe.Pointer(&buffer)) + slice.Cap = readBufferSize + slice.Len = readBufferSize + slice.Data = uintptr(ptr) + return buffer +} + const readBufferSize = 16384 var sourceManagerMapMutex sync.RWMutex @@ -78,7 +89,9 @@ func sourceSkip(dinfo *C.struct_jpeg_decompress_struct, bytes C.long) { if bytes > 0 { for bytes >= C.long(mgr.pub.bytes_in_buffer) { bytes -= C.long(mgr.pub.bytes_in_buffer) - sourceFill(dinfo) + if sourceFill(dinfo) != C.TRUE { + break + } } } mgr.pub.bytes_in_buffer -= C.size_t(bytes) @@ -96,42 +109,40 @@ func sourceTerm(dinfo *C.struct_jpeg_decompress_struct) { //export sourceFill func sourceFill(dinfo *C.struct_jpeg_decompress_struct) C.boolean { mgr := getSourceManager(dinfo) - buffer := [readBufferSize]byte{} - bytes, err := mgr.src.Read(buffer[:]) - C.memcpy(mgr.buffer, unsafe.Pointer(&buffer[0]), C.size_t(bytes)) + buffer := makePseudoSlice(mgr.buffer) + bytes, err := mgr.src.Read(buffer) mgr.pub.bytes_in_buffer = C.size_t(bytes) mgr.currentSize = bytes mgr.pub.next_input_byte = (*C.JOCTET)(mgr.buffer) if err == io.EOF { if bytes == 0 { if mgr.startOfFile { - releaseSourceManager(mgr) - panic("input is empty") + return C.FALSE } // EOF and need more data. Fill in a fake EOI to get a partial image. - footer := []byte{0xff, C.JPEG_EOI} - C.memcpy(mgr.buffer, unsafe.Pointer(&footer[0]), C.size_t(len(footer))) - mgr.pub.bytes_in_buffer = 2 + mgr.pub.bytes_in_buffer = C.size_t(copy(buffer, []byte{0xff, C.JPEG_EOI})) } } else if err != nil { - releaseSourceManager(mgr) - panic(err) + return C.FALSE } mgr.startOfFile = false return C.TRUE } -func makeSourceManager(src io.Reader, dinfo *C.struct_jpeg_decompress_struct) (mgr *sourceManager) { +func makeSourceManager(src io.Reader, dinfo *C.struct_jpeg_decompress_struct) (mgr *sourceManager, err error) { mgr = new(sourceManager) mgr.src = src mgr.pub = C.calloc_jpeg_source_mgr() if mgr.pub == nil { - panic("Failed to allocate C.struct_jpeg_source_mgr") + err = errors.New("failed to allocate C.struct_jpeg_source_mgr") + return } mgr.buffer = C.calloc(readBufferSize, 1) if mgr.buffer == nil { - panic("Failed to allocate buffer") + C.free_jpeg_source_mgr(mgr.pub) + err = errors.New("failed to allocate buffer") + return } mgr.pub.init_source = (*[0]byte)(C.sourceInit) mgr.pub.fill_input_buffer = (*[0]byte)(C.sourceFill)