png_conform 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +116 -6
  3. data/Gemfile +1 -1
  4. data/config/validation_profiles.yml +105 -0
  5. data/lib/png_conform/analyzers/comparison_analyzer.rb +41 -7
  6. data/lib/png_conform/analyzers/metrics_analyzer.rb +6 -9
  7. data/lib/png_conform/analyzers/optimization_analyzer.rb +30 -24
  8. data/lib/png_conform/analyzers/resolution_analyzer.rb +31 -32
  9. data/lib/png_conform/cli.rb +12 -0
  10. data/lib/png_conform/commands/check_command.rb +118 -53
  11. data/lib/png_conform/configuration.rb +147 -0
  12. data/lib/png_conform/container.rb +113 -0
  13. data/lib/png_conform/models/validation_result.rb +30 -4
  14. data/lib/png_conform/pipelines/pipeline_result.rb +39 -0
  15. data/lib/png_conform/pipelines/stages/analysis_stage.rb +35 -0
  16. data/lib/png_conform/pipelines/stages/base_stage.rb +23 -0
  17. data/lib/png_conform/pipelines/stages/chunk_validation_stage.rb +74 -0
  18. data/lib/png_conform/pipelines/stages/sequence_validation_stage.rb +77 -0
  19. data/lib/png_conform/pipelines/stages/signature_validation_stage.rb +41 -0
  20. data/lib/png_conform/pipelines/validation_pipeline.rb +90 -0
  21. data/lib/png_conform/readers/full_load_reader.rb +13 -4
  22. data/lib/png_conform/readers/streaming_reader.rb +27 -2
  23. data/lib/png_conform/reporters/color_reporter.rb +17 -14
  24. data/lib/png_conform/reporters/visual_elements.rb +22 -16
  25. data/lib/png_conform/services/analysis_manager.rb +120 -0
  26. data/lib/png_conform/services/chunk_processor.rb +195 -0
  27. data/lib/png_conform/services/file_signature.rb +226 -0
  28. data/lib/png_conform/services/file_strategy.rb +78 -0
  29. data/lib/png_conform/services/lru_cache.rb +170 -0
  30. data/lib/png_conform/services/parallel_validator.rb +118 -0
  31. data/lib/png_conform/services/profile_manager.rb +41 -12
  32. data/lib/png_conform/services/result_builder.rb +299 -0
  33. data/lib/png_conform/services/validation_cache.rb +210 -0
  34. data/lib/png_conform/services/validation_orchestrator.rb +188 -0
  35. data/lib/png_conform/services/validation_service.rb +53 -337
  36. data/lib/png_conform/services/validator_pool.rb +142 -0
  37. data/lib/png_conform/utils/colorizer.rb +149 -0
  38. data/lib/png_conform/validators/chunk_registry.rb +12 -0
  39. data/lib/png_conform/validators/streaming_idat_validator.rb +123 -0
  40. data/lib/png_conform/version.rb +1 -1
  41. data/png_conform.gemspec +1 -0
  42. metadata +38 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9838bb140397822f351596fc1ff51179969ba68ac9d4310718a6ddcd549acb67
4
- data.tar.gz: b88887d300166cb6aa8d38db1d277450a9ddec7d3662498b8529ec3d5eb1f168
3
+ metadata.gz: 8bcefecf4f13700db28e89f3ec81ad93df86ef0d5774057a3366ef9c74121169
4
+ data.tar.gz: bfd053fc3545d3ede27936476e8fce4d483c85e227d5eb5824e9faaa4f7c8872
5
5
  SHA512:
