blob: 5f94ece69c95e7794d02fa8f369acf7fe8b48ac8 [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
//
// Utility functions used by the image decoders.
//
#include "./imageio_util.h"
#if defined(_WIN32)
#include <fcntl.h> // for _O_BINARY
#include <io.h> // for _setmode()
#include <cstdio>
#endif
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <string>
#include "src/dsp/dsp.h"
#include "src/utils/utils.h"
#include "src/wp2/base.h"
#if defined(WP2_HAVE_WEBP)
#include "webp/encode.h"
#endif
namespace WP2 {
// useful struct for RAII
class FileCloser {
public:
explicit FileCloser(FILE* const file) : file_(file) {}
~FileCloser() {
if (file_ != nullptr && file_ != stdin && file_ != stdout) fclose(file_);
}
private:
FILE* file_;
};
// -----------------------------------------------------------------------------
// File I/O
FILE* IoUtilSetBinaryMode(FILE* const file) {
#if defined(_WIN32)
if (_setmode(_fileno(file), _O_BINARY) == -1) {
fprintf(stderr, "Failed to reopen file in O_BINARY mode.\n");
return nullptr;
}
#endif
return file;
}
WP2Status IoUtilReadFromStdin(Data* const data, size_t max_num_bytes) {
static constexpr size_t kBlockSize = 16384; // default initial size
const size_t block_size =
(max_num_bytes != 0) ? std::min(max_num_bytes, kBlockSize) : kBlockSize;
WP2_CHECK_OK(data != nullptr, WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(IoUtilSetBinaryMode(stdin) != nullptr,
WP2_STATUS_INVALID_PARAMETER);
size_t num_read_bytes = 0, max_size = 0;
while (!feof(stdin)) {
// We double the buffer size each time and read as much as possible.
size_t extra_size = (max_size == 0) ? block_size : max_size;
if (max_num_bytes != 0) {
assert(max_size <= max_num_bytes);
extra_size = std::min(max_num_bytes - max_size, extra_size);
}
WP2_CHECK_STATUS(data->Resize(max_size + extra_size,
/*keep_bytes=*/(num_read_bytes > 0)));
num_read_bytes += fread(data->bytes + num_read_bytes, 1, extra_size, stdin);
max_size += extra_size;
assert(num_read_bytes <= max_size);
if (num_read_bytes < max_size) break;
if (max_num_bytes != 0) {
assert(num_read_bytes <= max_num_bytes);
if (num_read_bytes >= max_num_bytes) break;
}
}
WP2_CHECK_OK(ferror(stdin) == 0, WP2_STATUS_BAD_READ);
// Trim unnecessary bytes.
WP2_CHECK_STATUS(data->Resize(num_read_bytes, /*keep_bytes=*/true));
return WP2_STATUS_OK;
}
static WP2Status IoUtilFileSize(FILE* const in, size_t* const size) {
assert(in != nullptr && size != nullptr);
fseek(in, 0, SEEK_END);
const long fsize = ftell(in); // NOLINT (long)
fseek(in, 0, SEEK_SET);
if (fsize < 0) return WP2_STATUS_BAD_READ;
if ((long)(size_t)fsize != fsize) return WP2_STATUS_BAD_READ; // NOLINT
*size = (size_t)fsize;
return WP2_STATUS_OK;
}
WP2Status IoUtilFileSize(const char* const file_path, size_t* const size) {
*size = 0;
WP2_CHECK_OK(file_path != nullptr && size != nullptr,
WP2_STATUS_NULL_PARAMETER);
FILE* const in = fopen(file_path, "rb");
WP2_CHECK_OK(in != nullptr, WP2_STATUS_INVALID_PARAMETER);
FileCloser closer(in);
return IoUtilFileSize(in, size);
}
WP2Status IoUtilReadFile(const char* const file_path, Data* const data,
size_t max_num_bytes) {
if ((file_path == nullptr) || !strcmp(file_path, "-")) {
return IoUtilReadFromStdin(data, max_num_bytes);
}
WP2_CHECK_OK(data != nullptr, WP2_STATUS_NULL_PARAMETER);
FILE* const in = fopen(file_path, "rb");
WP2_CHECK_OK(in != nullptr, WP2_STATUS_INVALID_PARAMETER);
FileCloser closer(in);
size_t file_size;
WP2_CHECK_STATUS(IoUtilFileSize(in, &file_size));
if (max_num_bytes != 0) file_size = std::min(file_size, max_num_bytes);
WP2_CHECK_STATUS(data->Resize(file_size, /*keep_bytes=*/false));
static constexpr size_t kNumReadElems = 1;
const bool success =
(file_size == 0 ||
fread(data->bytes, data->size, kNumReadElems, in) == kNumReadElems);
if (!success) {
data->Clear();
return WP2_STATUS_BAD_READ;
}
return WP2_STATUS_OK;
}
WP2Status IoUtilWriteFile(const uint8_t* const data, size_t data_size,
const char* const file_path, bool overwrite,
size_t* const file_size) {
const int to_stdout = (file_path == nullptr) || !strcmp(file_path, "-");
if (to_stdout) { // std::ftell() does not work with pipes.
WP2_CHECK_OK(file_size == nullptr, WP2_STATUS_INVALID_PARAMETER);
}
WP2_CHECK_OK(data != nullptr, WP2_STATUS_NULL_PARAMETER);
const char* const mode = (overwrite ? "wb" : "wxb");
FILE* const out =
to_stdout ? IoUtilSetBinaryMode(stdout) : fopen(file_path, mode);
WP2_CHECK_OK(out != nullptr, (!to_stdout && FileExists(file_path))
? WP2_STATUS_BAD_WRITE
: WP2_STATUS_INVALID_PARAMETER);
FileCloser closer(out);
static constexpr size_t kNumWrittenElems = 1;
const bool success =
(fwrite(data, data_size, kNumWrittenElems, out) == kNumWrittenElems);
if (file_size != nullptr) *file_size = std::ftell(out);
return success ? WP2_STATUS_OK : WP2_STATUS_BAD_WRITE;
}
bool FileExists(const char* const file_path) {
FILE* const out = fopen(file_path, "ab");
if (out == nullptr) return false;
fclose(out);
return true;
}
//------------------------------------------------------------------------------
// string variant
WP2Status IoUtilReadFile(const char* const file_path,
std::string* const data_string) {
if ((file_path == nullptr) || !strcmp(file_path, "-")) {
return IoUtilReadFromStdin(data_string);
}
WP2_CHECK_OK(data_string != nullptr, WP2_STATUS_NULL_PARAMETER);
data_string->clear();
FILE* const in = fopen(file_path, "rb");
WP2_CHECK_OK(in != nullptr, WP2_STATUS_INVALID_PARAMETER);
FileCloser closer(in);
size_t data_size;
WP2_CHECK_STATUS(IoUtilFileSize(in, &data_size));
WP2_CHECK_ALLOC_OK(data_size <= WP2_MAX_ALLOCABLE_MEMORY);
data_string->resize(data_size);
WP2_CHECK_ALLOC_OK(data_string->size() >= data_size);
void* const bytes = &(*data_string)[0]; // string::data() is const in C++11
static constexpr size_t kNumReadElems = 1;
const bool success =
(fread(bytes, data_string->size(), kNumReadElems, in) == kNumReadElems);
return success ? WP2_STATUS_OK : WP2_STATUS_BAD_READ;
}
WP2Status IoUtilReadFromStdin(std::string* const data_string) {
static constexpr size_t kBlockSize = 16384; // default initial size
WP2_CHECK_OK(data_string != nullptr, WP2_STATUS_NULL_PARAMETER);
WP2_CHECK_OK(IoUtilSetBinaryMode(stdin) != nullptr,
WP2_STATUS_INVALID_PARAMETER);
data_string->clear();
while (!feof(stdin)) {
const size_t current_size = data_string->size();
const size_t extra_size = current_size ? kBlockSize : current_size;
WP2_CHECK_ALLOC_OK(current_size + extra_size <= WP2_MAX_ALLOCABLE_MEMORY);
data_string->resize(current_size + extra_size);
WP2_CHECK_ALLOC_OK(data_string->size() >= current_size + extra_size);
void* const bytes = &(*data_string)[current_size];
data_string->resize(current_size + fread(bytes, 1, extra_size, stdin));
if (data_string->size() < current_size + extra_size) break;
}
WP2_CHECK_OK(ferror(stdin) == 0, WP2_STATUS_BAD_READ);
return WP2_STATUS_OK;
}
WP2Status IoUtilWriteFile(const std::string& data, const char* const file_path,
bool overwrite) {
return IoUtilWriteFile((const uint8_t*)data.data(), data.size(), file_path,
overwrite);
}
//------------------------------------------------------------------------------
#if defined(WP2_HAVE_WEBP)
WP2Status ConvertToWebPPicture(const WP2::ArgbBuffer& in,
WebPPicture* const out, bool use_yuv) {
WP2_CHECK_OK(WebPPictureInit(out), WP2_STATUS_VERSION_MISMATCH);
out->use_argb = 1;
out->width = in.width();
out->height = in.height();
WP2_CHECK_OK(WebPPictureAlloc(out), WP2_STATUS_OUT_OF_MEMORY);
WP2_CHECK_OK(in.format() == WP2_Argb_32 || in.format() == WP2_ARGB_32,
WP2_STATUS_UNSUPPORTED_FEATURE);
// TODO(skal): sharp-yuv conversion ?
// Convert from Argb_32 to WebP's ARGB
WP2ArgbConverterInit();
const WP2ArgbConverterF* convert_to =
in.format() == WP2_Argb_32 ? WP2ArgbConvertTo : WP2ARGBConvertTo;
for (int y = 0; y < out->height; ++y) {
// BGRA order for conversion from uint8_t[4] to uint32_t[1] endianness.
convert_to[WP2_BGRA_32](in.GetRow8(y), out->width,
(uint8_t*)(out->argb + y * out->argb_stride));
}
if (use_yuv) {
WP2_CHECK_OK(WebPPictureSharpARGBToYUVA(out), WP2_STATUS_OUT_OF_MEMORY);
}
return WP2_STATUS_OK;
}
WP2Status ConvertFromWebPPicture(const WebPPicture& in,
WP2::ArgbBuffer* const out) {
WP2_CHECK_OK(out != nullptr, WP2_STATUS_NULL_PARAMETER);
// TODO(skal): potential endian problem
return out->Import(WP2_bgrA_32, in.width, in.height, (const uint8_t*)in.argb,
in.argb_stride * sizeof(*in.argb));
}
#endif // WP2_HAVE_WEBP
//------------------------------------------------------------------------------
} // namespace WP2