#include "image.hpp" #include "stb_image.h" #include #include #include #include // Include jpeglib last #include #include namespace bookmouse { class JpegFile { public: ~JpegFile(); void decompress(fud::CBinaryFile& inFile); private: jpeg_decompress_struct m_cinfo{}; }; JpegFile::~JpegFile() { jpeg_destroy_decompress(&m_cinfo); } void JpegFile::decompress(fud::CBinaryFile& inFile) { jpeg_create_decompress(&m_cinfo); auto* file = inFile.file(); if (file == nullptr) { // early return } jpeg_stdio_src(&m_cinfo, inFile.file()); auto result = jpeg_read_header(&m_cinfo, true); if (result == JPEG_SUSPENDED) { // early return } } struct my_error_mgr { struct jpeg_error_mgr pub; /* "public" fields */ jmp_buf setjmp_buffer; /* for return to caller */ }; typedef struct my_error_mgr* my_error_ptr; METHODDEF(void) my_error_exit(j_common_ptr cinfo) __attribute__((__noreturn__)); METHODDEF(void) my_error_exit(j_common_ptr cinfo) { /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */ my_error_ptr myerr = (my_error_ptr)cinfo->err; /* Always display the message. */ /* We could postpone this until after returning, if we chose. */ (*cinfo->err->output_message)(cinfo); /* Return control to the setjmp point */ longjmp(myerr->setjmp_buffer, 1); } ImageResult JpegImage::output() const { auto err = [](ImageError eid) { return ImageResult::error(eid); }; ImageOutput image{}; struct jpeg_decompress_struct cinfo; struct my_error_mgr jerr; fud::CBinaryFile infile{m_filename, fud::CFileMode::ReadOnly}; JSAMPARRAY buffer = nullptr; /* Output row buffer */ J12SAMPARRAY buffer12 = nullptr; /* 12-bit output row buffer */ int col; int row_stride; /* physical row width in output buffer */ /* In this example we want to open the input and output files before doing * anything else, so that the setjmp() error recovery below can assume the * files are open. * * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that * requires it in order to read/write binary files. */ auto fileResult = infile.open(); using fud::FileResult; if (fileResult == FileResult::Error) { spdlog::error("can't open {}\n", m_filename.c_str()); return err(ImageError::FileError); } /* Step 1: allocate and initialize JPEG decompression object */ /* We set up the normal JPEG error routines, then override error_exit. */ cinfo.err = jpeg_std_error(&jerr.pub); jerr.pub.error_exit = my_error_exit; /* Establish the setjmp return context for my_error_exit to use. */ if (setjmp(jerr.setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error. * We need to clean up the JPEG object, close the input file, and return. */ jpeg_destroy_decompress(&cinfo); return err(ImageError::JpegError); } /* Now we can initialize the JPEG decompression object. */ jpeg_create_decompress(&cinfo); /* Step 2: specify data source (eg, a file) */ jpeg_stdio_src(&cinfo, infile.file()); /* Step 3: read file parameters with jpeg_read_header() */ (void)jpeg_read_header(&cinfo, TRUE); /* We can ignore the return value from jpeg_read_header since * (a) suspension is not possible with the stdio data source, and * (b) we passed TRUE to reject a tables-only JPEG file as an error. * See libjpeg.txt for more info. */ /* Step 4: Start decompressor */ jpeg_start_decompress(&cinfo); /* We can ignore the return value since suspension is not possible * with the stdio data source. */ /* We may need to do some setup of our own at this point before reading * the data. After jpeg_start_decompress() we have the correct scaled * output image dimensions available, as well as the output colormap * if we asked for color quantization. * In this example, we need to make an output work buffer of the right size. */ /* Samples per row in output buffer */ if (cinfo.output_components < 0) { // handle it return err(ImageError::JpegError); } row_stride = static_cast(cinfo.output_width) * cinfo.output_components; /* Make a one-row-high sample array that will go away when done with image */ if (cinfo.data_precision == 12) { buffer12 = (J12SAMPARRAY)(*cinfo.mem->alloc_sarray)( (j_common_ptr)&cinfo, JPOOL_IMAGE, static_cast(row_stride), 1); } else { buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, static_cast(row_stride), 1); } /* Step 5: while (scan lines remain to be read) */ /* jpeg_read_scanlines(...); */ /* Here we use the library's state variable cinfo.output_scanline as the * loop counter, so that we don't have to keep track ourselves. */ if (cinfo.data_precision == 12) { while (cinfo.output_scanline < cinfo.output_height) { /* jpeg12_read_scanlines expects an array of pointers to scanlines. * Here the array is only one element long, but you could ask for * more than one scanline at a time if that's more convenient. */ (void)jpeg12_read_scanlines(&cinfo, buffer12, 1); /* Swap MSB and LSB in each sample */ for (col = 0; col < row_stride; col++) { buffer12[0][col] = // break static_cast((buffer12[0][col] & 0xFF) << 8) | // break static_cast((buffer12[0][col] >> 8) & 0xFF); } } } else { while (cinfo.output_scanline < cinfo.output_height) { /* jpeg_read_scanlines expects an array of pointers to scanlines. * Here the array is only one element long, but you could ask for * more than one scanline at a time if that's more convenient. */ (void)jpeg_read_scanlines(&cinfo, buffer, 1); } } /* Step 6: Finish decompression */ (void)jpeg_finish_decompress(&cinfo); /* We can ignore the return value since suspension is not possible * with the stdio data source. */ /* Step 7: Release JPEG decompression object */ /* This is an important step since it will release a good deal of memory. */ /* At this point you may want to check to see whether any corrupt-data * warnings occurred (test whether jerr.pub.num_warnings is nonzero). */ /* And we're done! */ return ImageResult::okay(image); } } // namespace bookmouse