6
- metadata.gz: ab5ec06d2d129c872e75fe3002129ce258eb6faa80519f147a358be32cad5c16a49e88592226bab55391c274512a17f76679ac51e031dd1e296e949d12d981d2
7
- data.tar.gz: dbc3ceb3ceafc05d1f92e22c6693668249759fc7a1a54b2191854fb944e11c4445171fae162b0b145d7016afe9287afb9f278d6fc7d4e6cb0b71dc2957e56f40
6
+ metadata.gz: 8b16cfe85d3e17edc7dcdd5c4a7b1b9e416fd91be3825ac758bb2f95de19015cacc3c8c3301aabbac7174dc651e180930862b026a42968959539d4e1520857b6
7
+ data.tar.gz: c966fa4713fc24c7a4439e41ada2382814395d7be7bbe2e782fb388bf59d3f723ee5d36db5cac431c078c02b1dbdd9f517dbfa341ee0132ffee8403208185a69
data/.rubocop_todo.yml CHANGED
@@ -1,18 +1,93 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-11-23 07:50:45 UTC using RuboCop version 1.81.1.
3
+ # on 2026-01-19 08:52:09 UTC using RuboCop version 1.81.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
- # Offense count: 231
9
+ # Offense count: 6
10
+ # This cop supports safe autocorrection (--autocorrect).
11
+ # Configuration parameters: EnforcedStyle, IndentationWidth.
12
+ # SupportedStyles: with_first_argument, with_fixed_indentation
13
+ Layout/ArgumentAlignment:
14
+ Exclude:
15
+ - 'lib/png_conform/commands/check_command.rb'
16
+ - 'lib/png_conform/services/chunk_processor.rb'
17
+ - 'lib/png_conform/services/profile_manager.rb'
18
+ - 'lib/png_conform/services/validator_pool.rb'
19
+
20
+ # Offense count: 4
21
+ # This cop supports safe autocorrection (--autocorrect).
22
+ # Configuration parameters: EnforcedStyleAlignWith.
23
+ # SupportedStylesAlignWith: either, start_of_block, start_of_line
24
+ Layout/BlockAlignment:
25
+ Exclude:
26
+ - 'lib/png_conform/analyzers/comparison_analyzer.rb'
27
+ - 'lib/png_conform/analyzers/optimization_analyzer.rb'
28
+ - 'lib/png_conform/services/file_signature.rb'
29
+ - 'lib/png_conform/services/lru_cache.rb'
30
+
31
+ # Offense count: 4
32
+ # This cop supports safe autocorrection (--autocorrect).
33
+ Layout/BlockEndNewline:
34
+ Exclude:
35
+ - 'lib/png_conform/analyzers/comparison_analyzer.rb'
36
+ - 'lib/png_conform/analyzers/optimization_analyzer.rb'
37
+ - 'lib/png_conform/services/file_signature.rb'
38
+ - 'lib/png_conform/services/lru_cache.rb'
39
+
40
+ # Offense count: 1
41
+ # This cop supports safe autocorrection (--autocorrect).
42
+ Layout/ElseAlignment:
43
+ Exclude:
44
+ - 'lib/png_conform/services/validation_cache.rb'
45
+
46
+ # Offense count: 1
47
+ # This cop supports safe autocorrection (--autocorrect).
48
+ # Configuration parameters: EnforcedStyleAlignWith, Severity.
49
+ # SupportedStylesAlignWith: keyword, variable, start_of_line
50
+ Layout/EndAlignment:
51
+ Exclude:
52
+ - 'lib/png_conform/services/validation_cache.rb'
53
+
54
+ # Offense count: 2
55
+ # This cop supports safe autocorrection (--autocorrect).
56
+ # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
57
+ # SupportedHashRocketStyles: key, separator, table
58
+ # SupportedColonStyles: key, separator, table
59
+ # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
60
+ Layout/HashAlignment:
61
+ Exclude:
62
+ - 'lib/png_conform/services/validator_pool.rb'
63
+
64
+ # Offense count: 10
65
+ # This cop supports safe autocorrection (--autocorrect).
66
+ # Configuration parameters: Width, AllowedPatterns.
67
+ Layout/IndentationWidth:
68
+ Exclude:
69
+ - 'lib/png_conform/analyzers/comparison_analyzer.rb'
70
+ - 'lib/png_conform/analyzers/optimization_analyzer.rb'
71
+ - 'lib/png_conform/services/file_signature.rb'
72
+ - 'lib/png_conform/services/lru_cache.rb'
73
+ - 'lib/png_conform/services/validation_cache.rb'
74
+
75
+ # Offense count: 267
10
76
  # This cop supports safe autocorrection (--autocorrect).
