libpng 1.6.58.3-x86_64-linux → 1.6.58.4-x86_64-linux

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08d4136bbcd2e6c69cb4fbe7d9d36f1d700a036ffd5dce5acff0db3278c34e50'
4
- data.tar.gz: 40b542f5e72e19e2c4ee59a6880ee87c9f4b5c7ac29e9c1f1c63615025ffe365
3
+ metadata.gz: 1c6f560c4148a1c70cce0e991a7ebbe1118f8e143ee0b36835ce4c2708d6c5ec
4
+ data.tar.gz: 8fc10233fdf0cf97c829b8d0c7ab518f5a31a244b562b589ce54d0a86596d4d8
5
5
  SHA512:
6
- metadata.gz: 11ac2710092c7421fc363110202216689d89990779609197a9f7ecb8531f7cdb915de67fb4cc5700fc926534a58883057607db49d12a6a4a9634212a2ac221b5
7
- data.tar.gz: 154ec0fca608648628c2f1af8bab3593f1e0e7c4c8aa65b9dec398c5ba270876918ebe27a495576f77d99e88cea45f69ffae22bdfea5363fab2941383e1f1d53
6
+ metadata.gz: ffa253ffb2c4c0c9f6f14f7f2a6f2c541ad17364597a000b50496f60f04bb2e75e96d75aeb377bd657d4b87aed6e3499c7105243b2b62c106766d005db4f830b
7
+ data.tar.gz: 83c79f7438ae4e5e36677a6a6924ad4a14e0638b1a1256592b9ac0c5c22b3957c7c131598b3b4e6d4e3cc62cc7121a0175c116b08c5ba4b60542a207b0dbd337
@@ -143,6 +143,12 @@ jobs:
143
143
  platform: x86_64-linux-musl
144
144
  - os: ubuntu-24.04-arm
145
145
  platform: aarch64-linux-musl
146
+ # OHOS (OpenHarmony / Huawei HarmonyOS PC) is musl-based arm64.
147
+ # Built inside the same Alpine container as aarch64-linux-musl;
148
+ # the resulting binary is byte-compatible. Only the gem's
149
+ # platform label differs so RubyGems on OHOS selects it.
150
+ - os: ubuntu-24.04-arm
151
+ platform: aarch64-linux-ohos
146
152
 
147
153
  steps:
148
154
  - uses: actions/checkout@v4
@@ -168,12 +174,27 @@ jobs:
168
174
  git config --global --add safe.directory /work
169
175
  bundle install --jobs 4
170
176
  bundle exec rake "gem:native:${PLATFORM}"
171
- gem install -b pkg/libpng-*.gem
172
- # Smoke test from /tmp so bundler's source-tree LOAD_PATH doesn't
173
- # shadow the installed gem. Activate via `gem` so RubyGems resolves
174
- # the platform-specific binary gem (Alpine Ruby's platform string
175
- # is *-linux-musl, which must match the gem's platform suffix).
176
- (cd /tmp && ruby -e 'gem "libpng"; require "libpng"; png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); File.binwrite("test.png", png); puts Libpng.decode(png, pixel_format: "RGBA").inspect')
177
+ case "${PLATFORM}" in
178
+ *-ohos)
179
+ # OHOS gem is labeled aarch64-linux-ohos so RubyGems on OHOS
180
+ # selects it, but Alpine Ruby reports *-linux-musl -- a
181
+ # platform mismatch. The .so inside is musl-compatible, so
182
+ # unpack the gem and load it via -Ilib directly to verify
183
+ # the binary works without going through RubyGems install.
184
+ mkdir -p /tmp/ohos-smoke
185
+ gem unpack pkg/libpng-*.gem --target /tmp/ohos-smoke
186
+ (cd /tmp/ohos-smoke/libpng-* && \
187
+ ruby -Ilib -e 'require "libpng"; png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); puts Libpng.decode(png, pixel_format: "RGBA").inspect')
188
+ ;;
189
+ *)
190
+ gem install -b pkg/libpng-*.gem
191
+ # Smoke test from /tmp so bundler's source-tree LOAD_PATH doesn't
192
+ # shadow the installed gem. Activate via `gem` so RubyGems resolves
193
+ # the platform-specific binary gem (Alpine Ruby's platform string
194
+ # is *-linux-musl, which must match the gem's platform suffix).
195
+ (cd /tmp && ruby -e 'gem "libpng"; require "libpng"; png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); File.binwrite("test.png", png); puts Libpng.decode(png, pixel_format: "RGBA").inspect')
196
+ ;;
197
+ esac
177
198
  BUILD_EOF
178
199
  chmod +x /tmp/alpine-build.sh
179
200
  docker run --rm \
