LibGfx: Use LibRIFF for IFF parsing

This commit is contained in:
kleines Filmröllchen 2023-10-12 22:37:58 +02:00 committed by Andrew Kaster
parent 64598473cc
commit 60c3a1a77b
Notes: sideshowbarker 2024-07-18 05:01:22 +09:00
2 changed files with 27 additions and 73 deletions

View File

@ -82,7 +82,7 @@ set(SOURCES
)
serenity_lib(LibGfx gfx)
target_link_libraries(LibGfx PRIVATE LibCompress LibCore LibCrypto LibFileSystem LibTextCodec LibIPC LibUnicode)
target_link_libraries(LibGfx PRIVATE LibCompress LibCore LibCrypto LibFileSystem LibRIFF LibTextCodec LibIPC LibUnicode)
set(generated_sources TIFFMetadata.h TIFFTagHandler.cpp)
list(TRANSFORM generated_sources PREPEND "ImageFormats/")

View File

@ -12,21 +12,11 @@
#include <LibCompress/PackBitsDecoder.h>
#include <LibGfx/FourCC.h>
#include <LibGfx/ImageFormats/ILBMLoader.h>
#include <LibRIFF/IFF.h>
namespace Gfx {
struct IFFHeader {
FourCC form;
BigEndian<u32> file_size;
FourCC format;
};
static_assert(AssertSize<IFFHeader, 12>());
struct Chunk {
FourCC type;
ReadonlyBytes data;
};
static constexpr size_t const ilbm_header_size = 12;
enum class CompressionType : u8 {
None = 0,
@ -56,11 +46,6 @@ enum class Format : u8 {
AK_ENUM_BITWISE_OPERATORS(ViewportMode);
struct ChunkHeader {
FourCC chunk_type;
BigEndian<u32> chunk_size;
};
struct BMHDHeader {
BigEndian<u16> width;
BigEndian<u16> height;
@ -113,27 +98,27 @@ static ErrorOr<void> decode_iff_ilbm_header(ILBMLoadingContext& context)
if (context.state >= ILBMLoadingContext::State::HeaderDecoded)
return {};
if (context.data.size() < sizeof(IFFHeader))
if (context.data.size() < ilbm_header_size)
return Error::from_string_literal("Missing IFF header");
auto& header = *bit_cast<IFFHeader const*>(context.data.data());
if (header.form != FourCC("FORM") || (header.format != FourCC("ILBM") && header.format != FourCC("PBM ")))
auto header_stream = FixedMemoryStream { context.data };
auto header = TRY(IFF::FileHeader::read_from_stream(header_stream));
if (header.magic() != "FORM"sv || (header.subformat != "ILBM"sv && header.subformat != "PBM "sv))
return Error::from_string_literal("Invalid IFF-ILBM header");
context.format = header.format == FourCC("ILBM") ? Format::ILBM : Format::PBM;
context.format = header.subformat == "ILBM" ? Format::ILBM : Format::PBM;
return {};
}
static ErrorOr<Vector<Color>> decode_cmap_chunk(Chunk cmap_chunk)
static ErrorOr<Vector<Color>> decode_cmap_chunk(IFF::Chunk cmap_chunk)
{
size_t const size = cmap_chunk.data.size() / 3;
size_t const size = cmap_chunk.size() / 3;
Vector<Color> color_table;
TRY(color_table.try_ensure_capacity(size));
for (size_t i = 0; i < size; ++i) {
color_table.unchecked_append(Color(cmap_chunk.data[i * 3], cmap_chunk.data[(i * 3) + 1], cmap_chunk.data[(i * 3) + 2]));
color_table.unchecked_append(Color(cmap_chunk[i * 3], cmap_chunk[(i * 3) + 1], cmap_chunk[(i * 3) + 2]));
}
return color_table;
@ -295,23 +280,23 @@ static ErrorOr<void> reduce_ham_palette(ILBMLoadingContext& context)
return {};
}
static ErrorOr<void> decode_body_chunk(Chunk body_chunk, ILBMLoadingContext& context)
static ErrorOr<void> decode_body_chunk(IFF::Chunk body_chunk, ILBMLoadingContext& context)
{
dbgln_if(ILBM_DEBUG, "decode_body_chunk {}", body_chunk.data.size());
dbgln_if(ILBM_DEBUG, "decode_body_chunk {}", body_chunk.size());
ByteBuffer pixel_data;
if (context.bm_header.compression == CompressionType::ByteRun) {
auto plane_data = TRY(uncompress_byte_run(body_chunk.data, context));
auto plane_data = TRY(uncompress_byte_run(body_chunk.data(), context));
if (context.format == Format::ILBM)
pixel_data = TRY(planar_to_chunky(plane_data, context));
else
pixel_data = plane_data;
} else {
if (context.format == Format::ILBM)
pixel_data = TRY(planar_to_chunky(body_chunk.data, context));
pixel_data = TRY(planar_to_chunky(body_chunk.data(), context));
else
pixel_data = TRY(ByteBuffer::copy(body_chunk.data.data(), body_chunk.data.size()));
pixel_data = TRY(ByteBuffer::copy(body_chunk.data().data(), body_chunk.size()));
}
// Some files already have 64 colors defined in the palette,
@ -329,37 +314,6 @@ static ErrorOr<void> decode_body_chunk(Chunk body_chunk, ILBMLoadingContext& con
return {};
}
static ErrorOr<Chunk> decode_iff_chunk_header(ReadonlyBytes chunks)
{
if (chunks.size() < sizeof(ChunkHeader))
return Error::from_string_literal("Not enough data for IFF chunk header");
auto const& header = *bit_cast<ChunkHeader const*>(chunks.data());
if (chunks.size() < sizeof(ChunkHeader) + header.chunk_size)
return Error::from_string_literal("Not enough data for IFF chunk");
return Chunk { header.chunk_type, { chunks.data() + sizeof(ChunkHeader), header.chunk_size } };
}
static ErrorOr<Chunk> decode_iff_advance_chunk(ReadonlyBytes& chunks)
{
auto chunk = TRY(decode_iff_chunk_header(chunks));
chunks = chunks.slice(sizeof(ChunkHeader) + chunk.data.size());
// add padding if needed
if (chunk.data.size() % 2 != 0) {
if (chunks.is_empty())
return Error::from_string_literal("Missing data for padding byte");
if (*chunks.data() != 0)
return Error::from_string_literal("Padding byte is not 0");
chunks = chunks.slice(1);
}
return chunk;
}
static ErrorOr<void> decode_iff_chunks(ILBMLoadingContext& context)
{
auto& chunks = context.chunks_cursor;
@ -367,14 +321,14 @@ static ErrorOr<void> decode_iff_chunks(ILBMLoadingContext& context)
dbgln_if(ILBM_DEBUG, "decode_iff_chunks");
while (!chunks.is_empty()) {
auto chunk = TRY(decode_iff_advance_chunk(chunks));
if (chunk.type == FourCC("CMAP")) {
auto chunk = TRY(IFF::Chunk::decode_and_advance(chunks));
if (chunk.id() == "CMAP"sv) {
// Some files (HAM mainly) have CMAP chunks larger than the planes they advertise: I'm not sure
// why but we should not return an error in this case.
context.color_table = TRY(decode_cmap_chunk(chunk));
context.cmap_bits = AK::ceil_log2(context.color_table.size());
} else if (chunk.type == FourCC("BODY")) {
} else if (chunk.id() == "BODY"sv) {
if (context.color_table.is_empty() && context.bm_header.planes != 24)
return Error::from_string_literal("Decoding indexed BODY chunk without a color map is not currently supported");
@ -385,10 +339,10 @@ static ErrorOr<void> decode_iff_chunks(ILBMLoadingContext& context)
TRY(decode_body_chunk(chunk, context));
context.state = ILBMLoadingContext::State::BitmapDecoded;
} else if (chunk.type == FourCC("CRNG")) {
} else if (chunk.id() == "CRNG"sv) {
dbgln_if(ILBM_DEBUG, "Chunk:CRNG");
} else if (chunk.type == FourCC("CAMG")) {
context.viewport_mode = static_cast<ViewportMode>(AK::convert_between_host_and_big_endian(ByteReader::load32(chunk.data.data())));
} else if (chunk.id() == "CAMG"sv) {
context.viewport_mode = static_cast<ViewportMode>(AK::convert_between_host_and_big_endian(ByteReader::load32(chunk.data().data())));
dbgln_if(ILBM_DEBUG, "Chunk:CAMG, Viewport={}, EHB={}, HAM={}", (u32)context.viewport_mode, has_flag(context.viewport_mode, ViewportMode::EHB), has_flag(context.viewport_mode, ViewportMode::HAM));
}
}
@ -401,16 +355,16 @@ static ErrorOr<void> decode_iff_chunks(ILBMLoadingContext& context)
static ErrorOr<void> decode_bmhd_chunk(ILBMLoadingContext& context)
{
context.chunks_cursor = context.data.slice(sizeof(IFFHeader));
auto first_chunk = TRY(decode_iff_advance_chunk(context.chunks_cursor));
context.chunks_cursor = context.data.slice(sizeof(IFF::FileHeader));
auto first_chunk = TRY(IFF::Chunk::decode_and_advance(context.chunks_cursor));
if (first_chunk.type != FourCC("BMHD"))
if (first_chunk.id() != "BMHD"sv)
return Error::from_string_literal("IFFImageDecoderPlugin: Invalid chunk type, expected BMHD");
if (first_chunk.data.size() < sizeof(BMHDHeader))
if (first_chunk.size() < sizeof(BMHDHeader))
return Error::from_string_literal("IFFImageDecoderPlugin: Not enough data for header chunk");
context.bm_header = *bit_cast<BMHDHeader const*>(first_chunk.data.data());
context.bm_header = *bit_cast<BMHDHeader const*>(first_chunk.data().data());
if (context.bm_header.mask >= MaskType::__Count)
return Error::from_string_literal("IFFImageDecoderPlugin: Unsupported mask type");