11
77
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
12
78
  # URISchemes: http, https
13
79
  Layout/LineLength:
14
80
  Enabled: false
15
81
 
82
+ # Offense count: 3
83
+ # This cop supports safe autocorrection (--autocorrect).
84
+ # Configuration parameters: AllowInHeredoc.
85
+ Layout/TrailingWhitespace:
86
+ Exclude:
87
+ - 'lib/png_conform/commands/check_command.rb'
88
+ - 'lib/png_conform/services/profile_manager.rb'
89
+ - 'lib/png_conform/services/validator_pool.rb'
90
+
16
91
  # Offense count: 1
17
92
  Lint/BinaryOperatorWithIdenticalOperands:
18
93
  Exclude:
@@ -27,7 +102,16 @@ Lint/DuplicateBranch:
27
102
  - 'lib/png_conform/validators/ancillary/sbit_validator.rb'
28
103
  - 'spec/pngsuite/helpers/semantic_validator.rb'
29
104
 
30
- # Offense count: 113
105
+ # Offense count: 4
106
+ # Configuration parameters: AllowedParentClasses.
107
+ Lint/MissingSuper:
108
+ Exclude:
109
+ - 'lib/png_conform/pipelines/stages/analysis_stage.rb'
110
+ - 'lib/png_conform/pipelines/stages/chunk_validation_stage.rb'
111
+ - 'lib/png_conform/pipelines/stages/sequence_validation_stage.rb'
112
+ - 'lib/png_conform/pipelines/stages/signature_validation_stage.rb'
113
+
114
+ # Offense count: 124
31
115
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
32
116
  Metrics/AbcSize:
33
117
  Enabled: false
@@ -36,14 +120,14 @@ Metrics/AbcSize:
36
120
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
37
121
  # AllowedMethods: refine
38
122
  Metrics/BlockLength:
39
- Max: 54
123
+ Max: 52
40
124
 
41
- # Offense count: 57
125
+ # Offense count: 62
42
126
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
43
127
  Metrics/CyclomaticComplexity:
44
128
  Enabled: false
45
129
 
46
- # Offense count: 171
130
+ # Offense count: 193
47
131
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
48
132
  Metrics/MethodLength:
49
133
  Max: 66
@@ -114,6 +198,20 @@ RSpec/SpecFilePathFormat:
114
198
  RSpec/VerifiedDoubles:
115
199
  Enabled: false
116
200
 
201
+ # Offense count: 4
202
+ # This cop supports safe autocorrection (--autocorrect).
203
+ # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
204
+ # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
205
+ # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
206
+ # FunctionalMethods: let, let!, subject, watch
207
+ # AllowedMethods: lambda, proc, it
208
+ Style/BlockDelimiters:
209
+ Exclude:
210
+ - 'lib/png_conform/analyzers/comparison_analyzer.rb'
211
+ - 'lib/png_conform/analyzers/optimization_analyzer.rb'
212
+ - 'lib/png_conform/services/file_signature.rb'
213
+ - 'lib/png_conform/services/lru_cache.rb'
214
+
117
215
  # Offense count: 41
118
216
  # This cop supports safe autocorrection (--autocorrect).
119
217
  # Configuration parameters: MaxUnannotatedPlaceholdersAllowed, Mode, AllowedMethods, AllowedPatterns.
@@ -125,3 +223,15 @@ Style/FormatStringToken:
125
223
  Style/MissingRespondToMissing:
126
224
  Exclude:
127
225
  - 'lib/png_conform/cli.rb'
