srctree

Robin Linden parent cc8a5dda 1a3807ef
img: Implement parsing the width and height of GIFs

inlinesplit
filename was Deleted added: 175, removed: 3, total 172
@@ -0,0 +1,102 @@
// SPDX-FileCopyrightText: 2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "img/gif.h"
 
#include <istream>
#include <string>
#include <string_view>
 
using namespace std::literals;
 
namespace img {
namespace {
 
// 18. Logical Screen Descriptor
//
// 7 6 5 4 3 2 1 0 Field Name Type
// +---------------+
// 0 | | Logical Screen Width Unsigned
// +- -+
// 1 | |
// +---------------+
// 2 | | Logical Screen Height Unsigned
// +- -+
// 3 | |
// +---------------+
// 4 | | | | | <Packed Fields> See below
// +---------------+
// 5 | | Background Color Index Byte
// +---------------+
// 6 | | Pixel Aspect Ratio Byte
// +---------------+
//
// <Packed Fields> = Global Color Table Flag 1 Bit
// Color Resolution 3 Bits
// Sort Flag 1 Bit
// Size of Global Color Table 3 Bits
struct ScreenDescriptor {
std::uint16_t width{};
std::uint16_t height{};
 
static std::optional<ScreenDescriptor> from(std::istream &is) {
ScreenDescriptor screen{};
 
if (!is.read(reinterpret_cast<char *>(&screen.width), sizeof(screen.width))) {
return std::nullopt;
}
 
if (!is.read(reinterpret_cast<char *>(&screen.height), sizeof(screen.height))) {
return std::nullopt;
}
 
return screen;
}
};
 
} // namespace
 
// https://www.w3.org/Graphics/GIF/spec-gif87.txt
// https://www.w3.org/Graphics/GIF/spec-gif89a.txt
std::optional<Gif> Gif::from(std::istream &is) {
// 17. Header
 
// i) Signature - Identifies the GIF Data Stream. This field contains
// the fixed value 'GIF'.
 
// ii) Version - Version number used to format the data stream.
// Identifies the minimum set of capabilities necessary to a decoder
// to fully process the contents of the Data Stream.
 
// Version Numbers as of 10 July 1990 : "87a" - May 1987
// "89a" - July 1989
std::string magic{};
magic.resize(6);
 
if (!is.read(magic.data(), magic.size())) {
return std::nullopt;
}
 
Gif::Version version{};
if (magic == "GIF87a"sv) {
version = Gif::Version::Gif87a;
} else if (magic == "GIF89a"sv) {
version = Gif::Version::Gif89a;
} else {
return std::nullopt;
}
 
auto screen = ScreenDescriptor::from(is);
if (!screen) {
return std::nullopt;
}
 
return Gif{
.version = version,
.width = screen->width,
.height = screen->height,
};
}
 
} // namespace img
 
filename was Deleted added: 175, removed: 3, total 172
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#ifndef IMG_GIF_H_
#define IMG_GIF_H_
 
#include <cstdint>
#include <iosfwd>
#include <optional>
 
namespace img {
 
class Gif {
public:
enum class Version {
Gif87a,
Gif89a,
};
 
static std::optional<Gif> from(std::istream &&is) { return from(is); }
static std::optional<Gif> from(std::istream &is);
 
Version version{};
std::uint32_t width{};
std::uint32_t height{};
 
[[nodiscard]] bool operator==(Gif const &) const = default;
};
 
} // namespace img
 
#endif
 
filename was Deleted added: 175, removed: 3, total 172
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2023 Robin Lindén <dev@robinlinden.eu>
//
// SPDX-License-Identifier: BSD-2-Clause
 
#include "img/gif.h"
 
#include "etest/etest.h"
 
#include <optional>
#include <sstream>
#include <string>
 
using etest::expect_eq;
using img::Gif;
 
using namespace std::literals;
 
int main() {
etest::test("invalid signatures", [] {
expect_eq(Gif::from(std::stringstream{"GIF87"s}), std::nullopt);
expect_eq(Gif::from(std::stringstream{"GIF87b"s}), std::nullopt);
});
 
etest::test("version, width, and height", [] {
Gif expected{.version = Gif::Version::Gif89a, .width = 3, .height = 5};
expect_eq(Gif::from(std::stringstream{"GIF89a\3\0\5\0"s}), expected);
expected = Gif{.version = Gif::Version::Gif87a, .width = 15000, .height = 1};
expect_eq(Gif::from(std::stringstream{"GIF87a\x98\x3a\1\0"s}), expected);
});
 
etest::test("eof at height, width", [] {
expect_eq(Gif::from(std::stringstream{"GIF87a"s}), std::nullopt);
expect_eq(Gif::from(std::stringstream{"GIF89a\1\1\1"s}), std::nullopt);
});
 
return etest::run_all_tests();
}