// This code is in the public domain -- castanyo@yahoo.es #include "ImageIO.h" #include "Image.h" #include "FloatImage.h" #include "TgaFile.h" #include "PsdFile.h" #include "DirectDrawSurface.h" #include "PixelFormat.h" #include "nvmath/Color.h" #include "nvmath/Half.h" #include "nvcore/Ptr.h" #include "nvcore/Utils.h" #include "nvcore/Array.inl" #include "nvcore/StrLib.h" #include "nvcore/StdStream.h" #include "nvcore/TextWriter.h" // Extern #if defined(HAVE_FREEIMAGE) # include // If FreeImage available, do not use individual libraries, since that produces link conflicts in some platforms. # undef HAVE_JPEG # undef HAVE_PNG # undef HAVE_TIFF # undef HAVE_OPENEXR #endif #if defined(HAVE_JPEG) extern "C" { # include } #endif #if defined(HAVE_PNG) # include #endif #if defined(HAVE_TIFF) # define _TIFF_DATA_TYPEDEFS_ # include #endif #if defined(HAVE_OPENEXR) # include # include # include # include # include # include #endif #if defined(HAVE_STBIMAGE) # define STBI_NO_STDIO # include #endif using namespace nv; struct Color555 { uint16 b : 5; uint16 g : 5; uint16 r : 5; }; // Load TGA image. static Image * loadTGA(Stream & s) { nvCheck(!s.isError()); nvCheck(s.isLoading()); TgaHeader tga; s << tga; s.seek(TgaHeader::Size + tga.id_length); // Get header info. bool rle = false; bool pal = false; bool rgb = false; bool grey = false; switch( tga.image_type ) { case TGA_TYPE_RLE_INDEXED: rle = true; // no break is intended! case TGA_TYPE_INDEXED: if( tga.colormap_type!=1 || tga.colormap_size!=24 || tga.colormap_length>256 ) { nvDebug( "*** loadTGA: Error, only 24bit paletted images are supported.\n" ); return NULL; } pal = true; break; case TGA_TYPE_RLE_RGB: rle = true; // no break is intended! case TGA_TYPE_RGB: rgb = true; break; case TGA_TYPE_RLE_GREY: rle = true; // no break is intended! case TGA_TYPE_GREY: grey = true; break; default: nvDebug( "*** loadTGA: Error, unsupported image type.\n" ); return NULL; } const uint pixel_size = (tga.pixel_size/8); nvDebugCheck(pixel_size <= 4); const uint size = tga.width * tga.height * pixel_size; // Read palette uint8 palette[768]; if( pal ) { nvDebugCheck(tga.colormap_length <= 256); s.serialize(palette, 3 * tga.colormap_length); } // Decode image. uint8 * mem = new uint8[size]; if( rle ) { // Decompress image in src. uint8 * dst = mem; int num = size; while (num > 0) { // Get packet header uint8 c; s << c; uint count = (c & 0x7f) + 1; num -= count * pixel_size; if (c & 0x80) { // RLE pixels. uint8 pixel[4]; // uint8 pixel[pixel_size]; s.serialize( pixel, pixel_size ); do { memcpy(dst, pixel, pixel_size); dst += pixel_size; } while (--count); } else { // Raw pixels. count *= pixel_size; //file->Read8(dst, count); s.serialize(dst, count); dst += count; } } } else { s.serialize(mem, size); } // Allocate image. AutoPtr img(new Image()); img->allocate(tga.width, tga.height); int lstep; Color32 * dst; if( tga.flags & TGA_ORIGIN_UPPER ) { lstep = tga.width; dst = img->pixels(); } else { lstep = - tga.width; dst = img->pixels() + (tga.height-1) * tga.width; } // Write image. uint8 * src = mem; if( pal ) { for( int y = 0; y < tga.height; y++ ) { for( int x = 0; x < tga.width; x++ ) { uint8 idx = *src++; dst[x].setBGRA(palette[3*idx+0], palette[3*idx+1], palette[3*idx+2], 0xFF); } dst += lstep; } } else if( grey ) { img->setFormat(Image::Format_ARGB); for( int y = 0; y < tga.height; y++ ) { for( int x = 0; x < tga.width; x++ ) { dst[x].setBGRA(*src, *src, *src, *src); src++; } dst += lstep; } } else { if( tga.pixel_size == 16 ) { for( int y = 0; y < tga.height; y++ ) { for( int x = 0; x < tga.width; x++ ) { Color555 c = *reinterpret_cast(src); uint8 b = (c.b << 3) | (c.b >> 2); uint8 g = (c.g << 3) | (c.g >> 2); uint8 r = (c.r << 3) | (c.r >> 2); dst[x].setBGRA(b, g, r, 0xFF); src += 2; } dst += lstep; } } else if( tga.pixel_size == 24 ) { for( int y = 0; y < tga.height; y++ ) { for( int x = 0; x < tga.width; x++ ) { dst[x].setBGRA(src[0], src[1], src[2], 0xFF); src += 3; } dst += lstep; } } else if( tga.pixel_size == 32 ) { img->setFormat(Image::Format_ARGB); for( int y = 0; y < tga.height; y++ ) { for( int x = 0; x < tga.width; x++ ) { dst[x].setBGRA(src[0], src[1], src[2], src[3]); src += 4; } dst += lstep; } } } // free uncompressed data. delete [] mem; return img.release(); } // Save TGA image. static bool saveTGA(Stream & s, const Image * img) { nvCheck(!s.isError()); nvCheck(img != NULL); nvCheck(img->pixels() != NULL); TgaFile tga; tga.head.id_length = 0; tga.head.colormap_type = 0; tga.head.image_type = TGA_TYPE_RGB; tga.head.colormap_index = 0; tga.head.colormap_length = 0; tga.head.colormap_size = 0; tga.head.x_origin = 0; tga.head.y_origin = 0; tga.head.width = img->width(); tga.head.height = img->height(); if(img->format() == Image::Format_ARGB) { tga.head.pixel_size = 32; tga.head.flags = TGA_ORIGIN_UPPER | TGA_HAS_ALPHA; } else { tga.head.pixel_size = 24; tga.head.flags = TGA_ORIGIN_UPPER; } // @@ Serialize directly. tga.allocate(); const uint n = img->width() * img->height(); if(img->format() == Image::Format_ARGB) { for(uint i = 0; i < n; i++) { Color32 color = img->pixel(i); tga.mem[4 * i + 0] = color.b; tga.mem[4 * i + 1] = color.g; tga.mem[4 * i + 2] = color.r; tga.mem[4 * i + 3] = color.a; } } else { for(uint i = 0; i < n; i++) { Color32 color = img->pixel(i); tga.mem[3 * i + 0] = color.b; tga.mem[3 * i + 1] = color.g; tga.mem[3 * i + 2] = color.r; } } s << tga; tga.free(); return true; } /*static Image * loadPPM(Stream & s) { // @@ return NULL; }*/ // Save PPM image. static bool savePPM(Stream & s, const Image * img) { //if (img->depth() != 1) return false; //if (img->format() == Image::Format_ARGB) return false; uint w = img->width(); uint h = img->height(); TextWriter writer(&s); writer.format("P6\n"); writer.format("%d %d\n", w, h); writer.writeString("255\n"); for (uint i = 0; i < w * h; i++) { Color32 c = img->pixel(i); s << (uint8_t&)c.r << (uint8_t&)c.g << (uint8_t&)c.b; } return true; } /*static FloatImage * loadFloatPFM(Stream & s) { return NULL; }*/ /*static bool saveFloatPFM(Stream & s, const FloatImage * img, uint base_channel, uint channel_count) { return false; }*/ // Load PSD image. static Image * loadPSD(Stream & s) { nvCheck(!s.isError()); nvCheck(s.isLoading()); s.setByteOrder(Stream::BigEndian); PsdHeader header; s << header; if (!header.isValid()) { printf("invalid header!\n"); return NULL; } if (!header.isSupported()) { printf("unsupported file!\n"); return NULL; } int tmp; // Skip mode data. s << tmp; s.seek(s.tell() + tmp); // Skip image resources. s << tmp; s.seek(s.tell() + tmp); // Skip the reserved data. s << tmp; s.seek(s.tell() + tmp); // Find out if the data is compressed. // Known values: // 0: no compression // 1: RLE compressed uint16 compression; s << compression; if (compression > 1) { // Unknown compression type. return NULL; } uint channel_num = header.channel_count; AutoPtr img(new Image()); img->allocate(header.width, header.height); if (channel_num < 4) { // Clear the image. img->fill(Color32(0, 0, 0, 0xFF)); } else { // Enable alpha. img->setFormat(Image::Format_ARGB); // Ignore remaining channels. channel_num = 4; } const uint pixel_count = header.height * header.width; static const uint components[4] = {2, 1, 0, 3}; if (compression) { s.seek(s.tell() + header.height * header.channel_count * sizeof(uint16)); // Read RLE data. for (uint channel = 0; channel < channel_num; channel++) { uint8 * ptr = (uint8 *)img->pixels() + components[channel]; uint count = 0; while( count < pixel_count ) { if (s.isAtEnd()) return NULL; uint8 c; s << c; uint len = c; if (len < 128) { // Copy next len+1 bytes literally. len++; count += len; if (count > pixel_count) return NULL; while (len != 0) { s << *ptr; ptr += 4; len--; } } else if (len > 128) { // Next -len+1 bytes in the dest are replicated from next source byte. // (Interpret len as a negative 8-bit int.) len ^= 0xFF; len += 2; count += len; if (s.isAtEnd() || count > pixel_count) return NULL; uint8 val; s << val; while( len != 0 ) { *ptr = val; ptr += 4; len--; } } else if( len == 128 ) { // No-op. } } } } else { // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) // where each channel consists of an 8-bit value for each pixel in the image. // Read the data by channel. for (uint channel = 0; channel < channel_num; channel++) { uint8 * ptr = (uint8 *)img->pixels() + components[channel]; // Read the data. uint count = pixel_count; while (count != 0) { s << *ptr; ptr += 4; count--; } } } return img.release(); } static FloatImage * loadFloatDDS(Stream & s) { nvCheck(s.isLoading()); nvCheck(!s.isError()); DDSHeader header; s << header; // @@ We only support a few formats for now. if (header.pf.fourcc == D3DFMT_A16B16G16R16F) { const int size = header.width * header.height; uint16 * const data = new uint16[size * 4]; //s.serialize(data, size * 4 * sizeof(uint16)); for (int i = 0; i < 4* size; i++) { s << data[i]; } FloatImage * img = new FloatImage; img->allocate(4, header.width, header.height); uint32 * r = (uint32 *)img->channel(0); uint32 * g = (uint32 *)img->channel(1); uint32 * b = (uint32 *)img->channel(2); uint32 * a = (uint32 *)img->channel(3); uint16 * ptr = data; for (int i = 0; i < size; i++) { *r++ = half_to_float( *ptr++ ); *g++ = half_to_float( *ptr++ ); *b++ = half_to_float( *ptr++ ); *a++ = half_to_float( *ptr++ ); } delete [] data; return img; } else if (header.pf.fourcc == D3DFMT_R32F) { const int size = header.width * header.height; float * const data = new float[size]; for (int i = 0; i < size; i++) { s << data[i]; } FloatImage * img = new FloatImage; img->allocate(4, header.width, header.height); float * r = img->channel(0); float * ptr = data; for (int i = 0; i < size; i++) { *r++ = *ptr++; } delete [] data; img->clear(1, 0.0f); img->clear(2, 0.0f); img->clear(3, 1.0f); return img; } else if (header.pf.fourcc == D3DFMT_L16 || (header.pf.bitcount == 16 && header.pf.rmask == 0xFFFF && header.pf.gmask == 0 && header.pf.bmask == 0 && header.pf.amask == 0)) { const int size = header.width * header.height; uint16 * const data = new uint16[size]; for (int i = 0; i < size; i++) { s << data[i]; } FloatImage * img = new FloatImage; img->allocate(4, header.width, header.height); float * r = img->channel(0); uint16 * ptr = data; for (int i = 0; i < size; i++) { *r++ = float(*ptr++) / 65535.0f; } delete [] data; img->clear(1, 0.0f); img->clear(2, 0.0f); img->clear(3, 1.0f); return img; } else if (header.pf.fourcc == D3DFMT_L8 || (header.pf.bitcount == 8 && header.pf.rmask == 0xFF && header.pf.gmask == 0 && header.pf.bmask == 0 && header.pf.amask == 0)) { const int size = header.width * header.height; uint8 * const data = new uint8[size]; s.serialize(data, size); FloatImage * img = new FloatImage; img->allocate(4, header.width, header.height); float * r = img->channel(0); uint8 * ptr = data; for (int i = 0; i < size; i++) { *r++ = float(*ptr++) / 255.0f; } delete [] data; img->clear(1, 0.0f); img->clear(2, 0.0f); img->clear(3, 1.0f); return img; } return NULL; } static bool saveFloatDDS(Stream & s, const FloatImage * img, uint base_component, uint num_components) { nvCheck(s.isSaving()); nvCheck(!s.isError()); if (num_components != 4) return false; static const uint D3DFMT_A16B16G16R16F = 113; DDSHeader header; header.setTexture2D(); header.setWidth(img->width()); header.setHeight(img->height()); header.setFormatCode(D3DFMT_A16B16G16R16F); // ... s << header; uint32 * r = (uint32 *)img->channel(base_component + 0); uint32 * g = (uint32 *)img->channel(base_component + 1); uint32 * b = (uint32 *)img->channel(base_component + 2); uint32 * a = (uint32 *)img->channel(base_component + 3); const uint size = img->width() * img->height(); for (uint i = 0; i < size; i++) { uint16 R = half_from_float( *r++ ); uint16 G = half_from_float( *g++ ); uint16 B = half_from_float( *b++ ); uint16 A = half_from_float( *a++ ); s.serialize(&R, sizeof(uint16)); s.serialize(&G, sizeof(uint16)); s.serialize(&B, sizeof(uint16)); s.serialize(&A, sizeof(uint16)); } return true; } #if defined(HAVE_PNG) static void user_read_data(png_structp png_ptr, png_bytep data, png_size_t length) { nvDebugCheck(png_ptr != NULL); Stream * s = (Stream *)png_get_io_ptr(png_ptr); s->serialize(data, (int)length); if (s->isError()) { png_error(png_ptr, "Read Error"); } } static Image * loadPNG(Stream & s) { nvCheck(!s.isError()); // Set up a read buffer and check the library version png_structp png_ptr; png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { // nvDebug( "*** LoadPNG: Error allocating read buffer in file '%s'.\n", name ); return NULL; } // Allocate/initialize a memory block for the image information png_infop info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { png_destroy_read_struct(&png_ptr, NULL, NULL); // nvDebug( "*** LoadPNG: Error allocating image information for '%s'.\n", name ); return NULL; } // Set up the error handling if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, NULL); // nvDebug( "*** LoadPNG: Error reading png file '%s'.\n", name ); return NULL; } // Set up the I/O functions. png_set_read_fn(png_ptr, (void*)&s, user_read_data); // Retrieve the image header information png_uint_32 width, height; int bit_depth, color_type, interlace_type; png_read_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL); if (color_type == PNG_COLOR_TYPE_PALETTE && bit_depth <= 8) { // Convert indexed images to RGB. png_set_expand(png_ptr); } else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { // Convert grayscale to RGB. png_set_expand(png_ptr); } else if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { // Expand images with transparency to full alpha channels // so the data will be available as RGBA quartets. png_set_expand(png_ptr); } else if (bit_depth < 8) { // If we have < 8 scale it up to 8. //png_set_expand(png_ptr); png_set_packing(png_ptr); } // Reduce bit depth. if (bit_depth == 16) { png_set_strip_16(png_ptr); } // Represent gray as RGB if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_gray_to_rgb(png_ptr); } // Convert to RGBA filling alpha with 0xFF. if (!(color_type & PNG_COLOR_MASK_ALPHA)) { png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER); } // @todo Choose gamma according to the platform? double screen_gamma = 2.2; int intent; if (png_get_sRGB(png_ptr, info_ptr, &intent)) { png_set_gamma(png_ptr, screen_gamma, 0.45455); } else { double image_gamma; if (png_get_gAMA(png_ptr, info_ptr, &image_gamma)) { png_set_gamma(png_ptr, screen_gamma, image_gamma); } else { png_set_gamma(png_ptr, screen_gamma, 0.45455); } } // Perform the selected transforms. png_read_update_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL); AutoPtr img(new Image()); img->allocate(width, height); // Set internal format flags. if(color_type & PNG_COLOR_MASK_COLOR) { //img->flags |= PI_IF_HAS_COLOR; } if(color_type & PNG_COLOR_MASK_ALPHA) { //img->flags |= PI_IF_HAS_ALPHA; img->setFormat(Image::Format_ARGB); } // Read the image uint8 * pixels = (uint8 *)img->pixels(); png_bytep * row_data = new png_bytep[sizeof(png_byte) * height]; for (uint i = 0; i < height; i++) { row_data[i] = &(pixels[width * 4 * i]); } png_read_image(png_ptr, row_data); delete [] row_data; // Finish things up png_read_end(png_ptr, info_ptr); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); // RGBA to BGRA. uint num = width * height; for(uint i = 0; i < num; i++) { Color32 c = img->pixel(i); img->pixel(i) = Color32(c.b, c.g, c.r, c.a); } // Compute alpha channel if needed. /*if( img->flags & PI_IU_BUMPMAP || img->flags & PI_IU_ALPHAMAP ) { if( img->flags & PI_IF_HAS_COLOR && !(img->flags & PI_IF_HAS_ALPHA)) { img->ComputeAlphaFromColor(); } }*/ return img.release(); } static void user_write_data(png_structp png_ptr, png_bytep data, png_size_t length) { nvDebugCheck(png_ptr != NULL); Stream * s = (Stream *)png_get_io_ptr(png_ptr); s->serialize(data, (int)length); if (s->isError()) { png_error(png_ptr, "Write Error"); } } static void user_write_flush(png_structp png_ptr) { } static bool savePNG(Stream & s, const Image * img, const char ** tags/*=NULL*/) { nvCheck(!s.isError()); nvCheck(img != NULL); nvCheck(img->pixels() != NULL); // Set up a write buffer and check the library version png_structp png_ptr; png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) { return false; } // Allocate/initialize a memory block for the image information png_infop info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { png_destroy_write_struct(&png_ptr, NULL); return false; } // Set up the error handling if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); return false; } // Set up the I/O functions. png_set_write_fn(png_ptr, (void*)&s, user_write_data, user_write_flush); // Set image header information int color_type = PNG_COLOR_TYPE_RGBA; switch(img->format()) { case Image::Format_RGB: color_type = PNG_COLOR_TYPE_RGB; break; case Image::Format_ARGB: color_type = PNG_COLOR_TYPE_RGBA; break; } png_set_IHDR(png_ptr, info_ptr, img->width(), img->height(), 8, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // Set image data png_bytep * row_data = new png_bytep[sizeof(png_byte) * img->height()]; for (uint i = 0; i < img->height(); i++) { row_data[i] = (png_byte*)img->scanline (i); if (img->format() == Image::Format_RGB) row_data[i]--; // This is a bit of a hack, libpng expects images in ARGB format not BGRA, it supports BGR swapping, but not alpha swapping. } png_set_rows(png_ptr, info_ptr, row_data); png_text * text = NULL; if (tags != NULL) { int count = 0; while(tags[2 * count] != NULL) count++; text = new png_text[count]; memset(text, 0, count * sizeof(png_text); for (int i = 0; i < count; i++) { text[i].compression = PNG_TEXT_COMPRESSION_NONE; text[i].key = tags[2 * i + 0]; text[i].text = tags[2 * i + 1]; } png_set_text(png_ptr, info_ptr, text, count); } png_write_png(png_ptr, info_ptr, // component order is BGR(A) PNG_TRANSFORM_BGR | // Strip alpha byte for RGB images (img->format() == Image::Format_RGB ? PNG_TRANSFORM_STRIP_FILLER : 0) , NULL); // Finish things up png_destroy_write_struct(&png_ptr, &info_ptr); delete [] row_data; delete [] text; return true; } #endif // defined(HAVE_PNG) #if defined(HAVE_JPEG) static void init_source (j_decompress_ptr /*cinfo*/){ } static boolean fill_input_buffer (j_decompress_ptr cinfo) { struct jpeg_source_mgr * src = cinfo->src; static JOCTET FakeEOI[] = { 0xFF, JPEG_EOI }; // Generate warning nvDebug("jpeglib: Premature end of file\n"); // Insert a fake EOI marker src->next_input_byte = FakeEOI; src->bytes_in_buffer = 2; return TRUE; } static void skip_input_data (j_decompress_ptr cinfo, long num_bytes) { struct jpeg_source_mgr * src = cinfo->src; if(num_bytes >= (long)src->bytes_in_buffer) { fill_input_buffer(cinfo); return; } src->bytes_in_buffer -= num_bytes; src->next_input_byte += num_bytes; } static void term_source (j_decompress_ptr /*cinfo*/){ // no work necessary here } static Image * loadJPG(Stream & s) { nvCheck(!s.isError()); // Read the entire file. Array byte_array; byte_array.resize(s.size()); s.serialize(byte_array.buffer(), s.size()); jpeg_decompress_struct cinfo; jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jpeg_create_decompress(&cinfo); cinfo.src = (struct jpeg_source_mgr *) (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, sizeof(struct jpeg_source_mgr)); cinfo.src->init_source = init_source; cinfo.src->fill_input_buffer = fill_input_buffer; cinfo.src->skip_input_data = skip_input_data; cinfo.src->resync_to_restart = jpeg_resync_to_restart; // use default method cinfo.src->term_source = term_source; cinfo.src->bytes_in_buffer = byte_array.size(); cinfo.src->next_input_byte = byte_array.buffer(); jpeg_read_header(&cinfo, TRUE); jpeg_start_decompress(&cinfo); /* cinfo.do_fancy_upsampling = FALSE; // fast decompression cinfo.dct_method = JDCT_FLOAT; // Choose floating point DCT method. */ uint8 * tmp_buffer = new uint8 [cinfo.output_width * cinfo.output_height * cinfo.num_components]; uint8 * scanline = tmp_buffer; while( cinfo.output_scanline < cinfo.output_height ){ int num_scanlines = jpeg_read_scanlines (&cinfo, &scanline, 1); scanline += num_scanlines * cinfo.output_width * cinfo.num_components; } jpeg_finish_decompress(&cinfo); AutoPtr img(new Image()); img->allocate(cinfo.output_width, cinfo.output_height); Color32 * dst = img->pixels(); const int size = img->height() * img->width(); const uint8 * src = tmp_buffer; if( cinfo.num_components == 3 ) { img->setFormat(Image::Format_RGB); for( int i = 0; i < size; i++ ) { *dst++ = Color32(src[0], src[1], src[2]); src += 3; } } else { img->setFormat(Image::Format_ARGB); for( int i = 0; i < size; i++ ) { *dst++ = Color32(*src, *src, *src, *src); src++; } } delete [] tmp_buffer; jpeg_destroy_decompress (&cinfo); return img.release(); } #endif // defined(HAVE_JPEG) #if defined(HAVE_TIFF) /* static tsize_t tiffReadWriteProc(thandle_t h, tdata_t ptr, tsize_t size) { Stream * s = (Stream *)h; nvDebugCheck(s != NULL); s->serialize(ptr, size); return size; } static toff_t tiffSeekProc(thandle_t h, toff_t offset, int whence) { Stream * s = (Stream *)h; nvDebugCheck(s != NULL); if (!s->isSeekable()) { return (toff_t)-1; } if (whence == SEEK_SET) { s->seek(offset); } else if (whence == SEEK_CUR) { s->seek(s->tell() + offset); } else if (whence == SEEK_END) { s->seek(s->size() + offset); } return s->tell(); } static int tiffCloseProc(thandle_t) { return 0; } static toff_t tiffSizeProc(thandle_t h) { Stream * s = (Stream *)h; nvDebugCheck(s != NULL); return s->size(); } static int tiffMapFileProc(thandle_t, tdata_t*, toff_t*) { // @@ TODO, Implement these functions. return -1; } static void tiffUnmapFileProc(thandle_t, tdata_t, toff_t) { // @@ TODO, Implement these functions. } */ static FloatImage * loadFloatTIFF(const char * fileName, Stream & s) { nvCheck(!s.isError()); TIFF * tif = TIFFOpen(fileName, "r"); //TIFF * tif = TIFFClientOpen(fileName, "r", &s, tiffReadWriteProc, tiffReadWriteProc, tiffSeekProc, tiffCloseProc, tiffSizeProc, tiffMapFileProc, tiffUnmapFileProc); if (!tif) { nvDebug("Can't open '%s' for reading\n", fileName); return NULL; } ::uint16 spp, bpp, format; ::uint32 width, height; TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height); TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bpp); TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &spp); TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &format); if (bpp != 8 && bpp != 16 && bpp != 32) { nvDebug("Can't load '%s', only 1 sample per pixel supported\n", fileName); TIFFClose(tif); return NULL; } AutoPtr fimage(new FloatImage()); fimage->allocate(spp, width, height); int linesize = TIFFScanlineSize(tif); tdata_t buf = malloc(linesize); for (uint y = 0; y < height; y++) { TIFFReadScanline(tif, buf, y, 0); for (uint c=0; cscanline(y, c); for(uint x = 0; x < width; x++) { if (bpp == 8) { dst[x] = float(((::uint8 *)buf)[x*spp+c]) / float(0xFF); } else if (bpp == 16) { dst[x] = float(((::uint16 *)buf)[x*spp+c]) / float(0xFFFF); } else if (bpp == 32) { if (format==SAMPLEFORMAT_IEEEFP) { dst[x] = float(((float *)buf)[x*spp+c]); } else { dst[x] = float(((::uint32 *)buf)[x*spp+c] >> 8) / float(0xFFFFFF); } } } } } free(buf); TIFFClose(tif); return fimage.release(); } static bool saveFloatTIFF(const char * fileName, const FloatImage * fimage, uint base_component, uint num_components) { nvCheck(fileName != NULL); nvCheck(fimage != NULL); nvCheck(base_component + num_components <= fimage->componentCount()); const int iW = fimage->width(); const int iH = fimage->height(); const int iC = num_components; TIFF * image = TIFFOpen(fileName, "w"); // Open the TIFF file if (image == NULL) { nvDebug("Could not open '%s' for writing\n", fileName); return false; } TIFFSetField(image, TIFFTAG_IMAGEWIDTH, iW); TIFFSetField(image, TIFFTAG_IMAGELENGTH, iH); TIFFSetField(image, TIFFTAG_SAMPLESPERPIXEL, iC); TIFFSetField(image, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP); TIFFSetField(image, TIFFTAG_BITSPERSAMPLE, 32); uint32 rowsperstrip = TIFFDefaultStripSize(image, (uint32)-1); TIFFSetField(image, TIFFTAG_ROWSPERSTRIP, rowsperstrip); TIFFSetField(image, TIFFTAG_COMPRESSION, COMPRESSION_PACKBITS); if (num_components == 3) { // Set this so that it can be visualized with pfstools. TIFFSetField(image, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); } TIFFSetField(image, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); TIFFSetField(image, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); float * scanline = new float[iW * iC]; for (int y = 0; y < iH; y++) { for (int c = 0; c < iC; c++) { const float * src = fimage->scanline(y, base_component + c); for (int x = 0; x < iW; x++) scanline[x * iC + c] = src[x]; } if (TIFFWriteScanline(image, scanline, y, 0)==-1) { nvDebug("Error writing scanline %d\n", y); return false; } } delete [] scanline; // Close the file TIFFClose(image); return true; } #endif // defined(HAVE_TIFF) #if defined(HAVE_OPENEXR) namespace { class ExrStream : public Imf::IStream { public: ExrStream(const char * name, Stream & s) : Imf::IStream(name), m_stream(s) { nvDebugCheck(s.isLoading()); } virtual bool read(char c[], int n) { m_stream.serialize(c, n); if (m_stream.isError()) { throw Iex::InputExc("I/O error."); } return m_stream.isAtEnd(); } virtual Imf::Int64 tellg() { return m_stream.tell(); } virtual void seekg(Imf::Int64 pos) { nvDebugCheck(pos >= 0 && pos < UINT_MAX); m_stream.seek((uint)pos); } virtual void clear() { m_stream.clearError(); } private: Stream & m_stream; }; static int channelIndexFromName(const char* name) { char c = tolower(name[0]); switch (c) { default: case 'r': return 0; case 'g': return 1; case 'b': return 2; case 'a': return 3; } } } // namespace static FloatImage * loadFloatEXR(const char * fileName, Stream & s) { nvCheck(s.isLoading()); nvCheck(!s.isError()); ExrStream stream(fileName, s); Imf::InputFile inputFile(stream); Imath::Box2i box = inputFile.header().dataWindow(); int width = box.max.x - box.min.y + 1; int height = box.max.x - box.min.y + 1; const Imf::ChannelList & channels = inputFile.header().channels(); // Count channels. uint channelCount= 0; for (Imf::ChannelList::ConstIterator it = channels.begin(); it != channels.end(); ++it) { channelCount++; } // Allocate FloatImage. AutoPtr fimage(new FloatImage()); fimage->allocate(channelCount, width, height); // Describe image's layout with a framebuffer. Imf::FrameBuffer frameBuffer; uint i = 0; for (Imf::ChannelList::ConstIterator it = channels.begin(); it != channels.end(); ++it, ++i) { int channelIndex = channelIndexFromName(it.name()); frameBuffer.insert(it.name(), Imf::Slice(Imf::FLOAT, (char *)fimage->channel(channelIndex), sizeof(float), sizeof(float) * width)); } // Read it. inputFile.setFrameBuffer (frameBuffer); inputFile.readPixels (box.min.y, box.max.y); return fimage.release(); } static bool saveFloatEXR(const char * fileName, const FloatImage * fimage, uint base_component, uint num_components) { nvCheck(fileName != NULL); nvCheck(fimage != NULL); nvCheck(base_component + num_components <= fimage->componentCount()); nvCheck(num_components > 0 && num_components <= 4); const int w = fimage->width(); const int h = fimage->height(); const char * channelNames[] = {"R", "G", "B", "A"}; Imf::Header header (w, h); for (uint c = 0; c < num_components; c++) { header.channels().insert(channelNames[c], Imf::Channel(Imf::FLOAT)); } Imf::OutputFile file(fileName, header); Imf::FrameBuffer frameBuffer; for (uint c = 0; c < num_components; c++) { char * channel = (char *) fimage->channel(base_component + c); frameBuffer.insert(channelNames[c], Imf::Slice(Imf::FLOAT, channel, sizeof(float), sizeof(float) * w)); } file.setFrameBuffer(frameBuffer); file.writePixels(h); return true; } #endif // defined(HAVE_OPENEXR) #if defined(HAVE_FREEIMAGE) static unsigned DLL_CALLCONV ReadProc(void *buffer, unsigned size, unsigned count, fi_handle handle) { Stream * s = (Stream *) handle; s->serialize(buffer, size * count); return count; } static unsigned DLL_CALLCONV WriteProc(void *buffer, unsigned size, unsigned count, fi_handle handle) { Stream * s = (Stream *) handle; s->serialize(buffer, size * count); return count; } static int DLL_CALLCONV SeekProc(fi_handle handle, long offset, int origin) { Stream * s = (Stream *) handle; switch(origin) { case SEEK_SET : s->seek(offset); break; case SEEK_END : s->seek(s->size() + offset); break; case SEEK_CUR : s->seek(s->tell() + offset); break; default : return 1; } return 0; } static long DLL_CALLCONV TellProc(fi_handle handle) { Stream * s = (Stream *) handle; return s->tell(); } Image * nv::ImageIO::loadFreeImage(FREE_IMAGE_FORMAT fif, Stream & s) { nvCheck(!s.isError()); FreeImageIO io; io.read_proc = ReadProc; io.write_proc = NULL; io.seek_proc = SeekProc; io.tell_proc = TellProc; FIBITMAP * bitmap = FreeImage_LoadFromHandle(fif, &io, (fi_handle)&s, 0); if (bitmap == NULL) { return NULL; } const int w = FreeImage_GetWidth(bitmap); const int h = FreeImage_GetHeight(bitmap); if (FreeImage_GetImageType(bitmap) != FIT_BITMAP) { // @@ Use tone mapping? FIBITMAP * tmp = FreeImage_ConvertToType(bitmap, FIT_BITMAP, true); FreeImage_Unload(bitmap); bitmap = tmp; } nvDebugCheck(FreeImage_GetImageType(bitmap) == FIT_BITMAP); if (FreeImage_GetBPP(bitmap) != 32) { FIBITMAP * tmp = FreeImage_ConvertTo32Bits(bitmap); FreeImage_Unload(bitmap); bitmap = tmp; } Image * image = new Image(); image->allocate(w, h, 1); // freeimage can only load 2d images: // Copy the image over to our internal format, FreeImage has the scanlines bottom to top though. for (int y=0; y < h; y++) { const void * src = FreeImage_GetScanLine(bitmap, h - y - 1); void * dst = image->scanline(y); memcpy(dst, src, 4 * w); } FreeImage_Unload(bitmap); return image; } FloatImage * nv::ImageIO::loadFloatFreeImage(FREE_IMAGE_FORMAT fif, Stream & s) { nvCheck(!s.isError()); FreeImageIO io; io.read_proc = ReadProc; io.write_proc = NULL; io.seek_proc = SeekProc; io.tell_proc = TellProc; FIBITMAP * bitmap = FreeImage_LoadFromHandle(fif, &io, (fi_handle)&s, 0); if (bitmap == NULL) { return NULL; } const int w = FreeImage_GetWidth(bitmap); const int h = FreeImage_GetHeight(bitmap); FREE_IMAGE_TYPE fit = FreeImage_GetImageType(bitmap); FloatImage * floatImage = new FloatImage(); switch (fit) { case FIT_BITMAP: floatImage->allocate(4, w, h); { FIBITMAP * tmp = FreeImage_ConvertTo32Bits(bitmap); uint bitcount = FreeImage_GetBPP(bitmap); uint byteCount = bitcount / 8; for (int y=0; y < h; y++) { const Color32 * src = (const Color32 *)FreeImage_GetScanLine(bitmap, h - y - 1 ); float * r = floatImage->scanline(y, 0); float * g = floatImage->scanline(y, 1); float * b = floatImage->scanline(y, 2); float * a = floatImage->scanline(y, 3); for (int x=0; x < w; x++) { r[x] = float(src[x].r) / 255.0f; g[x] = float(src[x].g) / 255.0f; b[x] = float(src[x].b) / 255.0f; a[x] = float(src[x].a) / 255.0f; } src += byteCount; } FreeImage_Unload(tmp); } break; case FIT_FLOAT: floatImage->allocate(1, w, h); for (int y=0; y < h; y++) { const float * src = (const float *)FreeImage_GetScanLine(bitmap, h - y - 1 ); float * dst = floatImage->scanline(y, 0); for (int x=0; x < w; x++) { dst[x] = src[x]; } } break; case FIT_UINT16: floatImage->allocate(1, w, h); for (int y=0; y < h; y++) { const uint16 * src = (const uint16 *)FreeImage_GetScanLine(bitmap, h - y - 1 ); float * dst = floatImage->scanline(y, 0); for (int x=0; x < w; x++) { dst[x] = float(src[x]) / 65535; } } break; case FIT_COMPLEX: floatImage->allocate(2, w, h); for (int y=0; y < h; y++) { const FICOMPLEX * src = (const FICOMPLEX *)FreeImage_GetScanLine(bitmap, h - y - 1 ); float * dst_real = floatImage->scanline(y, 0); float * dst_imag = floatImage->scanline(y, 1); for (int x=0; x < w; x++) { dst_real[x] = (float)src[x].r; dst_imag[x] = (float)src[x].i; } } break; case FIT_RGBF: floatImage->allocate(3, w, h); for (int y=0; y < h; y++) { const FIRGBF * src = (const FIRGBF *)FreeImage_GetScanLine(bitmap, h - y - 1 ); float * dst_red = floatImage->scanline(y, 0); float * dst_green = floatImage->scanline(y, 1); float * dst_blue = floatImage->scanline(y, 2); for (int x=0; x < w; x++) { dst_red[x] = src[x].red; dst_green[x] = src[x].green; dst_blue[x] = src[x].blue; } } break; case FIT_RGBAF: floatImage->allocate(4, w, h); for (int y=0; y < h; y++) { const FIRGBAF * src = (const FIRGBAF *)FreeImage_GetScanLine(bitmap, h - y - 1 ); float * dst_red = floatImage->scanline(y, 0); float * dst_green = floatImage->scanline(y, 1); float * dst_blue = floatImage->scanline(y, 2); float * dst_alpha = floatImage->scanline(y, 3); for (int x=0; x < w; x++) { dst_red[x] = src[x].red; dst_green[x] = src[x].green; dst_blue[x] = src[x].blue; dst_alpha[x] = src[x].alpha; } } break; default: delete floatImage; floatImage = NULL; } FreeImage_Unload(bitmap); return floatImage; } bool nv::ImageIO::saveFreeImage(FREE_IMAGE_FORMAT fif, Stream & s, const Image * img, const char ** tags) { nvCheck(!s.isError()); FreeImageIO io; io.read_proc = NULL; io.write_proc = WriteProc; io.seek_proc = SeekProc; io.tell_proc = TellProc; const uint w = img->width(); const uint h = img->height(); FIBITMAP * bitmap = FreeImage_Allocate(w, h, 32); for (uint i = 0; i < h; i++) { uint8 * scanline = FreeImage_GetScanLine(bitmap, i); memcpy(scanline, img->scanline(h - i - 1), w * sizeof(Color32)); } if (tags != NULL) { #pragma NV_MESSAGE("TODO: Save image metadata") //FreeImage_SetMetadata( } bool result = FreeImage_SaveToHandle(fif, bitmap, &io, (fi_handle)&s, 0) != 0; FreeImage_Unload(bitmap); return result; } bool nv::ImageIO::saveFloatFreeImage(FREE_IMAGE_FORMAT fif, Stream & s, const FloatImage * img, uint baseComponent, uint componentCount) { nvCheck(!s.isError()); FreeImageIO io; io.read_proc = NULL; io.write_proc = WriteProc; io.seek_proc = SeekProc; io.tell_proc = TellProc; const uint w = img->width(); const uint h = img->height(); FREE_IMAGE_TYPE type; if (componentCount == 1) { type = FIT_FLOAT; } else if (componentCount == 3) { type = FIT_RGBF; } else if (componentCount == 4) { type = FIT_RGBAF; } else { return false; } FIBITMAP * bitmap = FreeImage_AllocateT(type, w, h); for (uint y = 0; y < h; y++) { float * scanline = (float *)FreeImage_GetScanLine(bitmap, y); for (uint x = 0; x < w; x++) { for (uint c = 0; c < componentCount; c++) { scanline[x * componentCount + c] = img->pixel(x, y, baseComponent + c); } } } bool result = FreeImage_SaveToHandle(fif, bitmap, &io, (fi_handle)&s, 0) != 0; FreeImage_Unload(bitmap); return result; } #endif // defined(HAVE_FREEIMAGE) #if defined(HAVE_STBIMAGE) static Image * loadSTB(Stream & s) { // @@ Assumes stream cursor is at the beginning and that image occupies the whole stream. const int size = s.size(); uint8 * buffer = new uint8[size]; s.serialize(buffer, size); int w, h, n; uint8 * data = stbi_load_from_memory(buffer, size, &w, &h, &n, 4); delete [] buffer; if (data != NULL) { Image * img = new Image; img->allocate(w, h); img->setFormat(n == 4 ? Image::Format_ARGB : Image::Format_RGB); for (int y = 0; y < h; ++y) { nv::Color32* dest = img->scanline(y); uint8* src = data + y * w * 4; for (int x = 0; x < w; ++x) { dest[x].r = src[x * 4 + 0]; dest[x].g = src[x * 4 + 1]; dest[x].b = src[x * 4 + 2]; dest[x].a = src[x * 4 + 3]; } } free(data); return img; } return NULL; } static FloatImage * loadFloatSTB(Stream & s) { // @@ Assumes stream cursor is at the beginning and that image occupies the whole stream. const int size = s.size(); uint8 * buffer = new uint8[size]; s.serialize(buffer, size); int w, h, n; float * data = stbi_loadf_from_memory(buffer, size, &w, &h, &n, 0); delete [] buffer; // Copy to image. if (data != NULL) { FloatImage * img = new FloatImage; img->allocate(n, w, h); const int count = w * h; for (int c = 0; c < n; c++) { float * dst = img->channel(c); for (int i = 0; i < count; i++) { dst[i] = data[i*n + c]; } } return img; } return NULL; } #endif // defined(HAVE_STBIMAGE) Image * nv::ImageIO::load(const char * fileName) { nvDebugCheck(fileName != NULL); StdInputStream stream(fileName); if (stream.isError()) { return NULL; } return ImageIO::load(fileName, stream); } Image * nv::ImageIO::load(const char * fileName, Stream & s) { nvDebugCheck(fileName != NULL); nvDebugCheck(s.isLoading()); const char * extension = Path::extension(fileName); if (strCaseDiff(extension, ".tga") == 0) { return loadTGA(s); } if (strCaseDiff(extension, ".psd") == 0) { return loadPSD(s); } /*if (strCaseDiff(extension, ".ppm") == 0) { return loadPPM(s); }*/ #if defined(HAVE_JPEG) if (strCaseDiff(extension, ".jpg") == 0 || strCaseDiff(extension, ".jpeg") == 0) { return loadJPG(s); } #endif #if defined(HAVE_PNG) if (strCaseDiff(extension, ".png") == 0) { return loadPNG(s); } #endif #if defined(HAVE_FREEIMAGE) FREE_IMAGE_FORMAT fif = FreeImage_GetFIFFromFilename(fileName); if (fif != FIF_UNKNOWN && FreeImage_FIFSupportsReading(fif)) { return loadFreeImage(fif, s); } #endif #if defined(HAVE_STBIMAGE) return loadSTB(s); #endif return NULL; } bool nv::ImageIO::save(const char * fileName, Stream & s, const Image * img, const char ** tags/*=NULL*/) { nvDebugCheck(fileName != NULL); nvDebugCheck(s.isSaving()); nvDebugCheck(img != NULL); const char * extension = Path::extension(fileName); if (strCaseDiff(extension, ".tga") == 0) { return saveTGA(s, img); } if (strCaseDiff(extension, ".ppm") == 0) { return savePPM(s, img); } #if defined(HAVE_PNG) if (strCaseDiff(extension, ".png") == 0) { return savePNG(s, img, tags); } #endif #if defined(HAVE_FREEIMAGE) FREE_IMAGE_FORMAT fif = FreeImage_GetFIFFromFilename(fileName); if (fif != FIF_UNKNOWN && FreeImage_FIFSupportsWriting(fif)) { return saveFreeImage(fif, s, img, tags); } #endif return false; } bool nv::ImageIO::save(const char * fileName, const Image * img, const char ** tags/*=NULL*/) { nvDebugCheck(fileName != NULL); nvDebugCheck(img != NULL); StdOutputStream stream(fileName); if (stream.isError()) { return false; } return ImageIO::save(fileName, stream, img, tags); } FloatImage * nv::ImageIO::loadFloat(const char * fileName) { nvDebugCheck(fileName != NULL); StdInputStream stream(fileName); if (stream.isError()) { return NULL; } return loadFloat(fileName, stream); } FloatImage * nv::ImageIO::loadFloat(const char * fileName, Stream & s) { nvDebugCheck(fileName != NULL); const char * extension = Path::extension(fileName); /*if (strCaseDiff(extension, ".pfm") == 0) { return loadFloatPFM(s); }*/ #if defined(HAVE_TIFF) #pragma NV_MESSAGE("TODO: Load TIFF from stream.") if (strCaseDiff(extension, ".tif") == 0 || strCaseDiff(extension, ".tiff") == 0) { return loadFloatTIFF(fileName, s); } #endif #if defined(HAVE_OPENEXR) #pragma NV_MESSAGE("TODO: Load EXR from stream.") if (strCaseDiff(extension, ".exr") == 0) { return loadFloatEXR(fileName, s); } #endif #if defined(HAVE_STBIMAGE) if (strCaseDiff(extension, ".hdr") == 0) { return loadFloatSTB(s); } #endif #if defined(HAVE_FREEIMAGE) FREE_IMAGE_FORMAT fif = FreeImage_GetFIFFromFilename(fileName); if (fif != FIF_UNKNOWN && FreeImage_FIFSupportsReading(fif)) { return loadFloatFreeImage(fif, s); } #endif if (strCaseDiff(extension, ".dds") == 0) { const uint spos = s.tell(); // Save stream position. FloatImage * floatImage = loadFloatDDS(s); if (floatImage != NULL) return floatImage; else s.seek(spos); } // Try to load as an RGBA8 image and convert to float. AutoPtr img(load(fileName, s)); if (img != NULL) { return new FloatImage(img.ptr()); } return NULL; } bool nv::ImageIO::saveFloat(const char * fileName, Stream & s, const FloatImage * fimage, uint baseComponent, uint componentCount) { if (componentCount == 0) { componentCount = fimage->componentCount() - baseComponent; } if (baseComponent + componentCount < fimage->componentCount()) { return false; } const char * extension = Path::extension(fileName); if (strCaseDiff(extension, ".dds") == 0) { return saveFloatDDS(s, fimage, baseComponent, componentCount); } /*if (strCaseDiff(extension, ".pfm") == 0) { return saveFloatPFM(s, fimage, baseComponent, componentCount); }*/ #if defined(HAVE_FREEIMAGE) FREE_IMAGE_FORMAT fif = FreeImage_GetFIFFromFilename(fileName); if (fif != FIF_UNKNOWN && FreeImage_FIFSupportsWriting(fif)) { return saveFloatFreeImage(fif, s, fimage, baseComponent, componentCount); } #endif // If everything else fails, save as LDR. if (componentCount <= 4) { AutoPtr image(fimage->createImage(baseComponent, componentCount)); nvCheck(image != NULL); if (componentCount == 1) { Color32 * c = image->pixels(); const uint count = image->width() * image->height(); for (uint i = 0; i < count; i++) { c[i].b = c[i].g = c[i].r; } } if (componentCount == 4) { image->setFormat(Image::Format_ARGB); } return ImageIO::save(fileName, s, image.ptr()); } return false; } bool nv::ImageIO::saveFloat(const char * fileName, const FloatImage * fimage, uint baseComponent, uint componentCount) { if (componentCount == 0) { componentCount = fimage->componentCount() - baseComponent; } if (baseComponent + componentCount < fimage->componentCount()) { return false; } const char * extension = Path::extension(fileName); #if defined(HAVE_OPENEXR) if (strCaseDiff(extension, ".exr") == 0) { return saveFloatEXR(fileName, fimage, baseComponent, componentCount); } #endif #if defined(HAVE_TIFF) if (strCaseDiff(extension, ".tif") == 0 || strCaseDiff(extension, ".tiff") == 0) { return saveFloatTIFF(fileName, fimage, baseComponent, componentCount); } #endif StdOutputStream stream(fileName); if (stream.isError()) { return false; } return saveFloat(fileName, stream, fimage, baseComponent, componentCount); }