Module: Libpng::MetadataWriter
- Defined in:
- lib/libpng/metadata_writer.rb
Overview
Validates and writes PNG metadata chunks (text, gamma, sRGB, chromaticities, ICC profile, pHYs) onto a (png_ptr, info_ptr) pair. Extracted from StandardEncoder so the encoder stays focused on pixel + IHDR/PLTE concerns; this module owns the metadata story.
The same module is reusable by any future encoder that wants to emit metadata -- it doesn't depend on StandardEncoder's state.
Constant Summary collapse
- REQUIRED_CHRM_KEYS =
%i[ white_point_x white_point_y red_x red_y green_x green_y blue_x blue_y ].freeze
Class Method Summary collapse
-
.apply(png_ptr, info_ptr, options) ⇒ Object
Apply metadata writers in spec-defined order: text first (tEXt/zTXt/iTXt come after PLTE but before oFFs/pHYs in the canonical chunk ordering), then color-space chunks (sRGB, gAMA, cHRM, iCCP), then pHYs.
- .apply_chromaticities(png_ptr, info_ptr, chrm) ⇒ Object
- .apply_gamma(png_ptr, info_ptr, gamma) ⇒ Object
- .apply_icc_profile(png_ptr, info_ptr, profile) ⇒ Object
- .apply_phys(png_ptr, info_ptr, phys) ⇒ Object
- .apply_srgb(png_ptr, info_ptr, intent) ⇒ Object
- .apply_text(png_ptr, info_ptr, text) ⇒ Object
-
.validate!(options) ⇒ Object
Validate the metadata portion of an options Hash.
- .validate_chromaticities!(chrm) ⇒ Object
- .validate_gamma!(gamma) ⇒ Object
- .validate_icc_profile!(profile) ⇒ Object
- .validate_phys!(phys) ⇒ Object
- .validate_srgb_intent!(intent) ⇒ Object
- .validate_text!(text) ⇒ Object
Class Method Details
.apply(png_ptr, info_ptr, options) ⇒ Object
Apply metadata writers in spec-defined order: text first (tEXt/zTXt/iTXt come after PLTE but before oFFs/pHYs in the canonical chunk ordering), then color-space chunks (sRGB, gAMA, cHRM, iCCP), then pHYs. libpng sorts these into the right file order on write, so call order here just needs to be consistent.
38 39 40 41 42 43 44 45 |
# File 'lib/libpng/metadata_writer.rb', line 38 def apply(png_ptr, info_ptr, ) apply_text(png_ptr, info_ptr, [:text]) if [:text] apply_srgb(png_ptr, info_ptr, [:srgb_intent]) if [:srgb_intent] apply_gamma(png_ptr, info_ptr, [:gamma]) if [:gamma] apply_chromaticities(png_ptr, info_ptr, [:chromaticities]) if [:chromaticities] apply_icc_profile(png_ptr, info_ptr, [:icc_profile]) if [:icc_profile] apply_phys(png_ptr, info_ptr, [:phys]) if [:phys] end |
.apply_chromaticities(png_ptr, info_ptr, chrm) ⇒ Object
114 115 116 117 118 119 120 121 122 |
# File 'lib/libpng/metadata_writer.rb', line 114 def apply_chromaticities(png_ptr, info_ptr, chrm) Libpng::Binding.png_set_cHRM( png_ptr, info_ptr, chrm[:white_point_x], chrm[:white_point_y], chrm[:red_x], chrm[:red_y], chrm[:green_x], chrm[:green_y], chrm[:blue_x], chrm[:blue_y] ) end |
.apply_gamma(png_ptr, info_ptr, gamma) ⇒ Object
110 111 112 |
# File 'lib/libpng/metadata_writer.rb', line 110 def apply_gamma(png_ptr, info_ptr, gamma) Libpng::Binding.png_set_gAMA(png_ptr, info_ptr, gamma) end |
.apply_icc_profile(png_ptr, info_ptr, profile) ⇒ Object
124 125 126 127 128 129 130 |
# File 'lib/libpng/metadata_writer.rb', line 124 def apply_icc_profile(png_ptr, info_ptr, profile) data = profile[:data] FFI::MemoryPointer.new(:uint8, data.bytesize) do |buf| buf.write_bytes(data) Libpng::Binding.png_set_iCCP(png_ptr, info_ptr, profile[:name], 0, buf, data.bytesize) end end |
.apply_phys(png_ptr, info_ptr, phys) ⇒ Object
132 133 134 135 136 137 |
# File 'lib/libpng/metadata_writer.rb', line 132 def apply_phys(png_ptr, info_ptr, phys) Libpng::Binding.png_set_pHYs(png_ptr, info_ptr, phys[:pixels_per_unit_x], phys[:pixels_per_unit_y], phys[:unit]) end |
.apply_srgb(png_ptr, info_ptr, intent) ⇒ Object
106 107 108 |
# File 'lib/libpng/metadata_writer.rb', line 106 def apply_srgb(png_ptr, info_ptr, intent) Libpng::Binding.png_set_sRGB(png_ptr, info_ptr, intent) end |
.apply_text(png_ptr, info_ptr, text) ⇒ Object
101 102 103 104 |
# File 'lib/libpng/metadata_writer.rb', line 101 def apply_text(png_ptr, info_ptr, text) entries = text.map { |k, v| Libpng::TextEntry.new(key: k, value: v) } Libpng::TextWriter.call(png_ptr, info_ptr, entries) end |
.validate!(options) ⇒ Object
Validate the metadata portion of an options Hash. Raises Libpng::Error on any invalid value. Keys with nil values are treated as absent.
24 25 26 27 28 29 30 31 |
# File 'lib/libpng/metadata_writer.rb', line 24 def validate!() validate_text!([:text]) if [:text] validate_gamma!([:gamma]) if [:gamma] validate_srgb_intent!([:srgb_intent]) if [:srgb_intent] validate_chromaticities!([:chromaticities]) if [:chromaticities] validate_icc_profile!([:icc_profile]) if [:icc_profile] validate_phys!([:phys]) if [:phys] end |
.validate_chromaticities!(chrm) ⇒ Object
70 71 72 73 74 75 |
# File 'lib/libpng/metadata_writer.rb', line 70 def validate_chromaticities!(chrm) missing = REQUIRED_CHRM_KEYS.reject { |k| chrm.is_a?(Hash) && chrm.key?(k) } return if missing.empty? raise Error, "chromaticities missing keys: #{missing.inspect}" end |
.validate_gamma!(gamma) ⇒ Object
58 59 60 61 62 |
# File 'lib/libpng/metadata_writer.rb', line 58 def validate_gamma!(gamma) return if gamma.is_a?(Float) && gamma.positive? && gamma <= 1.0 raise Error, 'gamma must be a Float 0 < g <= 1' end |
.validate_icc_profile!(profile) ⇒ Object
77 78 79 80 81 82 83 84 |
# File 'lib/libpng/metadata_writer.rb', line 77 def validate_icc_profile!(profile) unless profile.is_a?(Hash) && profile[:name].is_a?(String) && profile[:data].is_a?(String) raise Error, 'icc_profile must be a Hash with :name (String) and :data (binary String)' end return if profile[:name].length.between?(1, 79) raise Error, 'icc_profile[:name] must be 1..79 chars' end |
.validate_phys!(phys) ⇒ Object
86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/libpng/metadata_writer.rb', line 86 def validate_phys!(phys) unless phys.is_a?(Hash) && phys.key?(:pixels_per_unit_x) && phys.key?(:pixels_per_unit_y) && phys.key?(:unit) raise Error, 'phys must be a Hash with :pixels_per_unit_x, :pixels_per_unit_y, :unit' end unless phys[:unit].is_a?(Integer) && [0, 1].include?(phys[:unit]) raise Error, 'phys[:unit] must be 0 (unknown) or 1 (meters)' end [phys[:pixels_per_unit_x], phys[:pixels_per_unit_y]].each do |v| unless v.is_a?(Integer) && v.between?(0, 0xFFFFFFFF) raise Error, 'phys pixels_per_unit_* must be Integer 0..2^32-1' end end end |
.validate_srgb_intent!(intent) ⇒ Object
64 65 66 67 68 |
# File 'lib/libpng/metadata_writer.rb', line 64 def validate_srgb_intent!(intent) return if intent.is_a?(Integer) && (0..3).cover?(intent) raise Error, 'srgb_intent must be an Integer 0..3' end |
.validate_text!(text) ⇒ Object
47 48 49 50 51 52 53 54 55 56 |
# File 'lib/libpng/metadata_writer.rb', line 47 def validate_text!(text) raise Error, 'text must be a Hash' unless text.is_a?(Hash) text.each do |key, value| unless key.is_a?(String) && key.length.between?(1, 79) raise Error, "text key #{key.inspect} must be a 1..79 char ASCII String" end raise Error, "text[#{key.inspect}] must be a String, got #{value.class}" unless value.is_a?(String) end end |