TIFFGenerator.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
  3. #
  4. # SPDX-License-Identifier: BSD-2-Clause
  5. import argparse
  6. import re
  7. from enum import Enum
  8. from collections import namedtuple
  9. from pathlib import Path
  10. from typing import List, Optional, Type
  11. class TIFFType(Enum):
  12. Byte = 1
  13. ASCII = 2
  14. UnsignedShort = 3
  15. UnsignedLong = 4
  16. UnsignedRational = 5
  17. Undefined = 7
  18. SignedLong = 9
  19. SignedRational = 10
  20. Float = 11
  21. Double = 12
  22. UTF8 = 129
  23. class Predictor(Enum):
  24. NoPrediction = 1
  25. HorizontalDifferencing = 2
  26. class Compression(Enum):
  27. NoCompression = 1
  28. CCITT = 2
  29. Group3Fax = 3
  30. Group4Fax = 4
  31. LZW = 5
  32. JPEG = 6
  33. PackBits = 32773
  34. tag_fields = ['id', 'types', 'counts', 'default', 'name', 'associated_enum']
  35. Tag = namedtuple(
  36. 'Tag',
  37. field_names=tag_fields,
  38. defaults=(None,) * len(tag_fields)
  39. )
  40. # FIXME: Some tag have only a few allowed values, we should ensure that
  41. known_tags: List[Tag] = [
  42. Tag('256', [TIFFType.UnsignedShort, TIFFType.UnsignedLong], [1], None, "ImageWidth"),
  43. Tag('257', [TIFFType.UnsignedShort, TIFFType.UnsignedLong], [1], None, "ImageHeight"),
  44. Tag('258', [TIFFType.UnsignedShort], [3], None, "BitsPerSample"),
  45. Tag('259', [TIFFType.UnsignedShort], [1], None, "Compression", Compression),
  46. Tag('273', [TIFFType.UnsignedShort, TIFFType.UnsignedLong], [], None, "StripOffsets"),
  47. Tag('278', [TIFFType.UnsignedShort, TIFFType.UnsignedLong], [1], None, "RowsPerStrip"),
  48. Tag('279', [TIFFType.UnsignedShort, TIFFType.UnsignedLong], [], None, "StripByteCounts"),
  49. Tag('317', [TIFFType.UnsignedShort], [1], Predictor.NoPrediction, "Predictor", Predictor),
  50. ]
  51. HANDLE_TAG_SIGNATURE_TEMPLATE = ("ErrorOr<void> {namespace}handle_tag(Metadata& metadata, u16 tag,"
  52. " {namespace}Type type, u32 count, Vector<{namespace}Value>&& value)")
  53. HANDLE_TAG_SIGNATURE = HANDLE_TAG_SIGNATURE_TEMPLATE.format(namespace="")
  54. HANDLE_TAG_SIGNATURE_TIFF_NAMESPACE = HANDLE_TAG_SIGNATURE_TEMPLATE.format(namespace="TIFF::")
  55. LICENSE = R"""/*
  56. * Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
  57. *
  58. * SPDX-License-Identifier: BSD-2-Clause
  59. */"""
  60. def export_enum_to_cpp(e: Type[Enum], special_name: Optional[str] = None) -> str:
  61. output = f'enum class {special_name if special_name else e.__name__} {{\n'
  62. for entry in e:
  63. output += f' {entry.name} = {entry.value},\n'
  64. output += "};\n"
  65. return output
  66. def export_tag_related_enums(tags: List[Tag]) -> str:
  67. exported_enums = []
  68. for tag in tags:
  69. if tag.associated_enum:
  70. exported_enums.append(export_enum_to_cpp(tag.associated_enum))
  71. return '\n'.join(exported_enums)
  72. def promote_type(t: TIFFType) -> TIFFType:
  73. if t == TIFFType.UnsignedShort:
  74. return TIFFType.UnsignedLong
  75. return t
  76. def tiff_type_to_cpp(t: TIFFType, without_promotion: bool = False) -> str:
  77. # To simplify the code generator and the Metadata class API, all u16 are promoted to u32
  78. # Note that the Value<> type doesn't include u16 for this reason
  79. if not without_promotion:
  80. t = promote_type(t)
  81. if t == TIFFType.Undefined:
  82. return 'ByteBuffer'
  83. if t == TIFFType.UnsignedShort:
  84. return 'u16'
  85. if t == TIFFType.UnsignedLong:
  86. return 'u32'
  87. raise RuntimeError(f'Type "{t}" not recognized, please update tiff_type_to_read_only_cpp()')
  88. def is_container(t: TIFFType) -> bool:
  89. """
  90. Some TIFF types are defined on the unit scale but are intended to be used within a collection.
  91. An example of that are ASCII strings defined as N * byte. Let's intercept that and generate
  92. a nice API instead of Vector<u8>.
  93. """
  94. return t in [TIFFType.Byte, TIFFType.Undefined]
  95. def export_promoter() -> str:
  96. output = R"""template<typename T>
  97. struct TypePromoter {
  98. using Type = T;
  99. };
  100. """
  101. specialization_template = R"""template<>
  102. struct TypePromoter<{}> {{
  103. using Type = {};
  104. }};
  105. """
  106. for t in TIFFType:
  107. if promote_type(t) != t:
  108. output += specialization_template.format(tiff_type_to_cpp(t, without_promotion=True), tiff_type_to_cpp(t))
  109. return output
  110. def retrieve_biggest_type(types: List[TIFFType]) -> TIFFType:
  111. return TIFFType(max([t.value for t in types]))
  112. def pascal_case_to_snake_case(name: str) -> str:
  113. name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
  114. return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower()
  115. def generate_getter(tag: Tag) -> str:
  116. biggest_type = retrieve_biggest_type(tag.types)
  117. variant_inner_type = tiff_type_to_cpp(biggest_type)
  118. extracted_value_template = f"(*possible_value)[{{}}].get<{variant_inner_type}>()"
  119. tag_final_type = variant_inner_type
  120. if tag.associated_enum:
  121. tag_final_type = f"TIFF::{tag.associated_enum.__name__}"
  122. extracted_value_template = f"static_cast<{tag_final_type}>({extracted_value_template})"
  123. if len(tag.counts) == 1 and tag.counts[0] == 1 or is_container(biggest_type):
  124. return_type = tag_final_type
  125. if is_container(biggest_type):
  126. return_type += ' const&'
  127. unpacked_if_needed = f"return {extracted_value_template.format(0)};"
  128. else:
  129. if len(tag.counts) == 1:
  130. container_type = f'Array<{tag_final_type}, {tag.counts[0]}>'
  131. container_initialization = f'{container_type} tmp{{}};'
  132. else:
  133. container_type = f'Vector<{tag_final_type}>'
  134. container_initialization = fR"""{container_type} tmp{{}};
  135. auto maybe_failure = tmp.try_resize(possible_value->size());
  136. if (maybe_failure.is_error())
  137. return OptionalNone {{}};
  138. """
  139. return_type = container_type
  140. unpacked_if_needed = fR"""
  141. {container_initialization}
  142. for (u32 i = 0; i < possible_value->size(); ++i)
  143. tmp[i] = {extracted_value_template.format('i')};
  144. return tmp;"""
  145. signature = fR" Optional<{return_type}> {pascal_case_to_snake_case(tag.name)}() const"
  146. body = fR"""
  147. {{
  148. auto const& possible_value = m_data.get("{tag.name}"sv);
  149. if (!possible_value.has_value())
  150. return OptionalNone {{}};
  151. {unpacked_if_needed}
  152. }}
  153. """
  154. return signature + body
  155. def generate_metadata_class(tags: List[Tag]) -> str:
  156. getters = '\n'.join([generate_getter(tag) for tag in tags])
  157. output = fR"""class Metadata {{
  158. public:
  159. {getters}
  160. private:
  161. friend {HANDLE_TAG_SIGNATURE_TIFF_NAMESPACE};
  162. void add_entry(StringView key, Vector<TIFF::Value>&& value) {{
  163. m_data.set(key, move(value));
  164. }}
  165. HashMap<StringView, Vector<TIFF::Value>> m_data;
  166. }};
  167. """
  168. return output
  169. def generate_metadata_file(tags: List[Tag]) -> str:
  170. output = fR"""{LICENSE}
  171. #pragma once
  172. #include <AK/HashMap.h>
  173. #include <AK/Variant.h>
  174. #include <AK/Vector.h>
  175. #include <LibGfx/Size.h>
  176. namespace Gfx {{
  177. class Metadata;
  178. namespace TIFF {{
  179. {export_enum_to_cpp(TIFFType, 'Type')}
  180. template<OneOf<u32, i32> x32>
  181. struct Rational {{
  182. using Type = x32;
  183. x32 numerator;
  184. x32 denominator;
  185. }};
  186. {export_promoter()}
  187. // Note that u16 is not include on purpose
  188. using Value = Variant<ByteBuffer, String, u32, Rational<u32>, i32, Rational<i32>>;
  189. {export_tag_related_enums(known_tags)}
  190. {HANDLE_TAG_SIGNATURE};
  191. }}
  192. """
  193. output += generate_metadata_class(tags)
  194. output += '\n}\n'
  195. return output
  196. def generate_tag_handler(tag: Tag) -> str:
  197. not_in_type_list = f"({' && '.join([f'type != Type::{t.name}' for t in tag.types])})"
  198. not_in_count_list = ''
  199. if len(tag.counts) != 0:
  200. not_in_count_list = f"|| ({' && '.join([f'count != {c}' for c in tag.counts])})"
  201. pre_condition = fR"""if ({not_in_type_list}
  202. {not_in_count_list})
  203. return Error::from_string_literal("TIFFImageDecoderPlugin: Tag {tag.name} invalid");"""
  204. check_value = ''
  205. if tag.associated_enum is not None:
  206. not_in_value_list = f"({' && '.join([f'v != {v.value}' for v in tag.associated_enum])})"
  207. check_value = fR"""TRY(value[0].visit(
  208. []({tiff_type_to_cpp(tag.types[0])} const& v) -> ErrorOr<void> {{
  209. if ({not_in_value_list})
  210. return Error::from_string_literal("TIFFImageDecoderPlugin: Invalid value for tag {tag.name}");
  211. return {{}};
  212. }},
  213. [&](auto const&) -> ErrorOr<void> {{
  214. VERIFY_NOT_REACHED();
  215. }}));
  216. """
  217. output = fR""" case {tag.id}:
  218. // {tag.name}
  219. {pre_condition}
  220. {check_value}
  221. metadata.add_entry("{tag.name}"sv, move(value));
  222. break;
  223. """
  224. return output
  225. def generate_tag_handler_file(tags: List[Tag]) -> str:
  226. output = fR"""{LICENSE}
  227. #include <AK/Debug.h>
  228. #include <AK/String.h>
  229. #include <LibGfx/ImageFormats/TIFFMetadata.h>
  230. namespace Gfx::TIFF {{
  231. {HANDLE_TAG_SIGNATURE}
  232. {{
  233. switch (tag) {{
  234. """
  235. output += '\n'.join([generate_tag_handler(t) for t in tags])
  236. output += R"""
  237. default:
  238. dbgln_if(TIFF_DEBUG, "Unknown tag: {}", tag);
  239. }
  240. return {};
  241. }
  242. }
  243. """
  244. return output
  245. def update_file(target: Path, new_content: str):
  246. should_update = True
  247. if target.exists():
  248. with target.open('r') as file:
  249. content = file.read()
  250. if content == new_content:
  251. should_update = False
  252. if should_update:
  253. with target.open('w') as file:
  254. file.write(new_content)
  255. def main():
  256. parser = argparse.ArgumentParser()
  257. parser.add_argument('-o', '--output')
  258. args = parser.parse_args()
  259. output_path = Path(args.output)
  260. update_file(output_path / 'TIFFMetadata.h', generate_metadata_file(known_tags))
  261. update_file(output_path / 'TIFFTagHandler.cpp', generate_tag_handler_file(known_tags))
  262. if __name__ == '__main__':
  263. main()