static bool jpeg_get_dimensions(FILE *f, // or: const void *buf, uint32_t buflen, int32_t *w, int32_t *h, GError **err) { bool result = false; struct jpeg_decompress_struct cinfo; struct _openslide_jpeg_error_mgr jerr; jmp_buf env; if (setjmp(env) == 0) { cinfo.err = _openslide_jpeg_set_error_handler(&jerr, &env); jpeg_create_decompress(&cinfo); if (f) { _openslide_jpeg_stdio_src(&cinfo, f); } else { _openslide_jpeg_mem_src(&cinfo, (void *) buf, buflen); } int header_result = jpeg_read_header(&cinfo, TRUE); if ((header_result != JPEG_HEADER_OK && header_result != JPEG_HEADER_TABLES_ONLY)) { g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_FAILED, "Couldn't read JPEG header"); goto DONE; } jpeg_calc_output_dimensions(&cinfo); *w = cinfo.output_width; *h = cinfo.output_height; result = true; } else { // setjmp returned again g_propagate_error(err, jerr.err); } DONE: // free buffers jpeg_destroy_decompress(&cinfo); return result; }
static bool jpeg_decode(FILE *f, // or: const void *buf, uint32_t buflen, void * const _dest, bool grayscale, int32_t w, int32_t h, GError **err) { bool result = false; struct jpeg_decompress_struct cinfo; struct _openslide_jpeg_error_mgr jerr; jmp_buf env; volatile gsize row_size = 0; // preserve across longjmp JSAMPARRAY buffer = g_slice_alloc0(sizeof(JSAMPROW) * MAX_SAMP_FACTOR); if (setjmp(env) == 0) { cinfo.err = _openslide_jpeg_set_error_handler(&jerr, &env); jpeg_create_decompress(&cinfo); // set up I/O if (f) { _openslide_jpeg_stdio_src(&cinfo, f); } else { _openslide_jpeg_mem_src(&cinfo, (void *) buf, buflen); } // read header int header_result = jpeg_read_header(&cinfo, TRUE); if ((header_result != JPEG_HEADER_OK && header_result != JPEG_HEADER_TABLES_ONLY)) { g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_FAILED, "Couldn't read JPEG header"); goto DONE; } cinfo.out_color_space = grayscale ? JCS_GRAYSCALE : JCS_RGB; jpeg_start_decompress(&cinfo); // ensure buffer dimensions are correct int32_t width = cinfo.output_width; int32_t height = cinfo.output_height; if (w != width || h != height) { g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_FAILED, "Dimensional mismatch reading JPEG, " "expected %dx%d, got %dx%d", w, h, cinfo.output_width, cinfo.output_height); goto DONE; } // allocate scanline buffers row_size = sizeof(JSAMPLE) * cinfo.output_width * cinfo.output_components; for (int i = 0; i < cinfo.rec_outbuf_height; i++) { buffer[i] = g_slice_alloc(row_size); } // decompress uint32_t *dest32 = _dest; uint8_t *dest8 = _dest; while (cinfo.output_scanline < cinfo.output_height) { JDIMENSION rows_read = jpeg_read_scanlines(&cinfo, buffer, cinfo.rec_outbuf_height); int cur_buffer = 0; while (rows_read > 0) { // copy a row int32_t i; if (cinfo.output_components == 1) { // grayscale for (i = 0; i < (int32_t) cinfo.output_width; i++) { dest8[i] = buffer[cur_buffer][i]; } dest8 += cinfo.output_width; } else { // RGB for (i = 0; i < (int32_t) cinfo.output_width; i++) { dest32[i] = 0xFF000000 | // A buffer[cur_buffer][i * 3 + 0] << 16 | // R buffer[cur_buffer][i * 3 + 1] << 8 | // G buffer[cur_buffer][i * 3 + 2]; // B } dest32 += cinfo.output_width; } // advance 1 row rows_read--; cur_buffer++; } } result = true; } else { // setjmp has returned again g_propagate_error(err, jerr.err); } DONE: // free buffers for (int i = 0; i < cinfo.rec_outbuf_height; i++) { g_slice_free1(row_size, buffer[i]); } g_slice_free1(sizeof(JSAMPROW) * MAX_SAMP_FACTOR, buffer); jpeg_destroy_decompress(&cinfo); return result; }
// returns w and h and tw and th and comment as a convenience static bool verify_jpeg(FILE *f, int32_t *w, int32_t *h, int32_t *tw, int32_t *th, char **comment, GError **err) { struct jpeg_decompress_struct cinfo; struct _openslide_jpeg_error_mgr jerr; jmp_buf env; bool success = false; *w = 0; *h = 0; *tw = 0; *th = 0; if (comment) { *comment = NULL; } if (setjmp(env) == 0) { cinfo.err = _openslide_jpeg_set_error_handler(&jerr, &env); jpeg_create_decompress(&cinfo); _openslide_jpeg_stdio_src(&cinfo, f); int header_result; if (comment) { // extract comment jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF); } header_result = jpeg_read_header(&cinfo, TRUE); if (header_result != JPEG_HEADER_OK && header_result != JPEG_HEADER_TABLES_ONLY) { g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA, "Couldn't read JPEG header"); goto DONE; } if (cinfo.num_components != 3) { g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA, "JPEG color components != 3"); goto DONE; } if (cinfo.restart_interval == 0) { g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA, "No restart markers"); goto DONE; } jpeg_start_decompress(&cinfo); if (comment) { if (cinfo.marker_list) { // copy everything out char *com = g_strndup((const gchar *) cinfo.marker_list->data, cinfo.marker_list->data_length); // but only really save everything up to the first '\0' *comment = g_strdup(com); g_free(com); } jpeg_save_markers(&cinfo, JPEG_COM, 0); // stop saving } *w = cinfo.output_width; *h = cinfo.output_height; if (cinfo.restart_interval > cinfo.MCUs_per_row) { g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA, "Restart interval greater than MCUs per row"); goto DONE; } *tw = *w / (cinfo.MCUs_per_row / cinfo.restart_interval); *th = *h / cinfo.MCU_rows_in_scan; int leftover_mcus = cinfo.MCUs_per_row % cinfo.restart_interval; if (leftover_mcus != 0) { g_set_error(err, OPENSLIDE_ERROR, OPENSLIDE_ERROR_BAD_DATA, "Inconsistent restart marker spacing within row"); goto DONE; } // g_debug("w: %d, h: %d, restart_interval: %d\n" // "mcus_per_row: %d, mcu_rows_in_scan: %d\n" // "leftover mcus: %d", // cinfo.output_width, cinfo.output_height, // cinfo.restart_interval, // cinfo.MCUs_per_row, cinfo.MCU_rows_in_scan, // leftover_mcus); } else { // setjmp has returned again g_propagate_error(err, jerr.err); goto DONE; } success = true; DONE: jpeg_destroy_decompress(&cinfo); return success; }