Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibGfx+Tests(+AK): Add the beginning of a JPEG2000 loader #23682

Merged
merged 25 commits into from Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9bc3c14
LibGfx/ISOBMFF: FileTypeBox is not a FullBox
nico Mar 22, 2024
26ffe32
LibGfx/ISOBMFF: Remove prototypes for nonexistent methods
nico Mar 22, 2024
215ed4e
LibGfx/ISOBMFF: Put string literals in box type ENUMERATE_ONE()
nico Mar 22, 2024
ef2a11f
LibGfx/ISOBMFF: Alphabetize box type ENUMERATE_ONE() lines
nico Mar 22, 2024
1973cdd
LibGfx/ISOBMFF: Add JPEG2000 box types
nico Mar 22, 2024
1cba87a
AK: Allow creating a MaybeOwned<Superclass> from a MaybeOwned<Subclass>
nico Mar 22, 2024
ab6140e
LibGfx/ISOBMFF: Make BoxStream MaybeOwn its stream
nico Mar 22, 2024
defb3cf
LibGfx/ISOBMFF: Read JPEG2000HeaderBox
nico Mar 22, 2024
0879bb3
LibGfx/ISOBMFF: Start creating JPEG2000 box types
nico Mar 22, 2024
c1c93c5
LibGfx/ISOBMFF: Give JPEG2000HeaderBox its own type
nico Mar 22, 2024
28505fb
LibGfx/ISOBMFF: Remove Box::read_from_stream()
nico Mar 23, 2024
f988eae
LibGfx/ISOBMFF: Give Reader::read_entire_file() a factory callback
nico Mar 22, 2024
145bc64
LibGfx/ISOBMFF: Add JPEG2000ImageHeaderBox
nico Mar 22, 2024
808c1ad
LibGfx/ISOBMFF: Add JPEG2000ColorSpecificationBox
nico Mar 22, 2024
2a47c10
LibGfx/ISOBMFF: Add JPEG2000UUIDInfoBox
nico Mar 22, 2024
84867f5
LibGfx/ISOBMFF: Add JPEG2000ResolutionBox
nico Mar 22, 2024
b13303b
LibGfx/ISOBMFF: Add JPEG2000CaptureResolutionBox
nico Mar 22, 2024
e69fd09
LibGfx/ISOBMFF: Add JPEG2000UUIDListBox
nico Mar 22, 2024
1fbd38b
LibGfx/ISOBMFF: Add JPEG2000ContiguousCodestreamBox
nico Mar 22, 2024
40430c0
LibGfx/ISOBMFF: Add JPEG2000URLBox
nico Mar 22, 2024
eadccbd
LibGfx/ISOBMFF: Add JPEG2000ChannelDefinitionBox
nico Mar 22, 2024
59bb15c
LibGfx: Add the start of a JPEG2000 loader
nico Mar 22, 2024
1e0cb1b
LibGfx/JPEG2000: Read file structure
nico Mar 22, 2024
b235bce
Tests/JPEG2000: Add a simple JPEG2000 test
nico Mar 22, 2024
ef853c9
Fuzzers: Add JPEG2000 fuzzer
nico Mar 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions AK/Debug.h.in
Expand Up @@ -246,6 +246,10 @@
# cmakedefine01 JPEG_DEBUG
#endif

#ifndef JPEG2000_DEBUG
# cmakedefine01 JPEG2000_DEBUG
#endif

