1 /*M/////////////////////////////////////////////////////////////////////////////////////// 2 // 3 // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. 4 // 5 // By downloading, copying, installing or using the software you agree to this license. 6 // If you do not agree to this license, do not download, install, 7 // copy or use the software. 8 // 9 // 10 // License Agreement 11 // For Open Source Computer Vision Library 12 // 13 // Copyright (C) 2000-2008, Intel Corporation, all rights reserved. 14 // Copyright (C) 2009, Willow Garage Inc., all rights reserved. 15 // Third party copyrights are property of their respective owners. 16 // 17 // Redistribution and use in source and binary forms, with or without modification, 18 // are permitted provided that the following conditions are met: 19 // 20 // * Redistribution's of source code must retain the above copyright notice, 21 // this list of conditions and the following disclaimer. 22 // 23 // * Redistribution's in binary form must reproduce the above copyright notice, 24 // this list of conditions and the following disclaimer in the documentation 25 // and/or other materials provided with the distribution. 26 // 27 // * The name of the copyright holders may not be used to endorse or promote products 28 // derived from this software without specific prior written permission. 29 // 30 // This software is provided by the copyright holders and contributors "as is" and 31 // any express or implied warranties, including, but not limited to, the implied 32 // warranties of merchantability and fitness for a particular purpose are disclaimed. 33 // In no event shall the Intel Corporation or contributors be liable for any direct, 34 // indirect, incidental, special, exemplary, or consequential damages 35 // (including, but not limited to, procurement of substitute goods or services; 36 // loss of use, data, or profits; or business interruption) however caused 37 // and on any theory of liability, whether in contract, strict liability, 38 // or tort (including negligence or otherwise) arising in any way out of 39 // the use of this software, even if advised of the possibility of such damage. 40 // 41 //M*/ 42 43 #include "precomp.hpp" 44 45 #ifdef HAVE_OPENEXR 46 47 #if defined _MSC_VER && _MSC_VER >= 1200 48 # pragma warning( disable: 4100 4244 4267 ) 49 #endif 50 51 #if defined __GNUC__ && defined __APPLE__ 52 # pragma GCC diagnostic ignored "-Wshadow" 53 #endif 54 55 #include <ImfHeader.h> 56 #include <ImfInputFile.h> 57 #include <ImfOutputFile.h> 58 #include <ImfChannelList.h> 59 #include <ImfStandardAttributes.h> 60 #include <half.h> 61 #include "grfmt_exr.hpp" 62 63 #if defined _WIN32 64 65 #undef UINT 66 #define UINT ((Imf::PixelType)0) 67 #undef HALF 68 #define HALF ((Imf::PixelType)1) 69 #undef FLOAT 70 #define FLOAT ((Imf::PixelType)2) 71 72 #endif 73 74 namespace cv 75 { 76 77 /////////////////////// ExrDecoder /////////////////// 78 79 ExrDecoder::ExrDecoder() 80 { 81 m_signature = "\x76\x2f\x31\x01"; 82 m_file = 0; 83 m_red = m_green = m_blue = 0; 84 } 85 86 87 ExrDecoder::~ExrDecoder() 88 { 89 close(); 90 } 91 92 93 void ExrDecoder::close() 94 { 95 if( m_file ) 96 { 97 delete m_file; 98 m_file = 0; 99 } 100 } 101 102 103 int ExrDecoder::type() const 104 { 105 return CV_MAKETYPE((m_isfloat ? CV_32F : CV_32S), m_iscolor ? 3 : 1); 106 } 107 108 109 bool ExrDecoder::readHeader() 110 { 111 bool result = false; 112 113 m_file = new InputFile( m_filename.c_str() ); 114 115 if( !m_file ) // probably paranoid 116 return false; 117 118 m_datawindow = m_file->header().dataWindow(); 119 m_width = m_datawindow.max.x - m_datawindow.min.x + 1; 120 m_height = m_datawindow.max.y - m_datawindow.min.y + 1; 121 122 // the type HALF is converted to 32 bit float 123 // and the other types supported by OpenEXR are 32 bit anyway 124 m_bit_depth = 32; 125 126 if( hasChromaticities( m_file->header() )) 127 m_chroma = chromaticities( m_file->header() ); 128 129 const ChannelList &channels = m_file->header().channels(); 130 m_red = channels.findChannel( "R" ); 131 m_green = channels.findChannel( "G" ); 132 m_blue = channels.findChannel( "B" ); 133 if( m_red || m_green || m_blue ) 134 { 135 m_iscolor = true; 136 m_ischroma = false; 137 result = true; 138 } 139 else 140 { 141 m_green = channels.findChannel( "Y" ); 142 if( m_green ) 143 { 144 m_ischroma = true; 145 m_red = channels.findChannel( "RY" ); 146 m_blue = channels.findChannel( "BY" ); 147 m_iscolor = (m_blue || m_red); 148 result = true; 149 } 150 else 151 result = false; 152 } 153 154 if( result ) 155 { 156 int uintcnt = 0; 157 int chcnt = 0; 158 if( m_red ) 159 { 160 chcnt++; 161 uintcnt += ( m_red->type == UINT ); 162 } 163 if( m_green ) 164 { 165 chcnt++; 166 uintcnt += ( m_green->type == UINT ); 167 } 168 if( m_blue ) 169 { 170 chcnt++; 171 uintcnt += ( m_blue->type == UINT ); 172 } 173 m_type = (chcnt == uintcnt) ? UINT : FLOAT; 174 175 m_isfloat = (m_type == FLOAT); 176 } 177 178 if( !result ) 179 close(); 180 181 return result; 182 } 183 184 185 bool ExrDecoder::readData( Mat& img ) 186 { 187 m_native_depth = CV_MAT_DEPTH(type()) == img.depth(); 188 bool color = img.channels() > 1; 189 190 uchar* data = img.ptr(); 191 int step = img.step; 192 bool justcopy = m_native_depth; 193 bool chromatorgb = false; 194 bool rgbtogray = false; 195 bool result = true; 196 FrameBuffer frame; 197 int xsample[3] = {1, 1, 1}; 198 char *buffer; 199 int xstep; 200 int ystep; 201 202 xstep = m_native_depth ? 4 : 1; 203 204 if( !m_native_depth || (!color && m_iscolor )) 205 { 206 buffer = (char *)new float[ m_width * 3 ]; 207 ystep = 0; 208 } 209 else 210 { 211 buffer = (char *)data; 212 ystep = step; 213 } 214 215 if( m_ischroma ) 216 { 217 if( color ) 218 { 219 if( m_iscolor ) 220 { 221 if( m_blue ) 222 { 223 frame.insert( "BY", Slice( m_type, 224 buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep, 225 12, ystep, m_blue->xSampling, m_blue->ySampling, 0.0 )); 226 xsample[0] = m_blue->ySampling; 227 } 228 if( m_green ) 229 { 230 frame.insert( "Y", Slice( m_type, 231 buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 4, 232 12, ystep, m_green->xSampling, m_green->ySampling, 0.0 )); 233 xsample[1] = m_green->ySampling; 234 } 235 if( m_red ) 236 { 237 frame.insert( "RY", Slice( m_type, 238 buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 8, 239 12, ystep, m_red->xSampling, m_red->ySampling, 0.0 )); 240 xsample[2] = m_red->ySampling; 241 } 242 chromatorgb = true; 243 } 244 else 245 { 246 frame.insert( "Y", Slice( m_type, 247 buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep, 248 12, ystep, m_green->xSampling, m_green->ySampling, 0.0 )); 249 frame.insert( "Y", Slice( m_type, 250 buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 4, 251 12, ystep, m_green->xSampling, m_green->ySampling, 0.0 )); 252 frame.insert( "Y", Slice( m_type, 253 buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 8, 254 12, ystep, m_green->xSampling, m_green->ySampling, 0.0 )); 255 xsample[0] = m_green->ySampling; 256 xsample[1] = m_green->ySampling; 257 xsample[2] = m_green->ySampling; 258 } 259 } 260 else 261 { 262 frame.insert( "Y", Slice( m_type, 263 buffer - m_datawindow.min.x * 4 - m_datawindow.min.y * ystep, 264 4, ystep, m_green->xSampling, m_green->ySampling, 0.0 )); 265 xsample[0] = m_green->ySampling; 266 } 267 } 268 else 269 { 270 if( m_blue ) 271 { 272 frame.insert( "B", Slice( m_type, 273 buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep, 274 12, ystep, m_blue->xSampling, m_blue->ySampling, 0.0 )); 275 xsample[0] = m_blue->ySampling; 276 } 277 if( m_green ) 278 { 279 frame.insert( "G", Slice( m_type, 280 buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 4, 281 12, ystep, m_green->xSampling, m_green->ySampling, 0.0 )); 282 xsample[1] = m_green->ySampling; 283 } 284 if( m_red ) 285 { 286 frame.insert( "R", Slice( m_type, 287 buffer - m_datawindow.min.x * 12 - m_datawindow.min.y * ystep + 8, 288 12, ystep, m_red->xSampling, m_red->ySampling, 0.0 )); 289 xsample[2] = m_red->ySampling; 290 } 291 if(color == 0) 292 { 293 rgbtogray = true; 294 justcopy = false; 295 } 296 } 297 298 m_file->setFrameBuffer( frame ); 299 if( justcopy ) 300 { 301 m_file->readPixels( m_datawindow.min.y, m_datawindow.max.y ); 302 303 if( color ) 304 { 305 if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) 306 UpSample( data, 3, step / xstep, xsample[0], m_blue->ySampling ); 307 if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) 308 UpSample( data + xstep, 3, step / xstep, xsample[1], m_green->ySampling ); 309 if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) 310 UpSample( data + 2 * xstep, 3, step / xstep, xsample[2], m_red->ySampling ); 311 } 312 else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) 313 UpSample( data, 1, step / xstep, xsample[0], m_green->ySampling ); 314 } 315 else 316 { 317 uchar *out = data; 318 int x, y; 319 for( y = m_datawindow.min.y; y <= m_datawindow.max.y; y++ ) 320 { 321 m_file->readPixels( y, y ); 322 323 if( rgbtogray ) 324 { 325 if( xsample[0] != 1 ) 326 UpSampleX( (float *)buffer, 3, xsample[0] ); 327 if( xsample[1] != 1 ) 328 UpSampleX( (float *)buffer + 4, 3, xsample[1] ); 329 if( xsample[2] != 1 ) 330 UpSampleX( (float *)buffer + 8, 3, xsample[2] ); 331 332 RGBToGray( (float *)buffer, (float *)out ); 333 } 334 else 335 { 336 if( xsample[0] != 1 ) 337 UpSampleX( (float *)buffer, 3, xsample[0] ); 338 if( xsample[1] != 1 ) 339 UpSampleX( (float *)(buffer + 4), 3, xsample[1] ); 340 if( xsample[2] != 1 ) 341 UpSampleX( (float *)(buffer + 8), 3, xsample[2] ); 342 343 if( chromatorgb ) 344 ChromaToBGR( (float *)buffer, 1, step ); 345 346 if( m_type == FLOAT ) 347 { 348 float *fi = (float *)buffer; 349 for( x = 0; x < m_width * 3; x++) 350 { 351 out[x] = cv::saturate_cast<uchar>(fi[x]*5); 352 } 353 } 354 else 355 { 356 unsigned *ui = (unsigned *)buffer; 357 for( x = 0; x < m_width * 3; x++) 358 { 359 out[x] = cv::saturate_cast<uchar>(ui[x]); 360 } 361 } 362 } 363 364 out += step; 365 } 366 if( color ) 367 { 368 if( m_blue && (m_blue->xSampling != 1 || m_blue->ySampling != 1) ) 369 UpSampleY( data, 3, step / xstep, m_blue->ySampling ); 370 if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) 371 UpSampleY( data + xstep, 3, step / xstep, m_green->ySampling ); 372 if( m_red && (m_red->xSampling != 1 || m_red->ySampling != 1) ) 373 UpSampleY( data + 2 * xstep, 3, step / xstep, m_red->ySampling ); 374 } 375 else if( m_green && (m_green->xSampling != 1 || m_green->ySampling != 1) ) 376 UpSampleY( data, 1, step / xstep, m_green->ySampling ); 377 } 378 379 if( chromatorgb ) 380 ChromaToBGR( (float *)data, m_height, step / xstep ); 381 382 close(); 383 384 return result; 385 } 386 387 /** 388 // on entry pixel values are stored packed in the upper left corner of the image 389 // this functions expands them by duplication to cover the whole image 390 */ 391 void ExrDecoder::UpSample( uchar *data, int xstep, int ystep, int xsample, int ysample ) 392 { 393 for( int y = (m_height - 1) / ysample, yre = m_height - ysample; y >= 0; y--, yre -= ysample ) 394 { 395 for( int x = (m_width - 1) / xsample, xre = m_width - xsample; x >= 0; x--, xre -= xsample ) 396 { 397 for( int i = 0; i < ysample; i++ ) 398 { 399 for( int n = 0; n < xsample; n++ ) 400 { 401 if( !m_native_depth ) 402 data[(yre + i) * ystep + (xre + n) * xstep] = data[y * ystep + x * xstep]; 403 else if( m_type == FLOAT ) 404 ((float *)data)[(yre + i) * ystep + (xre + n) * xstep] = ((float *)data)[y * ystep + x * xstep]; 405 else 406 ((unsigned *)data)[(yre + i) * ystep + (xre + n) * xstep] = ((unsigned *)data)[y * ystep + x * xstep]; 407 } 408 } 409 } 410 } 411 } 412 413 /** 414 // on entry pixel values are stored packed in the upper left corner of the image 415 // this functions expands them by duplication to cover the whole image 416 */ 417 void ExrDecoder::UpSampleX( float *data, int xstep, int xsample ) 418 { 419 for( int x = (m_width - 1) / xsample, xre = m_width - xsample; x >= 0; x--, xre -= xsample ) 420 { 421 for( int n = 0; n < xsample; n++ ) 422 { 423 if( m_type == FLOAT ) 424 ((float *)data)[(xre + n) * xstep] = ((float *)data)[x * xstep]; 425 else 426 ((unsigned *)data)[(xre + n) * xstep] = ((unsigned *)data)[x * xstep]; 427 } 428 } 429 } 430 431 /** 432 // on entry pixel values are stored packed in the upper left corner of the image 433 // this functions expands them by duplication to cover the whole image 434 */ 435 void ExrDecoder::UpSampleY( uchar *data, int xstep, int ystep, int ysample ) 436 { 437 for( int y = m_height - ysample, yre = m_height - ysample; y >= 0; y -= ysample, yre -= ysample ) 438 { 439 for( int x = 0; x < m_width; x++ ) 440 { 441 for( int i = 1; i < ysample; i++ ) 442 { 443 if( !m_native_depth ) 444 data[(yre + i) * ystep + x * xstep] = data[y * ystep + x * xstep]; 445 else if( m_type == FLOAT ) 446 ((float *)data)[(yre + i) * ystep + x * xstep] = ((float *)data)[y * ystep + x * xstep]; 447 else 448 ((unsigned *)data)[(yre + i) * ystep + x * xstep] = ((unsigned *)data)[y * ystep + x * xstep]; 449 } 450 } 451 } 452 } 453 454 /** 455 // algorithm from ImfRgbaYca.cpp 456 */ 457 void ExrDecoder::ChromaToBGR( float *data, int numlines, int step ) 458 { 459 for( int y = 0; y < numlines; y++ ) 460 { 461 for( int x = 0; x < m_width; x++ ) 462 { 463 double b, Y, r; 464 if( !m_native_depth ) 465 { 466 b = ((uchar *)data)[y * step + x * 3]; 467 Y = ((uchar *)data)[y * step + x * 3 + 1]; 468 r = ((uchar *)data)[y * step + x * 3 + 2]; 469 } 470 else if( m_type == FLOAT ) 471 { 472 b = data[y * step + x * 3]; 473 Y = data[y * step + x * 3 + 1]; 474 r = data[y * step + x * 3 + 2]; 475 } 476 else 477 { 478 b = ((unsigned *)data)[y * step + x * 3]; 479 Y = ((unsigned *)data)[y * step + x * 3 + 1]; 480 r = ((unsigned *)data)[y * step + x * 3 + 2]; 481 } 482 r = (r + 1) * Y; 483 b = (b + 1) * Y; 484 Y = (Y - b * m_chroma.blue[1] - r * m_chroma.red[1]) / m_chroma.green[1]; 485 486 if( !m_native_depth ) 487 { 488 ((uchar *)data)[y * step + x * 3 + 0] = cv::saturate_cast<uchar>(b); 489 ((uchar *)data)[y * step + x * 3 + 1] = cv::saturate_cast<uchar>(Y); 490 ((uchar *)data)[y * step + x * 3 + 2] = cv::saturate_cast<uchar>(r); 491 } 492 else if( m_type == FLOAT ) 493 { 494 data[y * step + x * 3] = (float)b; 495 data[y * step + x * 3 + 1] = (float)Y; 496 data[y * step + x * 3 + 2] = (float)r; 497 } 498 else 499 { 500 int t = cvRound(b); 501 ((unsigned *)data)[y * step + x * 3 + 0] = (unsigned)MAX(t, 0); 502 t = cvRound(Y); 503 ((unsigned *)data)[y * step + x * 3 + 1] = (unsigned)MAX(t, 0); 504 t = cvRound(r); 505 ((unsigned *)data)[y * step + x * 3 + 2] = (unsigned)MAX(t, 0); 506 } 507 } 508 } 509 } 510 511 512 /** 513 // convert one row to gray 514 */ 515 void ExrDecoder::RGBToGray( float *in, float *out ) 516 { 517 if( m_type == FLOAT ) 518 { 519 if( m_native_depth ) 520 { 521 for( int i = 0, n = 0; i < m_width; i++, n += 3 ) 522 out[i] = in[n] * m_chroma.blue[0] + in[n + 1] * m_chroma.green[0] + in[n + 2] * m_chroma.red[0]; 523 } 524 else 525 { 526 uchar *o = (uchar *)out; 527 for( int i = 0, n = 0; i < m_width; i++, n += 3 ) 528 o[i] = (uchar) (in[n] * m_chroma.blue[0] + in[n + 1] * m_chroma.green[0] + in[n + 2] * m_chroma.red[0]); 529 } 530 } 531 else // UINT 532 { 533 if( m_native_depth ) 534 { 535 unsigned *ui = (unsigned *)in; 536 for( int i = 0; i < m_width * 3; i++ ) 537 ui[i] -= 0x80000000; 538 int *si = (int *)in; 539 for( int i = 0, n = 0; i < m_width; i++, n += 3 ) 540 ((int *)out)[i] = int(si[n] * m_chroma.blue[0] + si[n + 1] * m_chroma.green[0] + si[n + 2] * m_chroma.red[0]); 541 } 542 else // how to best convert float to uchar? 543 { 544 unsigned *ui = (unsigned *)in; 545 for( int i = 0, n = 0; i < m_width; i++, n += 3 ) 546 ((uchar *)out)[i] = uchar((ui[n] * m_chroma.blue[0] + ui[n + 1] * m_chroma.green[0] + ui[n + 2] * m_chroma.red[0]) * (256.0 / 4294967296.0)); 547 } 548 } 549 } 550 551 552 ImageDecoder ExrDecoder::newDecoder() const 553 { 554 return makePtr<ExrDecoder>(); 555 } 556 557 /////////////////////// ExrEncoder /////////////////// 558 559 560 ExrEncoder::ExrEncoder() 561 { 562 m_description = "OpenEXR Image files (*.exr)"; 563 } 564 565 566 ExrEncoder::~ExrEncoder() 567 { 568 } 569 570 571 bool ExrEncoder::isFormatSupported( int depth ) const 572 { 573 return CV_MAT_DEPTH(depth) >= CV_8U && CV_MAT_DEPTH(depth) < CV_64F; 574 } 575 576 577 // TODO scale appropriately 578 bool ExrEncoder::write( const Mat& img, const std::vector<int>& ) 579 { 580 int width = img.cols, height = img.rows; 581 int depth = img.depth(), channels = img.channels(); 582 bool result = false; 583 bool issigned = depth == CV_8S || depth == CV_16S || depth == CV_32S; 584 bool isfloat = depth == CV_32F || depth == CV_64F; 585 depth = CV_ELEM_SIZE1(depth)*8; 586 const int step = img.step; 587 588 Header header( width, height ); 589 Imf::PixelType type; 590 591 if(depth == 8) 592 type = HALF; 593 else if(isfloat) 594 type = FLOAT; 595 else 596 type = UINT; 597 598 if( channels == 3 ) 599 { 600 header.channels().insert( "R", Channel( type )); 601 header.channels().insert( "G", Channel( type )); 602 header.channels().insert( "B", Channel( type )); 603 //printf("bunt\n"); 604 } 605 else 606 { 607 header.channels().insert( "Y", Channel( type )); 608 //printf("gray\n"); 609 } 610 611 OutputFile file( m_filename.c_str(), header ); 612 613 FrameBuffer frame; 614 615 char *buffer; 616 int bufferstep; 617 int size; 618 if( type == FLOAT && depth == 32 ) 619 { 620 buffer = (char *)const_cast<uchar *>(img.ptr()); 621 bufferstep = step; 622 size = 4; 623 } 624 else if( depth > 16 || type == UINT ) 625 { 626 buffer = (char *)new unsigned[width * channels]; 627 bufferstep = 0; 628 size = 4; 629 } 630 else 631 { 632 buffer = (char *)new half[width * channels]; 633 bufferstep = 0; 634 size = 2; 635 } 636 637 //printf("depth %d %s\n", depth, types[type]); 638 639 if( channels == 3 ) 640 { 641 frame.insert( "B", Slice( type, buffer, size * 3, bufferstep )); 642 frame.insert( "G", Slice( type, buffer + size, size * 3, bufferstep )); 643 frame.insert( "R", Slice( type, buffer + size * 2, size * 3, bufferstep )); 644 } 645 else 646 frame.insert( "Y", Slice( type, buffer, size, bufferstep )); 647 648 file.setFrameBuffer( frame ); 649 650 int offset = issigned ? 1 << (depth - 1) : 0; 651 652 result = true; 653 if( type == FLOAT && depth == 32 ) 654 { 655 try 656 { 657 file.writePixels( height ); 658 } 659 catch(...) 660 { 661 result = false; 662 } 663 } 664 else 665 { 666 // int scale = 1 << (32 - depth); 667 // printf("scale %d\n", scale); 668 for(int line = 0; line < height; line++) 669 { 670 if(type == UINT) 671 { 672 unsigned *buf = (unsigned*)buffer; // FIXME 64-bit problems 673 674 if( depth <= 8 ) 675 { 676 const uchar* sd = img.ptr(line); 677 for(int i = 0; i < width * channels; i++) 678 buf[i] = sd[i] + offset; 679 } 680 else if( depth <= 16 ) 681 { 682 const unsigned short *sd = img.ptr<unsigned short>(line); 683 for(int i = 0; i < width * channels; i++) 684 buf[i] = sd[i] + offset; 685 } 686 else 687 { 688 const int *sd = img.ptr<int>(line); // FIXME 64-bit problems 689 for(int i = 0; i < width * channels; i++) 690 buf[i] = (unsigned) sd[i] + offset; 691 } 692 } 693 else 694 { 695 half *buf = (half *)buffer; 696 697 if( depth <= 8 ) 698 { 699 const uchar* sd = img.ptr(line); 700 for(int i = 0; i < width * channels; i++) 701 buf[i] = sd[i]; 702 } 703 else if( depth <= 16 ) 704 { 705 const unsigned short *sd = img.ptr<unsigned short>(line); 706 for(int i = 0; i < width * channels; i++) 707 buf[i] = sd[i]; 708 } 709 } 710 try 711 { 712 file.writePixels( 1 ); 713 } 714 catch(...) 715 { 716 result = false; 717 break; 718 } 719 } 720 delete[] buffer; 721 } 722 723 return result; 724 } 725 726 727 ImageEncoder ExrEncoder::newEncoder() const 728 { 729 return makePtr<ExrEncoder>(); 730 } 731 732 } 733 734 #endif 735 736 /* End of file. */ 737