226
+
227
+ # Offense count: 1
228
+ # This cop supports safe autocorrection (--autocorrect).
229
+ Style/MultilineTernaryOperator:
230
+ Exclude:
231
+ - 'lib/png_conform/services/validation_cache.rb'
232
+
233
+ # Offense count: 2
234
+ # This cop supports safe autocorrection (--autocorrect).
235
+ Style/RedundantParentheses:
236
+ Exclude:
237
+ - 'lib/png_conform/services/validation_cache.rb'
data/Gemfile CHANGED
@@ -5,7 +5,7 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in png_conform.gemspec
6
6
  gemspec
7
7
 
8
- gem "openssl"
8
+ gem "openssl", "~> 3.0"
9
9
  gem "rake"
10
10
  gem "rspec"
11
11
  gem "rubocop"
@@ -0,0 +1,105 @@
1
+ # PNG Conform Validation Profiles
2
+ #
3
+ # This file defines validation profiles for PNG files. Each profile specifies
4
+ # which chunks are required, optional, or prohibited.
5
+ #
6
+ # Profile structure:
7
+ # name: Display name
8
+ # description: Human-readable description
9
+ # required_chunks: List of chunk types that MUST be present
10
+ # optional_chunks: List of chunk types that MAY be present (use "*" for all)
11
+ # prohibited_chunks: List of chunk types that MUST NOT be present
12
+ #
13
+
14
+ minimal:
15
+ name: "Minimal validation"
16
+ description: "Only critical chunks required"
17
+ required_chunks:
18
+ - IHDR
19
+ - IDAT
20
+ - IEND
21
+ optional_chunks: "*"
22
+ prohibited_chunks: []
23
+
24
+ web:
25
+ name: "Web-optimized"
26
+ description: "Recommended for web images"
27
+ required_chunks:
28
+ - IHDR
29
+ - IDAT
30
+ - IEND
31
+ - gAMA
32
+ - sRGB
33
+ optional_chunks:
34
+ - tRNS
35
+ - bKGD
36
+ - tEXt
37
+ - iTXt
38
+ - zTXt
39
+ - pHYs
40
+ prohibited_chunks: []
41
+
42
+ print:
43
+ name: "Print-ready"
44
+ description: "For high-quality print output"
45
+ required_chunks:
46
+ - IHDR
47
+ - IDAT
48
+ - IEND
49
+ - pHYs
50
+ optional_chunks:
51
+ - gAMA
52
+ - cHRM
53
+ - sRGB
54
+ - iCCP
55
+ - tRNS
56
+ - bKGD
57
+ prohibited_chunks: []
58
+
59
+ archive:
60
+ name: "Archive quality"
61
+ description: "For long-term image storage"
62
+ required_chunks:
63
+ - IHDR
64
+ - IDAT
65
+ - IEND
66
+ optional_chunks:
67
+ - gAMA
68
+ - cHRM
69
+ - sRGB
70
+ - iCCP
71
+ - tEXt
72
+ - iTXt
73
+ - zTXt
74
+ - tIME
75
+ prohibited_chunks: []
76
+
77
+ strict:
78
+ name: "Strict PNG specification"
79
+ description: "Full compliance with PNG specification"
80
+ required_chunks:
81
+ - IHDR
82
+ - IDAT
83
+ - IEND
84
+ optional_chunks:
85
+ - PLTE
86
+ - gAMA
87
+ - cHRM
88
+ - sRGB
89
+ - iCCP
90
+ - tRNS
91
+ - bKGD
92
+ - hIST
93
+ - tEXt
94
+ - zTXt
95
+ - iTXt
96
+ - pHYs
97
+ - sPLT
98
+ - sBIT
99
+ - oFFs
100
+ - pCAL
101
+ - sCAL
102
+ - tIME
103
+ - cICP
104
+ - mDCv
105
+ prohibited_chunks: []
@@ -1,18 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../configuration"
4
+ require_relative "../services/file_signature"
5
+
3
6
  module PngConform
4
7
  module Analyzers
5
8
  # Compares two PNG files and reports differences
6
9
  class ComparisonAnalyzer
