355 lines
12 KiB
C++
355 lines
12 KiB
C++
// -*- 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 <iomanip>
|
|
#include <map>
|
|
#include <stdarg.h>
|
|
#include <stdexcept>
|
|
#include <filesystem>
|
|
|
|
|
|
// Search for and instantiate a plugin that can read the format
|
|
// and open the file.
|
|
std::unique_ptr<ImageInput>
|
|
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<std::stringstream> 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<std::stringstream>(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<ImageInput::Creator> formatsTried;
|
|
std::string specificError;
|
|
std::unique_ptr<ImageInput> in;
|
|
|
|
if (createFunction) {
|
|
formatsTried.push_back(createFunction);
|
|
// Try our guess.
|
|
try {
|
|
in = std::unique_ptr<ImageInput>(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<ImageInput>(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<std::stringstream>(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<uint8_t*>(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<uint8_t*>(pBufferOut);
|
|
}
|
|
|
|
readNativeScanline(pNativeBuffer, bufferByteCount, y, z, subimage, miplevel);
|
|
|
|
if (reinterpret_cast<uint16_t*>(pNativeBuffer) == nativeBuffer16.data()) {
|
|
rescale(static_cast<uint8_t*>(pBufferOut),
|
|
static_cast<uint8_t>(targetFormat.channelUpper()),
|
|
nativeBuffer16.data(),
|
|
static_cast<uint16_t>(spec().format().channelUpper()),
|
|
spec().imageChannelCount());
|
|
} else if (pNativeBuffer == nativeBuffer8.data()) {
|
|
rescale(static_cast<uint16_t*>(pBufferOut),
|
|
static_cast<uint16_t>(targetFormat.channelUpper()),
|
|
nativeBuffer8.data(),
|
|
static_cast<uint8_t>(spec().format().channelUpper()),
|
|
spec().imageChannelCount());
|
|
} else if (targetFormat.channelUpper() != spec().format().channelUpper()) {
|
|
if (spec().format().channelBitLength() == 16) {
|
|
rescale(static_cast<uint16_t*>(pBufferOut),
|
|
static_cast<uint16_t>(targetFormat.channelUpper()),
|
|
static_cast<uint16_t*>(pBufferOut),
|
|
static_cast<uint16_t>(spec().format().channelUpper()),
|
|
spec().imageChannelCount());
|
|
} else {
|
|
rescale(static_cast<uint8_t*>(pBufferOut),
|
|
static_cast<uint8_t>(targetFormat.channelUpper()),
|
|
static_cast<uint8_t*>(pBufferOut),
|
|
static_cast<uint8_t>(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<uint8_t*>(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);
|
|
}
|
|
|