static void DoApply( GenericImage<P>& image, const DisplayFunction& F ) { int i = image.FirstSelectedChannel(); if ( i < image.NumberOfNominalChannels() ) { int j = Max( image.LastSelectedChannel(), image.NumberOfNominalChannels()-1 ); image.Status().Initialize( "Display function transformation", (1+j-i)*image.NumberOfSelectedPixels() ); image.Status().DisableInitialization(); try { image.PushSelections(); for ( ; i <= j; ++i ) { image.SelectChannel( i ); F[i] >> image; } image.PopSelections(); image.Status().EnableInitialization(); } catch( ... ) { image.Status().EnableInitialization(); throw; } }
template <class P> static void Apply( GenericImage<P>& image, const typename P::sample* lut ) { if ( image.IsEmptySelection() ) return; Rect r = image.SelectedRectangle(); if ( image.Status().IsInitializationEnabled() ) image.Status().Initialize( "LUT-based histogram transformation", image.NumberOfSelectedSamples() ); for ( int c = image.FirstSelectedChannel(), w = r.Width(); c <= image.LastSelectedChannel(); ++c ) for ( int y = r.y0; y < r.y1; ++y ) for ( typename P::sample* p = image.ScanLine( y, c ) + r.x0, * pw = p + w; p < pw; ++p ) *p = lut[*p]; image.Status() += image.NumberOfSelectedSamples(); }
static void LoadAndTransformImage( GenericImage<P>& transform, const GenericImage<P1>& image, bool parallel, int maxProcessors ) { Rect r = image.SelectedRectangle(); if ( !r.IsRect() ) return; int w = FFTC::OptimizedLength( r.Width() ); int h = FFTC::OptimizedLength( r.Height() ); int dw2 = (w - r.Width()) >> 1; int dh2 = (h - r.Height()) >> 1; transform.AllocateData( w, h, image.NumberOfSelectedChannels(), (image.NumberOfSelectedChannels() < 3 || image.FirstSelectedChannel() != 0) ? ColorSpace::Gray : image.ColorSpace() ); transform.Zero().Move( image, Point( dw2, dh2 ) ); ApplyInPlaceFourierTransform( transform, FFTDirection::Forward, parallel, maxProcessors ); }
static void ApplyInPlaceFourierTransform( GenericImage<P>& image, FFTDirection::value_type dir, bool parallel, int maxProcessors ) { int w = FFTC::OptimizedLength( image.Width() ); int h = FFTC::OptimizedLength( image.Height() ); if ( w != image.Width() || h != image.Height() ) { StatusCallback* s = image.GetStatusCallback(); // don't update status here image.SetStatusCallback( 0 ); image.ShiftToCenter( w, h ); image.SetStatusCallback( s ); } bool statusInitialized = false; if ( image.Status().IsInitializationEnabled() ) { image.Status().Initialize( (dir == FFTDirection::Backward) ? "Inverse FFT" : "FFT", image.NumberOfSelectedChannels()*size_type( w + h ) ); image.Status().DisableInitialization(); statusInitialized = true; } try { FFTC F( h, w, image.Status() ); F.EnableParallelProcessing( parallel, maxProcessors ); for ( int c = image.FirstSelectedChannel(); c <= image.LastSelectedChannel(); ++c ) F( image[c], image[c], (dir == FFTDirection::Backward) ? PCL_FFT_BACKWARD : PCL_FFT_FORWARD ); if ( statusInitialized ) image.Status().EnableInitialization(); } catch ( ... ) { if ( statusInitialized ) image.Status().EnableInitialization(); throw; } }
template <class P> inline static void WriteJPEGImage( const GenericImage<P>& img, JPEGWriter& writer, const ICCProfile& icc, JPEGFileData* fileData ) { if ( !writer.IsOpen() ) throw JPEG::WriterNotInitialized( String() ); if ( writer.Options().lowerRange >= writer.Options().upperRange ) throw JPEG::InvalidNormalizationRange( writer.Path() ); if ( img.IsEmptySelection() ) throw JPEG::InvalidPixelSelection( writer.Path() ); // Retrieve information on selected subimage. Rect r = img.SelectedRectangle(); int width = r.Width(); int height = r.Height(); int channels = img.NumberOfSelectedChannels(); // JPEG doesn't support alpha channels, just plain grayscale and RGB images. if ( channels != 1 && channels != 3 ) throw JPEG::InvalidChannelSelection( writer.Path() ); unsigned char qualityMarker[] = { 'J', 'P', 'E', 'G', 'Q', 'u', 'a', 'l', 'i', 't', 'y', 0, 0 }; try { // Make safe copies of critical parameters. fileData->lowerRange = writer.Options().lowerRange; fileData->upperRange = writer.Options().upperRange; // Initialize status callback. if ( img.Status().IsInitializationEnabled() ) img.Status().Initialize( String().Format( "Compressing JPEG: %d channel(s), %dx%d pixels", channels, width, height ), img.NumberOfSelectedSamples() ); // // Allocate and initialize a JPEG compressor instance. // // This struct contains the JPEG compression parameters and pointers to // working space, which is allocated as needed by the IJG library. fileData->compressor = new ::jpeg_compress_struct; // Set up normal JPEG error routines, passing a pointer to our custom // error handler structure. jpeg_compressor->err = ::jpeg_std_error( (::jpeg_error_mgr*)(fileData->errorManager = new JPEGErrorManager( writer.Path() )) ); // Override error_exit. JPEG library errors will be handled by throwing // exceptions from the JPEGErrorExit() routine. ((JPEGErrorManager*)fileData->errorManager)->error.error_exit = JPEGErrorExit; // Initialize the JPEG compression object. ::jpeg_create_compress( jpeg_compressor ); // // Set parameters for compression. // jpeg_compressor->image_width = width; jpeg_compressor->image_height = height; jpeg_compressor->input_components = channels; jpeg_compressor->in_color_space = (channels == 1) ? JCS_GRAYSCALE : JCS_RGB; // Set default compression parameters. ::jpeg_set_defaults( jpeg_compressor ); // Set any non-default parameters. if ( writer.JPEGOptions().quality == JPEGQuality::Unknown ) const_cast<JPEGImageOptions&>( writer.JPEGOptions() ).quality = JPEGQuality::Default; // Set up compression according to specified JPEG quality. // We limit to baseline-JPEG values (TRUE arg in jpeg_set_quality). ::jpeg_set_quality( jpeg_compressor, writer.JPEGOptions().quality, TRUE ); jpeg_compressor->optimize_coding = boolean( writer.JPEGOptions().optimizedCoding ); jpeg_compressor->arith_code = boolean( writer.JPEGOptions().arithmeticCoding ); jpeg_compressor->dct_method = JDCT_FLOAT; jpeg_compressor->write_JFIF_header = boolean( writer.JPEGOptions().JFIFMarker ); jpeg_compressor->JFIF_major_version = writer.JPEGOptions().JFIFMajorVersion; jpeg_compressor->JFIF_minor_version = writer.JPEGOptions().JFIFMinorVersion; jpeg_compressor->density_unit = writer.Options().metricResolution ? 2 : 1; jpeg_compressor->X_density = RoundI( writer.Options().xResolution ); jpeg_compressor->Y_density = RoundI( writer.Options().yResolution ); if ( writer.JPEGOptions().progressive ) ::jpeg_simple_progression( jpeg_compressor ); IsoString path8 = #ifdef __PCL_WINDOWS File::UnixPathToWindows( writer.Path() ).ToMBS(); #else writer.Path().ToUTF8(); #endif fileData->handle = ::fopen( path8.c_str(), "wb" ); if ( fileData->handle == 0 ) throw JPEG::UnableToCreateFile( writer.Path() ); // Specify data destination (e.g., a file). ::jpeg_stdio_dest( jpeg_compressor, fileData->handle ); // Start compressor. ::jpeg_start_compress( jpeg_compressor, TRUE ); // Output ICC Profile data. // We use APP2 markers, as stated on ICC.1:2001-12. if ( writer.Options().embedICCProfile && icc.IsProfile() ) { // Get ICC profile size in bytes from the ICC profile header uint32 byteCount = uint32( icc.ProfileSize() ); if ( byteCount != 0 ) { // The ICC profile must be writen as a sequence of chunks if its // size is larger than (roughly) 64K. // Number of whole chunks. int chunkCount = byteCount/0xFF00; // Remainder chunk size in bytes. int lastChunk = byteCount - chunkCount*0xFF00; // chunkCount includes a possible remainder from now on. if ( lastChunk != 0 ) ++chunkCount; // Allocate a working buffer. 64K bytes to hold chunk data, ICC // marker id, etc. ByteArray iccData( 0xFFFF ); // ICC JFIF marker identifier, as recommended by ICC.1:2001-12. // Note that this is a null-terminated string, thus the total // length is 12 bytes. ::memcpy( iccData.Begin(), "ICC_PROFILE", 12 ); // Byte #12, next to id's null terminator, is a 1-based chunk index. // Byte #13 is the total count of chunks (including remainder). iccData[13] = uint8( chunkCount ); // Write sequence of markers for ( int i = 1, offset = 0; i <= chunkCount; ++i ) { // Size in bytes of this chunk. int size = (i == chunkCount) ? lastChunk : 0xFF00; // Copy ICC profile data for this chunk, preserve ICC id, etc. ::memcpy( iccData.At( 14 ), icc.ProfileData().At( offset ), size ); // Keep track of this chunk's index number, one-based. iccData[12] = uint8( i ); // Output an APP2 marker for this chunk. ::jpeg_write_marker( jpeg_compressor, JPEG_APP0+2, (const JOCTET *)iccData.Begin(), size+14 ); // Prepare for next chunk. offset += size; } } } /* // Output IPTC PhotoInfo data. // We use an APP13 marker, as recommended by IPTC. if ( iptc != nullptr ) { size_type byteCount = iptc->MakeInfo( iptcPureData ); if ( byteCount != 0 ) { iptcData = new char[ byteCount+14 ]; ::strcpy( (char*)iptcData, "IPTCPhotoInfo" ); ::memcpy( ((char*)iptcData)+14, iptcPureData, byteCount ); delete (uint8*)iptcPureData, iptcPureData = nullptr; ::jpeg_write_marker( jpeg_compressor, JPEG_APP0+13, (const JOCTET *)iptcData, unsigned( byteCount+14 ) ); delete (uint8*)iptcData, iptcData = nullptr; } } */ // Output JPEGQuality marker. // We use an APP15 marker for quality. qualityMarker[12] = writer.JPEGOptions().quality; ::jpeg_write_marker( jpeg_compressor, JPEG_APP0+15, qualityMarker, 13 ); // // Write pixels row by row. // // JSAMPLEs per row in image_buffer. int row_stride = width * channels; // Make a one-row-high sample array. Array<JSAMPLE> buffer( row_stride ); // JPEG organization is chunky; PCL images are planar. Array<const typename P::sample*> v( channels ); // Either rescaling or truncating to the [0,1] range is mandatory when // writing floating-point images to JPEG files. bool rescale = P::IsFloatSample() && (fileData->upperRange != 1 || fileData->lowerRange != 0); double k = 1.0; if ( rescale ) k /= fileData->upperRange - fileData->lowerRange; while ( jpeg_compressor->next_scanline < jpeg_compressor->image_height ) { JSAMPLE* b = buffer.Begin(); for ( int c = 0; c < channels; ++c ) v[c] = img.PixelAddress( r.x0, r.y0+jpeg_compressor->next_scanline, img.FirstSelectedChannel()+c ); for ( int i = 0; i < width; ++i ) for ( int c = 0; c < channels; ++c, ++b ) { typename P::sample f = *v[c]++; if ( P::IsFloatSample() ) { f = typename P::sample( Range( double( f ), fileData->lowerRange, fileData->upperRange ) ); if ( rescale ) f = typename P::sample( k*(f - fileData->lowerRange) ); } P::FromSample( *b, f ); } b = buffer.Begin(); ::jpeg_write_scanlines( jpeg_compressor, &b, 1 ); img.Status() += width * channels; ++img.Status(); } // Finish JPEG compression. ::jpeg_finish_compress( jpeg_compressor ); // Close the output file. ::fclose( fileData->handle ), fileData->handle = nullptr; // Release the JPEG compression object. ::jpeg_destroy_compress( jpeg_compressor ); delete jpeg_compressor, fileData->compressor = nullptr; } catch ( ... ) { if ( fileData->handle != nullptr ) ::fclose( fileData->handle ), fileData->handle = nullptr; throw; } }