7
- # Metadata chunk types
8
- METADATA_CHUNKS = %w[tEXt zTXt iTXt tIME].freeze
9
-
10
- def initialize(result1, result2)
10
+ def initialize(result1, result2, config: Configuration.instance)
11
11
  @result1 = result1
12
12
  @result2 = result2
13
+ @config = config
14
+
15
+ # Fast path: compute signatures for quick equality check
16
+ @sig1 = Services::FileSignature.from_result(result1).compute_signature
17
+ @sig2 = Services::FileSignature.from_result(result2).compute_signature
13
18
  end
14
19
 
15
20
  def analyze
21
+ # Fast return if signatures are identical
22
+ return identical_result if @sig1 == @sig2
23
+
24
+ # Full comparison for different files
25
+ full_comparison
26
+ end
27
+
28
+ private
29
+
30
+ # Return result for identical files
31
+ #
32
+ # @return [Hash] Analysis result for identical files
33
+ def identical_result
34
+ {
35
+ files: {
36
+ file1: @result1.filename,
37
+ file2: @result2.filename,
38
+ identical: true,
39
+ signature: @sig1.short_signature,
40
+ },
41
+ summary: ["Files are binary identical"],
42
+ }
43
+ end
44
+
45
+ # Full comparison for different files
46
+ #
47
+ # @return [Hash] Complete comparison analysis
48
+ def full_comparison
16
49
  {
17
50
  files: file_comparison,
18
51
  image: image_comparison,
@@ -23,8 +56,6 @@ module PngConform
23
56
  }
24
57
  end
25
58
 
26
- private
27
-
28
59
  def file_comparison
29
60
  size1 = @result1.file_size
30
61
  size2 = @result2.file_size
@@ -166,7 +197,10 @@ module PngConform
166
197
  end
167
198
 
168
199
  def metadata_count(result)
169
- result.chunks.count { |c| METADATA_CHUNKS.include?(c.type) }
200
+ # Use only text and time chunks from metadata (excluding pHYs which is physical)
201
+ result.chunks.count do |c|
202
+ @config.text_chunks.include?(c.type) || c.type == "tIME"
203
+ end
170
204
  end
171
205
 
172
206
  def format_size_change(diff, percent)
@@ -1,17 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../configuration"
4
+
3
5
  module PngConform
4
6
  module Analyzers
5
7
  # Generates comprehensive metrics for CI/CD and automation
6
8
  class MetricsAnalyzer
7
- # Text chunk types
8
- TEXT_CHUNKS = %w[tEXt zTXt iTXt].freeze
9
-
10
- # Metadata chunk types including time
11
- METADATA_CHUNKS = %w[tEXt zTXt iTXt tIME].freeze
12
-
13
- def initialize(result)
9
+ def initialize(result, config: Configuration.instance)
14
10
  @result = result
11
+ @config = config
15
12
  ihdr = result.ihdr_chunk
16
13
  @width = ihdr ? get_width(ihdr) : 0
17
14
  @height = ihdr ? get_height(ihdr) : 0
@@ -149,10 +146,10 @@ module PngConform
149
146
  has_iccp: @result.has_chunk?("iCCP"),
150
147
  has_transparency: @result.has_chunk?("tRNS"),
151
148
  has_metadata: @result.chunks.any? do |c|
152
- TEXT_CHUNKS.include?(c.type)
149
+ @config.text_chunks.include?(c.type)
153
150
  end,
154
151
  metadata_chunks_count: @result.chunks.count do |c|
155
- METADATA_CHUNKS.include?(c.type)
152
+ @config.metadata_chunks.include?(c.type)
156
153
  end,
157
154
  bytes_per_pixel: calculate_bytes_per_pixel,
158
155
  }
@@ -1,20 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../configuration"
4
+
3
5
  module PngConform
4
6
  module Analyzers
5
7
  # Analyzes PNG files for optimization opportunities
6
8
  class OptimizationAnalyzer
