libpng 1.6.58.2 → 1.6.58.4

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: bd40beeea8a0333bc4f83c461da33eec3bb0ffdcf9982dc289efab87da554d17
4
- data.tar.gz: d9d837770569d6fdc083d2d074138679c70b43eeac8f666f906fb44f053ec2ef
3
+ metadata.gz: 3764dc7e75c29be74bf732ba3d3ac4a66b17c4d20f5e1404f7d1d4fb4a7bff5d
4
+ data.tar.gz: 81d239f7c17eef66172e93543c949d1cee3a5d5bd281f997c3e26153e7beafe5
5
5
  SHA512:
6
- metadata.gz: 621b7892d277ccdb0ab0535c38ee69c11b9e4b866e05ec7ef7248fd1df9bc4aa6f5a81a5b560eb67a6491ce00c76f853c3e7d3014273890d2dc9a11b76beb1a6
7
- data.tar.gz: ec661e92e1f7c8b3ddeae8c2b43dc5ff9795ae2ee5aa34b344887134c605e54513b41209e19df74bc04d3bc4e63ed2524202b153841a724a98b48944515276e6
6
+ metadata.gz: 4d7d3caf69638aaf251a34631346007aed404d662a5d238dadf628b0f202ff2db51a751c6de310e76358c974c9b8d12207cb9f89f0f7c8b6d45fd47ae8d0a5be
7
+ data.tar.gz: aedc2495d938619919ba47c65fb1f54f3b38af2cddcf39d38fef0eb9464031d5dcdc3922f3e6230d2bf05c612e271c28e0878ef263b3200c5a15c14336453c97
@@ -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/.rubocop.yml CHANGED
@@ -22,7 +22,7 @@ Metrics/ModuleLength:
22
22
  Metrics/PerceivedComplexity:
23
23
  Max: 20
24
24
  Metrics/ParameterLists:
25
- Max: 7
25
+ Max: 9
26
26
 
27
27
  Metrics/BlockLength:
28
28
  Exclude:
