PAMLoader.h 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. /*
  2. * Copyright (c) 2024, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #pragma once
  7. #include <AK/StringView.h>
  8. #include <LibGfx/ImageFormats/ImageDecoder.h>
  9. #include <LibGfx/ImageFormats/PortableImageMapLoader.h>
  10. namespace Gfx {
  11. struct PAM {
  12. static constexpr auto binary_magic_number = '7';
  13. static constexpr StringView image_type = "PAM"sv;
  14. u16 max_val { 0 };
  15. u16 depth { 0 };
  16. String tupl_type {};
  17. Optional<NonnullRefPtr<CMYKBitmap>> cmyk_bitmap {};
  18. };
  19. using PAMLoadingContext = PortableImageMapLoadingContext<PAM>;
  20. template<class Context>
  21. ErrorOr<void> read_pam_header(Context& context)
  22. {
  23. // https://netpbm.sourceforge.net/doc/pam.html
  24. TRY(read_magic_number(context));
  25. Optional<u16> width;
  26. Optional<u16> height;
  27. Optional<u16> depth;
  28. Optional<u16> max_val;
  29. Optional<String> tupltype;
  30. while (true) {
  31. TRY(read_whitespace(context));
  32. auto const token = TRY(read_token(*context.stream));
  33. if (token == "ENDHDR") {
  34. auto newline = TRY(context.stream->template read_value<u8>());
  35. if (newline != '\n')
  36. return Error::from_string_view("PAM ENDHDR not followed by newline"sv);
  37. break;
  38. }
  39. TRY(read_whitespace(context));
  40. if (token == "WIDTH") {
  41. if (width.has_value())
  42. return Error::from_string_view("Duplicate PAM WIDTH field"sv);
  43. width = TRY(read_number(*context.stream));
  44. } else if (token == "HEIGHT") {
  45. if (height.has_value())
  46. return Error::from_string_view("Duplicate PAM HEIGHT field"sv);
  47. height = TRY(read_number(*context.stream));
  48. } else if (token == "DEPTH") {
  49. if (depth.has_value())
  50. return Error::from_string_view("Duplicate PAM DEPTH field"sv);
  51. depth = TRY(read_number(*context.stream));
  52. } else if (token == "MAXVAL") {
  53. if (max_val.has_value())
  54. return Error::from_string_view("Duplicate PAM MAXVAL field"sv);
  55. max_val = TRY(read_number(*context.stream));
  56. } else if (token == "TUPLTYPE") {
  57. // FIXME: tupltype should be all text until the next newline, with leading and trailing space stripped.
  58. // FIXME: If there are multipe TUPLTYPE lines, their values are all appended.
  59. tupltype = TRY(read_token(*context.stream));
  60. } else {
  61. return Error::from_string_view("Unknown PAM token"sv);
  62. }
  63. }
  64. if (!width.has_value() || !height.has_value() || !depth.has_value() || !max_val.has_value())
  65. return Error::from_string_view("Missing PAM header fields"sv);
  66. context.width = *width;
  67. context.height = *height;
  68. context.format_details.depth = *depth;
  69. context.format_details.max_val = *max_val;
  70. if (tupltype.has_value())
  71. context.format_details.tupl_type = *tupltype;
  72. context.state = Context::State::HeaderDecoded;
  73. return {};
  74. }
  75. using PAMImageDecoderPlugin = PortableImageDecoderPlugin<PAMLoadingContext>;
  76. ErrorOr<void> read_image_data(PAMLoadingContext& context);
  77. }