LibGfx: Use LibRIFF for IFF parsing
This commit is contained in:
parent
64598473cc
commit
60c3a1a77b
Notes:
sideshowbarker
2024-07-18 05:01:22 +09:00
Author: https://github.com/kleinesfilmroellchen Commit: https://github.com/SerenityOS/serenity/commit/60c3a1a77b Pull-request: https://github.com/SerenityOS/serenity/pull/22554 Reviewed-by: https://github.com/ADKaster ✅
2 changed files with 27 additions and 73 deletions
|
@ -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/")
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Add table
Reference in a new issue