data/CHANGELOG.md ADDED
@@ -0,0 +1,127 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `libpng` Ruby gem are documented here.
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ This gem follows a `{LIBPNG_VERSION}.{LIBPNG_RUBY_ITERATION}` version
7
+ scheme. `LIBPNG_VERSION` is the upstream libpng release; `ITERATION`
8
+ bumps for Ruby-side changes and resets to 0 when LIBPNG_VERSION bumps.
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
+
38
+ ## [1.6.58.3] - 2026-07-26
39
+
40
+ ### Added
41
+ - `Libpng.encode_standard` -- standard libpng write API
42
+ (`png_create_write_struct` -> `png_set_IHDR` -> `png_set_rows` ->
43
+ `png_write_png(PNG_TRANSFORM_IDENTITY)`). Mirrors libemf2svg's
44
+ `rgb2png` byte layout and emits only IHDR/IDAT/IEND directly (no
45
+ post-hoc chunk stripping).
46
+ - `interlace:` option on `encode_standard` -- `:none` (default) or
47
+ `:adam7`.
48
+ - `bit_depth:` option on `encode_standard` -- 8 (default) or 16 for
49
+ RGB/RGBA/GRAY.
50
+ - `pixel_format: :palette` with `palette:` option on `encode_standard` --
51
+ emits PNG_COLOR_TYPE_PALETTE via `png_set_PLTE` (and `png_set_tRNS`
52
+ when any palette entry has alpha).
53
+ - `Libpng::DecodedImage#bit_depth`, `#color_type`, `#interlace` -- IHDR
54
+ metadata fields populated by walking the source PNG after decode.
55
+ - `Libpng::ChunkWalker` -- public class for walking PNG chunks:
56
+ `#each_chunk`, `#strip_ancillary`, `#ihdr_fields`.
57
+ - `Libpng::BytesPerPixel` -- pure-data helper module mapping formats
58
+ and color types to bytes-per-pixel (8- and 16-bit aware).
59
+ - Architecture refactor: split the monolithic `lib/libpng.rb` (466
60
+ lines) into MECE per-concern files using Ruby `autoload`. New files:
61
+ `lib/libpng/error.rb`, `decoded_image.rb`, `chunk_walker.rb`,
62
+ `bytes_per_pixel.rb`, `simplified_encoder.rb`,
63
+ `simplified_decoder.rb`, `standard_encoder.rb`. Public API
64
+ unchanged.
65
+ - Specs: malformed-input suite, encode_standard Ractor suite,
66
+ benchmark suite, options suite for interlace/bit_depth/palette,
67
+ metadata suite for DecodedImage + ChunkWalker.
68
+
69
+ ### Changed
70
+ - `mini_portile2` dependency bumped from `~> 2.6` to `~> 2.8`.
71
+ - Removed all `require_relative` from library code in favor of
72
+ `autoload` (per project code-quality rules).
73
+ - `ext/extconf.rb` now triggers `Libpng::Recipe` autoload via
74
+ `require 'libpng'` instead of reaching into `lib/libpng/recipe.rb`
75
+ directly.
76
+
77
+ ### Fixed
78
+ - Filter option documentation: clarified that `:default`, `:adaptive`,
79
+ and `:all` produce byte-identical output but exercise different
80
+ libpng code paths. `:none` forces no filtering.
81
+ - Removed redundant `.dup.freeze` on `LIBPNG_VER_STRING_C` (the source
82
+ string is already a frozen literal).
83
+
84
+ ## [1.6.58.2] - 2026-07-26
85
+
86
+ ### Added
87
+ - `Libpng.encode_standard` initial release -- standard libpng write
88
+ API with memory-stream output via `png_set_write_fn` FFI callback
89
+ (no Tempfile), filter control, compression level control, and error
90
+ handling via `png_set_error_fn` callback.
91
+ - Specs: 32 specs covering encode_standard round-trips, filter
92
+ variants, compression levels, chunk-layout assertions, parity check
93
+ against simplified `encode`.
94
+
95
+ ## [1.6.58.1] - 2026-07-25
96
+
97
+ ### Added
98
+ - Full platform coverage: `aarch64-linux-musl`, `aarch64-linux` (native
99
+ on `ubuntu-24.04-arm`), `aarch64-mingw-ucrt` (native on
100
+ `windows-11-arm`), `x86_64-linux-musl` (Alpine via `docker run`).
101
+ - `step-security/msvc-dev-cmd@v1` replaces deleted `ilammy/msvc-dev-tools`
102
+ action.
103
+ - Release workflow tolerates already-published gems (skip-on-conflict).
104
+ - Specs: Ractor safety suite for `encode` and `decode` (Ruby 3.x and 4.0).
105
+
106
+ ### Fixed
107
+ - `ext/extconf.rb` now emits a dummy Makefile so RubyGems is satisfied
108
+ when installing the source (`ruby` platform) gem.
109
+ - Recipe globs `{bin,lib}/libpng16*.dll` on Windows (CMake's
110
+ GNUInstallDirs puts the .dll in `bin/`, not `lib/`).
111
+ - Alpine build runs inside `docker run` (not the `container:` field)
112
+ so it works on arm64 Ubuntu runners.
113
+ - `git config --global --add safe.directory /work` in Alpine container
114
+ so `git ls-files` works for the gemspec.
115
+
116
+ ## [1.6.58.0] - 2026-07-25
117
+
118
+ ### Added
119
+ - Initial pre-compiled libpng gem. Bundles libpng 1.6.58 shared
120
+ libraries for x86_64 Linux, x86_64 macOS, arm64 macOS, x64 Windows
121
+ (MSVCRT and UCRT).
122
+ - `Libpng.encode` / `Libpng.decode` -- simplified API binding via FFI.
123
+ - `strip_colorspace:` option on `encode` to drop sRGB/gAMA chunks the
124
+ simplified API emits by default.
125
+ - `convert_to_8bit:` option on `encode` for 16-bit input.
126
+ - MiniPortile-based recipe for building libpng from source when
127
+ installing the platform-agnostic `ruby` gem.
data/CLAUDE.md ADDED
@@ -0,0 +1,110 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project
6
+
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
+
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
+
11
+ ## Architecture
12
+
13
+ The library is split into MECE per-concern files under `lib/libpng/`,
14
+ loaded via `autoload` from `lib/libpng.rb`. **Never use `require_relative`
15
+ (or `require` with a path) for internal library code** — add an
16
+ `autoload` entry to `lib/libpng.rb` instead.
17
+
18
+ | File | Responsibility |
19
+ |---|---|
20
+ | `lib/libpng.rb` | Module + FFI setup + constants + public dispatch (`encode`/`decode`/`encode_standard`) + autoloads |
21
+ | `lib/libpng/version.rb` | `LIBPNG_VERSION`, `LIBPNG_RUBY_ITERATION`, `VERSION` |
22
+ | `lib/libpng/error.rb` | `Libpng::Error` |
23
+ | `lib/libpng/decoded_image.rb` | `Libpng::DecodedImage` (Struct returned by `decode`) |
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
+ | `lib/libpng/bytes_per_pixel.rb` | `Libpng::BytesPerPixel` (pure-data lookup) |
26
+ | `lib/libpng/simplified_encoder.rb` | `Libpng::SimplifiedEncoder` (libpng simplified write API) |
27
+ | `lib/libpng/simplified_decoder.rb` | `Libpng::SimplifiedDecoder` (libpng simplified read API + IHDR metadata) |
28
+ | `lib/libpng/standard_encoder.rb` | `Libpng::StandardEncoder` (libpng standard write API with filter/compression/interlace/bit_depth/palette options) |
29
+ | `lib/libpng/recipe.rb` | `Libpng::Recipe < MiniPortileCMake` (builds libpng from source for the source gem) |
30
+ | `ext/extconf.rb` | Gem extension entry. Triggers `Libpng::Recipe` autoload via `require 'libpng'`, then emits a dummy Makefile |
31
+
32
+ ### Public API
33
+
34
+ ```ruby
35
+ Libpng.encode(width, height, pixels, pixel_format:, convert_to_8bit:, strip_colorspace:)
36
+ Libpng.encode_standard(width, height, pixels, pixel_format:, filter:, compression_level:, interlace:, bit_depth:, palette:)
37
+ Libpng.decode(png, pixel_format:)
38
+ ```
39
+
40
+ Each is a thin dispatcher that constructs a dedicated encoder/decoder instance and calls `#call`. The instance is discarded after the call (per-call state, Ractor-safe).
41
+
42
+ ### FFI binding
43
+
44
+ `Libpng` extends `FFI::Library` and attaches:
45
+ - 4 simplified-API functions (`png_image_*`)
46
+ - 10 standard-API functions (`png_create_write_struct`, `png_set_IHDR`, `png_set_PLTE`, `png_set_tRNS`, `png_set_filter`, `png_set_compression_level`, `png_set_write_fn`, `png_write_png`, `png_set_rows`, `png_destroy_write_struct`, `png_create_info_struct`)
47
+
48
+ FFI calls inside encoder classes use the qualified form: `Libpng.png_set_IHDR(...)`. The function table is set up once at module load and is shareable across Ractors.
49
+
50
+ ### Ractor safety
51
+
52
+ Every encoder/decoder allocates its own `png_image` (simplified API) or `png_struct` (standard API) per call. No shared mutable state on the Ruby side. The Ractor specs in `spec/ractor_spec.rb` and `spec/ractor_standard_spec.rb` verify this across Ruby 3.3, 3.4, and 4.0.
53
+
54
+ Ruby 4.0 removed `Ractor#take`; use the helper `ractor_result(r)` which prefers `#value` (4.0+) and falls back to `#take` (3.x).
55
+
56
+ ## Common commands
57
+
58
+ ```sh
59
+ bundle install
60
+ bundle exec rake compile # build libpng via MiniPortile (needs cmake + zlib)
61
+ bundle exec rake spec # all specs (~105 examples)
62
+ bundle exec rspec spec/libpng_spec.rb:17 # single spec by line
63
+ bundle exec rake rubocop
64
+ bundle exec rake # default: spec + rubocop
65
+
66
+ bundle exec rake gem:native:arm64-darwin # build a pre-compiled gem
67
+ bundle exec rake gem:native:any # source gem (compiles on install)
68
+ ```
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`, `aarch64-linux-ohos`, `x86_64-darwin`, `arm64-darwin`.
71
+
72
+ ## Release process
73
+
74
+ Releases are tag-triggered via `.github/workflows/release.yml`. Trigger with:
75
+
76
+ ```sh
77
+ gh workflow run release.yml --repo claricle/libpng-ruby --ref main -f bump-type=iteration
78
+ # or: -f bump-type=libpng -f libpng-version=1.6.59
79
+ # or: -f bump-type=current # release current VERSION as-is
80
+ ```
81
+
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
+
84
+ **Never** push tags or merge to main directly — always go through PRs.
85
+
86
+ ## Code quality rules (project-wide)
87
+
88
+ - **Never** `require_relative` for internal library code — use `autoload`.
89
+ - **Never** `send` to call private methods. Redesign the API boundary instead.
90
+ - **Never** `instance_variable_set`/`get` across objects.
91
+ - **Never** `respond_to?` for type checks — use `is_a?` or design the type hierarchy so the check isn't needed.
92
+ - **DRY / MECE / OCP**: each concern lives in exactly one file. New pixel format = add to `FORMAT_BY_NAME` + `COLOR_TYPE_BY_FORMAT` + `BytesPerPixel` (and `FORMAT_TO_COLOR_TYPE` if standard-API).
93
+
94
+ ## Specs
95
+
96
+ - `spec/libpng_spec.rb` — `encode`/`decode` simplified API
97
+ - `spec/libpng_standard_spec.rb` — `encode_standard` core
98
+ - `spec/standard_encoder_options_spec.rb` — `interlace:`/`bit_depth:`/`palette:` options
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
102
+ - `spec/malformed_input_spec.rb` — corrupt PNG input handling
103
+ - `spec/ractor_spec.rb` — Ractor safety for simplified API
104
+ - `spec/ractor_standard_spec.rb` — Ractor safety for standard API
105
+ - `spec/benchmark_spec.rb` — encode/decode timing comparison
106
+
107
+ ## See also
108
+
109
+ - `CHANGELOG.md` — release history
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)
@@ -60,10 +62,15 @@ for the current set):
60
62
 
61
63
  # Decode a PNG into raw pixels.
62
64
  decoded = Libpng.decode(File.binread("out.png"), pixel_format: "RGBA")
63
- decoded.width # => Integer
64
- decoded.height # => Integer
65
- decoded.format # => "RGBA"
66
- decoded.pixels # => binary String of RGBA bytes
65
+ decoded.width # => Integer
66
+ decoded.height # => Integer
67
+ decoded.format # => "RGBA"
68
+ decoded.pixels # => binary String of RGBA bytes
69
+ decoded.bit_depth # => 8 (from IHDR)
70
+ decoded.color_type # => 6 (PNG_COLOR_TYPE_RGB_ALPHA, from IHDR)
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)
67
74
 
68
75
  === Supported pixel formats
69
76
 
@@ -94,33 +101,47 @@ output. Set to `false` to keep the ancillary color chunks.
94
101
  For parity with libpng's classic write path -- the same code path used
95
102
  by `libemf2svg`'s `rgb2png` -- use `encode_standard` instead of
96
103
  `encode`. It calls `png_create_write_struct` + `png_set_IHDR` +
97
- `png_set_rows` + `png_write_png(PNG_TRANSFORM_IDENTITY)` directly, with
98
- filter and zlib-compression control exposed:
104
+ `png_set_rows` + `png_write_png(PNG_TRANSFORM_IDENTITY)` directly.
99
105
 
100
106
  Libpng.encode_standard(width, height, pixels,
101
107
  pixel_format: "RGBA",
102
108
  filter: :default,
103
- compression_level: 6)
109
+ compression_level: 6,
110
+ interlace: :none,
111
+ bit_depth: 8,
112
+ palette: nil)
104
113
 
105
- `pixel_format`:: `GRAY`, `GA`, `RGB`, or `RGBA` only. The byte-order
106
- variants (`BGR`, `ARGB`, `BGRA`, `ABGR`) are simplified-API only --
107
- the standard API always emits host-order.
114
+ `pixel_format`:: `:gray`, `:ga`, `:rgb`, `:rgba`, or `:palette`. The
115
+ byte-order variants (`BGR`, `ARGB`, `BGRA`, `ABGR`) are simplified-API
116
+ only -- the standard API always emits host-order.
108
117
 
109
- `filter`:: One of `:default` (adaptive -- libpng picks the best filter
110
- per row, the libemf2svg default), `:adaptive` (same), `:none`, `:sub`,
111
- `:up`, `:avg`, `:paeth`, or `:all`.
118
+ `filter`:: `:default` (adaptive -- libpng picks the best filter per
119
+ row, the libemf2svg default), `:adaptive` (same output, exercises a
120
+ different libpng code path), `:none`, `:sub`, `:up`, `:avg`, `:paeth`,
121
+ `:all`.
112
122
 
113
123
  `compression_level`:: zlib level 0-9. Default 6 (Z_DEFAULT_COMPRESSION,
114
124
  matching libemf2svg).
115
125
 
126
+ `interlace`:: `:none` (default) or `:adam7`.
127
+
128
+ `bit_depth`:: 8 (default) or 16. Only valid for `:gray`, `:rgb`,
129
+ `:rgba`. 16-bit callers must provide 2 bytes per channel
130
+ (host-order little-endian). Always 8 for `:palette`.
131
+
132
+ `palette`:: Array of `[r, g, b]` or `[r, g, b, a]` (1..256 entries).
133
+ Required when `pixel_format: :palette`. If any entry has alpha, a `tRNS`
134
+ chunk is emitted.
135
+
116
136
  Output is written directly into a Ruby String via `png_set_write_fn` --
117
137
  no `Tempfile`, no disk I/O. The FFI write callback is held on a local
118
- Array so it survives the libpng calls.
138
+ `Array` so it survives the libpng calls.
119
139
 
120
140
  Differences from `encode`:
121
141
  - Emits only `IHDR` / `IDAT` / `IEND` (no post-hoc chunk stripping
122
142
  needed).
123
- - Standard API only -- no `convert_to_8bit` option.
143
+ - Supports palette, 16-bit, and interlaced output -- simplified API
144
+ doesn't.
124
145
  - Errors are raised via a `png_set_error_fn` callback (set on
125
146
  `png_create_write_struct`). libpng expects error callbacks to
126
147
  longjmp; we rb_raise instead, which unwinds the Ruby stack. The
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
  ]
data/ext/extconf.rb CHANGED
@@ -1,7 +1,14 @@
1
- $: << File.expand_path(File.join(File.dirname(__FILE__), '../lib'))
1
+ # frozen_string_literal: true
2
+
3
+ # extconf.rb is invoked by RubyGems when installing the source ('ruby'
4
+ # platform) gem. It needs to find lib/libpng.rb on the load path so it
5
+ # can trigger Libpng::Recipe autoload. We don't `require_relative`
6
+ # because that pins to a specific path; instead we extend $LOAD_PATH
7
+ # and let the normal autoload machinery do the work.
8
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '../lib'))
2
9
 
3
- require 'libpng/recipe'
4
10
  require 'mkmf'
11
+ require 'libpng' # triggers autoload setup; Libpng::Recipe loads lazily
5
12
 
6
13
  recipe = Libpng::Recipe.new
7
14
  recipe.cook_if_not
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module Libpng
6
+ # FFI bindings to the bundled `libpng16.{so,dylib,dll}`. Lives in its
7
+ # own autoloaded module so that requiring `libpng` (the top-level
8
+ # entry point used by `ext/extconf.rb` during the source-gem build)
9
+ # does NOT eagerly dlopen the shared library -- it may not exist yet
10
+ # at that point.
11
+ #
12
+ # The first time an encoder or decoder references `Libpng::Binding`,
13
+ # this file loads, ffi_lib fires, and the FFI functions attach. From
14
+ # then on, calls go directly through the attached C functions.
15
+ module Binding
16
+ extend FFI::Library
17
+
18
+ ffi_lib_flags :now, :global
19
+
20
+ lib_filename = if FFI::Platform.windows?
21
+ 'libpng16.dll'
22
+ elsif FFI::Platform.mac?
23
+ 'libpng16.dylib'
24
+ else
25
+ 'libpng16.so'
26
+ end
27
+
28
+ # __dir__ is lib/libpng/; the shared library ships at lib/libpng/.
29
+ ffi_lib File.expand_path(lib_filename, __dir__)
30
+
31
+ # libpng simplified API (png_image / png_image_*).
32
+ attach_function :png_image_begin_read_from_memory,
33
+ %i[pointer pointer size_t], :int
34
+ attach_function :png_image_finish_read,
35
+ %i[pointer pointer pointer int pointer], :int
36
+ attach_function :png_image_write_to_memory,
37
+ %i[pointer pointer pointer int pointer int pointer], :int
38
+ attach_function :png_image_free, [:pointer], :void
39
+
40
+ # libpng standard (non-simplified) write API.
41
+ attach_function :png_create_write_struct,
42
+ %i[string pointer pointer pointer], :pointer
43
+ attach_function :png_create_info_struct, [:pointer], :pointer
44
+ attach_function :png_destroy_write_struct, %i[pointer pointer], :void
45
+ attach_function :png_set_IHDR,
46
+ %i[pointer pointer uint32 uint32 int int int int int], :void
47
+ attach_function :png_set_rows, %i[pointer pointer pointer], :void
48
+ attach_function :png_set_PLTE, %i[pointer pointer pointer int], :void
49
+ attach_function :png_set_tRNS,
50
+ %i[pointer pointer pointer int pointer], :void
51
+ attach_function :png_set_filter, %i[pointer int int], :void
52
+ attach_function :png_set_compression_level, %i[pointer int], :void
53
+ attach_function :png_set_write_fn,
54
+ %i[pointer pointer pointer pointer], :void
55
+ attach_function :png_write_png, %i[pointer pointer int pointer], :void
56
+ end
57
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Libpng
4
+ # Maps pixel formats to bytes-per-pixel. Used by both the simplified
5
+ # and standard encoders / decoders to compute row stride.
6
+ #
7
+ # Pure data class -- no I/O, no allocations beyond the lookup itself.
8
+ module BytesPerPixel
9
+ # Per-channel byte count for the supported bit depths. Linear (16-bit)
10
+ # formats aren't currently exposed via the simplified API path; the
11
+ # standard encoder uses this table when bit_depth: 16 is requested.
12
+ CHANNEL_BYTES_8_BIT = 1
13
+ CHANNEL_BYTES_16_BIT = 2
14
+
15
+ # Channels per PNG_COLOR_TYPE_* (png.h).
16
+ CHANNELS_BY_COLOR_TYPE = {
17
+ Libpng::COLOR_TYPE_GRAY => 1,
18
+ Libpng::COLOR_TYPE_GRAY_ALPHA => 2,
19
+ Libpng::COLOR_TYPE_RGB => 3,
20
+ Libpng::COLOR_TYPE_RGB_ALPHA => 4,
21
+ Libpng::COLOR_TYPE_PALETTE => 1
22
+ }.freeze
23
+
24
+ def self.for_format(format_value)
25
+ case format_value
26
+ when Libpng::FORMAT_GRAY then 1
27
+ when Libpng::FORMAT_GA, Libpng::FORMAT_AG then 2
28
+ when Libpng::FORMAT_RGB, Libpng::FORMAT_BGR then 3
29
+ when Libpng::FORMAT_RGBA, Libpng::FORMAT_ARGB,
30
+ Libpng::FORMAT_BGRA, Libpng::FORMAT_ABGR then 4
31
+ else
32
+ raise Libpng::Error, "no bytes-per-pixel mapping for format 0x#{format_value.to_s(16)}"
33
+ end
34
+ end
35
+
36
+ def self.for_color_type(color_type, bit_depth: 8)
37
+ channels = CHANNELS_BY_COLOR_TYPE[color_type] ||
38
+ raise(Libpng::Error, "unknown color_type #{color_type}")
39
+ channel_bytes = bit_depth == 16 ? CHANNEL_BYTES_16_BIT : CHANNEL_BYTES_8_BIT
40
+ channels * channel_bytes
41
+ end
42
+ end
43
+ end