示例#1
0
void AnnotationInterface::DynamicMouseMove( View& v, const DPoint& p, unsigned buttons, unsigned modifiers )
{
   // mouse move is processed only on active view
   if (view == 0 || v != *view)
      return;

   // get view image window
   ImageWindow w = view->Window();

   // and image coordinates of the mouse position
   int imageX = RoundI(p.x);
   int imageY = RoundI(p.y);

   // set cursor to dragging cursor if mouse is inside one of the grip rectangles
   if (annotationPlaced && textRect.Includes(imageX, imageY))
      w.SetDynamicCursor(move_all_XPM, 10, 10);
   else if (leaderPlaced && instance.annotationShowLeader && leaderRect.Includes(imageX, imageY))
      w.SetDynamicCursor(move_all_XPM, 10, 10);
   else // otherwise, reset cursor
      w.ResetDynamicCursor();

   // if we are currently dragging something
   if ( dragging != DraggingType::None )
   {
      // compute delta
      int deltaX = imageX-lastX;
      int deltaY = imageY-lastY;

      // update text position if needed
      if (dragging == DraggingType::Text || dragging == DraggingType::Both)
      {
         instance.annotationPositionX += deltaX;
         instance.annotationPositionY += deltaY;
      }

      // update leader endpoint position if needed
      if (dragging == DraggingType::Leader || dragging == DraggingType::Both)
      {
         instance.annotationLeaderPositionX += deltaX;
         instance.annotationLeaderPositionY += deltaY;
      }

      // update annotation rectangle
      UpdateAnnotationRect();

      // redraw dynamic view
      UpdateView();

      // remember current point
      lastX = imageX;
      lastY = imageY;
   }
}
示例#2
0
void NumericControl::UpdateControls()
{
   NumericEdit::UpdateControls();

   int i0, i1;
   slider.GetRange( i0, i1 );
   slider.SetValue( i0 + RoundI( (value - lowerBound)/(upperBound - lowerBound)*(i1 - i0) ) );
}
示例#3
0
void AnnotationInterface::DynamicPaint( const View& v, VectorGraphics& g, const DRect& r ) const
{
   // we need valid view and annotation must be placed
   if ( view == 0 || v != *view || !annotationPlaced )
      return;

   // are we painting to rect with annotation?
   if ( !totalRect.Intersects( r ) )
      return;

   // get the image window
   ImageWindow w = view->Window();

   // render annotation to bitmap if needed
   if ( annotationBmp.IsNull() )
   {
      relPosX = 0;
      relPosY = 0;
      annotationBmp = AnnotationRenderer::CreateAnnotationBitmap( instance, relPosX, relPosY, true );
      screenBmp = Bitmap::Null();
   }

   // compute viewport coordinates of annotation text
   int posX = instance.annotationPositionX - relPosX;
   int posY = instance.annotationPositionY - relPosY;
   w.ImageToViewport( posX, posY );

   // compute zoom factor
   int zoomFactor = w.ZoomFactor();
   double z = (zoomFactor < 0) ? -1.0/zoomFactor : double( zoomFactor );

   // draw bitmap, scaled according to zoom factor
   Rect zr( RoundI( annotationBmp.Width()*z ), RoundI( annotationBmp.Height()*z ) );
   if ( screenBmp.IsNull() || zr != screenBmp.Bounds() )
      if ( zoomFactor < 0 )
         screenBmp = annotationBmp.Scaled( z );
      else
         screenBmp = annotationBmp;

   if ( zoomFactor < 0 )
      g.DrawBitmap( posX, posY, screenBmp );
   else
      g.DrawScaledBitmap( zr.MovedTo( posX, posY ), screenBmp );
}
示例#4
0
   static void EvaluateNoise( FVector& noiseEstimates,
                              FVector& noiseFractions,
                              StringList& noiseAlgorithms,
                              const Image& image, int algorithm )
   {
      SpinStatus spin;
      image.SetStatusCallback( &spin );
      image.Status().Initialize( "Noise evaluation" );
      image.Status().DisableInitialization();

      Console console;

      int numberOfThreads = Thread::NumberOfThreads( image.NumberOfPixels(), 1 );
      if ( numberOfThreads >= 3 )
      {
         int numberOfSubthreads = RoundI( numberOfThreads/3.0 );
         ReferenceArray<NoiseEvaluationThread> threads;
         threads.Add( new NoiseEvaluationThread( image, 0, algorithm, numberOfSubthreads ) );
         threads.Add( new NoiseEvaluationThread( image, 1, algorithm, numberOfSubthreads ) );
         threads.Add( new NoiseEvaluationThread( image, 2, algorithm, numberOfThreads - 2*numberOfSubthreads ) );

         AbstractImage::ThreadData data( image, 0 ); // unbounded
         AbstractImage::RunThreads( threads, data );

         for ( int i = 0; i < 3; ++i )
         {
            noiseEstimates[i]  = threads[i].noiseEstimate;
            noiseFractions[i]  = threads[i].noiseFraction;
            noiseAlgorithms[i] = String( threads[i].noiseAlgorithm );
         }

         threads.Destroy();
      }
      else
      {
         for ( int i = 0; i < 3; ++i )
         {
            NoiseEvaluationThread thread( image, i, algorithm, 1 );
            thread.Run();
            noiseEstimates[i]  = thread.noiseEstimate;
            noiseFractions[i]  = thread.noiseFraction;
            noiseAlgorithms[i] = String( thread.noiseAlgorithm );
         }
      }

      image.ResetSelections();
      image.Status().Complete();

      console.WriteLn( "<end><cbr>Gaussian noise estimates:" );
      for ( int i = 0; i < 3; ++i )
         console.WriteLn( String().Format( "s%d = %.3e, n%d = %.4f ",
                              i, noiseEstimates[i], i, noiseFractions[i] ) + '(' + noiseAlgorithms[i] + ')' );
   }
