void CqImage::saveToFile(const std::string& fileName) const { boost::mutex::scoped_lock lock(mutex()); CqTexFileHeader header; // Required attributes header.setWidth(m_realData->width()); header.setHeight(m_realData->height()); header.channelList() = m_realData->channelList(); // Informational strings header.set<Attr::Software>( (boost::format("Aqsis %s (%s %s)") % AQSIS_VERSION_STR % __DATE__ % __TIME__).str()); header.set<Attr::DisplayWindow>(SqImageRegion(m_frameWidth, m_frameHeight, m_originX, m_originY)); header.set<Attr::PixelAspectRatio>(1.0); // Set some default compression scheme for now - later we can accept user // input for this. header.set<Attr::Compression>("lzw"); // \todo: Attributes which might be good to add: // Host computer // Image description // Transformation matrices try { // Now create the image, and output the pixel data. boost::shared_ptr<IqTexOutputFile> outFile = IqTexOutputFile::open(fileName, ImageFile_Tiff, header); // Write all pixels out at once. outFile->writePixels(*m_realData); } catch(XqInternal& e) { Aqsis::log() << error << "Could not save image \"" << fileName << "\": " << e.what() << "\n"; return; } }
/** \brief Convert an OpenEXR header to our own header representation. * * \param exrHeader - input header * \param header - output header */ void convertHeader(const Imf::Header& exrHeader, CqTexFileHeader& header) { // Set width, height const Imath::Box2i& dataBox = exrHeader.dataWindow(); header.setWidth(dataBox.max.x - dataBox.min.x+1); header.setHeight(dataBox.max.y - dataBox.min.y+1); // display window const Imath::Box2i& displayBox = exrHeader.displayWindow(); header.set<Attr::DisplayWindow>( SqImageRegion( displayBox.max.x - displayBox.min.x, displayBox.max.y - displayBox.min.y, displayBox.min.x - dataBox.min.x, displayBox.min.y - dataBox.min.y) ); // Set tiling information ? // Aspect ratio header.set<Attr::PixelAspectRatio>(exrHeader.pixelAspectRatio()); TqChannelNameMap channelNameMap; // Convert channel representation const Imf::ChannelList& exrChannels = exrHeader.channels(); CqChannelList& channels = header.channelList(); for(Imf::ChannelList::ConstIterator i = exrChannels.begin(); i != exrChannels.end(); ++i) { // use lower case names for channels; OpenEXR uses upper case. std::string chanName = i.name(); std::transform(chanName.begin(), chanName.end(), chanName.begin(), ::tolower); channelNameMap[chanName] = i.name(); channels.addChannel( SqChannelInfo(chanName, channelTypeFromExr(i.channel().type)) ); } header.set<Attr::ExrChannelNameMap>(channelNameMap); channels.reorderChannels(); // Set compresssion type header.set<Attr::Compression>(exrCompressionToString(exrHeader.compression())); }
//------------------------------------------------------------------------------ // IqTexOutputFile implementation boost::shared_ptr<IqTexOutputFile> IqTexOutputFile::open( const boostfs::path& fileName, EqImageFileType fileType, const CqTexFileHeader& header) { // Check some of the header data to make sure it's minimally sane... if(header.width() <= 0 || header.height() <= 0) { AQSIS_THROW_XQERROR(XqInternal, EqE_BadFile, "Cannot open \"" << fileName << "\" - image width and height cannot be negative or zero."); } if(header.channelList().numChannels() == 0) { AQSIS_THROW_XQERROR(XqInternal, EqE_BadFile, "Cannot open \"" << fileName << "\" - no data channels present."); } // Create the new file object boost::shared_ptr<IqTexOutputFile> newFile = openMultiOutputFile(fileName, fileType, header); if(newFile) return newFile; switch(fileType) { // case ...: // Add new output formats here! case ImageFile_Exr: case ImageFile_Jpg: case ImageFile_Png: AQSIS_THROW_XQERROR(XqInternal, EqE_Unimplement, "Cannot open \"" << fileName << "\" - unimplemented file type \"" << fileType << "\""); default: AQSIS_THROW_XQERROR(XqInternal, EqE_BadFile, "Cannot open \"" << fileName << "\" - unknown file type \"" << fileType << "\""); } return newFile; }
void CqTiffDirHandle::writeCompressionAttrs(const CqTexFileHeader& header) { // Set the compression type. uint16 compression = tiffCompressionTagFromName(header.find<Attr::Compression>("none")); if(!TIFFIsCODECConfigured(compression)) { Aqsis::log() << warning << "No TIFF codec found for compression scheme \"" << header.find<Attr::Compression>("none") << "\"\n"; return; } setTiffTagValue<uint16>(TIFFTAG_COMPRESSION, compression); if(compression == COMPRESSION_LZW || compression == COMPRESSION_DEFLATE) { // Add a compression predictor if possible; this drastically increases // the compression ratios. Even though the online docs seem to suggest // that predictors are independent of the compression codec, this is // not the case for libtiff, which appears to give errors if predictors // used with anything other than the lzw or deflate codecs. // // (the innards of libtiff suggest that TIFFPredictorInit() is only // called by certian codecs) // // \todo Test whether PREDICTOR_FLOATINGPOINT is actually beneficial. // (Some places on the web suggest not.) if(header.channelList().sharedChannelType() == Channel_Float32) setTiffTagValue<uint16>(TIFFTAG_PREDICTOR, PREDICTOR_FLOATINGPOINT); else setTiffTagValue<uint16>(TIFFTAG_PREDICTOR, PREDICTOR_HORIZONTAL); } if(compression == COMPRESSION_JPEG) { // Set the jpeg compression quality level if necessary. setTiffTagValue<int>(TIFFTAG_JPEGQUALITY, header.find<Attr::CompressionQuality>(85)); } }
void CqTiffDirHandle::writeChannelAttrs(const CqTexFileHeader& header) { const CqChannelList& channelList = header.channelList(); EqChannelType channelType = channelList.sharedChannelType(); // Assume that the channel type is uniform across the various channels. assert(channelType != Channel_TypeUnknown && channelType != Channel_Float16); TqInt numChannels = channelList.numChannels(); assert(numChannels > 0); setTiffTagValue<uint16>(TIFFTAG_SAMPLESPERPIXEL, numChannels); setTiffTagValue<uint16>(TIFFTAG_BITSPERSAMPLE, 8*bytesPerPixel(channelType)); // It's hard to know which algorithm for deciding the photometric type is // the best here. Perhaps it would be better to simply depend on the // number of channels, since TIFF doesn't have a standard facility to store // channel names... if( (channelList.hasIntensityChannel() || numChannels <= 2) && !channelList.hasRgbChannel() ) { // greyscale image setTiffTagValue<uint16>(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); if(numChannels == 2) { // Set extra sample types std::vector<uint16> extraSamples(numChannels - 1, EXTRASAMPLE_UNSPECIFIED); if(channelList[1].name == "a") extraSamples[0] = EXTRASAMPLE_ASSOCALPHA; setTiffTagValue(TIFFTAG_EXTRASAMPLES, extraSamples); } // \todo PHOTOMETRIC_LOGL alternative for floats } else { // Assume a colour image by default (use PHOTOMETRIC_RGB) setTiffTagValue<uint16>(TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); /// \todo PHOTOMETRIC_LOGLUV alternative for floats if(numChannels > 3) { std::vector<uint16> extraSamples(numChannels - 3, EXTRASAMPLE_UNSPECIFIED); // Set type of extra samples. if(channelList[3].name == "a") extraSamples[0] = EXTRASAMPLE_ASSOCALPHA; if(numChannels >= 6) { // Initial support for setting extra samples for three channel // alpha... This isn't likely to be terribly robust... if(channelList[0].name == "r" && channelList[3].name == "ra") extraSamples[0] = EXTRASAMPLE_ASSOCALPHA; if(channelList[1].name == "g" && channelList[4].name == "ga") extraSamples[1] = EXTRASAMPLE_ASSOCALPHA; if(channelList[2].name == "b" && channelList[5].name == "ba") extraSamples[2] = EXTRASAMPLE_ASSOCALPHA; } setTiffTagValue(TIFFTAG_EXTRASAMPLES, extraSamples); } } /// \todo: deal with TIFFTAG_SGILOGDATAFMT uint16 sampleFormat = 0; switch(channelType) { case Channel_Float32: sampleFormat = SAMPLEFORMAT_IEEEFP; break; case Channel_Signed32: case Channel_Signed16: case Channel_Signed8: sampleFormat = SAMPLEFORMAT_INT; break; case Channel_Unsigned32: case Channel_Unsigned16: case Channel_Unsigned8: sampleFormat = SAMPLEFORMAT_UINT; break; default: AQSIS_THROW_XQERROR(XqInternal, EqE_Limit, "Cannot handle provided pixel sample format"); break; } setTiffTagValue<uint16>(TIFFTAG_SAMPLEFORMAT, sampleFormat); }
void CqZInputFile::readHeader(std::istream& inStream, CqTexFileHeader& header) { const char zFileMagicNum[] = "Aqsis ZFile"; const TqInt magicNumSize = sizeof(zFileMagicNum)-1; const TqInt versionNumSize = sizeof(AQSIS_VERSION_STR)-1; std::vector<char> buf(max(magicNumSize, versionNumSize)); // Read in magic number inStream.read(&buf[0], magicNumSize); if(!std::equal(buf.begin(), buf.begin() + magicNumSize, zFileMagicNum) || inStream.gcount() != magicNumSize) { AQSIS_THROW_XQERROR(XqBadTexture, EqE_BadFile, "Magic number missmatch in zfile"); } // Read in Aqsis version. We require this to match the current aqsis version. inStream.read(&buf[0], versionNumSize); if(!std::equal(buf.begin(), buf.begin() + versionNumSize, AQSIS_VERSION_STR) || inStream.gcount() != versionNumSize) { AQSIS_THROW_XQERROR(XqBadTexture, EqE_Version, "zfile was created with a different aqsis version"); } // Read in map width TqUint width = 0; inStream.read(reinterpret_cast<char*>(&width), sizeof(width)); if(inStream.gcount() != sizeof(width)) AQSIS_THROW_XQERROR(XqBadTexture, EqE_BadFile, "cannot read width from aqsis z-file"); // Read in map height TqUint height = 0; inStream.read(reinterpret_cast<char*>(&height), sizeof(height)); if(inStream.gcount() != sizeof(height)) AQSIS_THROW_XQERROR(XqBadTexture, EqE_BadFile, "cannot read height from aqsis z-file"); // Read world to camera transformation matrix CqMatrix worldToCamera; worldToCamera.SetfIdentity(false); inStream.read(reinterpret_cast<char*>(worldToCamera.pElements()), 16*sizeof(TqFloat)); if(inStream.gcount() != 16*sizeof(TqFloat)) AQSIS_THROW_XQERROR(XqBadTexture,EqE_BadFile, "could not read world to camera matrix from aqsis z-file"); // Read world to screen transformation matrix CqMatrix worldToScreen; worldToScreen.SetfIdentity(false); inStream.read(reinterpret_cast<char*>(worldToScreen.pElements()), 16*sizeof(TqFloat)); if(inStream.gcount() != 16*sizeof(TqFloat)) AQSIS_THROW_XQERROR(XqBadTexture, EqE_BadFile, "could not read world to screen matrix from aqsis z-file"); // Save the read header attributes into the file header. header.setWidth(width); header.setHeight(height); header.set<Attr::WorldToScreenMatrix>(worldToScreen); header.set<Attr::WorldToCameraMatrix>(worldToCamera); // Complete the header with some other attributes implied by the file format header.set<Attr::TextureFormat>(TextureFormat_Shadow); header.channelList().addChannel(SqChannelInfo("z", Channel_Float32)); }