#ifndef JS_BYTECODE_DEBUG
# cmakedefine01 JS_BYTECODE_DEBUG
#endif
Expand Down
23 changes: 22 additions & 1 deletion AK/MaybeOwned.h
Expand Up @@ -32,6 +32,12 @@ class MaybeOwned {
MaybeOwned(MaybeOwned&&) = default;
MaybeOwned& operator=(MaybeOwned&&) = default;

template<DerivedFrom<T> U>
MaybeOwned(MaybeOwned<U>&& other)
: m_handle(downcast<U, T>(move(other.m_handle)))
{
}
nico marked this conversation as resolved.
Show resolved Hide resolved

T* ptr()
{
if (m_handle.template has<T*>())
Expand All @@ -57,7 +63,22 @@ class MaybeOwned {
bool is_owned() const { return m_handle.template has<NonnullOwnPtr<T>>(); }

private:
Variant<NonnullOwnPtr<T>, T*> m_handle;
template<typename F>
friend class MaybeOwned;

template<typename HT>
using Handle = Variant<NonnullOwnPtr<HT>, HT*>;

template<typename U, typename D>
Handle<D> downcast(Handle<U>&& variant)
{
if (variant.template has<U*>())
return variant.template get<U*>();
else
return static_cast<NonnullOwnPtr<T>&&>(move(variant.template get<NonnullOwnPtr<U>>()));
}

Handle<T> m_handle;
};

}
Expand Down
2 changes: 1 addition & 1 deletion Base/res/apps/ImageViewer.af
Expand Up @@ -4,4 +4,4 @@ Executable=/bin/ImageViewer
Category=Gra&phics

[Launcher]
FileTypes=bmp,dds,gif,ico,iff,jb2,jbig2,jpeg,jpg,jxl,lbm,pbm,pgm,png,ppm,qoi,tga,tiff,tif,tvg
FileTypes=bmp,dds,gif,ico,iff,jb2,jbig2,jp2,jpeg,jpg,jpx,jxl,lbm,pbm,pgm,png,ppm,qoi,tga,tiff,tif,tvg
1 change: 1 addition & 0 deletions Meta/CMake/all_the_debug_macros.cmake
Expand Up @@ -92,6 +92,7 @@ set(ITEM_RECTS_DEBUG ON)
set(JBIG2_DEBUG ON)
set(JOB_DEBUG ON)
set(JPEG_DEBUG ON)
set(JPEG2000_DEBUG ON)
set(JS_BYTECODE_DEBUG ON)
set(JS_MODULE_DEBUG ON)
set(KEYBOARD_DEBUG ON)
Expand Down
20 changes: 20 additions & 0 deletions Meta/Lagom/Fuzzers/FuzzJPEG2000Loader.cpp
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2024, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#include <LibGfx/ImageFormats/JPEG2000Loader.h>
#include <stddef.h>
#include <stdint.h>

extern "C" int LLVMFuzzerTestOneInput(uint8_t const* data, size_t size)
{
AK::set_debug_enabled(false);
auto decoder_or_error = Gfx::JPEG2000ImageDecoderPlugin::create({ data, size });
if (decoder_or_error.is_error())
return 0;
auto decoder = decoder_or_error.release_value();
(void)decoder->frame(0);
return 0;
}
2 changes: 2 additions & 0 deletions Meta/Lagom/Fuzzers/fuzzers.cmake
Expand Up @@ -20,6 +20,7 @@ set(FUZZER_TARGETS
ILBMLoader
IMAPParser
JBIG2Loader
JPEG2000Loader
JPEGLoader
Js
JsonParser
Expand Down Expand Up @@ -94,6 +95,7 @@ set(FUZZER_DEPENDENCIES_ICOLoader LibGfx)
set(FUZZER_DEPENDENCIES_ILBMLoader LibGfx)
set(FUZZER_DEPENDENCIES_IMAPParser LibIMAP)
set(FUZZER_DEPENDENCIES_JBIG2Loader LibGfx)
set(FUZZER_DEPENDENCIES_JPEG2000Loader LibGfx)
set(FUZZER_DEPENDENCIES_JPEGLoader LibGfx)
set(FUZZER_DEPENDENCIES_Js LibJS)
set(FUZZER_DEPENDENCIES_LzmaDecompression LibArchive LibCompress)
Expand Down
1 change: 1 addition & 0 deletions Meta/gn/secondary/AK/BUILD.gn
Expand Up @@ -289,6 +289,7 @@ write_cmake_config("ak_debug_gen") {
"JOB_DEBUG=",
"JBIG2_DEBUG=",
"JPEG_DEBUG=",
"JPEG2000_DEBUG=",
"JS_BYTECODE_DEBUG=",
"JS_MODULE_DEBUG=",
"KEYBOARD_SHORTCUTS_DEBUG=",
Expand Down
2 changes: 2 additions & 0 deletions Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn
Expand Up @@ -72,9 +72,11 @@ shared_library("LibGfx") {
"ImageFormats/ICOLoader.cpp",
"ImageFormats/ILBMLoader.cpp",
"ImageFormats/ISOBMFF/Boxes.cpp",
"ImageFormats/ISOBMFF/JPEG2000Boxes.cpp",
"ImageFormats/ISOBMFF/Reader.cpp",
"ImageFormats/ImageDecoder.cpp",
"ImageFormats/JBIG2Loader.cpp",
"ImageFormats/JPEG2000Loader.cpp",
"ImageFormats/JPEGLoader.cpp",
"ImageFormats/JPEGWriter.cpp",
"ImageFormats/JPEGXLLoader.cpp",
Expand Down
10 changes: 10 additions & 0 deletions Tests/LibGfx/TestImageDecoder.cpp
Expand Up @@ -14,6 +14,7 @@
#include <LibGfx/ImageFormats/ILBMLoader.h>
#include <LibGfx/ImageFormats/ImageDecoder.h>
#include <LibGfx/ImageFormats/JBIG2Loader.h>
#include <LibGfx/ImageFormats/JPEG2000Loader.h>
#include <LibGfx/ImageFormats/JPEGLoader.h>
#include <LibGfx/ImageFormats/JPEGXLLoader.h>
#include <LibGfx/ImageFormats/PAMLoader.h>
Expand Down Expand Up @@ -568,6 +569,15 @@ TEST_CASE(test_jpeg_malformed_frame)
}
}

TEST_CASE(test_jpeg2000_simple)
nico marked this conversation as resolved.
Show resolved Hide resolved
{
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("jpeg2000/simple.jp2"sv)));
EXPECT(Gfx::JPEG2000ImageDecoderPlugin::sniff(file->bytes()));
auto plugin_decoder = TRY_OR_FAIL(Gfx::JPEG2000ImageDecoderPlugin::create(file->bytes()));

EXPECT_EQ(plugin_decoder->size(), Gfx::IntSize(119, 101));
}

