/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.47k | { |
29 | | // https://netpbm.sourceforge.net/doc/pam.html |
30 | 1.47k | TRY(read_magic_number(context)); |
31 | | |
32 | 1.44k | Optional<u16> width; |
33 | 1.44k | Optional<u16> height; |
34 | 1.44k | Optional<u16> depth; |
35 | 1.44k | Optional<u16> max_val; |
36 | 1.44k | Optional<String> tupltype; |
37 | | |
38 | 8.09k | while (true) { |
39 | 8.09k | TRY(read_whitespace(context)); |
40 | | |
41 | 8.04k | auto const token = TRY(read_token(*context.stream)); |
42 | | |
43 | 7.92k | if (token == "ENDHDR") { |
44 | 714 | auto newline = TRY(context.stream->template read_value<u8>()); |
45 | 713 | if (newline != '\n') |
46 | 2 | return Error::from_string_view("PAM ENDHDR not followed by newline"sv); |
47 | 711 | break; |
48 | 713 | } |
49 | | |
50 | 14.4k | TRY(read_whitespace(context)); |
51 | 14.4k | if (token == "WIDTH") { |
52 | 834 | if (width.has_value()) |
53 | 1 | return Error::from_string_view("Duplicate PAM WIDTH field"sv); |
54 | 833 | width = TRY(read_number(*context.stream)); |
55 | 6.36k | } else if (token == "HEIGHT") { |
56 | 759 | if (height.has_value()) |
57 | 1 | return Error::from_string_view("Duplicate PAM HEIGHT field"sv); |
58 | 758 | height = TRY(read_number(*context.stream)); |
59 | 5.60k | } else if (token == "DEPTH") { |
60 | 868 | if (depth.has_value()) |
61 | 1 | return Error::from_string_view("Duplicate PAM DEPTH field"sv); |
62 | 867 | depth = TRY(read_number(*context.stream)); |
63 | 4.73k | } else if (token == "MAXVAL") { |
64 | 767 | if (max_val.has_value()) |
65 | 2 | return Error::from_string_view("Duplicate PAM MAXVAL field"sv); |
66 | 765 | max_val = TRY(read_number(*context.stream)); |
67 | 3.96k | } 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.55k | tupltype = TRY(read_token(*context.stream)); |
71 | 3.54k | } else { |
72 | 419 | return Error::from_string_view("Unknown PAM token"sv); |
73 | 419 | } |
74 | 14.4k | } |
75 | | |
76 | 711 | 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 | 706 | context.width = *width; |
79 | 706 | context.height = *height; |
80 | 706 | context.format_details.depth = *depth; |
81 | 706 | context.format_details.max_val = *max_val; |
82 | 706 | if (tupltype.has_value()) |
83 | 678 | context.format_details.tupl_type = *tupltype; |
84 | | |
85 | 706 | context.state = Context::State::HeaderDecoded; |
86 | | |
87 | 706 | return {}; |
88 | 711 | } |
89 | | |
90 | | using PAMImageDecoderPlugin = PortableImageDecoderPlugin<PAMLoadingContext>; |
91 | | |
92 | | ErrorOr<void> read_image_data(PAMLoadingContext& context); |
93 | | } |