libpng 1.6.58.0 → 1.6.58.2

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: 424bb2ec057dfcb24902b7d42a4ca73936d54cd30ca0cbdb81df8277a97d3ba1
4
- data.tar.gz: f424a0836d9ce74aaccacd16ab7aa6cb60aee534576b25a72a4d22d9752b4adc
3
+ metadata.gz: bd40beeea8a0333bc4f83c461da33eec3bb0ffdcf9982dc289efab87da554d17
4
+ data.tar.gz: d9d837770569d6fdc083d2d074138679c70b43eeac8f666f906fb44f053ec2ef
5
5
  SHA512:
6
- metadata.gz: dd873ee26bb9b3a7e2c52cded5e501de1ed59bc1c1250132c60ef71ffdc99af9e5d6a09aa3399f5ddee3eff7a4ced98772fa3eb2f058076e7410e770bd0225ea
7
- data.tar.gz: 40b078365a95aff0413904b94092eed3f76f6f0cc42dd637d5b16c1ee1d72fe2cb05c89ac9f0513f140591dc28ccbc84f997dc3f64ed0e03e53487dd04b9da70
6
+ metadata.gz: 621b7892d277ccdb0ab0535c38ee69c11b9e4b866e05ec7ef7248fd1df9bc4aa6f5a81a5b560eb67a6491ce00c76f853c3e7d3014273890d2dc9a11b76beb1a6
7
+ data.tar.gz: ec661e92e1f7c8b3ddeae8c2b43dc5ff9795ae2ee5aa34b344887134c605e54513b41209e19df74bc04d3bc4e63ed2524202b153841a724a98b48944515276e6
@@ -53,7 +53,7 @@ jobs:
53
53
 
54
54
  - name: Install build tools (Windows)
55
55
  if: startsWith(matrix.os, 'windows')
56
- uses: ilammy/msvc-dev-tools@v1
56
+ uses: step-security/msvc-dev-cmd@v1
57
57
 
58
58
  - run: bundle exec rake
59
59
 
@@ -71,15 +71,24 @@ jobs:
71
71
  - os: ubuntu-latest
72
72
  platform: x86_64-linux
73
73
  ruby-version: ${{ needs.prepare.outputs.default-ruby-version }}
74
+ - os: ubuntu-24.04-arm
75
+ platform: aarch64-linux
76
+ ruby-version: ${{ needs.prepare.outputs.default-ruby-version }}
74
77
  - os: windows-latest
75
78
  platform: x64-mingw32
76
79
  ruby-version: ${{ needs.prepare.outputs.default-ruby-version }}
77
80
  - os: windows-latest
78
81
  platform: x64-mingw-ucrt
79
82
  ruby-version: ${{ needs.prepare.outputs.default-ruby-version }}
80
- - os: macos-latest
83
+ - os: windows-11-arm
84
+ platform: aarch64-mingw-ucrt
85
+ ruby-version: '3.4'
86
+ - os: macos-15-intel
81
87
  platform: x86_64-darwin
82
88
  ruby-version: ${{ needs.prepare.outputs.default-ruby-version }}
89
+ - os: macos-latest
90
+ platform: arm64-darwin
91
+ ruby-version: ${{ needs.prepare.outputs.default-ruby-version }}
83
92
 
84
93
  steps:
85
94
  - uses: actions/checkout@v4
@@ -103,7 +112,9 @@ jobs:
103
112
 
104
113
  - name: Install build tools (Windows)
105
114
  if: startsWith(matrix.os, 'windows')
106
- uses: ilammy/msvc-dev-tools@v1
115
+ uses: step-security/msvc-dev-cmd@v1
116
+ with:
117
+ arch: ${{ matrix.platform == 'aarch64-mingw-ucrt' && 'arm64' || 'x64' }}
107
118
 
108
119
  - run: bundle exec rake gem:native:${{ matrix.platform }}
109
120
 
@@ -116,5 +127,64 @@ jobs:
116
127
  run: gem install -b pkg/libpng-*.gem
117
128
 
118
129
  - name: Test conversion
130
+ shell: bash
131
+ run: |
132
+ ruby -rlibpng -e 'png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); File.binwrite("test.png", png); puts Libpng.decode(png, pixel_format: "RGBA").inspect'
133
+
134
+ build_musl:
135
+ name: build ${{ matrix.platform }} (Alpine)
136
+ runs-on: ${{ matrix.os }}
137
+ needs: prepare
138
+ strategy:
139
+ fail-fast: false
140
+ matrix:
141
+ include:
142
+ - os: ubuntu-latest
143
+ platform: x86_64-linux-musl
144
+ - os: ubuntu-24.04-arm
145
+ platform: aarch64-linux-musl
146
+
147
+ steps:
148
+ - uses: actions/checkout@v4
149
+
150
+ # Run the entire build inside an Alpine container via `docker run` rather
151
+ # than the workflow `container:` field. The latter only works on x64
152
+ # runners (Actions' JS runtime has no arm64-musl build), but arm64 Ubuntu
153
+ # runners can run arm64 Alpine images natively via Docker.
154
+ - name: Build in Alpine container
155
+ shell: bash
156
+ env:
157
+ PLATFORM: ${{ matrix.platform }}
158
+ RUBY_VERSION: ${{ needs.prepare.outputs.default-ruby-version }}
119
159
  run: |
