#include <whiteout/textures/jpeg/parser.h>
#include "../io_helpers.h"
#include "../issue_sink.h"
#include "../utils/color_convert.h"
#include "jpeg_common.h"
#include "jpeg_decode.h"
#include <algorithm>
#include <cstring>
#include <memory>
#include <stdexcept>
namespace whiteout::textures::jpeg {
class Parser::Impl : public IssueSink {
public:
interfaces::WorkerPool* pool = nullptr;
std::optional<Texture> parse(std::span<const u8> buffer);
};
std::optional<Texture> Parser::Impl::parse(std::span<const u8> buffer) {
issues.clear();
if (buffer.size() < 2) {
fail("Buffer too small for a JPEG file");
return std::nullopt;
}
if (buffer[0] != 0xFF || buffer[1] != 0xD8) {
fail("Invalid JPEG signature (no SOI marker)");
return std::nullopt;
}
std::unique_ptr<interfaces::TimelineSemaphore> sem;
JpegContext jctx;
JpegContext* ctxPtr = nullptr;
Image asyncImage;
if (pool && pool->threadCount() > 1) {
sem = pool->createTimelineSemaphore();
if (sem) {
jctx.pool = pool;
jctx.sem = sem.get();
jctx.currentValue = sem->value();
ctxPtr = &jctx;
}
}
if (ctxPtr) {
std::string decodeError;
whiteout::textures::jpeg::decode_raw(buffer, &decodeError, ctxPtr, &asyncImage);
if (asyncImage.width == 0 && asyncImage.height == 0) {
fail("JPEG decode failed: " + decodeError);
return std::nullopt;
}
} else {
std::string decodeError;
auto image = whiteout::textures::jpeg::decode_raw(buffer, &decodeError);
if (!image) {
fail("JPEG decode failed: " + decodeError);
return std::nullopt;
}
asyncImage = std::move(*image);
}
const u32 width = asyncImage.width;
const u32 height = asyncImage.height;
const u32 components = asyncImage.components;
if (width == 0 || height == 0) {
fail("JPEG image has zero dimensions");
return std::nullopt;
}
if (components != 1 && components != 3 && components != 4) {
fail("Unsupported JPEG component count: " + std::to_string(components));
return std::nullopt;
}
Texture texture = Texture::create2D(PixelFormat::RGBA8, width, height, 1);
u8* dest = texture.dataPtr();
const u32 pixelCount = width * height;
if (ctxPtr) {
parallel_for_blocks(
pixelCount, ctxPtr, [&asyncImage, dest, components](u32 begin, u32 end) {
const u8* src = asyncImage.pixels.data();
if (components == 1) {
for (u32 i = begin; i < end; ++i) {
u8 const y = src[i];
dest[i * 4 + 0] = y;
dest[i * 4 + 1] = y;
dest[i * 4 + 2] = y;
dest[i * 4 + 3] = 255;
}
} else if (components == 3) {
for (u32 i = begin; i < end; ++i) {
ycbcr_to_rgb(src[i * 3 + 0], src[i * 3 + 1], src[i * 3 + 2],
dest[i * 4 + 0], dest[i * 4 + 1], dest[i * 4 + 2]);
dest[i * 4 + 3] = 255;
}
} else if (components == 4) {
for (u32 i = begin; i < end; ++i) {
ycbcr_to_rgb(src[i * 4 + 0], src[i * 4 + 1], src[i * 4 + 2],
dest[i * 4 + 0], dest[i * 4 + 1], dest[i * 4 + 2]);
dest[i * 4 + 3] = src[i * 4 + 3];
}
}
});
jctx.sem->wait(jctx.currentValue);
} else {
const u8* src = asyncImage.pixels.data();
if (components == 1) {
for (u32 i = 0; i < pixelCount; ++i) {
u8 const y = src[i];
dest[i * 4 + 0] = y;
dest[i * 4 + 1] = y;
dest[i * 4 + 2] = y;
dest[i * 4 + 3] = 255;
}
} else if (components == 3) {
for (u32 i = 0; i < pixelCount; ++i) {
ycbcr_to_rgb(src[i * 3 + 0], src[i * 3 + 1], src[i * 3 + 2], dest[i * 4 + 0],
dest[i * 4 + 1], dest[i * 4 + 2]);
dest[i * 4 + 3] = 255;
}
} else if (components == 4) {
for (u32 i = 0; i < pixelCount; ++i) {
ycbcr_to_rgb(src[i * 4 + 0], src[i * 4 + 1], src[i * 4 + 2], dest[i * 4 + 0],
dest[i * 4 + 1], dest[i * 4 + 2]);
dest[i * 4 + 3] = src[i * 4 + 3];
}
}
}
return texture;
}
Parser::Parser(interfaces::WorkerPool* pool) : pImpl(std::make_unique<Impl>()) {
pImpl->pool = pool;
}
Parser::~Parser() = default;
std::optional<Texture> Parser::parse(const std::string& filePath) {
pImpl->issues.clear();
auto buf = read_file_bytes(filePath, *pImpl);
if (!buf) {
return std::nullopt;
}
return pImpl->parse(std::span<const u8>{*buf});
}
std::optional<Texture> Parser::parse(std::span<const u8> buffer) {
return pImpl->parse(buffer);
}
bool Parser::hasIssues() const {
return !pImpl->issues.empty();
}
const std::vector<std::string>& Parser::getIssues() const {
return pImpl->issues;
}
}