7
- # Chunks that are often unnecessary for web/mobile use
8
- UNNECESSARY_FOR_WEB = %w[tIME pHYs oFFs pCAL sCAL sTER].freeze
9
-
10
- # Text chunk types
11
- TEXT_CHUNKS = %w[tEXt zTXt iTXt].freeze
12
-
13
- # Metadata chunk types
14
- METADATA_CHUNKS = %w[tEXt zTXt iTXt tIME pHYs].freeze
15
-
16
- def initialize(result)
9
+ def initialize(result, config: Configuration.instance)
17
10
  @result = result
11
+ @config = config
18
12
  @suggestions = []
19
13
  end
20
14
 
@@ -37,7 +31,7 @@ module PngConform
37
31
 
38
32
  def check_unnecessary_chunks
39
33
  unnecessary = @result.chunks.select do |c|
40
- UNNECESSARY_FOR_WEB.include?(c.type)
34
+ @config.unnecessary_web_chunks.include?(c.type)
41
35
  end
42
36
  return if unnecessary.empty?
43
37
 
@@ -61,14 +55,17 @@ module PngConform
61
55
  # Estimate if 8-bit would be sufficient
62
56
  if could_use_8_bit?
63
57
  current_size = @result.file_size
64
- estimated_savings = (current_size * 0.45).to_i # ~45% reduction
58
+ estimated_savings = (
59
+ current_size *
60
+ @config.optimization_percentages[:bit_depth_reduction] / 100.0
61
+ ).to_i
65
62
 
66
63
  @suggestions << {
67
64
  type: :reduce_bit_depth,
68
65
  priority: :high,
69
66
  savings_bytes: estimated_savings,
70
67
  description: "Convert from 16-bit to 8-bit depth " \
71
- "(estimated ~45% file size reduction)",
68
+ "(estimated ~#{@config.optimization_percentages[:bit_depth_reduction]}% file size reduction)",
72
69
  current: "16-bit",
73
70
  recommended: "8-bit",
74
71
  }
@@ -79,15 +76,18 @@ module PngConform
79
76
  # Get color type from IHDR
80
77
  ihdr = @result.ihdr_chunk
81
78
  return unless ihdr && get_color_type(ihdr) == 2 # RGB
82
- return if @result.file_size < 10_000 # Skip small files
79
+ return if @result.file_size < @config.size_thresholds[:palette_opportunity]
83
80
 
84
81
  # If it's RGB but could be palette
85
82
  @suggestions << {
86
83
  type: :convert_to_palette,
87
84
  priority: :medium,
88
- savings_bytes: (@result.file_size * 0.30).to_i,
85
+ savings_bytes: (
86
+ @result.file_size *
87
+ @config.optimization_percentages[:palette_conversion] / 100.0
88
+ ).to_i,
89
89
  description: "Consider converting to palette mode if using limited colors " \
90
- "(potential ~30% reduction)",
90
+ "(potential ~#{@config.optimization_percentages[:palette_conversion]}% reduction)",
91
91
  current: "RGB (Truecolor)",
92
92
  recommended: "Indexed (Palette)",
93
93
  }
@@ -99,25 +99,30 @@ module PngConform
99
99
  return unless ihdr && get_interlace_method(ihdr) == 1
100
100
 
101
101
  # Interlaced PNGs are larger
102
- savings = (@result.file_size * 0.15).to_i
102
+ savings = (
103
+ @result.file_size *
104
+ @config.optimization_percentages[:interlace_removal] / 100.0
105
+ ).to_i
103
106
 
104
107
  @suggestions << {
105
108
  type: :remove_interlacing,
106
109
  priority: :low,
107
110
  savings_bytes: savings,
108
111
  description: "Remove interlacing for smaller file size " \
109
- "(~15% reduction, but slower initial display)",
112
+ "(~#{@config.optimization_percentages[:interlace_removal]}% reduction, but slower initial display)",
110
113
  current: "Adam7 interlaced",
111
114
  recommended: "Non-interlaced",
112
115
  }
113
116
  end
114
117
 
115
118
  def check_text_chunks
