--- /dev/null
+root = true
+
+# All files
+[*]
+indent_style = space
+
+# Xml files
+[*.xml]
+indent_size = 2
+
+# C# files
+[*.cs]
+
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
+indent_size = 4
+tab_width = 4
+
+# New line preferences
+insert_final_newline = false
+
+#### .NET Coding Conventions ####
+[*.{cs,vb}]
+
+# Organize usings
+dotnet_separate_import_directive_groups = true
+dotnet_sort_system_directives_first = true
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false:silent
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_property = false:silent
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+
+# Field preferences
+dotnet_style_readonly_field = true:warning
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all:suggestion
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+#### C# Coding Conventions ####
+[*.cs]
+
+# var preferences
+csharp_style_var_elsewhere = false:silent
+csharp_style_var_for_built_in_types = false:silent
+csharp_style_var_when_type_is_apparent = false:silent
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_lambdas = true:suggestion
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_prefer_extended_property_pattern = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
+csharp_style_prefer_pattern_matching = true:silent
+csharp_style_prefer_switch_expression = true:suggestion
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Modifier preferences
+csharp_prefer_static_anonymous_function = true:suggestion
+csharp_prefer_static_local_function = true:warning
+csharp_preferred_modifier_order = public,private,protected,internal,file,const,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion
+csharp_style_prefer_readonly_struct = true:suggestion
+csharp_style_prefer_readonly_struct_member = true:suggestion
+
+# Code-block preferences
+csharp_prefer_braces = true:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_style_namespace_declarations = file_scoped:suggestion
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_prefer_primary_constructors = true:suggestion
+csharp_style_prefer_top_level_statements = true:silent
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_null_check_over_type_check = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_prefer_tuple_swap = true:suggestion
+csharp_style_prefer_utf8_string_literals = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:silent
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = false
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+[*.{cs,vb}]
+
+# Naming rules
+
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
+dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
+dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
+
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
+
+dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
+dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
+dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.events_should_be_pascalcase.symbols = events
+dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
+dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
+dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
+dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
+dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
+dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
+dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
+
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
+
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
+dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
+dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
+
+# Symbol specifications
+
+dotnet_naming_symbols.interfaces.applicable_kinds = interface
+dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interfaces.required_modifiers =
+
+dotnet_naming_symbols.enums.applicable_kinds = enum
+dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.enums.required_modifiers =
+
+dotnet_naming_symbols.events.applicable_kinds = event
+dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.events.required_modifiers =
+
+dotnet_naming_symbols.methods.applicable_kinds = method
+dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.methods.required_modifiers =
+
+dotnet_naming_symbols.properties.applicable_kinds = property
+dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.properties.required_modifiers =
+
+dotnet_naming_symbols.public_fields.applicable_kinds = field
+dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_fields.required_modifiers =
+
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_fields.required_modifiers =
+
+dotnet_naming_symbols.private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_fields.required_modifiers = static
+
+dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
+dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types_and_namespaces.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
+dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
+dotnet_naming_symbols.type_parameters.required_modifiers =
+
+dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.local_variables.applicable_kinds = local
+dotnet_naming_symbols.local_variables.applicable_accessibilities = local
+dotnet_naming_symbols.local_variables.required_modifiers =
+
+dotnet_naming_symbols.local_constants.applicable_kinds = local
+dotnet_naming_symbols.local_constants.applicable_accessibilities = local
+dotnet_naming_symbols.local_constants.required_modifiers = const
+
+dotnet_naming_symbols.parameters.applicable_kinds = parameter
+dotnet_naming_symbols.parameters.applicable_accessibilities = *
+dotnet_naming_symbols.parameters.required_modifiers =
+
+dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.local_functions.applicable_kinds = local_function
+dotnet_naming_symbols.local_functions.applicable_accessibilities = *
+dotnet_naming_symbols.local_functions.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.pascalcase.required_prefix =
+dotnet_naming_style.pascalcase.required_suffix =
+dotnet_naming_style.pascalcase.word_separator =
+dotnet_naming_style.pascalcase.capitalization = pascal_case
+
+dotnet_naming_style.ipascalcase.required_prefix = I
+dotnet_naming_style.ipascalcase.required_suffix =
+dotnet_naming_style.ipascalcase.word_separator =
+dotnet_naming_style.ipascalcase.capitalization = pascal_case
+
+dotnet_naming_style.tpascalcase.required_prefix = T
+dotnet_naming_style.tpascalcase.required_suffix =
+dotnet_naming_style.tpascalcase.word_separator =
+dotnet_naming_style.tpascalcase.capitalization = pascal_case
+
+dotnet_naming_style._camelcase.required_prefix = _
+dotnet_naming_style._camelcase.required_suffix =
+dotnet_naming_style._camelcase.word_separator =
+dotnet_naming_style._camelcase.capitalization = camel_case
+
+dotnet_naming_style.camelcase.required_prefix =
+dotnet_naming_style.camelcase.required_suffix =
+dotnet_naming_style.camelcase.word_separator =
+dotnet_naming_style.camelcase.capitalization = camel_case
+
+dotnet_naming_style.s_camelcase.required_prefix = s_
+dotnet_naming_style.s_camelcase.required_suffix =
+dotnet_naming_style.s_camelcase.word_separator =
+dotnet_naming_style.s_camelcase.capitalization = camel_case
+
--- /dev/null
+bin/
+obj/
+.vs/
+.vscode
+
--- /dev/null
+using Xunit.Abstractions;
+using Shouldly;
+
+namespace JpegUtil.Test;
+
+public class JpegReaderTests
+{
+ const string testPath = "Data/a.jpg";
+
+ private ITestOutputHelper _outputhelper { get; init; }
+ public JpegReaderTests(ITestOutputHelper testOutputHelper)
+ {
+ _outputhelper = testOutputHelper;
+ }
+
+ [Fact]
+ public void Test1()
+ {
+ var path = Path.IsPathRooted(testPath)
+ ? testPath
+ : Path.GetRelativePath(Directory.GetCurrentDirectory(), "../../../" + testPath);
+ using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
+ using var reader = new JpegReader(stream, _outputhelper.WriteLine);
+ var data = reader.Exif().ToList();
+ data.Count.ShouldBe(12);
+ // data.Select(x => x. ShouldContain()
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net9.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ <IsPackable>false</IsPackable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="coverlet.collector" Version="6.0.2" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
+ <PackageReference Include="xunit" Version="2.9.3" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5" />
+ <PackageReference Include="Shouldly" Version="4.3.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="../JpegUtil/JpegUtil.csproj" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Using Include="Xunit" />
+ </ItemGroup>
+
+</Project>
--- /dev/null
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JpegUtil", "JpegUtil\JpegUtil.csproj", "{6380C354-3DC0-4BEF-82D4-69DAE207A908}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JpegUtil.Test", "JpegUtil.Test\JpegUtil.Test.csproj", "{2CF6525D-DBFB-4B8D-877D-17CAE798D9CA}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {6380C354-3DC0-4BEF-82D4-69DAE207A908}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6380C354-3DC0-4BEF-82D4-69DAE207A908}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6380C354-3DC0-4BEF-82D4-69DAE207A908}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6380C354-3DC0-4BEF-82D4-69DAE207A908}.Debug|x64.Build.0 = Debug|Any CPU
+ {6380C354-3DC0-4BEF-82D4-69DAE207A908}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6380C354-3DC0-4BEF-82D4-69DAE207A908}.Debug|x86.Build.0 = Debug|Any CPU
+ {6380C354-3DC0-4BEF-82D4-69DAE207A908}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6380C354-3DC0-4BEF-82D4-69DAE207A908}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6380C354-3DC0-4BEF-82D4-69DAE207A908}.Release|x64.ActiveCfg = Release|Any CPU
+ {6380C354-3DC0-4BEF-82D4-69DAE207A908}.Release|x64.Build.0 = Release|Any CPU
+ {6380C354-3DC0-4BEF-82D4-69DAE207A908}.Release|x86.ActiveCfg = Release|Any CPU
+ {6380C354-3DC0-4BEF-82D4-69DAE207A908}.Release|x86.Build.0 = Release|Any CPU
+ {2CF6525D-DBFB-4B8D-877D-17CAE798D9CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2CF6525D-DBFB-4B8D-877D-17CAE798D9CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2CF6525D-DBFB-4B8D-877D-17CAE798D9CA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2CF6525D-DBFB-4B8D-877D-17CAE798D9CA}.Debug|x64.Build.0 = Debug|Any CPU
+ {2CF6525D-DBFB-4B8D-877D-17CAE798D9CA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {2CF6525D-DBFB-4B8D-877D-17CAE798D9CA}.Debug|x86.Build.0 = Debug|Any CPU
+ {2CF6525D-DBFB-4B8D-877D-17CAE798D9CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2CF6525D-DBFB-4B8D-877D-17CAE798D9CA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2CF6525D-DBFB-4B8D-877D-17CAE798D9CA}.Release|x64.ActiveCfg = Release|Any CPU
+ {2CF6525D-DBFB-4B8D-877D-17CAE798D9CA}.Release|x64.Build.0 = Release|Any CPU
+ {2CF6525D-DBFB-4B8D-877D-17CAE798D9CA}.Release|x86.ActiveCfg = Release|Any CPU
+ {2CF6525D-DBFB-4B8D-877D-17CAE798D9CA}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
--- /dev/null
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+
+using ExifTag = ushort;
+
+namespace JpegUtil;
+
+public enum ExifType
+{
+ Byte = 1, // uint8_t
+ Ascii = 2, // ASCII NUL terminated
+ Short = 3, // uint16_t
+ Long = 4, // uint32_t
+ Rational = 5, // 2 LONGs numerator : denominator
+ SByte = 6, // int8_t
+ Undefined = 7, // uint8_t
+ SShort = 8, // int16_t
+ SLong = 9, // int32_t
+ SRational = 10, // 2 SLONGs numerator : denominator
+ Float32 = 11, // IEEE 32 bit floating point
+ Float64 = 12 // IEEE 64 bit floating point
+}
+
+public struct ExifItem
+{
+ public ExifTag Tag { get; private set; }
+ public ExifType Type { get; private set; }
+ public object Value { get; private set; }
+
+ const ExifTag EXIF_IFD_TAG = 0x8769; // special tag that is a pointer to more tags
+ const ushort EXIF_IFD_SIZE = 12; // size in bytes of an IFD entry
+ const ushort EXIF_USHORT_SIZE = 2;
+ const ushort EXIF_BOM_BE = 0x4d4d;
+ const ushort EXIF_BOM_LE = 0x4949;
+ const ushort EXIF_HDR_MAGIC = 0x2a;
+
+ private static bool IsBigEndian = false;
+
+ private static ushort get_uint16(byte[] data, int startIndex)
+ {
+ return IsBigEndian
+ ? (ushort)((data[startIndex] << 8) | data[startIndex + 1])
+ : BitConverter.ToUInt16(data, startIndex);
+ }
+
+ private static uint get_uint32(byte[] data, int startIndex)
+ {
+ return IsBigEndian
+ ? (uint)(
+ (data[startIndex + 0] << 24)
+ | (data[startIndex + 1] << 16)
+ | (data[startIndex + 2] << 8)
+ | (data[startIndex + 3]))
+ : BitConverter.ToUInt32(data, startIndex);
+ }
+ private static int get_int32(byte[] data, int startIndex)
+ {
+ return IsBigEndian
+ ? (int)(
+ (data[startIndex + 0] << 24)
+ | (data[startIndex + 1] << 16)
+ | (data[startIndex + 2] << 8)
+ | (data[startIndex + 3]))
+ : BitConverter.ToInt32(data, startIndex);
+ }
+
+ public static IEnumerable<ExifItem> ExifParseItem(byte[] data, int off)
+ {
+ // uint item_count = 0;
+ ushort tag = get_uint16(data, off);
+ ExifType type = (ExifType)get_uint16(data, off + 2);
+ int count = (int)get_uint32(data, off + 4);
+ int dataOffset = (int)get_uint32(data, off + 8);
+ int hdrEnd = off + 8;
+
+ object? value = null;
+ switch (type)
+ {
+ case ExifType.Byte:
+ case ExifType.Undefined:
+ if (count < 5)
+ dataOffset = hdrEnd;
+ value = new ArraySegment<byte>(data, dataOffset, count);
+ break;
+ case ExifType.Ascii:
+ {
+ if (count < 5)
+ dataOffset = hdrEnd;
+ value = Encoding.ASCII.GetString(data, dataOffset, count);
+ break;
+ }
+ case ExifType.Short:
+ {
+ var lst = new List<ushort>();
+ if (count < 3) // handle small values being stored in the offset area
+ dataOffset = hdrEnd;
+ for (int n = 0; n < count; ++n)
+ {
+ lst.Add(get_uint16(data, dataOffset + (n * 2)));
+ dataOffset += 2;
+ }
+ value = count == 1 ? lst[0] : lst;
+ break;
+ }
+ case ExifType.Long:
+ {
+ if (tag == EXIF_IFD_TAG)
+ {
+ ushort ifd_count = get_uint16(data, dataOffset);
+ dataOffset += 2;
+ for (int n = 0; n < ifd_count; ++n)
+ {
+ foreach (var item in ExifParseItem(data, dataOffset + (n * EXIF_IFD_SIZE)))
+ {
+ yield return item;
+ }
+ }
+ }
+ else
+ {
+ var lst = new List<uint>();
+ if (count == 1)
+ dataOffset = hdrEnd;
+ for (int n = 0; n < count; ++n)
+ {
+ lst.Add(get_uint32(data, dataOffset + (n * 4)));
+ dataOffset += 4;
+ }
+ value = count == 1 ? lst[0] : lst;
+ }
+ break;
+ }
+ case ExifType.Rational:
+ {
+ var lst = new List<Tuple<uint, uint>>();
+ for (int n = 0; n < count; ++n)
+ {
+ lst.Add(new Tuple<uint, uint>(
+ get_uint32(data, dataOffset),
+ get_uint32(data, dataOffset + 4)
+ ));
+ dataOffset += 8;
+ }
+ value = count == 1 ? lst[0] : lst;
+ break;
+ }
+ case ExifType.SLong:
+ {
+ var lst = new List<int>();
+ if (count == 1)
+ dataOffset = hdrEnd;
+ for (int n = 0; n < count; ++n)
+ {
+ lst.Add(get_int32(data, dataOffset + (n * 4)));
+ dataOffset += 4;
+ }
+ value = count == 1 ? lst[0] : lst;
+ break;
+ }
+ case ExifType.SRational:
+ {
+ var lst = new List<Tuple<int, int>>();
+ for (int n = 0; n < count; ++n)
+ {
+ lst.Add(new Tuple<int, int>(
+ get_int32(data, dataOffset),
+ get_int32(data, dataOffset + 4)
+ ));
+ dataOffset += 8;
+ }
+ value = count == 1 ? lst[0] : lst;
+ break;
+ }
+ default:
+ throw new Exception($"unhandled EXIF type {Enum.GetName(typeof(ExifType), type)}");
+ }
+
+ if (value != null)
+ {
+ yield return new ExifItem()
+ {
+ Tag = tag,
+ Type = type,
+ Value = value
+ };
+ }
+ }
+
+ public static IEnumerable<ExifItem> ExifParse(byte[] data)
+ {
+ List<ExifItem> items = new();
+ IsBigEndian = get_uint16(data, 0) == EXIF_BOM_BE;
+ ushort magic = get_uint16(data, 2);
+ if (magic != EXIF_HDR_MAGIC)
+ throw new Exception("invalid exif magic");
+ uint offset = get_uint32(data, 4);
+ while (offset != 0)
+ {
+ // read the number of EXIF items stored (big endian)
+ ushort count = get_uint16(data, (int)offset);
+
+ // process each EXIF tag from the buffer
+ int entry = (int)offset + 2;
+ for (ushort n = 0; n < count; ++n, entry += EXIF_IFD_SIZE)
+ {
+ foreach (var item in ExifParseItem(data, entry))
+ {
+ Console.WriteLine(item.Print());
+ yield return item;
+ }
+ }
+ // after the IFD table is an offset to the next IFD table (if any)
+ offset = get_uint16(data, (int)(offset + EXIF_USHORT_SIZE + (count * EXIF_IFD_SIZE)));
+ }
+ }
+}
+
+public static class ObjectExtensions
+{
+ public static bool IsGenericList(this object o)
+ {
+ var oType = o.GetType();
+ return (oType.IsGenericType && (oType.GetGenericTypeDefinition() == typeof(List<>)));
+ }
+}
+
+public static class ExifExtensions
+{
+ public static string Print(this ExifItem item)
+ {
+ StringBuilder str = new(
+ TagNames.ContainsKey(item.Tag) ? TagNames[item.Tag] : $"{item.Tag:X4}"
+ );
+ int count = 1;
+ if (item.Value.IsGenericList())
+ count = ((IList)item.Value).Count;
+ if (item.Type == ExifType.Ascii)
+ str.AppendFormat(" {0}", item.Value as string);
+ else if ((item.Type == ExifType.Undefined || item.Type == ExifType.Byte) && count > 8)
+ {
+ str.Append($" [{count} bytes]");
+ }
+ else if (item.Value is IList lst)
+ {
+ for (int n = 0; n < count; ++n)
+ {
+ str.AppendFormat(" {0}", lst[n]);
+ }
+ }
+ else
+ {
+ str.AppendFormat(" {0}", item.Value);
+ }
+ return str.ToString();
+ }
+
+ public static readonly Dictionary<ExifTag, string> TagNames = new()
+ {
+ { 0x0100, "ImageWidth" },
+ { 0x0101, "ImageHeight" },
+ { 0x0102, "BitsPerSample" },
+ { 0x0103, "Compression" },
+ { 0x0106, "PhotometricInterpretation" },
+ { 0x0107, "Thresholding" },
+ { 0x010e, "ImageDescription" },
+ { 0x010f, "Make" },
+ { 0x0112, "Orientation" },
+ { 0x011a, "XResolution" },
+ { 0x011b, "YResolution" },
+ { 0x011c, "PlanarConfiguration" },
+ { 0x011e, "XPosition" },
+ { 0x011f, "YPosition" },
+ { 0x0128, "ResolutionUnit" },
+ { 0x0131, "Software" },
+ { 0x0132, "ModifyDate" },
+ { 0xa001, "ColorSpace" },
+ { 0xa002, "ExifImageWidth" },
+ { 0xa003, "ExifImageHeight" },
+ { 0xa20e, "FocalPlaneXResolution" },
+ { 0xa20f, "FocalPlaneYResolution" },
+ { 0xa210, "FocalPlaneResolutionUnit" },
+ /* Renishaw WiRE Custom Tags */
+ { 0xfea0, "WiREPosition" },
+ { 0xfea1, "WiREFoV" },
+ { 0xfea2, "WiREObjective" },
+ { 0xfea3, "WiRELUTLimits" },
+ { 0xfea4, "WiRERotationAngle" },
+ { 0xfea5, "WiRERotationCenter" },
+ { 0xfea6, "WiREZPosition" }
+ };
+}
--- /dev/null
+namespace JpegUtil;
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+public enum JfifMark
+{
+ SOF0 = 0xffc0, // start of frame (baseline DCT)
+ DHT = 0xffc4, // define Huffman table
+ RST0 = 0xffd0,// restart (d0 .. d7)
+ SOI = 0xffd8, // start of image
+ EOI = 0xffd9, // end of image
+ SOS = 0xffda, // start of scan
+ DQT = 0xffdb, // define quantization table
+ APP0 = 0xffe0, // Application specific (e0..ef)
+ Comment = 0xfffe, // comment
+}
+
+public static class JfifMarkExtensions
+{
+ public static bool IsRST(this JfifMark mark)
+ {
+ int rst0 = (int)JfifMark.RST0;
+ int val = (int)mark;
+ return val >= rst0 && val < (rst0 + 8);
+ }
+ public static bool IsAPP(this JfifMark mark)
+ {
+ int app0 = (int)JfifMark.APP0;
+ int val = (int)mark;
+ return val >= app0 && val < (app0 + 0xf);
+ }
+ public static int AppN(this JfifMark mark)
+ {
+ return ((int)mark) % 16;
+ }
+ public static int RstN(this JfifMark mark)
+ {
+ return ((int)mark) % 16;
+ }
+}
+
+public delegate void Logger(string format, params object[] args);
+
+public class JpegReader : IDisposable
+{
+ private readonly BinaryReader _reader;
+ private Logger Log { get; set; }
+
+ public JpegReader(Stream stream, Logger logger)
+ {
+ _reader = new BinaryReader(stream);
+ Log = logger;
+ }
+
+ #region Disposable
+ private bool _disposed = false;
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ _reader.Dispose();
+ }
+ _disposed = true;
+ }
+ }
+ #endregion Disposable
+
+ // Read a big endian 16 bit value
+ private int ReadInt16BE()
+ {
+ var data = new byte[2];
+ _reader.Read(data, 0, 2);
+ // .net 8+: return (int)BinaryPrimitives.ReadUInt16BigEndian(data);
+ return ((data[0] << 8) | data[1]);
+ }
+ // Read a big endian 16 bit value minus the 2 bytes for this value
+ private int ReadSize() => ReadInt16BE() - 2;
+
+ private IEnumerable<ExifItem>? ReadApp0(int len)
+ {
+ IEnumerable<ExifItem>? result = null;
+ byte[] label = _reader.ReadBytes(5);
+ if (label[0] == 'J' && label[1] == 'F' && label[2] == 'I' && label[3] == 'F')
+ {
+ // read the JFIF header
+ byte[] data = _reader.ReadBytes(len - 5);
+ Log("JFIF header");
+ }
+ else if (label[0] == 'E' && label[1] == 'x' && label[2] == 'i' && label[3] == 'f')
+ {
+ Log("Exif block");
+ // read the padding byte
+ byte pad = _reader.ReadByte();
+ // read all the exif data into a buffer
+ byte[] data = _reader.ReadBytes(len - 6);
+ // parse it : Exif(data) or span
+ result = ExifItem.ExifParse(data);
+ }
+ else
+ {
+ _reader.BaseStream.Seek(-5, SeekOrigin.Current);
+ }
+ return result;
+ }
+
+ public IEnumerable<ExifItem> Exif()
+ {
+ int len = 0;
+ JfifMark mark = 0;
+ while (_reader.BaseStream.CanRead)
+ {
+ mark = (JfifMark)ReadInt16BE();
+ // Log("{0:X8} Mark {1:X4}", _reader.BaseStream.Position, (int)mark);
+ switch (mark)
+ {
+ case JfifMark.SOI:
+ len = 0;
+ Log("{0:X8} SOI", _reader.BaseStream.Position);
+ break;
+ case JfifMark.DQT:
+ len = ReadSize();
+ Log("{0:X8} DQT {1:X4}", _reader.BaseStream.Position, len);
+ break;
+ case JfifMark.DHT:
+ len = ReadSize();
+ Log("{0:X8} DHT {1:X4}", _reader.BaseStream.Position, len);
+ break;
+ case JfifMark.SOS:
+ len = ReadSize();
+ Log("{0:X8} SOS {1:X4}", _reader.BaseStream.Position, len);
+ yield break;
+ case JfifMark.SOF0:
+ {
+ len = ReadSize();
+ Log("{0:X8} SOF0 {1:X4}", _reader.BaseStream.Position, len);
+ // read the frame, set len 0
+ break;
+ }
+ default:
+ {
+ if (mark.IsRST())
+ {
+ len = ReadSize();
+ Log("{0:X8} RST{1} {2:X4}",
+ _reader.BaseStream.Position, mark.RstN(), len);
+ }
+ else if (mark.IsAPP())
+ {
+ len = ReadSize();
+ Log("{0:X8} APP{1} {2:X4}",
+ _reader.BaseStream.Position, mark.AppN(), len);
+ var items = ReadApp0(len);
+ if (items != null)
+ {
+ foreach (ExifItem item in items)
+ yield return item;
+ }
+ len = 0;
+ }
+ else
+ {
+ Log($"{0:X8} OOPS {(int)mark:X4}", _reader.BaseStream.Position);
+ throw new Exception($"oops {(int)mark:X4}");
+ }
+ break;
+ }
+ }
+ _reader.BaseStream.Seek(len, SeekOrigin.Current);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+ <LangVersion>12.0</LangVersion>
+ <ImplicitUsings>false</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+</Project>