#include <whiteout/textures/tiff/writer.h>
#include <cstring>
#include <memory>
#include <vector>
#include "tiff_internal.h"
#include "../../common/byte_order.h"
#include "../io_helpers.h"
#include "../issue_sink.h"
namespace whiteout::textures::tiff {
namespace {
constexpr u16 kEntryCount = 9;
void pushEntry(std::vector<u8>& out, u16 tag, FieldType type, u32 count,
const std::array<u8, 4>& value4) {
::whiteout::common::pushLE16(out, tag);
::whiteout::common::pushLE16(out, static_cast<u16>(type));
::whiteout::common::pushLE32(out, count);
out.insert(out.end(), value4.begin(), value4.end());
}
std::array<u8, 4> inlineU16(u16 v) {
std::array<u8, 4> a{};
::whiteout::common::writeLE16(a.data(), v);
return a;
}
std::array<u8, 4> inlineU32(u32 v) {
std::array<u8, 4> a{};
::whiteout::common::writeLE32(a.data(), v);
return a;
}
}
class Writer::Impl : public IssueSink {
public:
std::vector<u8> write(const Texture& texture);
};
std::vector<u8> Writer::Impl::write(const Texture& texture) {
issues.clear();
if (texture.width() == 0 || texture.height() == 0) {
fail("Cannot save an empty texture");
return {};
}
Texture rgba = texture.copyAsFormat(PixelFormat::RGBA8);
const u32 W = rgba.width();
const u32 H = rgba.height();
const u64 pixelSize = static_cast<u64>(W) * H * 4;
const u32 pixelOffset = 8;
const u32 ifdOffset = static_cast<u32>(pixelOffset + pixelSize);
const u32 ifdSize = 2 + kEntryCount * 12 + 4;
const u32 bpsOffset = ifdOffset + ifdSize;
const u32 totalSize = bpsOffset + 8;
std::vector<u8> out(totalSize, 0);
out[0] = 'I'; out[1] = 'I';
::whiteout::common::writeLE16(out.data() + 2, 42);
::whiteout::common::writeLE32(out.data() + 4, ifdOffset);
std::memcpy(out.data() + pixelOffset, rgba.dataPtr(),
static_cast<size_t>(pixelSize));
std::vector<u8> ifd;
::whiteout::common::pushLE16(ifd, kEntryCount);
pushEntry(ifd, Tag::ImageWidth, FieldType::Long, 1, inlineU32(W));
pushEntry(ifd, Tag::ImageLength, FieldType::Long, 1, inlineU32(H));
pushEntry(ifd, Tag::BitsPerSample, FieldType::Short, 4, inlineU32(bpsOffset));
pushEntry(ifd, Tag::Compression, FieldType::Short, 1, inlineU16(Compression::None));
pushEntry(ifd, Tag::PhotometricInterpretation, FieldType::Short, 1,
inlineU16(Photometric::Rgb));
pushEntry(ifd, Tag::StripOffsets, FieldType::Long, 1, inlineU32(pixelOffset));
pushEntry(ifd, Tag::SamplesPerPixel, FieldType::Short, 1, inlineU16(4));
pushEntry(ifd, Tag::RowsPerStrip, FieldType::Long, 1, inlineU32(H));
pushEntry(ifd, Tag::StripByteCounts, FieldType::Long, 1,
inlineU32(static_cast<u32>(pixelSize)));
::whiteout::common::pushLE32(ifd, 0);
std::memcpy(out.data() + ifdOffset, ifd.data(), ifd.size());
for (u32 i = 0; i < 4; ++i)
::whiteout::common::writeLE16(out.data() + bpsOffset + i * 2, 8);
return out;
}
Writer::Writer() : pImpl(std::make_unique<Impl>()) {}
Writer::~Writer() = default;
void Writer::write(const std::string& filePath, const Texture& texture) {
auto data = pImpl->write(texture);
if (data.empty()) {
return;
}
if (!write_file_bytes(filePath, data, *pImpl)) {
return;
}
}
std::vector<u8> Writer::write(const Texture& texture) {
return pImpl->write(texture);
}
bool Writer::hasIssues() const {
return !pImpl->issues.empty();
}
const std::vector<std::string>& Writer::getIssues() const {
return pImpl->issues;
}
}