@@ -201,6 +201,10 @@ jobs:
201
201
  platform: x86_64-linux-musl
202
202
  - os: ubuntu-24.04-arm
203
203
  platform: aarch64-linux-musl
204
+ # OHOS: same Alpine-built musl arm64 binary as aarch64-linux-musl,
205
+ # packaged under a distinct platform label.
206
+ - os: ubuntu-24.04-arm
207
+ platform: aarch64-linux-ohos
204
208
  steps:
205
209
  - uses: actions/checkout@v4
206
210
  with:
@@ -218,8 +222,22 @@ jobs:
218
222
  git config --global --add safe.directory /work
219
223
  bundle install --jobs 4
220
224
  bundle exec rake "gem:native:${PLATFORM}"
221
- gem install -b pkg/libpng-*.gem
222
- (cd /tmp && ruby -e 'gem "libpng"; require "libpng"; png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); puts Libpng.decode(png, pixel_format: "RGBA").inspect')
225
+ case "${PLATFORM}" in
226
+ *-ohos)
227
+ # OHOS gem is labeled aarch64-linux-ohos so RubyGems on OHOS
228
+ # selects it, but Alpine Ruby reports *-linux-musl. The .so
229
+ # inside is musl-compatible -- unpack and load via -Ilib to
230
+ # verify the binary without going through RubyGems install.
231
+ mkdir -p /tmp/ohos-smoke
232
+ gem unpack pkg/libpng-*.gem --target /tmp/ohos-smoke
233
+ (cd /tmp/ohos-smoke/libpng-* && \
234
+ ruby -Ilib -e 'require "libpng"; png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); puts Libpng.decode(png, pixel_format: "RGBA").inspect')
235
+ ;;
236
+ *)
237
+ gem install -b pkg/libpng-*.gem
238
+ (cd /tmp && ruby -e 'gem "libpng"; require "libpng"; png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); puts Libpng.decode(png, pixel_format: "RGBA").inspect')
239
+ ;;
240
+ esac
223
241
  BUILD_EOF
224
242
  chmod +x /tmp/alpine-build.sh
225
243
  docker run --rm \
