// -*- tab-width: 4; -*- // vi: set sw=2 ts=4 expandtab: // Copyright 2022 The Khronos Group Inc. // SPDX-License-Identifier: Apache-2.0 //! //! @internal //! @~English //! @file //! //! @brief ImageInput:open function //! #include "imageio.h" #include "imageio_utility.h" #include "platform_utils.h" #include #include #include #include #include // Search for and instantiate a plugin that can read the format // and open the file. std::unique_ptr ImageInput::open(const std::string& filename, const ImageSpec* /*config*/, WarningCallbackFunction wcb) //Filesystem::IOProxy* ioproxy, string_view plugin_searchpath) { // Populate inputFormats. if (Imageio::inputFormats.empty()) { Imageio::catalogBuiltinPlugins(); assert(!Imageio::inputFormats.empty() && "No image input plugins compiled in."); } std::ifstream ifs; std::unique_ptr buffer; ImageInput::Creator createFunction = nullptr; const std::string* fn; const std::string sn("stdin"); bool doBuffer = true; if (filename.compare("-")) { // Regular file. // Check file exists, before looking for a suitable plugin. // MS's STL has `open` overloads that accept wchar_t to handle // Window's Unicode file names. ifs.open(std::filesystem::path(DecodeUTF8Path(filename)), std::ios::binary | std::ios::in); if (ifs.fail()) { throw std::runtime_error( fmt::format("Open of \"{}\" failed. {}", filename, strerror(errno)) ); } // Extract the file extension from the filename (without the leading dot) Imageio::string format = filename.substr(filename.find_last_of('.')+1); format.tolower(); // Look for a plugin for that extension. if (!format.empty()) { // First try the plug-in that matches the extension Imageio::InputPluginMap::const_iterator found = Imageio::inputFormats.find(format); if (found != Imageio::inputFormats.end()) { createFunction = found->second; } } fn = &filename; } else { // cin (stdin) #if defined(_WIN32) // Set "stdin" to have binary mode. There is no way to this via cin. (void)_setmode( _fileno( stdin ), _O_BINARY ); // Windows shells set the FILE_SYNCHRONOUS_IO_NONALERT option when // creating pipes. Cygwin since 3.4.x does the same thing, a change // which affects anything dependent on it, e.g. Git for Windows // (since 2.41.0) and MSYS2. When this option is set, cin.seekg(0) // erroneously returns success. Always buffer. doBuffer = true; #else // Can we seek in this cin? std::cin.seekg(0); doBuffer = std::cin.fail(); #endif if (doBuffer) { // Can't seek. Buffer stdin. This is a potentially large buffer. // Must avoid copy. If use stack variable for ss, it's streambuf // will also be on the stack and lost after this function exits // even with std::move. buffer = std::unique_ptr(new std::stringstream); *buffer << std::cin.rdbuf(); buffer->seekg(0, std::ios::beg); } fn = &sn; } // Remember which formats we've already tried, so we don't double dip. std::vector formatsTried; std::string specificError; std::unique_ptr in; if (createFunction) { formatsTried.push_back(createFunction); // Try our guess. try { in = std::unique_ptr(createFunction()); in->connectCallback(wcb); ImageSpec tmpspec; in->open(*fn, ifs, buffer, tmpspec); return in; } catch (const std::runtime_error& e) { // Oops, it failed. Apparently, this file can't be // opened with this II. if (in) { specificError = e.what(); in.reset(); } } } // A plugin designated for the requested extension either couldn't be // found or was unable to open the file or there is no requested extension // due to stdin. Try every plugin and see if one will open the file. for (auto&& plugin : Imageio::inputFormats) { // If we already tried this create function, don't do it again if (std::find(formatsTried.begin(), formatsTried.end(), plugin.second) != formatsTried.end()) { continue; } formatsTried.push_back(plugin.second); // remember createFunction = plugin.second; try { in = std::unique_ptr(createFunction()); in->connectCallback(wcb); ImageSpec tmpspec; in->open(*fn, ifs, buffer, tmpspec); return in; } catch (...) { if (in.get()) in.reset(); continue; } } if (!specificError.empty()) { // Pass along any specific error message we got from our // best guess of the format. throw std::runtime_error(specificError); } else { throw std::runtime_error( fmt::format( "No image plugin recognized the format of \"{}\". " "Is it a file format that we don't know about?\n", filename.compare("-") ? filename : "the data on stdin") ); } } /// @brief Open a file for image input. /// /// Default implementation for derived classes. void ImageInput::open(const std::string& filename, ImageSpec& newspec) { close(); // previously opened file. if (filename.compare("-")) { // Regular file _filename = filename; file.open(filename); if (file.fail()) { throw std::runtime_error( fmt::format("Open of \"{}\" failed. {}", filename, strerror(errno)) ); } isp = &file; open(newspec); } else { // stdin _filename = "stdin"; #if defined(_WIN32) /* Set "stdin" to have binary mode */ (void)_setmode( _fileno( stdin ), _O_BINARY ); #endif // Can we seek in this cin? std::cin.seekg(0); if (std::cin.fail()) { // Can't seek. Buffer stdin. This is a potentially large buffer. // The class uses a unique_ptr due to the constraints of supporting // the static create function. See comment in that function above. buffer = std::unique_ptr(new std::stringstream); *buffer << std::cin.rdbuf(); isp = buffer.get(); open(newspec); } else { open(std::cin, newspec); } } } /// @brief Read an entire image into contiguous memory performing conversions /// to @a format. /// /// Default implementation for derived classes. Input channel values are scaled /// from the input's channelUpper value to the max representable value of the /// output format (unorm8 or unorm16). /// /// Supported conversions are /// - bit scaling /// - unorm8<->unorm16 void ImageInput::readScanline(void* pBufferOut, size_t bufferByteCount, uint32_t y, uint32_t z, uint32_t subimage, uint32_t miplevel, const FormatDescriptor& format) { const auto& targetFormat = format.isUnknown() ? spec().format() : format; const auto targetBitLength = targetFormat.largestChannelBitLength(); const auto requestBits = std::max(imageio::bit_ceil(targetBitLength), 8u); if (requestBits != 8 && requestBits != 16) throw std::runtime_error(fmt::format( "Requested decode into {}-bit format is not supported.", requestBits) ); const bool targetL = targetFormat.samples[0].qualifierLinear; const bool targetE = targetFormat.samples[0].qualifierExponent; const bool targetS = targetFormat.samples[0].qualifierSigned; const bool targetF = targetFormat.samples[0].qualifierFloat; // Only UNORM requests are supported here. if (targetE || targetL || targetS || targetF) throw std::runtime_error(fmt::format( "Requested format conversion to {}-bit{}{}{}{} is not supported.", requestBits, targetL ? " Linear" : "", targetE ? " Exponent" : "", targetS ? " Signed" : "", targetF ? " Float" : "") ); seekSubimage(subimage, miplevel); size_t imageByteCount = (requestBits * spec().imageChannelCount()) / 8; if (imageByteCount < bufferByteCount) throw buffer_too_small(); uint8_t* pNativeBuffer; if (targetFormat.channelBitLength() != spec().format().channelBitLength()) { if (spec().format().channelBitLength() == 16) { if (nativeBuffer16.size() < spec().imageChannelCount()) nativeBuffer16.resize(spec().imageChannelCount()); pNativeBuffer = reinterpret_cast(nativeBuffer16.data()); bufferByteCount = nativeBuffer16.size() * sizeof(uint16_t); } else { if (nativeBuffer8.size() < spec().imageChannelCount()) nativeBuffer8.resize(spec().imageChannelCount()); pNativeBuffer = nativeBuffer8.data(); bufferByteCount = nativeBuffer16.size() * sizeof(uint8_t); } } else { pNativeBuffer = static_cast(pBufferOut); } readNativeScanline(pNativeBuffer, bufferByteCount, y, z, subimage, miplevel); if (reinterpret_cast(pNativeBuffer) == nativeBuffer16.data()) { rescale(static_cast(pBufferOut), static_cast(targetFormat.channelUpper()), nativeBuffer16.data(), static_cast(spec().format().channelUpper()), spec().imageChannelCount()); } else if (pNativeBuffer == nativeBuffer8.data()) { rescale(static_cast(pBufferOut), static_cast(targetFormat.channelUpper()), nativeBuffer8.data(), static_cast(spec().format().channelUpper()), spec().imageChannelCount()); } else if (targetFormat.channelUpper() != spec().format().channelUpper()) { if (spec().format().channelBitLength() == 16) { rescale(static_cast(pBufferOut), static_cast(targetFormat.channelUpper()), static_cast(pBufferOut), static_cast(spec().format().channelUpper()), spec().imageChannelCount()); } else { rescale(static_cast(pBufferOut), static_cast(targetFormat.channelUpper()), static_cast(pBufferOut), static_cast(spec().format().channelUpper()), spec().imageChannelCount()); } } } /// @brief Read an entire image into contiguous memory performing conversions /// to @a format. /// /// Default implementation for derived classes. /// /// @sa readScanline() for support conversions. void ImageInput::readImage(void* pBuffer, size_t bufferByteCount, uint32_t subimage, uint32_t miplevel, const FormatDescriptor& format) { const auto& targetFormat = format.isUnknown() ? spec().format() : format; size_t outScanlineByteCount = targetFormat.pixelByteCount() * spec().width(); if (bufferByteCount < outScanlineByteCount * spec().height()) throw buffer_too_small(); uint8_t* pDst = static_cast(pBuffer); for (uint32_t y = 0; y < spec().height(); y++) { readScanline(pDst, bufferByteCount, y, 0, subimage, miplevel, targetFormat); pDst += outScanlineByteCount; bufferByteCount -= outScanlineByteCount; } } void ImageInput::throwOnReadFailure() { if (isp->eof()) { throw std::runtime_error("Unexpected end-of-file."); } else { throw std::runtime_error( fmt::format("I/O error reading file: {}", strerror(errno)) ); } } void ImageInput::warning(const std::string& wmsg) { if (sendWarning) { sendWarning(wmsg); } } void ImageInput::fwarning(const std::string& wmsg) { std::string fwmsg = _filename + ": "; fwmsg += wmsg; warning(fwmsg); }