116
- text_chunks = @result.chunks.select { |c| TEXT_CHUNKS.include?(c.type) }
119
+ text_chunks = @result.chunks.select do |c|
120
+ @config.text_chunks.include?(c.type)
121
+ end
117
122
  return if text_chunks.empty?
118
123
 
119
124
  total_text_size = text_chunks.sum { |c| c.length + 12 }
120
- return if total_text_size < 500 # Ignore small metadata
125
+ return if total_text_size < @config.size_thresholds[:text_metadata]
121
126
 
122
127
  @suggestions << {
123
128
  type: :reduce_metadata,
@@ -131,14 +136,15 @@ module PngConform
131
136
 
132
137
  def check_metadata_size
133
138
  metadata_chunks = @result.chunks.select do |c|
134
- METADATA_CHUNKS.include?(c.type)
139
+ @config.metadata_chunks.include?(c.type)
135
140
  end
136
141
 
137
142
  total_metadata = metadata_chunks.sum { |c| c.length + 12 }
138
143
  file_size = @result.file_size
139
144
 
140
- # If metadata is more than 10% of file size
141
- return unless total_metadata > file_size * 0.1
145
+ # If metadata is more than threshold percent of file size
146
+ threshold = @config.optimization_percentages[:metadata_threshold]
147
+ return unless total_metadata > file_size * threshold / 100.0
142
148
 
143
149
  @suggestions << {
144
150
  type: :excessive_metadata,
@@ -173,7 +179,7 @@ module PngConform
173
179
  def could_use_8_bit?
174
180
  # Conservative heuristic: suggest 8-bit for smaller files
175
181
  # Without pixel analysis, we're conservative
176
- @result.file_size < 100_000
182
+ @result.file_size < @config.size_thresholds[:small_file]
177
183
  end
178
184
 
179
185
  def calculate_total_savings
@@ -1,22 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../configuration"
4
+
3
5
  module PngConform
4
6
  module Analyzers
5
7
  # Analyzes PNG resolution and DPI for various use cases
6
8
  class ResolutionAnalyzer
7
- # Standard DPI values
8
- SCREEN_DPI = 72
9
- PRINT_DPI_LOW = 150
10
- PRINT_DPI_STANDARD = 300
11
- PRINT_DPI_HIGH = 600
12
-
13
- # Retina display densities
14
- RETINA_1X = 1.0
15
- RETINA_2X = 2.0
16
- RETINA_3X = 3.0
17
-
18
- def initialize(result)
9
+ def initialize(result, config: Configuration.instance)
19
10
  @result = result
11
+ @config = config
20
12
  ihdr = result.ihdr_chunk
21
13
  @width = ihdr ? get_width(ihdr) : 0
22
14
  @height = ihdr ? get_height(ihdr) : 0
@@ -63,9 +55,9 @@ module PngConform
63
55
  def retina_analysis
64
56
  analysis = {
65
57
  is_retina_ready: check_retina_ready,
66
- at_1x: calculate_physical_size(RETINA_1X),
67
- at_2x: calculate_physical_size(RETINA_2X),
68
- at_3x: calculate_physical_size(RETINA_3X),
58
+ at_1x: calculate_physical_size(@config.retina_scalers["1x"]),
59
+ at_2x: calculate_physical_size(@config.retina_scalers["2x"]),
60
+ at_3x: calculate_physical_size(@config.retina_scalers["3x"]),
69
61
  }
70
62
 
71
63
  analysis[:recommended_density] = recommend_density
@@ -82,7 +74,7 @@ module PngConform
82
74
  height_inches = @height.to_f / @dpi
83
75
 
84
76
  {
85
- capable: @dpi >= PRINT_DPI_LOW,
77
+ capable: @dpi >= @config.print_dpi_thresholds[:minimum],
86
78
  dpi: @dpi,
87
79
  physical_size: {
88
80
  width_inches: width_inches.round(2),
@@ -97,9 +89,11 @@ module PngConform
97
89
 
98
90
  def web_analysis
99
91
  {
100
- suitable_for_web: @width <= 4096 && @height <= 4096,
92
+ suitable_for_web: @width <= @config.size_thresholds[:large_for_web] &&
93
+ @height <= @config.size_thresholds[:large_for_web],
101
94
  typical_screen_size: calculate_screen_coverage,
102
- mobile_friendly: @width <= 1920 && @height <= 1920,
95
+ mobile_friendly: @width <= @config.size_thresholds[:large_for_mobile] &&
96
+ @height <= @config.size_thresholds[:large_for_mobile],
103
97
  retina_optimized: @width >= 1000 && @height >= 1000,
104
98
  load_time_estimate: estimate_load_time,
105
99
  }
@@ -119,15 +113,15 @@ module PngConform
119
113
  end
120
114
 
121
115
  def check_retina_ready
122
- @width >= 88 && @height >= 88
116
+ @width >= @config.size_thresholds[:retina_ready_min] &&
117
+ @height >= @config.size_thresholds[:retina_ready_min]
123
118
  end
124
119
 
125
120
  def calculate_physical_size(density)
126
- css_reference_dpi = 163
127
- effective_dpi = css_reference_dpi * density
121
+ effective_dpi = @config.css_reference_dpi * density
128
122
 
129
- width_points = (@width.to_f / effective_dpi * 72).round(1)
130
- height_points = (@height.to_f / effective_dpi * 72).round(1)
123
+ width_points = (@width.to_f / effective_dpi * @config.screen_dpi).round(1)
124
+ height_points = (@height.to_f / effective_dpi * @config.screen_dpi).round(1)
131
125
 
132
126
  {
133
127
  width_points: width_points,
@@ -181,15 +175,19 @@ module PngConform
181
175
  return "Unknown" unless @dpi
182
176
 
183
177
  case @dpi
184
- when 0...PRINT_DPI_LOW then "Not suitable"
185
- when PRINT_DPI_LOW...PRINT_DPI_STANDARD then "Acceptable"
186
- when PRINT_DPI_STANDARD...PRINT_DPI_HIGH then "Good"
187
- else "Excellent"
178
+ when 0...@config.print_dpi_thresholds[:minimum]
179
+ "Not suitable"
180
+ when @config.print_dpi_thresholds[:minimum]...@config.print_dpi_thresholds[:good]
181
+ "Acceptable"
182
+ when @config.print_dpi_thresholds[:good]...@config.print_dpi_thresholds[:excellent]
183
+ "Good"
184
+ else
185
+ "Excellent"
188
186
  end
189
187
  end
190
188
 
191
189
  def suitable_print_sizes
192
- return [] unless @dpi && @dpi >= PRINT_DPI_LOW
190
+ return [] unless @dpi && @dpi >= @config.print_dpi_thresholds[:minimum]
193
191
 
194
192
  width_in = @width.to_f / @dpi
195
193
  height_in = @height.to_f / @dpi
@@ -227,8 +225,7 @@ module PngConform
227
225
 
228
226
  def estimate_load_time
229
227
  file_size = @result.file_size
230
- mbps = 5
231
- bytes_per_second = (mbps * 1_000_000 / 8).to_i
228
+ bytes_per_second = @config.network_speeds[:fast]
232
229
  seconds = file_size.to_f / bytes_per_second
233
230
 
234
231
  if seconds < 0.1
@@ -243,7 +240,8 @@ module PngConform
243
240
  def generate_recommendations
244
241
  recs = []
245
242
 
246
- if @width < 100 && @height < 100
243
+ if @width < @config.size_thresholds[:retina_ready_min] &&
244
+ @height < @config.size_thresholds[:retina_ready_min]
247
245
  recs << {
248
246
  category: :retina,
249
247
  priority: :high,
@@ -259,7 +257,8 @@ module PngConform
259
257
  }
260
258
  end
261
259
 
262
- if @width > 3000 || @height > 3000
260
+ if @width > @config.size_thresholds[:very_large] ||
261
+ @height > @config.size_thresholds[:very_large]
263
262
  recs << {
264
263
  category: :web,
265
264
  priority: :high,