/* * * Copyright (C) 1997-2021, OFFIS e.V. * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation were developed by * * OFFIS e.V. * R&D Division Health * Escherweg 2 * D-26121 Oldenburg, Germany * * * Module: dcmjpeg * * Author: Marco Eichelberg, Norbert Olges * * Purpose: compression routines of the IJG JPEG library configured for 8 bits/sample. * */ #include "dcmtk/config/osconfig.h" #include "dcmtk/dcmjpeg/djeijg8.h" #include "dcmtk/dcmjpeg/djcparam.h" #include "dcmtk/dcmdata/dcerror.h" #include "dcmtk/ofstd/ofstdinc.h" #include "dcmtk/ofstd/ofdiag.h" #include // These two macros are re-defined in the IJG header files. // We undefine them here and hope that IJG's configure has // come to the same conclusion that we have... #ifdef HAVE_STDLIB_H #undef HAVE_STDLIB_H #endif #ifdef HAVE_STDDEF_H #undef HAVE_STDDEF_H #endif // use 16K blocks for temporary storage of compressed JPEG data #define IJGE8_BLOCKSIZE 16384 BEGIN_EXTERN_C #define boolean ijg_boolean #include "jpeglib8.h" #include "jerror8.h" #include "jpegint8.h" #undef boolean // disable any preprocessor magic the IJG library might be doing with the "const" keyword #ifdef const #undef const #endif // Solaris defines longjmp() in namespace std, other compilers don't... using STD_NAMESPACE longjmp; using STD_NAMESPACE jmp_buf; #include DCMTK_DIAGNOSTIC_PUSH #include DCMTK_DIAGNOSTIC_IGNORE_VISUAL_STUDIO_DECLSPEC_PADDING_WARNING // private error handler struct struct DJEIJG8ErrorStruct { // the standard IJG error handler object struct jpeg_error_mgr pub; // our jump buffer jmp_buf setjmp_buffer; // pointer to this DJCompressIJG8Bit *instance; }; #include DCMTK_DIAGNOSTIC_POP // callback forward declarations void DJEIJG8ErrorExit(j_common_ptr); void DJEIJG8EmitMessage(j_common_ptr cinfo, int msg_level); void DJEIJG8initDestination(j_compress_ptr cinfo); ijg_boolean DJEIJG8emptyOutputBuffer(j_compress_ptr cinfo); void DJEIJG8termDestination(j_compress_ptr cinfo); // helper methods to fix old-style casts warnings static void OFjpeg_create_compress(j_compress_ptr cinfo) { jpeg_create_compress(cinfo); } static void OF_ERREXIT1(j_compress_ptr cinfo, int code, int p1) { ERREXIT1(cinfo, code, p1); } END_EXTERN_C // error handler, executes longjmp void DJEIJG8ErrorExit(j_common_ptr cinfo) { DJEIJG8ErrorStruct *myerr = OFreinterpret_cast(DJEIJG8ErrorStruct*, cinfo->err); longjmp(myerr->setjmp_buffer, 1); } // message handler for warning messages and the like void DJEIJG8EmitMessage(j_common_ptr cinfo, int msg_level) { DJEIJG8ErrorStruct *myerr = OFreinterpret_cast(DJEIJG8ErrorStruct*, cinfo->err); myerr->instance->emitMessage(cinfo, msg_level); } // callbacks for compress-destination-manager void DJEIJG8initDestination(j_compress_ptr cinfo) { DJCompressIJG8Bit *encoder = OFreinterpret_cast(DJCompressIJG8Bit*, cinfo->client_data); encoder->initDestination(cinfo); } ijg_boolean DJEIJG8emptyOutputBuffer(j_compress_ptr cinfo) { DJCompressIJG8Bit *encoder = OFreinterpret_cast(DJCompressIJG8Bit*, cinfo->client_data); return encoder->emptyOutputBuffer(cinfo); } void DJEIJG8termDestination(j_compress_ptr cinfo) { DJCompressIJG8Bit *encoder = OFreinterpret_cast(DJCompressIJG8Bit*, cinfo->client_data); encoder->termDestination(cinfo); } /* * jpeg_simple_spectral_selection() creates a scan script * for progressive JPEG with spectral selection only, * similar to jpeg_simple_progression() for full progression. * The scan sequence for YCbCr is as proposed in the IJG documentation. * The scan sequence for all other color models is somewhat arbitrary. */ static void jpeg_simple_spectral_selection(j_compress_ptr cinfo) { int ncomps = cinfo->num_components; jpeg_scan_info *scanptr = NULL; int nscans = 0; /* Safety check to ensure start_compress not called yet. */ if (cinfo->global_state != CSTATE_START) { OF_ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); } if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) nscans = 7; else nscans = 1 + 2 * ncomps; /* 1 DC scan; 2 AC scans per component */ /* Allocate space for script. * We need to put it in the permanent pool in case the application performs * multiple compressions without changing the settings. To avoid a memory * leak if jpeg_simple_spectral_selection is called repeatedly for the same JPEG * object, we try to re-use previously allocated space, and we allocate * enough space to handle YCbCr even if initially asked for grayscale. */ if (cinfo->script_space == NULL || cinfo->script_space_size < nscans) { cinfo->script_space_size = nscans > 7 ? nscans : 7; cinfo->script_space = OFreinterpret_cast(jpeg_scan_info*, (*cinfo->mem->alloc_small) (OFreinterpret_cast(j_common_ptr, cinfo), JPOOL_PERMANENT, cinfo->script_space_size * sizeof(jpeg_scan_info))); } scanptr = cinfo->script_space; cinfo->scan_info = scanptr; cinfo->num_scans = nscans; if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) { /* Custom script for YCbCr color images. */ // Interleaved DC scan for Y,Cb,Cr: scanptr[0].component_index[0] = 0; scanptr[0].component_index[1] = 1; scanptr[0].component_index[2] = 2; scanptr[0].comps_in_scan = 3; scanptr[0].Ss = 0; scanptr[0].Se = 0; scanptr[0].Ah = 0; scanptr[0].Al = 0; // AC scans // First two Y AC coefficients scanptr[1].component_index[0] = 0; scanptr[1].comps_in_scan = 1; scanptr[1].Ss = 1; scanptr[1].Se = 2; scanptr[1].Ah = 0; scanptr[1].Al = 0; // Three more scanptr[2].component_index[0] = 0; scanptr[2].comps_in_scan = 1; scanptr[2].Ss = 3; scanptr[2].Se = 5; scanptr[2].Ah = 0; scanptr[2].Al = 0; // All AC coefficients for Cb scanptr[3].component_index[0] = 1; scanptr[3].comps_in_scan = 1; scanptr[3].Ss = 1; scanptr[3].Se = 63; scanptr[3].Ah = 0; scanptr[3].Al = 0; // All AC coefficients for Cr scanptr[4].component_index[0] = 2; scanptr[4].comps_in_scan = 1; scanptr[4].Ss = 1; scanptr[4].Se = 63; scanptr[4].Ah = 0; scanptr[4].Al = 0; // More Y coefficients scanptr[5].component_index[0] = 0; scanptr[5].comps_in_scan = 1; scanptr[5].Ss = 6; scanptr[5].Se = 9; scanptr[5].Ah = 0; scanptr[5].Al = 0; // Remaining Y coefficients scanptr[6].component_index[0] = 0; scanptr[6].comps_in_scan = 1; scanptr[6].Ss = 10; scanptr[6].Se = 63; scanptr[6].Ah = 0; scanptr[6].Al = 0; } else { /* All-purpose script for other color spaces. */ int j=0; // Interleaved DC scan for all components for (j=0; jformat_message)(OFreinterpret_cast(jpeg_common_struct*, &cinfo), buffer); /* Create the message */ jpeg_destroy_compress(&cinfo); return makeOFCondition(OFM_dcmjpeg, EJCode_IJG8_Compression, OF_error, buffer); } OFjpeg_create_compress(&cinfo); // initialize client_data cinfo.client_data = this; // Specify destination manager jpeg_destination_mgr dest; dest.init_destination = DJEIJG8initDestination; dest.empty_output_buffer = DJEIJG8emptyOutputBuffer; dest.term_destination = DJEIJG8termDestination; cinfo.dest = &dest; cinfo.image_width = columns; cinfo.image_height = rows; cinfo.input_components = samplesPerPixel; cinfo.in_color_space = getJpegColorSpace(colorSpace); jpeg_set_defaults(&cinfo); if (cparam->getCompressionColorSpaceConversion() != ECC_lossyYCbCr) { // prevent IJG library from doing any color space conversion jpeg_set_colorspace (&cinfo, cinfo.in_color_space); } cinfo.optimize_coding = cparam->getOptimizeHuffmanCoding(); switch (modeofOperation) { case EJM_baseline: jpeg_set_quality(&cinfo, quality, 1); break; case EJM_sequential: jpeg_set_quality(&cinfo, quality, 0); cinfo.force_extended_sequential_marker = TRUE; break; case EJM_spectralSelection: jpeg_set_quality(&cinfo, quality, 0); jpeg_simple_spectral_selection(&cinfo); break; case EJM_progressive: jpeg_set_quality(&cinfo, quality, 0); jpeg_simple_progression(&cinfo); break; case EJM_lossless: // always disables any kind of color space conversion jpeg_simple_lossless(&cinfo,psv,pt); break; } cinfo.smoothing_factor = cparam->getSmoothingFactor(); // initialize sampling factors if ((cinfo.jpeg_color_space == JCS_YCbCr) && (modeofOperation != EJM_lossless)) { switch(cparam->getSampleFactors()) { case ESS_444: /* 4:4:4 sampling (no subsampling) */ cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; break; case ESS_422: /* 4:2:2 sampling (horizontal subsampling of chroma components) */ cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 1; break; case ESS_411: /* 4:1:1 sampling (horizontal and vertical subsampling of chroma components) */ cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 2; break; } } else { // JPEG color space is not YCbCr, or we are using lossless compression. // Disable subsampling. cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; } // all other components are set to 1x1 for (int sfi=1; sfi< MAX_COMPONENTS; sfi++) { cinfo.comp_info[sfi].h_samp_factor = 1; cinfo.comp_info[sfi].v_samp_factor = 1; } JSAMPROW row_pointer[1]; jpeg_start_compress(&cinfo,TRUE); int row_stride = columns * samplesPerPixel; while (cinfo.next_scanline < cinfo.image_height) { row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride]; jpeg_write_scanlines(&cinfo, row_pointer, 1); } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); length = OFstatic_cast(Uint32, bytesInLastBlock); if (pixelDataList.size() > 1) length += OFstatic_cast(Uint32, (pixelDataList.size() - 1)*IJGE8_BLOCKSIZE); OFBool length_is_odd = (length % 2) > 0; if (length_is_odd) length++; // ensure even length to = new Uint8[length]; if (to == NULL) return EC_MemoryExhausted; size_t offset=0; OFListIterator(unsigned char *) first = pixelDataList.begin(); OFListIterator(unsigned char *) last = pixelDataList.end(); OFListIterator(unsigned char *) shortBlock = last; --shortBlock; while (first != last) { if (first == shortBlock) { memcpy(to+offset, *first, bytesInLastBlock); offset += bytesInLastBlock; } else { memcpy(to+offset, *first, IJGE8_BLOCKSIZE); offset += IJGE8_BLOCKSIZE; } ++first; } if (length_is_odd) DcmJpegHelper::fixPadding(to, length); cleanup(); return EC_Normal; } #include DCMTK_DIAGNOSTIC_POP void DJCompressIJG8Bit::initDestination(jpeg_compress_struct *cinfo) { cleanup(); // erase old list of compressed blocks, if any unsigned char *newBlock = new unsigned char[IJGE8_BLOCKSIZE]; if (newBlock) { pixelDataList.push_back(newBlock); cinfo->dest->next_output_byte = newBlock; cinfo->dest->free_in_buffer = IJGE8_BLOCKSIZE; } else { cinfo->dest->next_output_byte = NULL; cinfo->dest->free_in_buffer = 0; } } int DJCompressIJG8Bit::emptyOutputBuffer(jpeg_compress_struct *cinfo) { bytesInLastBlock = 0; unsigned char *newBlock = new unsigned char[IJGE8_BLOCKSIZE]; if (newBlock) { pixelDataList.push_back(newBlock); cinfo->dest->next_output_byte = newBlock; cinfo->dest->free_in_buffer = IJGE8_BLOCKSIZE; } else { // should never happen cinfo->dest->next_output_byte = NULL; cinfo->dest->free_in_buffer = 0; OF_ERREXIT1(cinfo, JERR_OUT_OF_MEMORY, 0xFF); } return TRUE; } void DJCompressIJG8Bit::termDestination(jpeg_compress_struct *cinfo) { bytesInLastBlock = IJGE8_BLOCKSIZE - cinfo->dest->free_in_buffer; } void DJCompressIJG8Bit::cleanup() { OFListIterator(unsigned char *) first = pixelDataList.begin(); OFListIterator(unsigned char *) last = pixelDataList.end(); while (first != last) { delete[] *first; first = pixelDataList.erase(first); } bytesInLastBlock = 0; } void DJCompressIJG8Bit::emitMessage(void *arg, int msg_level) const { jpeg_common_struct *cinfo = OFreinterpret_cast(jpeg_common_struct*, arg); // This is how we map the message levels: // -1 - 0: Warning (could also be errors, but no way to find out) // 1 : Debug // Everything else: Trace (No point in splitting this further up) OFLogger::LogLevel level; switch (msg_level) { case -1: case 0: level = OFLogger::WARN_LOG_LEVEL; break; case 1: level = OFLogger::DEBUG_LOG_LEVEL; break; default: level = OFLogger::TRACE_LOG_LEVEL; break; } if (cinfo && DCM_dcmjpegLogger.isEnabledFor(level)) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message)(cinfo, buffer); /* Create the message */ DCM_dcmjpegLogger.forcedLog(level, buffer, __FILE__, __LINE__); } }