TEST_CASE(test_pam_rgb)
{
auto file = TRY_OR_FAIL(Core::MappedFile::map(TEST_INPUT("pnm/2x1.pam"sv)));
Expand Down
Binary file added Tests/LibGfx/test-inputs/jpeg2000/simple.jp2
Binary file not shown.
1 change: 1 addition & 0 deletions Userland/Libraries/LibCore/MimeData.cpp
Expand Up @@ -113,6 +113,7 @@ static Array const s_registered_mime_type = {
MimeType { .name = "image/bmp"sv, .common_extensions = { ".bmp"sv }, .description = "BMP image data"sv, .magic_bytes = Vector<u8> { 'B', 'M' } },
MimeType { .name = "image/gif"sv, .common_extensions = { ".gif"sv }, .description = "GIF image data"sv, .magic_bytes = Vector<u8> { 'G', 'I', 'F', '8', '7', 'a' } },
MimeType { .name = "image/gif"sv, .common_extensions = { ".gif"sv }, .description = "GIF image data"sv, .magic_bytes = Vector<u8> { 'G', 'I', 'F', '8', '9', 'a' } },
MimeType { .name = "image/jp2"sv, .common_extensions = { ".jp2"sv, ".jpx"sv }, .description = "JPEG2000 image data"sv, .magic_bytes = Vector<u8> { 0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A } },
MimeType { .name = "image/jpeg"sv, .common_extensions = { ".jpg"sv, ".jpeg"sv }, .description = "JPEG image data"sv, .magic_bytes = Vector<u8> { 0xFF, 0xD8, 0xFF } },
MimeType { .name = "image/jxl"sv, .common_extensions = { ".jxl"sv }, .description = "JPEG XL image data"sv, .magic_bytes = Vector<u8> { 0xFF, 0x0A } },
MimeType { .name = "image/png"sv, .common_extensions = { ".png"sv }, .description = "PNG image data"sv, .magic_bytes = Vector<u8> { 0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A } },
Expand Down
2 changes: 1 addition & 1 deletion Userland/Libraries/LibGUI/FileTypeFilter.h
Expand Up @@ -30,7 +30,7 @@ struct FileTypeFilter {

static FileTypeFilter image_files()
{
return FileTypeFilter { "Image Files", Vector<ByteString> { "png", "gif", "bmp", "dip", "pam", "pbm", "pgm", "ppm", "ico", "iff", "jb2", "jbig2", "jpeg", "jpg", "jxl", "dds", "qoi", "tif", "tiff", "webp", "tvg" } };
return FileTypeFilter { "Image Files", Vector<ByteString> { "png", "gif", "bmp", "dip", "pam", "pbm", "pgm", "ppm", "ico", "iff", "jb2", "jbig2", "jp2", "jpeg", "jpg", "jpx", "jxl", "dds", "qoi", "tif", "tiff", "webp", "tvg" } };
}

static FileTypeFilter video_files()
Expand Down
46 changes: 24 additions & 22 deletions Userland/Libraries/LibGfx/Bitmap.h
Expand Up @@ -16,28 +16,30 @@
#include <LibGfx/Forward.h>
#include <LibGfx/Rect.h>

#define ENUMERATE_IMAGE_FORMATS \
__ENUMERATE_IMAGE_FORMAT(bmp, ".bmp") \
__ENUMERATE_IMAGE_FORMAT(dds, ".dds") \
__ENUMERATE_IMAGE_FORMAT(gif, ".gif") \
__ENUMERATE_IMAGE_FORMAT(ico, ".ico") \
__ENUMERATE_IMAGE_FORMAT(iff, ".iff") \
__ENUMERATE_IMAGE_FORMAT(jpeg, ".jb2") \
__ENUMERATE_IMAGE_FORMAT(jpeg, ".jbig2") \
__ENUMERATE_IMAGE_FORMAT(jpeg, ".jpeg") \
__ENUMERATE_IMAGE_FORMAT(jpeg, ".jpg") \
__ENUMERATE_IMAGE_FORMAT(jxl, ".jxl") \
__ENUMERATE_IMAGE_FORMAT(iff, ".lbm") \
__ENUMERATE_IMAGE_FORMAT(pam, ".pam") \
__ENUMERATE_IMAGE_FORMAT(pbm, ".pbm") \
__ENUMERATE_IMAGE_FORMAT(pgm, ".pgm") \
__ENUMERATE_IMAGE_FORMAT(png, ".png") \
__ENUMERATE_IMAGE_FORMAT(ppm, ".ppm") \
__ENUMERATE_IMAGE_FORMAT(qoi, ".qoi") \
__ENUMERATE_IMAGE_FORMAT(tga, ".tga") \
__ENUMERATE_IMAGE_FORMAT(tiff, ".tif") \
__ENUMERATE_IMAGE_FORMAT(tiff, ".tiff") \
__ENUMERATE_IMAGE_FORMAT(tvg, ".tvg") \
#define ENUMERATE_IMAGE_FORMATS \
__ENUMERATE_IMAGE_FORMAT(bmp, ".bmp") \
__ENUMERATE_IMAGE_FORMAT(dds, ".dds") \
__ENUMERATE_IMAGE_FORMAT(gif, ".gif") \
__ENUMERATE_IMAGE_FORMAT(ico, ".ico") \
__ENUMERATE_IMAGE_FORMAT(iff, ".iff") \
__ENUMERATE_IMAGE_FORMAT(jpeg, ".jb2") \
__ENUMERATE_IMAGE_FORMAT(jpeg, ".jbig2") \
__ENUMERATE_IMAGE_FORMAT(jpeg2000, ".jp2") \
__ENUMERATE_IMAGE_FORMAT(jpeg, ".jpeg") \
__ENUMERATE_IMAGE_FORMAT(jpeg, ".jpg") \
__ENUMERATE_IMAGE_FORMAT(jpeg2000, ".jpx") \
__ENUMERATE_IMAGE_FORMAT(jxl, ".jxl") \
__ENUMERATE_IMAGE_FORMAT(iff, ".lbm") \
__ENUMERATE_IMAGE_FORMAT(pam, ".pam") \
__ENUMERATE_IMAGE_FORMAT(pbm, ".pbm") \
__ENUMERATE_IMAGE_FORMAT(pgm, ".pgm") \
__ENUMERATE_IMAGE_FORMAT(png, ".png") \
__ENUMERATE_IMAGE_FORMAT(ppm, ".ppm") \
__ENUMERATE_IMAGE_FORMAT(qoi, ".qoi") \
__ENUMERATE_IMAGE_FORMAT(tga, ".tga") \
__ENUMERATE_IMAGE_FORMAT(tiff, ".tif") \
__ENUMERATE_IMAGE_FORMAT(tiff, ".tiff") \
__ENUMERATE_IMAGE_FORMAT(tvg, ".tvg") \
__ENUMERATE_IMAGE_FORMAT(tvg, ".webp")

namespace Gfx {
Expand Down
2 changes: 2 additions & 0 deletions Userland/Libraries/LibGfx/CMakeLists.txt
Expand Up @@ -46,8 +46,10 @@ set(SOURCES
ImageFormats/ILBMLoader.cpp
ImageFormats/ImageDecoder.cpp
ImageFormats/ISOBMFF/Boxes.cpp
ImageFormats/ISOBMFF/JPEG2000Boxes.cpp
ImageFormats/ISOBMFF/Reader.cpp
ImageFormats/JBIG2Loader.cpp
ImageFormats/JPEG2000Loader.cpp
ImageFormats/JPEGLoader.cpp
ImageFormats/JPEGXLLoader.cpp
ImageFormats/JPEGWriter.cpp
Expand Down
15 changes: 8 additions & 7 deletions Userland/Libraries/LibGfx/ImageFormats/ISOBMFF/BoxStream.h
Expand Up @@ -6,24 +6,25 @@

#pragma once

#include <AK/MaybeOwned.h>
#include <AK/Stream.h>

namespace Gfx::ISOBMFF {

class BoxStream final : public Stream {
public:
explicit BoxStream(Stream& stream, size_t size)
: m_stream(stream)
explicit BoxStream(MaybeOwned<Stream> stream, size_t size)
: m_stream(move(stream))
, m_data_left(size)
{
}

virtual bool is_eof() const override { return m_stream.is_eof() || remaining() == 0; }
virtual bool is_open() const override { return m_stream.is_open(); }
virtual void close() override { m_stream.close(); }
virtual bool is_eof() const override { return m_stream->is_eof() || remaining() == 0; }
virtual bool is_open() const override { return m_stream->is_open(); }
virtual void close() override { m_stream->close(); }
virtual ErrorOr<Bytes> read_some(Bytes bytes) override
{
auto read_bytes = TRY(m_stream.read_some(bytes));
auto read_bytes = TRY(m_stream->read_some(bytes));
m_data_left -= min(read_bytes.size(), m_data_left);
return read_bytes;
}
Expand All @@ -41,7 +42,7 @@ class BoxStream final : public Stream {
}

private:
Stream& m_stream;
MaybeOwned<Stream> m_stream;
size_t m_data_left;
};

Expand Down
19 changes: 18 additions & 1 deletion Userland/Libraries/LibGfx/ImageFormats/ISOBMFF/Boxes.cpp
Expand Up @@ -5,6 +5,8 @@
*/

#include "Boxes.h"
#include "Reader.h"
#include <AK/Function.h>

namespace Gfx::ISOBMFF {

Expand Down Expand Up @@ -88,7 +90,7 @@ ErrorOr<void> FileTypeBox::read_from_stream(BoxStream& stream)

void FileTypeBox::dump(String const& prepend) const
{
FullBox::dump(prepend);
Box::dump(prepend);

auto indented_prepend = add_indent(prepend);

Expand All @@ -103,4 +105,19 @@ void FileTypeBox::dump(String const& prepend) const
outln("{}{}", prepend, compatible_brands_string.string_view());
}

ErrorOr<void> SuperBox::read_from_stream(BoxStream& stream, BoxCallback box_factory)
{
auto reader = TRY(Gfx::ISOBMFF::Reader::create(MaybeOwned { stream }));
m_child_boxes = TRY(reader.read_entire_file(move(box_factory)));
return {};
}

void SuperBox::dump(String const& prepend) const
{
Box::dump(prepend);
auto indented_prepend = add_indent(prepend);
for (auto const& child_box : m_child_boxes)
child_box->dump(indented_prepend);
}

}
23 changes: 18 additions & 5 deletions Userland/Libraries/LibGfx/ImageFormats/ISOBMFF/Boxes.h
Expand Up @@ -31,15 +31,14 @@ ErrorOr<BoxHeader> read_box_header(BoxStream& stream);
struct Box {
Box() = default;
virtual ~Box() = default;
virtual ErrorOr<void> read_from_stream(BoxStream&) { return {}; }
virtual BoxType box_type() const { return BoxType::None; }
virtual void dump(String const& prepend = {}) const;
};

using BoxList = Vector<NonnullOwnPtr<Box>>;

struct FullBox : public Box {
virtual ErrorOr<void> read_from_stream(BoxStream& stream) override;
ErrorOr<void> read_from_stream(BoxStream& stream);
virtual void dump(String const& prepend = {}) const override;

u8 version { 0 };
Expand All @@ -59,7 +58,7 @@ struct UnknownBox final : public Box {
{
}
virtual ~UnknownBox() override = default;
virtual ErrorOr<void> read_from_stream(BoxStream&) override;
ErrorOr<void> read_from_stream(BoxStream&);
virtual BoxType box_type() const override { return m_box_type; }
virtual void dump(String const& prepend = {}) const override;

Expand All @@ -77,20 +76,34 @@ struct UnknownBox final : public Box {
} \
BoxName() = default; \
virtual ~BoxName() override = default; \
virtual ErrorOr<void> read_from_stream(BoxStream& stream) override; \
ErrorOr<void> read_from_stream(BoxStream& stream); \
virtual BoxType box_type() const override \
{ \
return BoxType::BoxName; \
} \
virtual void dump(String const& prepend = {}) const override;

// 4.3 File Type Box
struct FileTypeBox final : public FullBox {
struct FileTypeBox final : public Box {
BOX_SUBTYPE(FileTypeBox);

BrandIdentifier major_brand { BrandIdentifier::None };
u32 minor_version;
Vector<BrandIdentifier> compatible_brands;
};

// A box that contains other boxes.
struct SuperBox : public Box {
SuperBox() = default;

using BoxCallback = Function<ErrorOr<Optional<NonnullOwnPtr<Box>>>(BoxType, BoxStream&)>;
ErrorOr<void> read_from_stream(BoxStream&, BoxCallback);
virtual void dump(String const& prepend = {}) const override;

BoxList const& child_boxes() const { return m_child_boxes; }

private:
BoxList m_child_boxes;
};

}