/src/serenity/Userland/Libraries/LibGfx/ImageFormats/PAMLoader.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2024, the SerenityOS developers. |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #pragma once |
8 | | |
9 | | #include <AK/StringView.h> |
10 | | #include <LibGfx/ImageFormats/ImageDecoder.h> |
11 | | #include <LibGfx/ImageFormats/PortableImageMapLoader.h> |
12 | | |
13 | | namespace Gfx { |
14 | | |
15 | | struct PAM { |
16 | | static constexpr auto binary_magic_number = '7'; |
17 | | static constexpr StringView image_type = "PAM"sv; |
18 | | u16 max_val { 0 }; |
19 | | u16 depth { 0 }; |
20 | | String tupl_type {}; |
21 | | Optional<NonnullRefPtr<CMYKBitmap>> cmyk_bitmap {}; |
22 | | }; |
23 | | |
24 | | using PAMLoadingContext = PortableImageMapLoadingContext<PAM>; |
25 | | |
26 | | template<class Context> |
27 | | ErrorOr<void> read_pam_header(Context& context) |
28 | 1.40k | { |
29 | | // https://netpbm.sourceforge.net/doc/pam.html |
30 | 1.40k | TRY(read_magic_number(context)); |
31 | | |
32 | 1.37k | Optional<u16> width; |
33 | 1.37k | Optional<u16> height; |
34 | 1.37k | Optional<u16> depth; |
35 | 1.37k | Optional<u16> max_val; |
36 | 1.37k | Optional<String> tupltype; |
37 | | |
38 | 7.92k | while (true) { |
39 | 7.92k | TRY(read_whitespace(context)); |
40 | | |
41 | 7.88k | auto const token = TRY(read_token(*context.stream)); |
42 | | |
43 | 7.78k | if (token == "ENDHDR") { |
44 | 664 | auto newline = TRY(context.stream->template read_value<u8>()); |
45 | 663 | if (newline != '\n') |
46 | 2 | return Error::from_string_view("PAM ENDHDR not followed by newline"sv); |
47 | 661 | break; |
48 | 663 | } |
49 | | |
50 | 14.2k | TRY(read_whitespace(context)); |
51 | 14.2k | if (token == "WIDTH") { |
52 | 784 | if (width.has_value()) |
53 | 1 | return Error::from_string_view("Duplicate PAM WIDTH field"sv); |
54 | 783 | width = TRY(read_number(*context.stream)); |
55 | 6.32k | } else if (token == "HEIGHT") { |
56 | 707 | if (height.has_value()) |
57 | 1 | return Error::from_string_view("Duplicate PAM HEIGHT field"sv); |
58 | 706 | height = TRY(read_number(*context.stream)); |
59 | 5.61k | } else if (token == "DEPTH") { |
60 | 813 | if (depth.has_value()) |
61 | 1 | return Error::from_string_view("Duplicate PAM DEPTH field"sv); |
62 | 812 | depth = TRY(read_number(*context.stream)); |
63 | 4.80k | } else if (token == "MAXVAL") { |
64 | 717 | if (max_val.has_value()) |
65 | 2 | return Error::from_string_view("Duplicate PAM MAXVAL field"sv); |
66 | 715 | max_val = TRY(read_number(*context.stream)); |
67 | 4.08k | } else if (token == "TUPLTYPE") { |
68 | | // FIXME: tupltype should be all text until the next newline, with leading and trailing space stripped. |
69 | | // FIXME: If there are multiple TUPLTYPE lines, their values are all appended. |
70 | 3.66k | tupltype = TRY(read_token(*context.stream)); |
71 | 3.66k | } else { |
72 | 418 | return Error::from_string_view("Unknown PAM token"sv); |
73 | 418 | } |
74 | 14.2k | } |
75 | | |
76 | 661 | if (!width.has_value() || !height.has_value() || !depth.has_value() || !max_val.has_value()) |
77 | 5 | return Error::from_string_view("Missing PAM header fields"sv); |
78 | 656 | context.width = *width; |
79 | 656 | context.height = *height; |
80 | 656 | context.format_details.depth = *depth; |
81 | 656 | context.format_details.max_val = *max_val; |
82 | 656 | if (tupltype.has_value()) |
83 | 630 | context.format_details.tupl_type = *tupltype; |
84 | | |
85 | 656 | context.state = Context::State::HeaderDecoded; |
86 | | |
87 | 656 | return {}; |
88 | 661 | } Unexecuted instantiation: ImageDecoder.cpp:AK::ErrorOr<void, AK::Error> Gfx::read_pam_header<Gfx::PortableImageMapLoadingContext<Gfx::PAM> >(Gfx::PortableImageMapLoadingContext<Gfx::PAM>&) FuzzPAMLoader.cpp:AK::ErrorOr<void, AK::Error> Gfx::read_pam_header<Gfx::PortableImageMapLoadingContext<Gfx::PAM> >(Gfx::PortableImageMapLoadingContext<Gfx::PAM>&) Line | Count | Source | 28 | 1.40k | { | 29 | | // https://netpbm.sourceforge.net/doc/pam.html | 30 | 1.40k | TRY(read_magic_number(context)); | 31 | | | 32 | 1.37k | Optional<u16> width; | 33 | 1.37k | Optional<u16> height; | 34 | 1.37k | Optional<u16> depth; | 35 | 1.37k | Optional<u16> max_val; | 36 | 1.37k | Optional<String> tupltype; | 37 | | | 38 | 7.92k | while (true) { | 39 | 7.92k | TRY(read_whitespace(context)); | 40 | | | 41 | 7.88k | auto const token = TRY(read_token(*context.stream)); | 42 | | | 43 | 7.78k | if (token == "ENDHDR") { | 44 | 664 | auto newline = TRY(context.stream->template read_value<u8>()); | 45 | 663 | if (newline != '\n') | 46 | 2 | return Error::from_string_view("PAM ENDHDR not followed by newline"sv); | 47 | 661 | break; | 48 | 663 | } | 49 | | | 50 | 14.2k | TRY(read_whitespace(context)); | 51 | 14.2k | if (token == "WIDTH") { | 52 | 784 | if (width.has_value()) | 53 | 1 | return Error::from_string_view("Duplicate PAM WIDTH field"sv); | 54 | 783 | width = TRY(read_number(*context.stream)); | 55 | 6.32k | } else if (token == "HEIGHT") { | 56 | 707 | if (height.has_value()) | 57 | 1 | return Error::from_string_view("Duplicate PAM HEIGHT field"sv); | 58 | 706 | height = TRY(read_number(*context.stream)); | 59 | 5.61k | } else if (token == "DEPTH") { | 60 | 813 | if (depth.has_value()) | 61 | 1 | return Error::from_string_view("Duplicate PAM DEPTH field"sv); | 62 | 812 | depth = TRY(read_number(*context.stream)); | 63 | 4.80k | } else if (token == "MAXVAL") { | 64 | 717 | if (max_val.has_value()) | 65 | 2 | return Error::from_string_view("Duplicate PAM MAXVAL field"sv); | 66 | 715 | max_val = TRY(read_number(*context.stream)); | 67 | 4.08k | } else if (token == "TUPLTYPE") { | 68 | | // FIXME: tupltype should be all text until the next newline, with leading and trailing space stripped. | 69 | | // FIXME: If there are multiple TUPLTYPE lines, their values are all appended. | 70 | 3.66k | tupltype = TRY(read_token(*context.stream)); | 71 | 3.66k | } else { | 72 | 418 | return Error::from_string_view("Unknown PAM token"sv); | 73 | 418 | } | 74 | 14.2k | } | 75 | | | 76 | 661 | if (!width.has_value() || !height.has_value() || !depth.has_value() || !max_val.has_value()) | 77 | 5 | return Error::from_string_view("Missing PAM header fields"sv); | 78 | 656 | context.width = *width; | 79 | 656 | context.height = *height; | 80 | 656 | context.format_details.depth = *depth; | 81 | 656 | context.format_details.max_val = *max_val; | 82 | 656 | if (tupltype.has_value()) | 83 | 630 | context.format_details.tupl_type = *tupltype; | 84 | | | 85 | 656 | context.state = Context::State::HeaderDecoded; | 86 | | | 87 | 656 | return {}; | 88 | 661 | } |
|
89 | | |
90 | | using PAMImageDecoderPlugin = PortableImageDecoderPlugin<PAMLoadingContext>; |
91 | | |
92 | | ErrorOr<void> read_image_data(PAMLoadingContext& context); |
93 | | } |