data/.gitignore CHANGED
@@ -12,6 +12,8 @@
12
12
  /lib/libpng/*.so.*
13
13
  /lib/libpng/*.dylib
14
14
  /lib/libpng/*.dll
15
+ /Makefile
16
+ /lib/libpng/Makefile
15
17
  /.idea/
16
18
  /.vscode/
17
19
  *.gem
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
- RSpec
2
1
  --format documentation
3
2
  --color
3
+ --require spec_helper
data/CHANGELOG.md CHANGED
@@ -7,6 +7,34 @@ This gem follows a `{LIBPNG_VERSION}.{LIBPNG_RUBY_ITERATION}` version
7
7
  scheme. `LIBPNG_VERSION` is the upstream libpng release; `ITERATION`
8
8
  bumps for Ruby-side changes and resets to 0 when LIBPNG_VERSION bumps.
9
9
 
10
+ ## [1.6.58.4] - 2026-07-26
11
+
12
+ ### Added
13
+ - `Libpng::ChunkWalker#text_chunks` -- parses `tEXt`, `zTXt`, and `iTXt`
14
+ chunks into a flat Hash of keyword -> UTF-8 String. zTXt values are
15
+ zlib-inflated; tEXt/zTXt values are transcoded from Latin-1 to UTF-8;
16
+ iTXt values stay UTF-8 (with optional compression). Malformed chunks
17
+ are silently skipped so a single broken entry doesn't poison the rest
18
+ of the decode.
19
+ - `Libpng::ChunkWalker#color_chunks` -- parses `gAMA`, `cHRM`, `sRGB`,
20
+ and `iCCP` chunks into a Hash with Symbol keys (`:gamma`,
21
+ `:white_point_x/y`, `:red_x/y`, `:green_x/y`, `:blue_x/y`,
22
+ `:srgb_intent`, `:icc_profile_name`, `:icc_profile`). iCCP profiles
23
+ are zlib-decompressed into raw binary bytes for downstream ICC
24
+ libraries.
25
+ - `Libpng::DecodedImage#text` and `#color` -- new keyword Struct fields,
26
+ populated by `Libpng.decode` via the new ChunkWalker accessors. Both
27
+ default to an empty Hash when the source PNG has no relevant chunks.
28
+ - New platform: `aarch64-linux-ohos` (OpenHarmony / Huawei HarmonyOS PC).
29
+ OHOS is musl-based arm64 -- the resulting shared library is
30
+ byte-compatible with `aarch64-linux-musl`; only the gem's platform
31
+ label differs so RubyGems on OHOS selects the right variant. Built in
32
+ the same Alpine container as the musl gem.
33
+ - Specs: 24 new specs across `spec/text_chunk_spec.rb` and
34
+ `spec/color_metadata_spec.rb` covering all 7 chunk types, malformed
35
+ input handling, Ractor moving, and end-to-end metadata exposure on
36
+ `DecodedImage`.
37
+
10
38
  ## [1.6.58.3] - 2026-07-26
11
39
 
12
40
  ### Added
data/CLAUDE.md CHANGED
@@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
6
6
 
7
7
  `libpng` is a Ruby gem that wraps the official libpng shared library via FFI. The native `libpng16.{so,dylib,dll}` is **pre-compiled per target platform and shipped inside the gem** — `gem install libpng` must not require a C compiler on the host.
8
8
 
9
- 10 platform gems are published per release (see README for the full matrix).
9
+ 11 platform gems are published per release (10 native + the source `ruby` gem). Native targets: `x86_64-linux`, `x86_64-linux-musl`, `aarch64-linux`, `aarch64-linux-musl`, `aarch64-linux-ohos`, `x64-mingw32`, `x64-mingw-ucrt`, `aarch64-mingw-ucrt`, `x86_64-darwin`, `arm64-darwin`.
10
10
 
11
11
  ## Architecture
12
12
 
@@ -21,7 +21,7 @@ loaded via `autoload` from `lib/libpng.rb`. **Never use `require_relative`
21
21
  | `lib/libpng/version.rb` | `LIBPNG_VERSION`, `LIBPNG_RUBY_ITERATION`, `VERSION` |
22
22
  | `lib/libpng/error.rb` | `Libpng::Error` |
23
23
  | `lib/libpng/decoded_image.rb` | `Libpng::DecodedImage` (Struct returned by `decode`) |
24
- | `lib/libpng/chunk_walker.rb` | `Libpng::ChunkWalker` (walk/strip/extract metadata from PNG bytes) |
24
+ | `lib/libpng/chunk_walker.rb` | `Libpng::ChunkWalker` (walk/strip/extract metadata: `#each_chunk`, `#ihdr_fields`, `#text_chunks` for tEXt/zTXt/iTXt, `#color_chunks` for gAMA/cHRM/sRGB/iCCP, `#strip_ancillary`) |
25
25
  | `lib/libpng/bytes_per_pixel.rb` | `Libpng::BytesPerPixel` (pure-data lookup) |
26
26
  | `lib/libpng/simplified_encoder.rb` | `Libpng::SimplifiedEncoder` (libpng simplified write API) |
27
27
  | `lib/libpng/simplified_decoder.rb` | `Libpng::SimplifiedDecoder` (libpng simplified read API + IHDR metadata) |
@@ -58,7 +58,7 @@ Ruby 4.0 removed `Ractor#take`; use the helper `ractor_result(r)` which prefers
58
58
  ```sh
59
59
  bundle install
60
60
  bundle exec rake compile # build libpng via MiniPortile (needs cmake + zlib)
61
- bundle exec rake spec # all specs (~81 examples)
61
+ bundle exec rake spec # all specs (~105 examples)
62
62
  bundle exec rspec spec/libpng_spec.rb:17 # single spec by line
63
63
  bundle exec rake rubocop
64
64
  bundle exec rake # default: spec + rubocop
@@ -67,7 +67,7 @@ bundle exec rake gem:native:arm64-darwin # build a pre-compiled gem
67
67
  bundle exec rake gem:native:any # source gem (compiles on install)
68
68
  ```
69
69
 
70
- Platform gem tasks: `x64-mingw32`, `x64-mingw-ucrt`, `aarch64-mingw-ucrt`, `x86_64-linux`, `x86_64-linux-musl`, `aarch64-linux`, `aarch64-linux-musl`, `x86_64-darwin`, `arm64-darwin`.
70
+ Platform gem tasks: `x64-mingw32`, `x64-mingw-ucrt`, `aarch64-mingw-ucrt`, `x86_64-linux`, `x86_64-linux-musl`, `aarch64-linux`, `aarch64-linux-musl`, `aarch64-linux-ohos`, `x86_64-darwin`, `arm64-darwin`.
71
71
 
72
72
  ## Release process
73
73
 
@@ -79,7 +79,7 @@ gh workflow run release.yml --repo claricle/libpng-ruby --ref main -f bump-type=
79
79
  # or: -f bump-type=current # release current VERSION as-is
80
80
  ```
81
81
 
82
- The workflow bumps `lib/libpng/version.rb`, pushes a `v*` tag, builds all 10 platform gems, and publishes to RubyGems via OIDC Trusted Publishing.
82
+ The workflow bumps `lib/libpng/version.rb`, pushes a `v*` tag, builds all 11 platform gems (10 native + the source `ruby` gem), and publishes to RubyGems via OIDC Trusted Publishing.
83
83
 
84
84
  **Never** push tags or merge to main directly — always go through PRs.
85
85
 
@@ -97,6 +97,8 @@ The workflow bumps `lib/libpng/version.rb`, pushes a `v*` tag, builds all 10 pla
97
97
  - `spec/libpng_standard_spec.rb` — `encode_standard` core
98
98
  - `spec/standard_encoder_options_spec.rb` — `interlace:`/`bit_depth:`/`palette:` options
99
99
  - `spec/decoded_image_metadata_spec.rb` — IHDR metadata + `ChunkWalker`
100
+ - `spec/text_chunk_spec.rb` — tEXt/zTXt/iTXt parsing
101
+ - `spec/color_metadata_spec.rb` — gAMA/cHRM/sRGB/iCCP parsing
100
102
  - `spec/malformed_input_spec.rb` — corrupt PNG input handling
101
103
  - `spec/ractor_spec.rb` — Ractor safety for simplified API
102
104
  - `spec/ractor_standard_spec.rb` — Ractor safety for standard API
@@ -104,6 +106,5 @@ The workflow bumps `lib/libpng/version.rb`, pushes a `v*` tag, builds all 10 pla
104
106
 
105
107
  ## See also
106
108
 
107
- - `TODO.roadmap/` — prioritized backlog with status (done/planned/wontdo) per item
108
109
  - `CHANGELOG.md` — release history
109
110
  - `REPORT-complex-api-needs.md` — historical report that drove `encode_standard`
data/README.adoc CHANGED
@@ -31,8 +31,9 @@ source tarball at install time and compiles it via CMake (requires
31
31
 
32
32
  == Supported platforms
33
33
 
34
- The following pre-compiled platform gems are published for each release
35
- (see the link:https://rubygems.org/gems/libpng/versions[RubyGems versions page]
34
+ The following 11 pre-compiled platform gems are published for each
35
+ release -- 10 native targets plus the platform-agnostic source (`ruby`)
36
+ gem (see the link:https://rubygems.org/gems/libpng/versions[RubyGems versions page]
36
37
  for the current set):
37
38
 
38
39
  [cols="1,1,3", options="header"]
@@ -43,6 +44,7 @@ for the current set):
43
44
  | `x86_64-linux-musl` | x86_64 Linux (musl, e.g. Alpine) | `ruby:<ver>-alpine` on x86_64
44
45
  | `aarch64-linux` | ARM64 Linux (glibc) | `ubuntu-24.04-arm` (native)
45
46
  | `aarch64-linux-musl` | ARM64 Linux (musl) | `ruby:<ver>-alpine` on arm64
47
+ | `aarch64-linux-ohos` | ARM64 OpenHarmony / Huawei HarmonyOS PC | `ruby:<ver>-alpine` on arm64 (byte-compatible with `aarch64-linux-musl`)
46
48
  | `x64-mingw32` | x64 Windows, RubyInstaller < 3.0 (MSVCRT) | `windows-latest`
47
49
  | `x64-mingw-ucrt` | x64 Windows, RubyInstaller >= 3.0 (UCRT) | `windows-latest`
48
50
  | `aarch64-mingw-ucrt` | ARM64 Windows (Ruby >= 3.4) | `windows-11-arm` (native)
@@ -67,6 +69,8 @@ for the current set):
67
69
  decoded.bit_depth # => 8 (from IHDR)
68
70
  decoded.color_type # => 6 (PNG_COLOR_TYPE_RGB_ALPHA, from IHDR)
69
71
  decoded.interlace # => 0 (PNG_INTERLACE_NONE, from IHDR)
72
+ decoded.text # => {"Title" => "Sunset", "Author" => "Jane"} (tEXt/zTXt/iTXt)
73
+ decoded.color # => {:gamma => 0.45455, :srgb_intent => 0, ...} (gAMA/cHRM/sRGB/iCCP)
70
74
 
71
75
  === Supported pixel formats
72
76
 
data/Rakefile CHANGED
@@ -39,6 +39,7 @@ platforms = %w[
39
39
  x86_64-linux-musl
40
40
  aarch64-linux
41
41
  aarch64-linux-musl
42
+ aarch64-linux-ohos
42
43
  x86_64-darwin
43
44
  arm64-darwin
44
45
  ]
@@ -6,7 +6,7 @@ module Libpng
6
6
  # Walks a PNG byte buffer chunk-by-chunk. Used to strip ancillary
7
7
  # chunks (sRGB, gAMA, cHRM, iCCP, etc.) that libpng's simplified
8
8
  # write API emits by default, and to extract metadata (IHDR fields,
9
- # text chunks, gAMA, pHYs) during decode.
9
+ # text chunks, color chunks, pHYs) during decode.
10
10
  #
11
11
  # The chunk layout is documented in the PNG spec:
12
12
  # [4 bytes length][4 bytes type][length bytes data][4 bytes CRC]
@@ -19,6 +19,12 @@ module Libpng
19
19
  # EOF, CRC mismatch, missing IEND.
20
20
  class FormatError < Error; end
21
21
 
22
+ # PNG gAMA fixed-point scale (per spec: gamma = uint32 / 100000).
23
+ GAMMA_SCALE = 100_000.0
24
+
25
+ # PNG cHRM fixed-point scale (per spec: chromaticity = uint32 / 100000).
26
+ CHRM_SCALE = 100_000.0
27
+
22
28
  def initialize(png_bytes)
23
29
  @bytes = png_bytes.bytes
24
30
  end
@@ -93,11 +99,173 @@ module Libpng
93
99
  }
94
100
  end
95
101
 
102
+ # Parse text chunks (tEXt, zTXt, iTXt) into a flat Hash of
103
+ # keyword -> UTF-8 String. When the same keyword appears in
104
+ # multiple chunks, the last occurrence wins (PNG spec permits
105
+ # this; behavior matches libpng's read path).
106
+ #
107
+ # - tEXt: Latin-1 keyword + Latin-1 value, transcoded to UTF-8.
108
+ # - zTXt: Latin-1 keyword + zlib-compressed Latin-1 value,
109
+ # inflated then transcoded to UTF-8.
110
+ # - iTXt: UTF-8 keyword + UTF-8 value (optionally zlib-compressed),
111
+ # with separate language tag and translated keyword that are
112
+ # dropped here (callers who need them should walk chunks directly).
113
+ #
114
+ # Malformed chunks are silently skipped: a single broken zTXt
115
+ # should not poison the rest of the decode.
116
+ def text_chunks
117
+ result = {}
118
+ each_chunk do |type, data, _|
119
+ case type
120
+ when 'tEXt'
121
+ parse_text(data, result)
122
+ when 'zTXt'
123
+ parse_ztxt(data, result)
124
+ when 'iTXt'
125
+ parse_itxt(data, result)
126
+ end
127
+ end
128
+ result
129
+ end
130
+
131
+ # Parse color-space chunks (gAMA, cHRM, sRGB, iCCP) into a Hash.
132
+ # Returns Symbol keys:
133
+ #
134
+ # :gamma -> Float (gAMA, value/100000)
135
+ # :white_point_x, :white_point_y -> Float (cHRM)
136
+ # :red_x, :red_y, :green_x, :green_y, :blue_x, :blue_y -> Float
137
+ # :srgb_intent -> Integer 0..3 (sRGB)
138
+ # :icc_profile_name -> UTF-8 String (iCCP, profile name)
139
+ # :icc_profile -> binary String (iCCP, decompressed bytes)
140
+ #
141
+ # Malformed chunks (e.g. truncated cHRM, bad iCCP compression) are
142
+ # silently skipped. ICC profiles can be large (10KB-1MB); callers
143
+ # who don't need them should walk chunks manually rather than call
144
+ # this method.
145
+ def color_chunks
146
+ result = {}
147
+ each_chunk do |type, data, _|
148
+ case type
149
+ when 'gAMA'
150
+ parse_gama(data, result)
151
+ when 'cHRM'
152
+ parse_chrm(data, result)
153
+ when 'sRGB'
154
+ parse_srgb(data, result)
155
+ when 'iCCP'
156
+ parse_iccp(data, result)
157
+ end
158
+ end
159
+ result
160
+ end
161
+
96
162
  private
97
163
 
98
164
  def verify_signature
99
165
  sig = @bytes.first(8)
100
166
  raise FormatError, 'not a PNG file (bad signature)' unless sig == SIGNATURE
101
167
  end
168
+
169
+ # Split a binary string at the first NUL byte. Returns [head, tail]
170
+ # where head excludes the NUL and tail is everything after. Returns
171
+ # [nil, nil] when no NUL is present.
172
+ def split_at_null(bytes)
173
+ idx = bytes.index("\x00")
174
+ return [nil, nil] if idx.nil?
175
+
176
+ [bytes[0, idx], bytes[(idx + 1)..]]
177
+ end
178
+
179
+ # Force Latin-1 byte sequence into UTF-8. Bytes 0x80-0xFF that have
180
+ # no assigned meaning in ISO-8859-1 still round-trip through this
181
+ # encoding because Latin-1 is a strict superset of the byte values.
182
+ def latin1_to_utf8(bytes)
183
+ bytes.force_encoding('ISO-8859-1').encode('UTF-8')
184
+ end
185
+
186
+ def parse_text(data, result)
187
+ key, value = split_at_null(data)
188
+ return if key.nil?
189
+
190
+ result[key] = latin1_to_utf8(value)
191
+ end
192
+
193
+ def parse_ztxt(data, result)
194
+ key, rest = split_at_null(data)
195
+ return if key.nil? || rest.nil? || rest.length < 2
196
+
197
+ _compression_method = rest.getbyte(0)
198
+ inflated = Zlib.inflate(rest[1..])
199
+ result[key] = latin1_to_utf8(inflated)
200
+ rescue Zlib::Error
201
+ # Skip malformed zTXt rather than failing the whole decode.
202
+ end
203
+
204
+ def parse_itxt(data, result)
205
+ # iTXt layout:
206
+ # keyword\0
207
+ # compression_flag (1 byte, 0 = uncompressed, 1 = zlib)
208
+ # compression_method (1 byte, 0 = zlib/deflate)
209
+ # language_tag\0 (ASCII, may be empty)
210
+ # translated_keyword\0 (UTF-8, may be empty)
211
+ # text (UTF-8, optionally zlib-compressed)
212
+ key, rest = split_at_null(data)
213
+ return if key.nil? || rest.nil? || rest.length < 3
214
+
215
+ compression_flag = rest.getbyte(0)
216
+ _compression_method = rest.getbyte(1)
217
+ _lang, after_lang = split_at_null(rest[2..])
218
+ return if after_lang.nil?
219
+
220
+ _translated, text_bytes = split_at_null(after_lang)
221
+ return if text_bytes.nil?
222
+
223
+ text_bytes = Zlib.inflate(text_bytes) if compression_flag == 1
224
+ result[key] = text_bytes.force_encoding('UTF-8')
225
+ rescue Zlib::Error
226
+ # Skip malformed iTXt rather than failing the whole decode.
227
+ end
228
+
229
+ def parse_gama(data, result)
230
+ return if data.length < 4
231
+
232
+ result[:gamma] = data.unpack1('N') / GAMMA_SCALE
233
+ end
234
+
235
+ def parse_chrm(data, result)
236
+ return if data.length < 32
237
+
238
+ wp_x, wp_y, rx, ry, gx, gy, bx, by = data.unpack('N8')
239
+ result[:white_point_x] = wp_x / CHRM_SCALE
240
+ result[:white_point_y] = wp_y / CHRM_SCALE
241
+ result[:red_x] = rx / CHRM_SCALE
242
+ result[:red_y] = ry / CHRM_SCALE
243
+ result[:green_x] = gx / CHRM_SCALE
244
+ result[:green_y] = gy / CHRM_SCALE
245
+ result[:blue_x] = bx / CHRM_SCALE
246
+ result[:blue_y] = by / CHRM_SCALE
247
+ end
248
+
249
+ def parse_srgb(data, result)
250
+ return if data.empty?
251
+
252
+ result[:srgb_intent] = data.getbyte(0)
253
+ end
254
+
255
+ def parse_iccp(data, result)
256
+ # iCCP layout:
257
+ # profile_name\0
258
+ # compression_method (1 byte, must be 0 = zlib/deflate)
259
+ # compressed_profile (zlib stream)
260
+ name, rest = split_at_null(data)
261
+ return if name.nil? || rest.nil? || rest.length < 2
262
+
263
+ _compression_method = rest.getbyte(0)
264
+ profile = Zlib.inflate(rest[1..])
265
+ result[:icc_profile_name] = name.force_encoding('UTF-8')
266
+ result[:icc_profile] = profile
267
+ rescue Zlib::Error
268
+ # Skip malformed iCCP rather than failing the whole decode.
269
+ end
102
270
  end
103
271
  end
@@ -11,7 +11,12 @@ module Libpng
11
11
  # +bit_depth+ bits per channel (8 or 16). nil if not extracted.
12
12
  # +color_type+ PNG_COLOR_TYPE_* integer from IHDR. nil if not extracted.
13
13
  # +interlace+ PNG_INTERLACE_* integer from IHDR. nil if not extracted.
14
+ # +text+ Hash<String,String> of tEXt/zTXt/iTXt keyword -> UTF-8
15
+ # value. Empty Hash when no text chunks present.
16
+ # +color+ Hash<Symbol,*> of gAMA/cHRM/sRGB/iCCP fields. Empty
17
+ # Hash when no color metadata present.
14
18
  DecodedImage = Struct.new(:width, :height, :format, :pixels,
15
19
  :bit_depth, :color_type, :interlace,
20
+ :text, :color,
16
21
  keyword_init: true)
17
22
  end
data/lib/libpng/recipe.rb CHANGED
@@ -174,6 +174,13 @@ module Libpng
174
174
  'x86_64-darwin'
175
175
  when /\A(arm64|aarch64).*linux-musl/
176
176
  'aarch64-linux-musl'
177
+ when /\A(arm64|aarch64).*linux-ohos/
178
+ # OHOS (OpenHarmony / Huawei HarmonyOS PC) is musl-based arm64.
179
+ # The resulting binary is ELF64 aarch64 linked against musl,
180
+ # identical at the file-format level to aarch64-linux-musl --
181
+ # only the gem's platform label differs so RubyGems on OHOS
182
+ # selects the right variant.
183
+ 'aarch64-linux-ohos'
177
184
  when /\A(arm64|aarch64).*linux/
178
185
  'aarch64-linux'
179
186
  when /\Ax86_64.*linux-musl/
@@ -198,7 +205,8 @@ module Libpng
198
205
 
199
206
  def cpu_type
200
207
  case target_platform
201
- when 'aarch64-linux', 'aarch64-linux-musl', 'arm64-darwin', 'aarch64-mingw-ucrt' then 'aarch64'
208
+ when 'aarch64-linux', 'aarch64-linux-musl', 'aarch64-linux-ohos',
209
+ 'arm64-darwin', 'aarch64-mingw-ucrt' then 'aarch64'
202
210
  when 'x86_64-linux', 'x86_64-linux-musl', 'x86_64-darwin', /\Ax64-mingw/ then 'x86_64'
203
211
  else
204
212
  super
@@ -207,7 +215,8 @@ module Libpng
207
215
 
208
216
  def cmake_system_name
209
217
  case target_platform
210
- when 'aarch64-linux', 'x86_64-linux', 'aarch64-linux-musl', 'x86_64-linux-musl' then 'Linux'
218
+ when 'aarch64-linux', 'x86_64-linux', 'aarch64-linux-musl',
219
+ 'x86_64-linux-musl', 'aarch64-linux-ohos' then 'Linux'
211
220
  when 'arm64-darwin', 'x86_64-darwin' then 'Darwin'
212
221
  when /\A(aarch64-)?mingw/, /\Ax64-mingw/ then 'Windows'
213
222
  else
@@ -222,7 +231,7 @@ module Libpng
222
231
  /Mach-O 64-bit dynamically linked shared library arm64/
223
232
  when 'x86_64-darwin'
224
233
  /Mach-O 64-bit dynamically linked shared library x86_64/
225
- when 'aarch64-linux', 'aarch64-linux-musl'
234
+ when 'aarch64-linux', 'aarch64-linux-musl', 'aarch64-linux-ohos'
226
235
  /ELF 64-bit LSB shared object, ARM aarch64/
227
236
  when 'x86_64-linux', 'x86_64-linux-musl'
228
237
  /ELF 64-bit LSB shared object, x86-64/
@@ -4,9 +4,11 @@ module Libpng
4
4
  # Decodes a PNG byte buffer into raw pixels via libpng's "simplified"
5
5
  # read API (png_image_begin_read_from_memory + png_image_finish_read).
6
6
  #
7
- # Also parses the IHDR to populate DecodedImage#bit_depth,
8
- # #color_type, #interlace for callers that want metadata without
9
- # walking the chunks themselves.
7
+ # Also parses the byte buffer via ChunkWalker to populate
8
+ # DecodedImage#bit_depth, #color_type, #interlace (from IHDR),
9
+ # #text (from tEXt/zTXt/iTXt), and #color (from gAMA/cHRM/sRGB/iCCP).
10
+ # The simplified API itself drops these ancillary chunks on read --
11
+ # walking them separately is the only way to surface their content.
10
12
  #
11
13
  # One instance per decode call. Ractor-safe.
12
14
  class SimplifiedDecoder
@@ -41,16 +43,12 @@ module Libpng
41
43
  ok = Libpng::Binding.png_image_finish_read(img, nil, out_buf, stride, nil)
42
44
  raise Error, "png_image_finish_read failed: #{read_message(img)}" if ok.zero?
43
45
 
44
- pixels = out_buf.read_bytes(out_size)
45
- metadata = extract_metadata
46
46
  return DecodedImage.new(
47
47
  width: width,
48
48
  height: height,
49
49
  format: @pixel_format.to_s.upcase,
50
- pixels: pixels,
51
- bit_depth: metadata[:bit_depth],
52
- color_type: metadata[:color_type],
53
- interlace: metadata[:interlace]
50
+ pixels: out_buf.read_bytes(out_size),
51
+ **walker_metadata
54
52
  )
55
53
  end
56
54
  end
@@ -64,11 +62,36 @@ module Libpng
64
62
  FORMAT_BY_NAME[@pixel_format.to_s.upcase]
65
63
  end
66
64
 
67
- def extract_metadata
68
- fields = ChunkWalker.new(@png).ihdr_fields
69
- { bit_depth: fields[:bit_depth],
70
- color_type: fields[:color_type],
71
- interlace: fields[:interlace] }
65
+ # Walks the PNG once via ChunkWalker to gather all metadata fields
66
+ # the simplified API does not surface. Each accessor is best-effort:
67
+ # a malformed IHDR or text chunk should not poison the rest of the
68
+ # decode -- empty Hashes / nil values are acceptable fall-throughs.
69
+ def walker_metadata
70
+ walker = ChunkWalker.new(@png)
71
+ ihdr = safe_ihdr(walker)
72
+ {
73
+ bit_depth: ihdr[:bit_depth],
74
+ color_type: ihdr[:color_type],
75
+ interlace: ihdr[:interlace],
76
+ text: safe_text(walker),
77
+ color: safe_color(walker)
78
+ }
79
+ end
80
+
81
+ def safe_ihdr(walker)
82
+ walker.ihdr_fields
83
+ rescue ChunkWalker::FormatError
84
+ {}
85
+ end
86
+
87
+ def safe_text(walker)
88
+ walker.text_chunks
89
+ rescue ChunkWalker::FormatError
90
+ {}
91
+ end
92
+
93
+ def safe_color(walker)
94
+ walker.color_chunks
72
95
  rescue ChunkWalker::FormatError
73
96
  {}
74
97
  end
@@ -11,6 +11,6 @@ module Libpng
11
11
  # new libpng release. The iteration resets to 0 each time
12
12
  # LIBPNG_VERSION bumps.
13
13
  LIBPNG_VERSION = '1.6.58'
14
- LIBPNG_RUBY_ITERATION = 3
14
+ LIBPNG_RUBY_ITERATION = 4
15
15
  VERSION = "#{LIBPNG_VERSION}.#{LIBPNG_RUBY_ITERATION}"
16
16
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: libpng
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.58.3
4
+ version: 1.6.58.4
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - Ribose Inc.
@@ -44,27 +44,6 @@ files:
44
44
  - LICENSE.txt
45
45
  - README.adoc
46
46
  - Rakefile
47
- - TODO.roadmap/01-claude-md-update.md
48
- - TODO.roadmap/02-encode-standard-ractor-spec.md
49
- - TODO.roadmap/03-mini-portile2-bump.md
50
- - TODO.roadmap/04-changelog.md
51
- - TODO.roadmap/05-benchmarks.md
52
- - TODO.roadmap/06-malformed-input-tests.md
53
- - TODO.roadmap/07-interlace-option.md
54
- - TODO.roadmap/08-palette-support.md
55
- - TODO.roadmap/09-16-bit-encode.md
56
- - TODO.roadmap/10-libpng-1.6.59-bump.md
57
- - TODO.roadmap/11-read-side-metadata.md
58
- - TODO.roadmap/12-full-api-surface.md
59
- - TODO.roadmap/13-c-extension-setjmp.md
60
- - TODO.roadmap/14-progressive-decode.md
61
- - TODO.roadmap/15-file-io-variants.md
62
- - TODO.roadmap/16-architecture-refactor.md
63
- - TODO.roadmap/17-ruby-version-policy.md
64
- - TODO.roadmap/18-dependabot.md
65
- - TODO.roadmap/19-text-chunk-read.md
66
- - TODO.roadmap/20-color-metadata-read.md
67
- - TODO.roadmap/README.md
68
47
  - bin/console
69
48
  - bin/setup
70
49
  - ext/extconf.rb
@@ -1,27 +0,0 @@
1
- # 01 - CLAUDE.md update
2
-
3
- - **Priority**: P0
4
- - **Status**: Done
5
-
6
- ## Context
7
-
8
- The agent-facing `CLAUDE.md` was out of sync after `encode_standard`,
9
- the platform matrix expansion, and the architecture refactor. Future
10
- Claude instances would land in a codebase that no longer matched the
11
- documented structure.
12
-
13
- ## Approach
14
-
15
- Re-created `CLAUDE.md` with:
16
- - File-responsibility table for the new autoloaded structure
17
- - Public API surface (encode / encode_standard / decode) with options
18
- - FFI binding inventory (simplified + standard APIs)
19
- - Ractor safety section including the Ruby 4.0 `#value` migration
20
- - Code quality rules (no `require_relative`, no `send`, no `iv_get`,
21
- no `respond_to?` -- the project-wide rules)
22
- - Spec inventory
23
- - Cross-links to `TODO.roadmap/`, `CHANGELOG.md`, `REPORT-complex-api-needs.md`
24
-
25
- ## Delivered
26
-
27
- PR #7 (this branch).