120
- ruby -rlibpng -e "png = Libpng.encode(2, 2, \"\\xff\" * 16, pixel_format: 'RGBA'); File.binwrite('test.png', png); puts Libpng.decode(png, pixel_format: 'RGBA').inspect"
160
+ cat > /tmp/alpine-build.sh <<'BUILD_EOF'
161
+ #!/bin/sh
162
+ set -e
163
+ apk add --no-cache build-base cmake ninja git file zlib-dev
164
+ # The mounted /work volume is owned by the host runner user; git in
165
+ # the container (running as root) refuses to operate on it without
166
+ # this. Without it, `git ls-files` in the gemspec returns empty and
167
+ # the built gem is missing lib/libpng.rb.
168
+ git config --global --add safe.directory /work
169
+ bundle install --jobs 4
170
+ 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
+ BUILD_EOF
178
+ chmod +x /tmp/alpine-build.sh
179
+ docker run --rm \
180
+ -v "$PWD:/work" \
181
+ -v /tmp/alpine-build.sh:/alpine-build.sh \
182
+ -w /work \
183
+ -e PLATFORM \
184
+ ruby:${RUBY_VERSION}-alpine \
185
+ /alpine-build.sh
186
+
187
+ - uses: actions/upload-artifact@v4
188
+ with:
189
+ name: pkg-${{ matrix.platform }}
190
+ path: pkg/*.gem
@@ -129,15 +129,24 @@ jobs:
129
129
  - os: ubuntu-latest
130
130
  platform: x86_64-linux
131
131
  ruby-version: '3.3'
132
+ - os: ubuntu-24.04-arm
133
+ platform: aarch64-linux
134
+ ruby-version: '3.3'
132
135
  - os: windows-latest
133
136
  platform: x64-mingw32
134
137
  ruby-version: '3.3'
135
138
  - os: windows-latest
136
139
  platform: x64-mingw-ucrt
137
140
  ruby-version: '3.3'
138
- - os: macos-latest
141
+ - os: windows-11-arm
142
+ platform: aarch64-mingw-ucrt
143
+ ruby-version: '3.4'
144
+ - os: macos-15-intel
139
145
  platform: x86_64-darwin
140
146
  ruby-version: '3.3'
147
+ - os: macos-latest
148
+ platform: arm64-darwin
149
+ ruby-version: '3.3'
141
150
  steps:
142
151
  - uses: actions/checkout@v4
143
152
  with:
@@ -161,7 +170,9 @@ jobs:
161
170
 
162
171
  - name: Install build tools (Windows)
163
172
  if: startsWith(matrix.os, 'windows')
164
- uses: ilammy/msvc-dev-tools@v1
173
+ uses: step-security/msvc-dev-cmd@v1
174
+ with:
175
+ arch: ${{ matrix.platform == 'aarch64-mingw-ucrt' && 'arm64' || 'x64' }}
165
176
 
166
177
  - run: bundle exec rake gem:native:${{ matrix.platform }}
167
178
 
@@ -174,10 +185,11 @@ jobs:
174
185
  run: gem install -b pkg/libpng-*.gem
175
186
 
176
187
  - name: Test conversion
188
+ shell: bash
177
189
  run: |
178
- ruby -rlibpng -e "png = Libpng.encode(2, 2, \"\\xff\" * 16, pixel_format: 'RGBA'); puts Libpng.decode(png, pixel_format: 'RGBA').inspect"
190
+ ruby -rlibpng -e 'png = Libpng.encode(2, 2, "\xff" * 16, pixel_format: "RGBA"); puts Libpng.decode(png, pixel_format: "RGBA").inspect'
179
191
 
180
- cross:
192
+ build_musl:
181
193
  needs: bump
182
194
  if: always() && !cancelled() && !failure() && (needs.bump.result == 'success' || needs.bump.result == 'skipped')
183
195
  runs-on: ${{ matrix.os }}
@@ -186,43 +198,45 @@ jobs:
186
198
  matrix:
187
199
  include:
188
200
  - os: ubuntu-latest
189
- platform: aarch64-linux
190
- ruby-version: '3.3'
191
- - os: macos-latest
192
- platform: arm64-darwin
193
- ruby-version: '3.3'
201
+ platform: x86_64-linux-musl
202
+ - os: ubuntu-24.04-arm
203
+ platform: aarch64-linux-musl
194
204
  steps:
195
205
  - uses: actions/checkout@v4
196
206
  with:
197
207
  ref: ${{ needs.bump.outputs.sha || github.ref }}
198
208
 
199
- - name: Install Ubuntu cross-build packages
200
- if: startsWith(matrix.os, 'ubuntu')
209
+ - name: Build in Alpine container
210
+ shell: bash
211
+ env:
212
+ PLATFORM: ${{ matrix.platform }}
201
213
  run: |
202
- sudo apt-get update
203
- sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu binutils-aarch64-linux-gnu cmake ninja-build zlib1g-dev:arm64
204
-
205
- - name: Install macOS build packages
206
- if: startsWith(matrix.os, 'macos')
207
- run: brew install cmake ninja
208
-
209
- - uses: ruby/setup-ruby@master
210
- with:
211
- ruby-version: ${{ matrix.ruby-version }}
212
- bundler-cache: true
213
-
214
- - run: bundle exec rake gem:native:${{ matrix.platform }}
214
+ cat > /tmp/alpine-build.sh <<'BUILD_EOF'
215
+ #!/bin/sh
216
+ set -e
217
+ apk add --no-cache build-base cmake ninja git file zlib-dev
218
+ git config --global --add safe.directory /work
219
+ bundle install --jobs 4
220
+ 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')
223
+ BUILD_EOF
224
+ chmod +x /tmp/alpine-build.sh
225
+ docker run --rm \
226
+ -v "$PWD:/work" \
227
+ -v /tmp/alpine-build.sh:/alpine-build.sh \
228
+ -w /work \
229
+ -e PLATFORM \
230
+ ruby:3.3-alpine \
231
+ /alpine-build.sh
215
232
 
216
233
  - uses: actions/upload-artifact@v4
217
234
  with:
218
235
  name: pkg-${{ matrix.platform }}
219
236
  path: pkg/*.gem
220
237
 
221
- - name: Install gem
222
- run: gem install -b pkg/libpng-*.gem
223
-
224
238
  publish:
225
- needs: [ bump, build, cross ]
239
+ needs: [ bump, build, build_musl ]
226
240
  if: always() && !cancelled() && !failure()
227
241
  runs-on: ubuntu-latest
228
242
  steps:
@@ -245,5 +259,23 @@ jobs:
245
259
  uses: rubygems/configure-rubygems-credentials@main
246
260
 
247
261
  - name: Publish to rubygems.org
262
+ shell: bash
248
263
  run: |
249
- for gem in pkg/*.gem; do gem push -V "$gem"; done
264
+ set +e
265
+ for gem in pkg/*.gem; do
266
+ output=$(gem push -V "$gem" 2>&1)
267
+ rc=$?
268
+ if [ $rc -eq 0 ]; then
269
+ echo "$output"
270
+ continue
271
+ fi
272
+ # RubyGems rejects re-pushing the same version with 422, even for
273
+ # identical content. Tolerate that so we can publish newly-added
274
+ # platform variants at a version whose source gem already exists.
275
+ if echo "$output" | grep -qE "already (exists|pushed)|Conflict|Repushing"; then
276
+ echo "Skip $(basename "$gem") (already published)"
277
+ continue
278
+ fi
279
+ echo "$output" >&2
280
+ exit 1
281
+ done
data/.rubocop.yml CHANGED
@@ -10,15 +10,15 @@ Gemspec/DevelopmentDependencies:
10
10
  # methods that walk libpng's chunked API; splitting would obscure the
11
11
  # 1-to-1 mapping with the C API.
12
12
  Metrics/AbcSize:
13
- Max: 60
13
+ Max: 70
14
14
  Metrics/ClassLength:
15
15
  Max: 200
16
16
  Metrics/CyclomaticComplexity:
17
17
  Max: 20
18
18
  Metrics/MethodLength:
19
- Max: 50
19
+ Max: 70
20
20
  Metrics/ModuleLength:
21
- Max: 250
21
+ Max: 350
22
22
  Metrics/PerceivedComplexity:
23
23
  Max: 20
24
24
  Metrics/ParameterLists:
data/README.adoc CHANGED
@@ -22,9 +22,33 @@ Or install manually:
22
22
 
23
23
  gem install libpng
24
24
 
25
- The gem downloads a pre-compiled binary for your platform automatically.
26
- No C compiler, libpng development headers, or system packages are needed
27
- at install time.
25
+ RubyGems automatically selects the pre-compiled gem that matches your
26
+ platform. No C compiler, libpng development headers, or system packages
27
+ are required at install time when a binary gem is available. The
28
+ platform-agnostic `ruby` gem is the fallback: it downloads the libpng
29
+ source tarball at install time and compiles it via CMake (requires
30
+ `cmake`, `ninja`, and zlib headers).
31
+
32
+ == Supported platforms
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]
36
+ for the current set):
37
+
38
+ [cols="1,1,3", options="header"]
39
+ |===
40
+ | Gem platform | Target | Build host
41
+ | `ruby` (source) | Any platform (compiles on install) | n/a
42
+ | `x86_64-linux` | x86_64 Linux (glibc) | `ubuntu-latest`
43
+ | `x86_64-linux-musl` | x86_64 Linux (musl, e.g. Alpine) | `ruby:<ver>-alpine` on x86_64
44
+ | `aarch64-linux` | ARM64 Linux (glibc) | `ubuntu-24.04-arm` (native)
45
+ | `aarch64-linux-musl` | ARM64 Linux (musl) | `ruby:<ver>-alpine` on arm64
46
+ | `x64-mingw32` | x64 Windows, RubyInstaller < 3.0 (MSVCRT) | `windows-latest`
47
+ | `x64-mingw-ucrt` | x64 Windows, RubyInstaller >= 3.0 (UCRT) | `windows-latest`
48
+ | `aarch64-mingw-ucrt` | ARM64 Windows (Ruby >= 3.4) | `windows-11-arm` (native)
49
+ | `x86_64-darwin` | Intel macOS | `macos-15-intel`
50
+ | `arm64-darwin` | Apple Silicon macOS | `macos-latest`
51
+ |===
28
52
 
29
53
  == Usage
30
54
 
@@ -52,12 +76,57 @@ at install time.
52
76
 
53
77
  Libpng.encode(width, height, pixels,
54
78
  pixel_format: "RGBA",
55
- convert_to_8bit: false)
79
+ convert_to_8bit: false,
80
+ strip_colorspace: true)
56
81
 
57
- `convert_to_8bit: true` causes libpng to convert 16-bit input to 8-bit
82
+ `convert_to_8bit: true`:: causes libpng to convert 16-bit input to 8-bit
58
83
  on write (input is still passed as a packed byte buffer; use this if you
59
84
  have packed 16-bit-per-channel data).
60
85
 
86
+ `strip_colorspace: true`:: (default) walks the PNG chunk list after
87
+ encode and keeps only `IHDR`, `IDAT`, and `IEND`, dropping the sRGB/gAMA
88
+ chunks libpng's simplified API emits by default. This matches the
89
+ "classic" libpng write path (e.g. libemf2svg) and produces byte-identical
90
+ output. Set to `false` to keep the ancillary color chunks.
91
+
92
+ === Standard API (encode_standard)
93
+
94
+ For parity with libpng's classic write path -- the same code path used
95
+ by `libemf2svg`'s `rgb2png` -- use `encode_standard` instead of
96
+ `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:
99
+
100
+ Libpng.encode_standard(width, height, pixels,
101
+ pixel_format: "RGBA",
102
+ filter: :default,
103
+ compression_level: 6)
104
+
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.
108
+
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`.
112
+
113
+ `compression_level`:: zlib level 0-9. Default 6 (Z_DEFAULT_COMPRESSION,
114
+ matching libemf2svg).
115
+
116
+ Output is written directly into a Ruby String via `png_set_write_fn` --
117
+ no `Tempfile`, no disk I/O. The FFI write callback is held on a local
118
+ Array so it survives the libpng calls.
119
+
120
+ Differences from `encode`:
121
+ - Emits only `IHDR` / `IDAT` / `IEND` (no post-hoc chunk stripping
122
+ needed).
123
+ - Standard API only -- no `convert_to_8bit` option.
124
+ - Errors are raised via a `png_set_error_fn` callback (set on
125
+ `png_create_write_struct`). libpng expects error callbacks to
126
+ longjmp; we rb_raise instead, which unwinds the Ruby stack. The
127
+ `ensure` block calls `png_destroy_write_struct` to release the C
128
+ state.
129
+
61
130
  == Ractor safety
62
131
 
63
132
  `Libpng.encode` and `Libpng.decode` are Ractor-safe. Every call
@@ -65,25 +134,26 @@ allocates and frees its own libpng `png_image`; no shared mutable state
65
134
  exists on the Ruby side. Calls from multiple Ractors run concurrently
66
135
  without locking.
67
136
 
68
- Example:
137
+ Example (Ruby 3.x uses `Ractor#take`; Ruby 4.0+ uses `Ractor#value`):
69
138
 
70
139
  r = Ractor.new do
71
140
  Libpng.encode(2, 2, "\xFF" * 16, pixel_format: "RGBA")
72
141
  end
73
- r.take # => PNG binary
142
+ r.take # Ruby 3.x
143
+ r.value # Ruby 4.0+ (Ractor#take was removed in 4.0)
74
144
 
75
145
  == Versioning
76
146
 
77
147
  This gem follows a `{LIBPNG_VERSION}.{ITERATION}` scheme:
78
148
 
79
149
  - `LIBPNG_VERSION` is the upstream libpng release the gem is built
80
- against (currently 1.6.48).
150
+ against (currently `1.6.58`).
81
151
  - `ITERATION` is a counter that bumps on Ruby-side changes (recipe bug
82
152
  fixes, CI tweaks, doc updates). It resets to 0 each time
83
153
  `LIBPNG_VERSION` bumps.
84
154
 
85
- For example, `1.6.48.0` is the first release against libpng 1.6.48,
86
- `1.6.48.1` is a Ruby-side fix, and `1.6.49.0` would be a release against
155
+ For example, `1.6.58.0` is the first release against libpng 1.6.58,
156
+ `1.6.58.1` is a Ruby-side fix, and `1.6.59.0` would be a release against
87
157
  the next upstream libpng.
88
158
 
89
159
  == How it works
@@ -91,15 +161,54 @@ the next upstream libpng.
91
161
  The gem has three layers:
92
162
 
93
163
  1. `lib/libpng.rb` -- Ruby FFI wrapper. Loads `libpng16.{so,dylib,dll}`
94
- from the gem's `lib/libpng/` directory at runtime.
95
- 2. `lib/libpng/recipe.rb` -- a MiniPortileCMake recipe that downloads
96
- the libpng source tarball and runs CMake to produce the shared
97
- library. This is invoked by `ext/extconf.rb` when the gem is installed
98
- from source.
99
- 3. `Rakefile` defines `gem:native:<platform>` tasks that pre-compile
100
- the gem for each target platform. The pre-compiled gems bundle the
101
- `.so`/`.dylib`/`.dll` and disable `extconf.rb`, so installation
102
- requires no C compiler.
164
+ from the gem's `lib/libpng/` directory at runtime and exposes
165
+ `Libpng.encode` / `Libpng.decode` against libpng's "simplified"
166
+ high-level C API (`png_image_*`). The wrapper manually lays out the
167
+ `png_image` struct via explicit byte offsets into an
168
+ `FFI::MemoryPointer` rather than `FFI::Struct`, because `FFI::Struct`
169
+ carries class-level state that is not shareable across non-main
170
+ Ractors.
171
+
172
+ 2. `lib/libpng/recipe.rb` -- a `MiniPortileCMake` recipe that downloads
173
+ the libpng source tarball pinned by `LIBPNG_URL` / `LIBPNG_SHA256`,
174
+ runs CMake with `PNG_SHARED=ON PNG_STATIC=OFF PNG_TESTS=OFF`, and
175
+ copies the resulting shared lib into `lib/libpng/`. Invoked only when
176
+ installing the **source** (`ruby` platform) gem.
177
+
178
+ 3. `ext/extconf.rb` -- gem extension entry point. Calls `Recipe#cook_if_not`,
179
+ which is a no-op once the per-platform checkpoint file
180
+ (`ports/.../<name>-<ver>-<platform>.installed`) exists. After cooking,
181
+ emits a dummy `Makefile` via `mkmf` so RubyGems is satisfied (no Ruby
182
+ C extension is compiled). The pre-compiled platform gems unset
183
+ `spec.extensions`, so this never runs for them.
184
+
185
+ === Pre-compiled platform gems
186
+
187
+ `Rakefile` defines `gem:native:<platform>` tasks for each target. Each
188
+ task clones the gemspec, sets `spec.platform`, adds
189
+ `lib/libpng/*.{dll,so,dylib}` to `spec.files`, removes the
190
+ `mini_portile2` dependency (no longer needed at install time), and
191
+ unsets `spec.extensions` -- the resulting gem installs with no compiler.
192
+
193
+ The release workflow (`.github/workflows/release.yml`) builds all 10
194
+ platforms in parallel on the appropriate native runner:
195
+
196
+ - x86_64 Linux glibc -- `ubuntu-latest`
197
+ - ARM64 Linux glibc -- `ubuntu-24.04-arm` (native)
198
+ - x86_64 Linux musl -- `ruby:<ver>-alpine` via `docker run` on `ubuntu-latest`
199
+ - ARM64 Linux musl -- `ruby:<ver>-alpine` via `docker run` on `ubuntu-24.04-arm`
200
+ - x64 Windows (both MSVCRT and UCRT) -- `windows-latest` with `step-security/msvc-dev-cmd`
201
+ - ARM64 Windows -- `windows-11-arm` (native) with `arch: arm64`
202
+ - Intel macOS -- `macos-15-intel`
203
+ - Apple Silicon macOS -- `macos-latest`
204
+
205
+ Linux glibc and macOS builds run natively on the matching runner. Linux
206
+ musl builds run inside `ruby:<ver>-alpine` containers via `docker run`
207
+ rather than the workflow `container:` field, because Actions' JS runtime
208
+ has no arm64-musl build and therefore cannot run Alpine containers on
209
+ arm64 runners. Windows builds use the Visual Studio toolchain via
210
+ `step-security/msvc-dev-cmd` (a maintained drop-in for the deleted
211
+ `ilammy/msvc-dev-tools`).
103
212
 
104
213
  == License
105
214
 
data/Rakefile CHANGED
@@ -34,8 +34,11 @@ end
34
34
  platforms = %w[
35
35
  x64-mingw32
36
36
  x64-mingw-ucrt
37
+ aarch64-mingw-ucrt
37
38
  x86_64-linux
39
+ x86_64-linux-musl
38
40
  aarch64-linux
41
+ aarch64-linux-musl
39
42
  x86_64-darwin
40
43
  arm64-darwin
41
44
  ]
data/ext/extconf.rb CHANGED
@@ -1,6 +1,12 @@
1
1
  $: << File.expand_path(File.join(File.dirname(__FILE__), '../lib'))
2
2
 
3
3
  require 'libpng/recipe'
4
+ require 'mkmf'
4
5
 
5
6
  recipe = Libpng::Recipe.new
6
7
  recipe.cook_if_not
8
+
9
+ # RubyGems requires every extconf.rb to leave a Makefile behind, even if
10
+ # no native extension is compiled (libpng is built by Recipe#cook above
11
+ # and loaded via FFI at runtime). dummy_makefile satisfies that contract.
12
+ create_makefile('libpng/dummy')
data/lib/libpng/recipe.rb CHANGED
@@ -32,6 +32,7 @@ module Libpng
32
32
 
33
33
  @target = ROOT.join(@target).to_s
34
34
  @printed = {}
35
+ setup_cross_compile if cross_compile?
35
36
  end
36
37
 
37
38
  # libpng ships a CMake build alongside the autotools one. We use CMake
@@ -69,10 +70,12 @@ module Libpng
69
70
 
70
71
  def install
71
72
  super
72
- # After `make install`, the .so/.dylib/.dll is under ports/<name>/<ver>/lib/.
73
- # Copy it into the gem's lib/libpng/ so FFI can load it at runtime.
74
- libs = Dir.glob(File.join(port_path, 'lib', shared_lib_glob))
75
- raise "no libpng shared lib produced at #{port_path}/lib" if libs.empty?
73
+ # After `make install`, the shared lib lives under ports/<name>/<ver>/.
74
+ # On Linux/macOS that's lib/. On Windows, CMake's GNUInstallDirs puts
75
+ # the .dll in bin/ and the import library (.dll.a) in lib/ — we only
76
+ # ship the .dll, so search both.
77
+ libs = Dir.glob(File.join(port_path, shared_lib_install_glob))
78
+ raise "no libpng shared lib produced under #{port_path}" if libs.empty?
76
79
 
77
80
  target_dir = ROOT.join('lib', 'libpng')
78
81
  FileUtils.mkdir_p(target_dir)
@@ -125,6 +128,16 @@ module Libpng
125
128
  end
126
129
  end
127
130
 
131
+ # Glob (with port_path prefix) for the freshly installed shared lib.
132
+ # On Windows the .dll installs to bin/; on Unix-likes it stays in lib/.
133
+ def shared_lib_install_glob
134
+ if MiniPortile.windows?
135
+ '{bin,lib}/libpng16*.dll'
136
+ else
137
+ "lib/#{shared_lib_glob}"
138
+ end
139
+ end
140
+
128
141
  def each_built_lib(&block)
129
142
  Dir.glob(ROOT.join('lib', 'libpng', shared_lib_glob)).each(&block)
130
143
  end
@@ -134,10 +147,16 @@ module Libpng
134
147
  case @host
135
148
  when /\Ax86_64.*mingw32/
136
149
  'x64-mingw32'
150
+ when /\A(aarch64|arm64).*mingw/
151
+ 'aarch64-mingw-ucrt'
152
+ when /\Ax86_64.*linux-musl/
153
+ 'x86_64-linux-musl'
154
+ when /\A(aarch64|arm64).*linux-musl/
155
+ 'aarch64-linux-musl'
137
156
  when /\Ax86_64.*linux/
138
157
  'x86_64-linux'
139
- when /\A(arm64|aarch64).*linux/
140
- 'arm64-linux'
158
+ when /\A(aarch64|arm64).*linux/
159
+ 'aarch64-linux'
141
160
  when /\Ax86_64.*(darwin|macos|osx)/
142
161
  'x86_64-darwin'
143
162
  when /\A(arm64|aarch64).*(darwin|macos|osx)/
@@ -154,13 +173,49 @@ module Libpng
154
173
  'arm64-darwin'
155
174
  when /\Ax86_64.*(darwin|macos|osx)/
156
175
  'x86_64-darwin'
176
+ when /\A(arm64|aarch64).*linux-musl/
177
+ 'aarch64-linux-musl'
157
178
  when /\A(arm64|aarch64).*linux/
158
179
  'aarch64-linux'
180
+ when /\Ax86_64.*linux-musl/
181
+ 'x86_64-linux-musl'
159
182
  else
160
183
  ENV.fetch('target_platform', host_platform)
161
184
  end
162
185
  end
163
186
 
187
+ def cross_compile?
188
+ target_platform != host_platform
189
+ end
190
+
191
+ # Configure MiniPortile + CMake for cross-compilation. Native builds
192
+ # (host_platform == target_platform) skip this entirely.
193
+ def setup_cross_compile
194
+ # All targeted platforms now have native runners (ubuntu-24.04-arm for
195
+ # aarch64-linux, windows-11-arm for aarch64-mingw-ucrt, Alpine containers
196
+ # for the musl variants). This hook is kept as a seam for future
197
+ # cross-compile targets (e.g. aarch64-linux on an x86_64 host).
198
+ end
199
+
200
+ def cpu_type
201
+ case target_platform
202
+ when 'aarch64-linux', 'aarch64-linux-musl', 'arm64-darwin', 'aarch64-mingw-ucrt' then 'aarch64'
203
+ when 'x86_64-linux', 'x86_64-linux-musl', 'x86_64-darwin', /\Ax64-mingw/ then 'x86_64'
204
+ else
205
+ super
206
+ end
207
+ end
208
+
209
+ def cmake_system_name
210
+ case target_platform
211
+ when 'aarch64-linux', 'x86_64-linux', 'aarch64-linux-musl', 'x86_64-linux-musl' then 'Linux'
212
+ when 'arm64-darwin', 'x86_64-darwin' then 'Darwin'
213
+ when /\A(aarch64-)?mingw/, /\Ax64-mingw/ then 'Windows'
214
+ else
215
+ super
216
+ end
217
+ end
218
+
164
219
  def target_format
165
220
  @target_format ||=
166
221
  case target_platform
@@ -168,10 +223,12 @@ module Libpng
168
223
  /Mach-O 64-bit dynamically linked shared library arm64/
169
224
  when 'x86_64-darwin'
170
225
  /Mach-O 64-bit dynamically linked shared library x86_64/
171
- when 'aarch64-linux'
226
+ when 'aarch64-linux', 'aarch64-linux-musl'
172
227
  /ELF 64-bit LSB shared object, ARM aarch64/
173
- when 'x86_64-linux'
228
+ when 'x86_64-linux', 'x86_64-linux-musl'
174
229
  /ELF 64-bit LSB shared object, x86-64/
230
+ when 'aarch64-mingw-ucrt'
231
+ /PE32\+ executable.*\(DLL\).*ARM64/
175
232
  when /\Ax64-mingw(32|-ucrt)/
176
233
  /PE32\+ executable.*\(DLL\).*x86-64/
177
234
  else
@@ -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 = 0
14
+ LIBPNG_RUBY_ITERATION = 2
15
15
  VERSION = "#{LIBPNG_VERSION}.#{LIBPNG_RUBY_ITERATION}"
16
16
  end
data/lib/libpng.rb CHANGED
@@ -45,6 +45,20 @@ module Libpng
45
45
  %i[pointer pointer pointer int pointer int pointer], :int
46
46
  attach_function :png_image_free, [:pointer], :void
47
47
 
48
+ # libpng standard (non-simplified) write API. Used by encode_standard.
49
+ attach_function :png_create_write_struct,
50
+ %i[string pointer pointer pointer], :pointer
51
+ attach_function :png_create_info_struct, [:pointer], :pointer
52
+ attach_function :png_destroy_write_struct, %i[pointer pointer], :void
53
+ attach_function :png_set_IHDR,
54
+ %i[pointer pointer uint32 uint32 int int int int int], :void
55
+ attach_function :png_set_rows, %i[pointer pointer pointer], :void
56
+ attach_function :png_set_filter, %i[pointer int int], :void
57
+ attach_function :png_set_compression_level, %i[pointer int], :void
58
+ attach_function :png_set_write_fn,
59
+ %i[pointer pointer pointer pointer], :void
60
+ attach_function :png_write_png, %i[pointer pointer int pointer], :void
61
+
48
62
  # PNG_IMAGE_FORMAT_* bit flags and named formats from png.h.
49
63
  FORMAT_FLAG_ALPHA = 0x01
50
64
  FORMAT_FLAG_COLOR = 0x02
@@ -77,6 +91,64 @@ module Libpng
77
91
  'ABGR' => FORMAT_ABGR
78
92
  }.freeze
79
93
 
94
+ # PNG_COLOR_MASK_* and PNG_COLOR_TYPE_* (png.h).
95
+ COLOR_MASK_PALETTE = 0x01
96
+ COLOR_MASK_COLOR = 0x02
97
+ COLOR_MASK_ALPHA = 0x04
98
+
99
+ COLOR_TYPE_GRAY = 0
100
+ COLOR_TYPE_PALETTE = COLOR_MASK_COLOR | COLOR_MASK_PALETTE
101
+ COLOR_TYPE_RGB = COLOR_MASK_COLOR
102
+ COLOR_TYPE_RGB_ALPHA = COLOR_MASK_COLOR | COLOR_MASK_ALPHA
103
+ COLOR_TYPE_GRAY_ALPHA = COLOR_MASK_ALPHA
104
+
105
+ # Map our FORMAT_* to PNG_COLOR_TYPE_*. encode_standard doesn't support
106
+ # the BGR/ARGB/BGRA/ABGR byte-order variants because the standard API
107
+ # encodes host-order; callers wanting those formats should use the
108
+ # simplified encode path or pre-swap bytes.
109
+ COLOR_TYPE_BY_FORMAT = {
110
+ FORMAT_GRAY => COLOR_TYPE_GRAY,
111
+ FORMAT_GA => COLOR_TYPE_GRAY_ALPHA,
112
+ FORMAT_AG => COLOR_TYPE_GRAY_ALPHA,
113
+ FORMAT_RGB => COLOR_TYPE_RGB,
114
+ FORMAT_RGBA => COLOR_TYPE_RGB_ALPHA
115
+ }.freeze
116
+
117
+ # png_set_IHDR pass-through constants (png.h).
118
+ INTERLACE_NONE = 0
119
+ INTERLACE_ADAM7 = 1
120
+ COMPRESSION_TYPE_DEFAULT = 0
121
+ FILTER_TYPE_DEFAULT = 0
122
+ TRANSFORM_IDENTITY = 0x0000
123
+
124
+ # png_set_filter bitmask (PNG_FILTER_*). :default lets libpng use
125
+ # adaptive filtering across all filter types -- this is what
126
+ # libemf2svg/rgb2png effectively does via PNG_FILTER_TYPE_DEFAULT in
127
+ # the IHDR.
128
+ FILTER_HEURISTIC_DEFAULT = 0
129
+ FILTER_NONE = 0x08
130
+ FILTER_SUB = 0x10
131
+ FILTER_UP = 0x20
132
+ FILTER_AVG = 0x40
133
+ FILTER_PAETH = 0x80
134
+ FILTER_ALL = FILTER_NONE | FILTER_SUB | FILTER_UP | FILTER_AVG | FILTER_PAETH
135
+
136
+ FILTER_MASK_BY_NAME = {
137
+ default: nil, # don't call png_set_filter at all
138
+ adaptive: FILTER_ALL, # call with all filters allowed (same as default)
139
+ none: FILTER_NONE,
140
+ sub: FILTER_SUB,
141
+ up: FILTER_UP,
142
+ avg: FILTER_AVG,
143
+ paeth: FILTER_PAETH,
144
+ all: FILTER_ALL
145
+ }.freeze
146
+
147
+ # PNG_LIBPNG_VER_STRING. Used as the user_png_ver arg to
148
+ # png_create_write_struct. Must match the libpng16.{so,dylib,dll} we
149
+ # ship -- if these ever drift, png_create_write_struct aborts.
150
+ LIBPNG_VER_STRING_C = Libpng::LIBPNG_VERSION.dup.freeze
151
+
80
152
  # png_image::version value libpng checks for. Defined in png.h as
81
153
  # PNG_IMAGE_VERSION == 1.
82
154
  PNG_IMAGE_VERSION = 1
@@ -192,6 +264,111 @@ module Libpng
192
264
  end
193
265
  end
194
266
 
267
+ # Encode raw pixels via libpng's standard write API
268
+ # (png_create_write_struct -> png_set_IHDR -> png_set_rows ->
269
+ # png_write_png(PNG_TRANSFORM_IDENTITY)). This matches the byte
270
+ # output of the "classic" libpng write path used by libemf2svg's
271
+ # rgb2png, and avoids the sRGB/gAMA chunks the simplified API
272
+ # injects by default.
273
+ #
274
+ # +width+, +height+ image dimensions in pixels
275
+ # +pixels+ String of raw pixel bytes (row-major, top-down)
276
+ # +pixel_format+ "RGB", "RGBA", "GRAY", "GA" (default: "RGBA")
277
+ # +filter+ :default (adaptive), :none, :sub, :up, :avg,
278
+ # :paeth, :all, :adaptive (default: :default)
279
+ # +compression_level+ zlib level 0-9 (default: 6 = Z_DEFAULT_COMPRESSION)
280
+ #
281
+ # Output is accumulated in a Ruby String via png_set_write_fn --
282
+ # no Tempfile, no disk I/O. The FFI write callback is held on a
283
+ # local Array so it isn't GC'd mid-call.
284
+ #
285
+ # Errors are raised via a png_set_error_fn callback (set on
286
+ # png_create_write_struct). libpng expects error callbacks to
287
+ # longjmp; rb_raise from the FFI callback unwinds the Ruby stack
288
+ # instead, which works for our usage but means the png_struct is
289
+ # not cleanly torn down inside libpng. The ensure block calls
290
+ # png_destroy_write_struct to release the C state.
291
+ #
292
+ # Ractor-safe: all state is per-call. FFI callbacks are not
293
+ # shareable across Ractors, but they're locals and stay scoped
294
+ # to one Ractor.
295
+ def encode_standard(width, height, pixels, pixel_format: 'RGBA',
296
+ filter: :default, compression_level: 6)
297
+ raise Error, 'width must be positive' unless width.positive?
298
+ raise Error, 'height must be positive' unless height.positive?
299
+
300
+ fmt = FORMAT_BY_NAME[pixel_format.to_s.upcase] ||
301
+ raise(Error, "unknown pixel_format #{pixel_format.inspect}")
302
+ color_type = COLOR_TYPE_BY_FORMAT[fmt] ||
303
+ raise(Error, "encode_standard does not support #{pixel_format} " \
304
+ '(use GRAY, GA, RGB, or RGBA; byte-order variants ' \
305
+ 'like BGRA/ARGB/ABGR are simplified-API only)')
306
+
307
+ bytes_per_pixel = bytes_per_pixel_for_format(fmt)
308
+ stride = width * bytes_per_pixel
309
+ expected = stride * height
310
+ raise Error, "pixels too short: expected #{expected}, got #{pixels.bytesize}" if pixels.bytesize < expected
311
+
312
+ filter_sym = filter.to_sym
313
+ unless FILTER_MASK_BY_NAME.key?(filter_sym)
314
+ raise Error, "unknown filter #{filter.inspect} " \
315
+ '(expected :default, :adaptive, :none, :sub, :up, :avg, :paeth, or :all)'
316
+ end
317
+ filter_mask = FILTER_MASK_BY_NAME[filter_sym]
318
+ raise Error, 'compression_level must be 0..9' unless (0..9).cover?(compression_level)
319
+
320
+ output = String.new.force_encoding('ASCII-8BIT')
321
+
322
+ # FFI::Function objects need a stable reference for the duration of
323
+ # the libpng calls -- otherwise they can be GC'd before libpng
324
+ # invokes them.
325
+ callbacks = []
326
+ write_cb = FFI::Function.new(:void, %i[pointer pointer size_t]) do |_, data, len|
327
+ output << data.read_bytes(len)
328
+ end
329
+ error_cb = FFI::Function.new(:void, %i[pointer string]) do |_, msg|
330
+ raise Error, "libpng: #{msg}"
331
+ end
332
+ callbacks << write_cb << error_cb
333
+
334
+ png_ptr = FFI::Pointer.new(0)
335
+ info_ptr = FFI::Pointer.new(0)
336
+ begin
337
+ png_ptr = png_create_write_struct(LIBPNG_VER_STRING_C, nil, error_cb, nil)
338
+ raise Error, 'png_create_write_struct returned NULL' if png_ptr.null?
339
+
340
+ info_ptr = png_create_info_struct(png_ptr)
341
+ raise Error, 'png_create_info_struct returned NULL' if info_ptr.null?
342
+
343
+ png_set_compression_level(png_ptr, compression_level)
344
+ png_set_write_fn(png_ptr, nil, write_cb, nil)
345
+ png_set_IHDR(png_ptr, info_ptr, width, height, 8, color_type,
346
+ INTERLACE_NONE, COMPRESSION_TYPE_DEFAULT, FILTER_TYPE_DEFAULT)
347
+ png_set_filter(png_ptr, FILTER_HEURISTIC_DEFAULT, filter_mask) if filter_mask
348
+
349
+ FFI::MemoryPointer.new(:uint8, pixels.bytesize) do |px|
350
+ px.write_bytes(pixels)
351
+ FFI::MemoryPointer.new(:pointer, height) do |rows|
352
+ height.times { |y| rows.put_pointer(y * FFI.type_size(:pointer), px + (y * stride)) }
353
+ png_set_rows(png_ptr, info_ptr, rows)
354
+ png_write_png(png_ptr, info_ptr, TRANSFORM_IDENTITY, nil)
355
+ end
356
+ end
357
+
358
+ output
359
+ ensure
360
+ unless png_ptr.null?
361
+ FFI::MemoryPointer.new(:pointer) do |pp|
362
+ pp.write_pointer(png_ptr)
363
+ FFI::MemoryPointer.new(:pointer) do |ip|
364
+ ip.write_pointer(info_ptr)
365
+ png_destroy_write_struct(pp, ip)
366
+ end
367
+ end
368
+ end
369
+ end
370
+ end
371
+
195
372
  private
196
373
 
197
374
  # Lower-level encode that produces the raw libpng output (with sRGB
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: libpng
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.58.0
4
+ version: 1.6.58.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-07-26 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: ffi
@@ -71,6 +72,7 @@ metadata:
71
72
  source_code_uri: https://github.com/claricle/libpng-ruby
72
73
  changelog_uri: https://github.com/claricle/libpng-ruby/blob/main/README.adoc#versioning
73
74
  rubygems_mfa_required: 'true'
75
+ post_install_message:
74
76
  rdoc_options: []
75
77
  require_paths:
76
78
  - lib
@@ -85,7 +87,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
87
  - !ruby/object:Gem::Version
86
88
  version: '0'
87
89
  requirements: []
88
- rubygems_version: 4.0.16
90
+ rubygems_version: 3.5.22
91
+ signing_key:
89
92
  specification_version: 4
90
93
  summary: libpng for Ruby (pre-compiled, FFI-based).
91
94
  test_files: []