示例#5
0
void NewImageInterface::__ColorSample_Paint( Control& sender, const Rect& /*updateRect*/ )
{
   Graphics g( sender );

   RGBA rgba = RGBAColor( float( instance.v0 ), float( instance.v1 ), float( instance.v2 ) );

   if ( HASALPHA )
   {
      g.SetBrush( Bitmap( ":/image-window/transparent-small.png" ) );
      g.SetPen( Pen::Null() );
      g.DrawRect( sender.BoundsRect() );

      SetAlpha( rgba, RoundI( 255*instance.va ) );
   }

   g.SetBrush( rgba );
   g.SetPen( RGBAColor( 0, 0, 0 ) );
   g.DrawRect( sender.BoundsRect() );
}
void AdaptiveStretchCurveGraphInterface::GenerateGraphCurve()
{
   if ( m_curveBitmap.IsNull() )
      m_curveBitmap = Bitmap( m_width, m_height );

   m_curveBitmap.Fill( 0 ); // transparent

   if ( !m_curve.IsEmpty() )
   {
      Array<Point> points( m_curveRect.Width() );
      for ( int i = 0; i < m_curveRect.Width(); ++i )
      {
         points[i].x = m_curveRect.x0 + i;
         points[i].y = RoundI( m_curveRect.y1 - 1 - (m_curveRect.Height() - 1)*m_curve( double( i )/(m_curveRect.Width() - 1) ) );
      }
      Graphics G( m_curveBitmap );
      G.EnableAntialiasing();
      G.SetPen( m_curveColor );
      G.DrawPolyline( points );
      G.EndPaint();
   }
}
示例#7
0
文件: Slider.cpp 项目: morserover/PCL
void Slider::SetNormalizedValue( double f )
{
   int v0, v1; GetRange( v0, v1 );
   SetValue( v0 + RoundI( Range( f, 0.0, 1.0 )*(v1 - v0) ) );
}
示例#8
0
void AnnotationInterface::DynamicMousePress( View& v, const DPoint& p, int button, unsigned buttons, unsigned modifiers )
{
   // we only use left mouse button
   if ( button != MouseButton::Left )
      return;

   if (view == 0)
   {
      // can not run on previews
      if ( !v.IsMainView() )
         throw Error( "Annotation cannot run on previews. Please select a main view." );

      view = new View( v );
   }

   // only handle events in our active view
   if (v != *view)
      return;

   // get view image window
   ImageWindow w = view->Window();

   // and image coordinates of the click point
   int imageX = RoundI(p.x);
   int imageY = RoundI(p.y);

   // if annotation is not yet placed, place it now
   if (!annotationPlaced)
   {
      // set annotation position
      instance.annotationPositionX = imageX;
      instance.annotationPositionY = imageY;
      annotationPlaced = true;

      // place leader if needed
      if (instance.annotationShowLeader)
      {
         PlaceLeaderDefault();
      }

      // update annotation rectangle
      UpdateAnnotationRect( true );

      // redraw dynamic view
      UpdateView();

      // start dragging mode until user release the mouse
      dragging = DraggingType::Text;
      w.SetDynamicCursor(move_all_XPM, 10, 10);
   }
   // if the annotation is already placed
   else
   {
      // if mouse is pressed on annotation text rectangle
      // let's start dragging
      if (textRect.Includes(imageX, imageY))
      {
         // with Ctrl, both text and leader are dragged simulaneously
         if (modifiers & KeyModifier::Control)
         {
            dragging = DraggingType::Both;
         }
         // otherwise, just text is dragged
         else
         {
            dragging = DraggingType::Text;
         }
      }
      // if mouse is pressed on annotation leader rectangle and leader is visible
      // let's start dragging
      else if (instance.annotationShowLeader && leaderRect.Includes(imageX, imageY))
      {
         // with Ctrl, both text and leader are dragged simulaneously
         if (modifiers & KeyModifier::Control)
         {
            dragging = DraggingType::Both;
         }
         // otherwise, just leader endpoint is dragged
         else
         {
            dragging = DraggingType::Leader;
         }
      }
   }

   // if any dragging is started, remember current point
   if (dragging != DraggingType::None)
   {
      lastX = imageX;
      lastY = imageY;
   }
}
示例#9
0
void NewImageInterface::UpdateInitialValue( Edit& e, Slider& s, double v )
{
   e.SetText( String().Format( "%.8lf", v ) );
   s.SetValue( RoundI( v*s.MaxValue() ) );
}
void AdaptiveStretchCurveGraphInterface::GenerateGraphGrid()
{
   pcl::Font font( m_fontFace );
   font.SetPixelSize( m_fontSize );

   int labelHeight = font.TightBoundingRect( "123" ).Height();
   int labelSeparation = font.Width( '-' );
   int xLabelHeight = labelHeight + labelSeparation;
   int yLabelWidth = font.Width( "1.0" ) + labelSeparation;

   m_curveRect = Rect( m_margin + yLabelWidth + m_tickSize,
                       m_margin + m_tickSize,
                       m_width  - m_margin - m_tickSize,
                       m_height - m_margin - xLabelHeight - m_tickSize );

   if ( m_gridBitmap.IsNull() )
      m_gridBitmap = Bitmap( m_width, m_height );

   m_gridBitmap.Fill( m_backgroundColor );

   Graphics G( m_gridBitmap );

   G.SetPen( m_axisColor );
   G.StrokeRect( m_curveRect );

   G.SetFont( font );

   // Plot horizontal axes + vertical grid
   for ( int w = 0; w <= 100; w += 10 )
   {
      double f = w/100.0;
      int x = RoundI( m_curveRect.x0 + f*(m_curveRect.Width() - 1) );
      G.DrawLine( x, m_curveRect.y0-m_tickSize, x, m_curveRect.y0 );
      G.DrawLine( x, m_curveRect.y1, x, m_curveRect.y1+m_tickSize );
      G.DrawText( x - labelHeight/2, m_height-m_margin, String().Format( "%.1f", f ) );
      if ( w > 0 && w < 100 )
      {
         G.SetPen( m_gridColor );
         G.DrawLine( x, m_curveRect.y0+1, x, m_curveRect.y1-2 );
         G.SetPen( m_axisColor );
      }
   }

   // Plot vertical axes + horizontal grid
   for ( int h = 0; h <= 100; h += 10 )
   {
      double f = h/100.0;
      int y = RoundI( m_curveRect.y1 - 1 - f*(m_curveRect.Height() - 1) );
      G.DrawLine( m_curveRect.x0-m_tickSize, y, m_curveRect.x0, y );
      G.DrawLine( m_curveRect.x1, y, m_curveRect.x1+m_tickSize, y );
      G.DrawText( m_curveRect.x0-m_tickSize-yLabelWidth, y+labelHeight/2, String().Format( "%.1f", f ) );
      if ( h > 0 && h < 100 )
      {
         G.SetPen( m_gridColor );
         G.DrawLine( m_curveRect.x0+1, y, m_curveRect.x1-2, y );
         G.SetPen( m_axisColor );
      }
   }

   G.EndPaint();
}
示例#11
0
void JPCInstance::CreateImage( const ImageInfo& info )
{
   CheckOpenStream( !m_path.IsEmpty(), "CreateImage" );

   InitJasPer();

   if ( !info.IsValid() )
      JP2KERROR( "Invalid image parameters in JPEG2000 file creation" );

   if ( info.colorSpace != ColorSpace::RGB && info.colorSpace != ColorSpace::Gray )
      JP2KERROR( "Unsupported color space in JPEG2000 file creation" );

   if ( m_options.bitsPerSample != 8 )
      if ( m_jp2Options.lossyCompression || m_options.bitsPerSample < 8 )
         m_options.bitsPerSample = 8;
      else if ( m_options.bitsPerSample != 16 )
         m_options.bitsPerSample = 16;

   if ( m_jp2CMProfile == nullptr )
      m_options.embedICCProfile = false;

   m_options.ieeefpSampleFormat = m_options.complexSample = false;

   IsoString path8 =
#ifdef __PCL_WINDOWS
      File::UnixPathToWindows( m_path ).ToMBS();
#else
      m_path.ToUTF8();
#endif
   m_jp2Stream = jas_stream_fopen( path8.c_str(), "wb" );
   if ( m_jp2Stream == nullptr )
      JP2KERROR( "Unable to create JPEG2000 file" );

   m_jp2Image = jas_image_create0();
   if ( m_jp2Image == nullptr )
      JP2KERROR( "Unable to create JPEG2000 image" );

   for ( int c = 0; c < info.numberOfChannels; ++c )
   {
      jas_image_cmptparm_t p;
      ::memset( &p, 0, sizeof( jas_image_cmptparm_t ) );
      p.tlx = p.tly = 0;                  // top-left corner position
      p.hstep = p.vstep = 1;              // coordinate grid step sizes
      p.width = info.width;               // width in pixels
      p.height = info.height;             // height in pixels
      p.prec = m_options.bitsPerSample;   // bit depth: 8 or 16 bits
      p.sgnd = m_jp2Options.signedSample; // signed or unsigned samples

      if ( jas_image_addcmpt( m_jp2Image, c, &p ) != 0 )
         JP2KERROR( "Unable to create JPEG2000 image component" );
   }

   if ( info.colorSpace == ColorSpace::Gray )
   {
      jas_image_setclrspc( m_jp2Image, m_options.embedICCProfile ? JAS_CLRSPC_GENGRAY : JAS_CLRSPC_SGRAY );

      jas_image_setcmpttype( m_jp2Image, 0, JAS_IMAGE_CT_COLOR( JAS_CLRSPC_CHANIND_GRAY_Y ) );

      if ( info.numberOfChannels > 1 )
         jas_image_setcmpttype( m_jp2Image, 1, JAS_IMAGE_CT_COLOR( JAS_IMAGE_CT_OPACITY ) );
   }
   else
   {
      jas_image_setclrspc( m_jp2Image, m_options.embedICCProfile ? JAS_CLRSPC_GENRGB : JAS_CLRSPC_SRGB );

      jas_image_setcmpttype( m_jp2Image, 0, JAS_IMAGE_CT_COLOR( JAS_CLRSPC_CHANIND_RGB_R ) );
      jas_image_setcmpttype( m_jp2Image, 1, JAS_IMAGE_CT_COLOR( JAS_CLRSPC_CHANIND_RGB_G ) );
      jas_image_setcmpttype( m_jp2Image, 2, JAS_IMAGE_CT_COLOR( JAS_CLRSPC_CHANIND_RGB_B ) );

      if ( info.numberOfChannels > 3 )
         jas_image_setcmpttype( m_jp2Image, 3, JAS_IMAGE_CT_COLOR( JAS_IMAGE_CT_OPACITY ) );
   }

   if ( m_options.embedICCProfile )
      jas_image_setcmprof( m_jp2Image, m_jp2CMProfile );

   if ( m_jp2Options.resolutionData )
   {
      m_jp2Image->rescm_ = m_options.metricResolution;
      m_jp2Image->hdispres_ = RoundI( m_options.xResolution );
      m_jp2Image->vdispres_ = RoundI( m_options.yResolution );
   }
}
示例#12
0
文件: JPEG.cpp 项目: SunGong1993/PCL
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;
   }
}