libpng 1.6.58.0
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 +7 -0
- data/.github/workflows/build.yml +120 -0
- data/.github/workflows/release.yml +249 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.rubocop.yml +39 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +27 -0
- data/README.adoc +108 -0
- data/Rakefile +70 -0
- data/bin/console +5 -0
- data/bin/setup +5 -0
- data/ext/extconf.rb +6 -0
- data/lib/libpng/recipe.rb +182 -0
- data/lib/libpng/version.rb +16 -0
- data/lib/libpng.rb +297 -0
- data/libpng.gemspec +38 -0
- metadata +91 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 424bb2ec057dfcb24902b7d42a4ca73936d54cd30ca0cbdb81df8277a97d3ba1
|
|
4
|
+
data.tar.gz: f424a0836d9ce74aaccacd16ab7aa6cb60aee534576b25a72a4d22d9752b4adc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: dd873ee26bb9b3a7e2c52cded5e501de1ed59bc1c1250132c60ef71ffdc99af9e5d6a09aa3399f5ddee3eff7a4ced98772fa3eb2f058076e7410e770bd0225ea
|
|
7
|
+
data.tar.gz: 40b078365a95aff0413904b94092eed3f76f6f0cc42dd637d5b16c1ee1d72fe2cb05c89ac9f0513f140591dc28ccbc84f997dc3f64ed0e03e53487dd04b9da70
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
name: build
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
paths-ignore:
|
|
7
|
+
- '*.adoc'
|
|
8
|
+
pull_request:
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
prepare:
|
|
17
|
+
uses: metanorma/ci/.github/workflows/prepare-rake.yml@main
|
|
18
|
+
|
|
19
|
+
test:
|
|
20
|
+
name: Test on Ruby ${{ matrix.ruby.version }} ${{ matrix.os }}
|
|
21
|
+
runs-on: ${{ matrix.os }}
|
|
22
|
+
|
|
23
|
+
needs: prepare
|
|
24
|
+
if: needs.prepare.outputs.push-for-tag != 'true'
|
|
25
|
+
|
|
26
|
+
continue-on-error: ${{ matrix.ruby.experimental }}
|
|
27
|
+
strategy:
|
|
28
|
+
fail-fast: false
|
|
29
|
+
max-parallel: 5
|
|
30
|
+
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
|
|
31
|
+
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
|
|
35
|
+
- uses: ruby/setup-ruby@master
|
|
36
|
+
with:
|
|
37
|
+
bundler-cache: true
|
|
38
|
+
ruby-version: ${{ matrix.ruby.version }}
|
|
39
|
+
rubygems: ${{ matrix.ruby.rubygems }}
|
|
40
|
+
|
|
41
|
+
# libpng's CMake needs cmake >= 3.10 and a working zlib. Both are
|
|
42
|
+
# pre-installed on GitHub Actions runners; this is just a sanity check.
|
|
43
|
+
- name: Install build tools
|
|
44
|
+
if: startsWith(matrix.os, 'ubuntu')
|
|
45
|
+
run: |
|
|
46
|
+
sudo apt-get update
|
|
47
|
+
sudo apt-get install -y cmake ninja-build zlib1g-dev
|
|
48
|
+
|
|
49
|
+
- name: Install build tools (macOS)
|
|
50
|
+
if: startsWith(matrix.os, 'macos')
|
|
51
|
+
run: |
|
|
52
|
+
brew install cmake ninja
|
|
53
|
+
|
|
54
|
+
- name: Install build tools (Windows)
|
|
55
|
+
if: startsWith(matrix.os, 'windows')
|
|
56
|
+
uses: ilammy/msvc-dev-tools@v1
|
|
57
|
+
|
|
58
|
+
- run: bundle exec rake
|
|
59
|
+
|
|
60
|
+
build:
|
|
61
|
+
name: build ${{ matrix.os }}, ${{ matrix.ruby-version }}, ${{ matrix.platform }}
|
|
62
|
+
runs-on: ${{ matrix.os }}
|
|
63
|
+
needs: prepare
|
|
64
|
+
strategy:
|
|
65
|
+
fail-fast: false
|
|
66
|
+
matrix:
|
|
67
|
+
include:
|
|
68
|
+
- os: ubuntu-latest
|
|
69
|
+
platform: any
|
|
70
|
+
ruby-version: ${{ needs.prepare.outputs.default-ruby-version }}
|
|
71
|
+
- os: ubuntu-latest
|
|
72
|
+
platform: x86_64-linux
|
|
73
|
+
ruby-version: ${{ needs.prepare.outputs.default-ruby-version }}
|
|
74
|
+
- os: windows-latest
|
|
75
|
+
platform: x64-mingw32
|
|
76
|
+
ruby-version: ${{ needs.prepare.outputs.default-ruby-version }}
|
|
77
|
+
- os: windows-latest
|
|
78
|
+
platform: x64-mingw-ucrt
|
|
79
|
+
ruby-version: ${{ needs.prepare.outputs.default-ruby-version }}
|
|
80
|
+
- os: macos-latest
|
|
81
|
+
platform: x86_64-darwin
|
|
82
|
+
ruby-version: ${{ needs.prepare.outputs.default-ruby-version }}
|
|
83
|
+
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/checkout@v4
|
|
86
|
+
|
|
87
|
+
- name: Setup Ruby
|
|
88
|
+
uses: ruby/setup-ruby@master
|
|
89
|
+
with:
|
|
90
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
91
|
+
bundler-cache: true
|
|
92
|
+
|
|
93
|
+
- name: Install build tools (Linux)
|
|
94
|
+
if: startsWith(matrix.os, 'ubuntu')
|
|
95
|
+
run: |
|
|
96
|
+
sudo apt-get update
|
|
97
|
+
sudo apt-get install -y cmake ninja-build zlib1g-dev
|
|
98
|
+
|
|
99
|
+
- name: Install build tools (macOS)
|
|
100
|
+
if: startsWith(matrix.os, 'macos')
|
|
101
|
+
run: |
|
|
102
|
+
brew install cmake ninja
|
|
103
|
+
|
|
104
|
+
- name: Install build tools (Windows)
|
|
105
|
+
if: startsWith(matrix.os, 'windows')
|
|
106
|
+
uses: ilammy/msvc-dev-tools@v1
|
|
107
|
+
|
|
108
|
+
- run: bundle exec rake gem:native:${{ matrix.platform }}
|
|
109
|
+
|
|
110
|
+
- uses: actions/upload-artifact@v4
|
|
111
|
+
with:
|
|
112
|
+
name: pkg-${{ matrix.platform }}
|
|
113
|
+
path: pkg/*.gem
|
|
114
|
+
|
|
115
|
+
- name: Install gem
|
|
116
|
+
run: gem install -b pkg/libpng-*.gem
|
|
117
|
+
|
|
118
|
+
- name: Test conversion
|
|
119
|
+
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"
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
bump-type:
|
|
10
|
+
description: 'What to bump before tagging'
|
|
11
|
+
type: choice
|
|
12
|
+
options:
|
|
13
|
+
- current
|
|
14
|
+
- iteration
|
|
15
|
+
- libpng
|
|
16
|
+
default: iteration
|
|
17
|
+
libpng-version:
|
|
18
|
+
description: 'New libpng version (required when bump-type=libpng, e.g. 1.6.49)'
|
|
19
|
+
type: string
|
|
20
|
+
required: false
|
|
21
|
+
|
|
22
|
+
# contents: write is needed for workflow_dispatch to push the bump commit + tag.
|
|
23
|
+
# id-token: write is required for RubyGems.org Trusted Publishing (OIDC).
|
|
24
|
+
permissions:
|
|
25
|
+
contents: write
|
|
26
|
+
id-token: write
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
bump:
|
|
30
|
+
if: github.event_name == 'workflow_dispatch'
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
outputs:
|
|
33
|
+
sha: ${{ steps.sha.outputs.sha }}
|
|
34
|
+
new-version: ${{ steps.bump.outputs.new-version }}
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
|
|
38
|
+
- uses: ruby/setup-ruby@master
|
|
39
|
+
with:
|
|
40
|
+
ruby-version: '3.3'
|
|
41
|
+
|
|
42
|
+
- name: Bump version.rb
|
|
43
|
+
id: bump
|
|
44
|
+
shell: bash
|
|
45
|
+
env:
|
|
46
|
+
BUMP_TYPE: ${{ inputs.bump-type }}
|
|
47
|
+
LIBPNG_VERSION_INPUT: ${{ inputs.libpng-version }}
|
|
48
|
+
run: |
|
|
49
|
+
set -euo pipefail
|
|
50
|
+
|
|
51
|
+
if [[ "$BUMP_TYPE" == "current" ]]; then
|
|
52
|
+
NEW_VERSION=$(ruby -Ilib -rlibpng/version -e 'puts Libpng::VERSION')
|
|
53
|
+
echo "new-version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
|
|
54
|
+
echo "sha=${{ github.sha }}" >> "$GITHUB_OUTPUT"
|
|
55
|
+
echo "Releasing current VERSION (${NEW_VERSION}) -- no bump."
|
|
56
|
+
exit 0
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
ruby <<'RUBY'
|
|
60
|
+
bump_type = ENV.fetch("BUMP_TYPE")
|
|
61
|
+
path = "lib/libpng/version.rb"
|
|
62
|
+
content = File.read(path)
|
|
63
|
+
|
|
64
|
+
case bump_type
|
|
65
|
+
when "iteration"
|
|
66
|
+
m = %r{LIBPNG_RUBY_ITERATION = (\d+)}.match(content) or
|
|
67
|
+
raise "Could not find LIBPNG_RUBY_ITERATION in #{path}"
|
|
68
|
+
content.sub!(%r{LIBPNG_RUBY_ITERATION = \d+},
|
|
69
|
+
"LIBPNG_RUBY_ITERATION = #{m[1].to_i + 1}") or
|
|
70
|
+
raise "Failed to substitute iteration"
|
|
71
|
+
when "libpng"
|
|
72
|
+
new_v = ENV.fetch("LIBPNG_VERSION_INPUT", "")
|
|
73
|
+
raise "libpng-version input is required when bump-type=libpng" if new_v.empty?
|
|
74
|
+
raise "Invalid version format: #{new_v} (expected X.Y.Z)" unless new_v.match?(/\A\d+\.\d+\.\d+\z/)
|
|
75
|
+
content.sub!(%r{LIBPNG_VERSION = "[^"]+"},
|
|
76
|
+
"LIBPNG_VERSION = \"#{new_v}\"") or
|
|
77
|
+
raise "Failed to substitute libpng version"
|
|
78
|
+
content.sub!(%r{LIBPNG_RUBY_ITERATION = \d+},
|
|
79
|
+
"LIBPNG_RUBY_ITERATION = 0") or
|
|
80
|
+
raise "Failed to reset iteration"
|
|
81
|
+
else
|
|
82
|
+
raise "Unknown bump-type: #{bump_type}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
File.write(path, content)
|
|
86
|
+
load File.expand_path(path)
|
|
87
|
+
File.open(ENV.fetch("GITHUB_OUTPUT"), "a") do |f|
|
|
88
|
+
f.puts "new-version=#{Libpng::VERSION}"
|
|
89
|
+
end
|
|
90
|
+
warn "Bumped to #{Libpng::VERSION}"
|
|
91
|
+
RUBY
|
|
92
|
+
|
|
93
|
+
NEW_VERSION=$(ruby -Ilib -rlibpng/version -e 'puts Libpng::VERSION')
|
|
94
|
+
echo "new-version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
|
|
95
|
+
|
|
96
|
+
git config user.name "github-actions[bot]"
|
|
97
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
98
|
+
git add lib/libpng/version.rb
|
|
99
|
+
git commit -m "Release v${NEW_VERSION}"
|
|
100
|
+
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
|
101
|
+
|
|
102
|
+
- name: Show what's being released
|
|
103
|
+
run: |
|
|
104
|
+
echo "Releasing version: ${{ steps.bump.outputs.new-version }}"
|
|
105
|
+
git --no-pager log --oneline -1 "${{ steps.bump.outputs.sha }}"
|
|
106
|
+
|
|
107
|
+
- name: Push bump commit + tag
|
|
108
|
+
env:
|
|
109
|
+
NEW_VERSION: ${{ steps.bump.outputs.new-version }}
|
|
110
|
+
SHA: ${{ steps.bump.outputs.sha }}
|
|
111
|
+
run: |
|
|
112
|
+
if [[ -n "${SHA}" && "${SHA}" != "${{ github.sha }}" ]]; then
|
|
113
|
+
git push origin "HEAD:${{ github.event.repository.default_branch }}"
|
|
114
|
+
fi
|
|
115
|
+
git tag "v${NEW_VERSION}" "${SHA}"
|
|
116
|
+
git push origin "v${NEW_VERSION}"
|
|
117
|
+
|
|
118
|
+
build:
|
|
119
|
+
needs: bump
|
|
120
|
+
if: always() && !cancelled() && !failure() && (needs.bump.result == 'success' || needs.bump.result == 'skipped')
|
|
121
|
+
runs-on: ${{ matrix.os }}
|
|
122
|
+
strategy:
|
|
123
|
+
fail-fast: false
|
|
124
|
+
matrix:
|
|
125
|
+
include:
|
|
126
|
+
- os: ubuntu-latest
|
|
127
|
+
platform: any
|
|
128
|
+
ruby-version: '3.3'
|
|
129
|
+
- os: ubuntu-latest
|
|
130
|
+
platform: x86_64-linux
|
|
131
|
+
ruby-version: '3.3'
|
|
132
|
+
- os: windows-latest
|
|
133
|
+
platform: x64-mingw32
|
|
134
|
+
ruby-version: '3.3'
|
|
135
|
+
- os: windows-latest
|
|
136
|
+
platform: x64-mingw-ucrt
|
|
137
|
+
ruby-version: '3.3'
|
|
138
|
+
- os: macos-latest
|
|
139
|
+
platform: x86_64-darwin
|
|
140
|
+
ruby-version: '3.3'
|
|
141
|
+
steps:
|
|
142
|
+
- uses: actions/checkout@v4
|
|
143
|
+
with:
|
|
144
|
+
ref: ${{ needs.bump.outputs.sha || github.ref }}
|
|
145
|
+
|
|
146
|
+
- uses: ruby/setup-ruby@master
|
|
147
|
+
with:
|
|
148
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
149
|
+
bundler-cache: true
|
|
150
|
+
|
|
151
|
+
- name: Install build tools (Linux)
|
|
152
|
+
if: startsWith(matrix.os, 'ubuntu')
|
|
153
|
+
run: |
|
|
154
|
+
sudo apt-get update
|
|
155
|
+
sudo apt-get install -y cmake ninja-build zlib1g-dev
|
|
156
|
+
|
|
157
|
+
- name: Install build tools (macOS)
|
|
158
|
+
if: startsWith(matrix.os, 'macos')
|
|
159
|
+
run: |
|
|
160
|
+
brew install cmake ninja
|
|
161
|
+
|
|
162
|
+
- name: Install build tools (Windows)
|
|
163
|
+
if: startsWith(matrix.os, 'windows')
|
|
164
|
+
uses: ilammy/msvc-dev-tools@v1
|
|
165
|
+
|
|
166
|
+
- run: bundle exec rake gem:native:${{ matrix.platform }}
|
|
167
|
+
|
|
168
|
+
- uses: actions/upload-artifact@v4
|
|
169
|
+
with:
|
|
170
|
+
name: pkg-${{ matrix.platform }}
|
|
171
|
+
path: pkg/*.gem
|
|
172
|
+
|
|
173
|
+
- name: Install gem
|
|
174
|
+
run: gem install -b pkg/libpng-*.gem
|
|
175
|
+
|
|
176
|
+
- name: Test conversion
|
|
177
|
+
run: |
|
|
178
|
+
ruby -rlibpng -e "png = Libpng.encode(2, 2, \"\\xff\" * 16, pixel_format: 'RGBA'); puts Libpng.decode(png, pixel_format: 'RGBA').inspect"
|
|
179
|
+
|
|
180
|
+
cross:
|
|
181
|
+
needs: bump
|
|
182
|
+
if: always() && !cancelled() && !failure() && (needs.bump.result == 'success' || needs.bump.result == 'skipped')
|
|
183
|
+
runs-on: ${{ matrix.os }}
|
|
184
|
+
strategy:
|
|
185
|
+
fail-fast: false
|
|
186
|
+
matrix:
|
|
187
|
+
include:
|
|
188
|
+
- 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'
|
|
194
|
+
steps:
|
|
195
|
+
- uses: actions/checkout@v4
|
|
196
|
+
with:
|
|
197
|
+
ref: ${{ needs.bump.outputs.sha || github.ref }}
|
|
198
|
+
|
|
199
|
+
- name: Install Ubuntu cross-build packages
|
|
200
|
+
if: startsWith(matrix.os, 'ubuntu')
|
|
201
|
+
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 }}
|
|
215
|
+
|
|
216
|
+
- uses: actions/upload-artifact@v4
|
|
217
|
+
with:
|
|
218
|
+
name: pkg-${{ matrix.platform }}
|
|
219
|
+
path: pkg/*.gem
|
|
220
|
+
|
|
221
|
+
- name: Install gem
|
|
222
|
+
run: gem install -b pkg/libpng-*.gem
|
|
223
|
+
|
|
224
|
+
publish:
|
|
225
|
+
needs: [ bump, build, cross ]
|
|
226
|
+
if: always() && !cancelled() && !failure()
|
|
227
|
+
runs-on: ubuntu-latest
|
|
228
|
+
steps:
|
|
229
|
+
- uses: actions/checkout@v4
|
|
230
|
+
with:
|
|
231
|
+
ref: ${{ needs.bump.outputs.sha || github.ref }}
|
|
232
|
+
|
|
233
|
+
- uses: ruby/setup-ruby@master
|
|
234
|
+
with:
|
|
235
|
+
ruby-version: '3.3'
|
|
236
|
+
bundler-cache: true
|
|
237
|
+
|
|
238
|
+
- uses: actions/download-artifact@v4
|
|
239
|
+
with:
|
|
240
|
+
pattern: pkg-*
|
|
241
|
+
path: pkg
|
|
242
|
+
merge-multiple: true
|
|
243
|
+
|
|
244
|
+
- name: Configure RubyGems credentials (Trusted Publishing / OIDC)
|
|
245
|
+
uses: rubygems/configure-rubygems-credentials@main
|
|
246
|
+
|
|
247
|
+
- name: Publish to rubygems.org
|
|
248
|
+
run: |
|
|
249
|
+
for gem in pkg/*.gem; do gem push -V "$gem"; done
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
TargetRubyVersion: 2.7
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
|
|
6
|
+
Gemspec/DevelopmentDependencies:
|
|
7
|
+
Enabled: false
|
|
8
|
+
|
|
9
|
+
# The wrapper is by nature a single file with FFI declarations + several
|
|
10
|
+
# methods that walk libpng's chunked API; splitting would obscure the
|
|
11
|
+
# 1-to-1 mapping with the C API.
|
|
12
|
+
Metrics/AbcSize:
|
|
13
|
+
Max: 60
|
|
14
|
+
Metrics/ClassLength:
|
|
15
|
+
Max: 200
|
|
16
|
+
Metrics/CyclomaticComplexity:
|
|
17
|
+
Max: 20
|
|
18
|
+
Metrics/MethodLength:
|
|
19
|
+
Max: 50
|
|
20
|
+
Metrics/ModuleLength:
|
|
21
|
+
Max: 250
|
|
22
|
+
Metrics/PerceivedComplexity:
|
|
23
|
+
Max: 20
|
|
24
|
+
Metrics/ParameterLists:
|
|
25
|
+
Max: 7
|
|
26
|
+
|
|
27
|
+
Metrics/BlockLength:
|
|
28
|
+
Exclude:
|
|
29
|
+
- spec/**/*.rb
|
|
30
|
+
- Rakefile
|
|
31
|
+
- libpng.gemspec
|
|
32
|
+
|
|
33
|
+
Style/SpecialGlobalVars:
|
|
34
|
+
Exclude:
|
|
35
|
+
- ext/extconf.rb
|
|
36
|
+
Style/FrozenStringLiteralComment:
|
|
37
|
+
Exclude:
|
|
38
|
+
- ext/extconf.rb
|
|
39
|
+
- lib/libpng/recipe.rb
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright (c) 2026 Ribose Inc.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
|
4
|
+
modification, are permitted provided that the following conditions are met:
|
|
5
|
+
|
|
6
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
7
|
+
this list of conditions and the following disclaimer.
|
|
8
|
+
|
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
10
|
+
notice, this list of conditions and the following disclaimer in the
|
|
11
|
+
documentation and/or other materials provided with the distribution.
|
|
12
|
+
|
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
14
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
15
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
16
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
17
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
18
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
19
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
20
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
21
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
22
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
23
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
24
|
+
|
|
25
|
+
This gem bundles pre-compiled binaries of libpng, which is itself
|
|
26
|
+
distributed under the libpng license (see the upstream LICENSE file
|
|
27
|
+
in https://github.com/pnggroup/libpng for details).
|
data/README.adoc
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
= libpng: pre-compiled libpng for Ruby
|
|
2
|
+
|
|
3
|
+
image:https://img.shields.io/badge/Ruby-%3E%3D%202.7.0-ruby.svg[Ruby]
|
|
4
|
+
image:https://img.shields.io/badge/license-BSD--2--Clause-blue.svg[License]
|
|
5
|
+
|
|
6
|
+
`libpng` is a Ruby binding for the official PNG reference library
|
|
7
|
+
(https://github.com/pnggroup/libpng[libpng]). The native shared library
|
|
8
|
+
is pre-compiled for each target platform and shipped inside the gem, so
|
|
9
|
+
`gem install libpng` Just Works without a C compiler on the host.
|
|
10
|
+
|
|
11
|
+
== Installation
|
|
12
|
+
|
|
13
|
+
Add to your Gemfile:
|
|
14
|
+
|
|
15
|
+
gem "libpng"
|
|
16
|
+
|
|
17
|
+
Then:
|
|
18
|
+
|
|
19
|
+
bundle install
|
|
20
|
+
|
|
21
|
+
Or install manually:
|
|
22
|
+
|
|
23
|
+
gem install libpng
|
|
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.
|
|
28
|
+
|
|
29
|
+
== Usage
|
|
30
|
+
|
|
31
|
+
require "libpng"
|
|
32
|
+
|
|
33
|
+
# Encode raw RGBA pixels (row-major, top-down) as a PNG.
|
|
34
|
+
png = Libpng.encode(width, height, rgba_bytes, pixel_format: "RGBA")
|
|
35
|
+
File.binwrite("out.png", png)
|
|
36
|
+
|
|
37
|
+
# Decode a PNG into raw pixels.
|
|
38
|
+
decoded = Libpng.decode(File.binread("out.png"), pixel_format: "RGBA")
|
|
39
|
+
decoded.width # => Integer
|
|
40
|
+
decoded.height # => Integer
|
|
41
|
+
decoded.format # => "RGBA"
|
|
42
|
+
decoded.pixels # => binary String of RGBA bytes
|
|
43
|
+
|
|
44
|
+
=== Supported pixel formats
|
|
45
|
+
|
|
46
|
+
- `"GRAY"` / `"GRAYSCALE"` -- 8-bit grayscale (1 byte/pixel)
|
|
47
|
+
- `"GA"` / `"AG"` -- 8-bit grayscale + alpha (2 bytes/pixel)
|
|
48
|
+
- `"RGB"`, `"BGR"` -- 8-bit truecolor (3 bytes/pixel)
|
|
49
|
+
- `"RGBA"`, `"ARGB"`, `"BGRA"`, `"ABGR"` -- 8-bit truecolor + alpha (4 bytes/pixel)
|
|
50
|
+
|
|
51
|
+
=== Encoding options
|
|
52
|
+
|
|
53
|
+
Libpng.encode(width, height, pixels,
|
|
54
|
+
pixel_format: "RGBA",
|
|
55
|
+
convert_to_8bit: false)
|
|
56
|
+
|
|
57
|
+
`convert_to_8bit: true` causes libpng to convert 16-bit input to 8-bit
|
|
58
|
+
on write (input is still passed as a packed byte buffer; use this if you
|
|
59
|
+
have packed 16-bit-per-channel data).
|
|
60
|
+
|
|
61
|
+
== Ractor safety
|
|
62
|
+
|
|
63
|
+
`Libpng.encode` and `Libpng.decode` are Ractor-safe. Every call
|
|
64
|
+
allocates and frees its own libpng `png_image`; no shared mutable state
|
|
65
|
+
exists on the Ruby side. Calls from multiple Ractors run concurrently
|
|
66
|
+
without locking.
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
|
|
70
|
+
r = Ractor.new do
|
|
71
|
+
Libpng.encode(2, 2, "\xFF" * 16, pixel_format: "RGBA")
|
|
72
|
+
end
|
|
73
|
+
r.take # => PNG binary
|
|
74
|
+
|
|
75
|
+
== Versioning
|
|
76
|
+
|
|
77
|
+
This gem follows a `{LIBPNG_VERSION}.{ITERATION}` scheme:
|
|
78
|
+
|
|
79
|
+
- `LIBPNG_VERSION` is the upstream libpng release the gem is built
|
|
80
|
+
against (currently 1.6.48).
|
|
81
|
+
- `ITERATION` is a counter that bumps on Ruby-side changes (recipe bug
|
|
82
|
+
fixes, CI tweaks, doc updates). It resets to 0 each time
|
|
83
|
+
`LIBPNG_VERSION` bumps.
|
|
84
|
+
|
|
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
|
|
87
|
+
the next upstream libpng.
|
|
88
|
+
|
|
89
|
+
== How it works
|
|
90
|
+
|
|
91
|
+
The gem has three layers:
|
|
92
|
+
|
|
93
|
+
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.
|
|
103
|
+
|
|
104
|
+
== License
|
|
105
|
+
|
|
106
|
+
BSD-2-Clause; see link:LICENSE.txt[LICENSE.txt]. The gem bundles
|
|
107
|
+
pre-compiled libpng binaries; libpng itself is distributed under the
|
|
108
|
+
https://github.com/pnggroup/libpng/blob/main/LICENSE[libpng license].
|
data/Rakefile
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rspec/core/rake_task'
|
|
5
|
+
|
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
7
|
+
|
|
8
|
+
require 'rubocop/rake_task'
|
|
9
|
+
|
|
10
|
+
RuboCop::RakeTask.new
|
|
11
|
+
|
|
12
|
+
task default: %i[spec rubocop]
|
|
13
|
+
|
|
14
|
+
task :compile do
|
|
15
|
+
require_relative 'ext/extconf'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
task spec: :compile
|
|
19
|
+
|
|
20
|
+
desc 'Build install-compilation gem'
|
|
21
|
+
task 'gem:native:any' do
|
|
22
|
+
sh 'rake platform:any gem'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
require 'rubygems/package_task'
|
|
26
|
+
|
|
27
|
+
desc 'Define the gem task to build on any platform (compile on install)'
|
|
28
|
+
task 'platform:any' do
|
|
29
|
+
spec = Gem::Specification.load('libpng.gemspec').dup
|
|
30
|
+
task = Gem::PackageTask.new(spec)
|
|
31
|
+
task.define
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
platforms = %w[
|
|
35
|
+
x64-mingw32
|
|
36
|
+
x64-mingw-ucrt
|
|
37
|
+
x86_64-linux
|
|
38
|
+
aarch64-linux
|
|
39
|
+
x86_64-darwin
|
|
40
|
+
arm64-darwin
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
platforms.each do |platform|
|
|
44
|
+
desc "Build pre-compiled gem for the #{platform} platform"
|
|
45
|
+
task "gem:native:#{platform}" do
|
|
46
|
+
sh "rake compile platform:#{platform} gem target_platform=#{platform}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
desc "Define the gem task to build on the #{platform} platform (binary gem)"
|
|
50
|
+
task "platform:#{platform}" do
|
|
51
|
+
spec = Gem::Specification.load('libpng.gemspec').dup
|
|
52
|
+
spec.platform = Gem::Platform.new(platform)
|
|
53
|
+
spec.files += Dir.glob('lib/libpng/*.{dll,so,dylib}')
|
|
54
|
+
spec.extensions = []
|
|
55
|
+
spec.dependencies.reject! { |d| d.name == 'mini_portile2' }
|
|
56
|
+
|
|
57
|
+
task = Gem::PackageTask.new(spec)
|
|
58
|
+
task.define
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
require 'rake/clean'
|
|
63
|
+
|
|
64
|
+
CLOBBER.include('pkg')
|
|
65
|
+
CLEAN.include('ports',
|
|
66
|
+
'tmp',
|
|
67
|
+
'lib/libpng/*.dll',
|
|
68
|
+
'lib/libpng/*.dylib',
|
|
69
|
+
'lib/libpng/*.so',
|
|
70
|
+
'lib/libpng/*.so.*')
|
data/bin/console
ADDED
data/bin/setup
ADDED
data/ext/extconf.rb
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
require 'rbconfig'
|
|
2
|
+
require 'mini_portile2'
|
|
3
|
+
require 'pathname'
|
|
4
|
+
require 'tmpdir'
|
|
5
|
+
require 'open3'
|
|
6
|
+
require_relative 'version'
|
|
7
|
+
|
|
8
|
+
module Libpng
|
|
9
|
+
# MiniPortile-based recipe for building libpng from source. Mirrors the
|
|
10
|
+
# pattern used by emf2svg-ruby: during `gem install`, ext/extconf.rb
|
|
11
|
+
# invokes Recipe#cook, which downloads the libpng source tarball, runs
|
|
12
|
+
# configure + make, and installs the shared library into the gem's lib/
|
|
13
|
+
# directory. The pre-compiled gems (built via `rake gem:native:<plat>`)
|
|
14
|
+
# ship the .so/.dylib/.dll and disable extconf.rb entirely.
|
|
15
|
+
class Recipe < MiniPortileCMake
|
|
16
|
+
# Pinned libpng source URL + sha256. Bump deliberately to refresh
|
|
17
|
+
# the upstream — and remember to update both fields together.
|
|
18
|
+
LIBPNG_URL = "https://downloads.sourceforge.net/project/libpng/libpng16/#{Libpng::LIBPNG_VERSION}/libpng-#{Libpng::LIBPNG_VERSION}.tar.gz".freeze
|
|
19
|
+
# sha256 of the libpng-X.Y.Z.tar.gz tarball. Verify with:
|
|
20
|
+
# curl -sL <URL> | shasum -a 256
|
|
21
|
+
LIBPNG_SHA256 = '8c9b05b675ca7301a458df2c2e46f26e1d41ff36b8863f8c33530bc58c2e6225'.freeze
|
|
22
|
+
|
|
23
|
+
ROOT = Pathname.new(File.expand_path('../..', __dir__))
|
|
24
|
+
|
|
25
|
+
def initialize
|
|
26
|
+
super('libpng', Libpng::LIBPNG_VERSION)
|
|
27
|
+
|
|
28
|
+
@files << {
|
|
29
|
+
url: LIBPNG_URL,
|
|
30
|
+
sha256: LIBPNG_SHA256
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@target = ROOT.join(@target).to_s
|
|
34
|
+
@printed = {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# libpng ships a CMake build alongside the autotools one. We use CMake
|
|
38
|
+
# for consistency with mini_portile2's defaults and to handle Windows
|
|
39
|
+
# cleanly (autotools on native Windows is fragile).
|
|
40
|
+
# No additional configuration is needed; defaults build PNG_SHARED=ON
|
|
41
|
+
# and PNG_STATIC=OFF, which is what we want.
|
|
42
|
+
|
|
43
|
+
def cook_if_not
|
|
44
|
+
cook unless File.exist?(checkpoint)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def cook
|
|
48
|
+
super
|
|
49
|
+
FileUtils.touch(checkpoint)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def checkpoint
|
|
53
|
+
File.join(@target, "#{name}-#{version}-#{target_platform}.installed")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def configure_defaults
|
|
57
|
+
# Build a static+self-contained shared library: we only ship the .so
|
|
58
|
+
# to consumers, so libpng's transitive dep on zlib must be linked in.
|
|
59
|
+
opts = super
|
|
60
|
+
opts << '-DPNG_SHARED=ON'
|
|
61
|
+
opts << '-DPNG_STATIC=OFF'
|
|
62
|
+
opts << '-DPNG_TESTS=OFF'
|
|
63
|
+
opts << '-DPNG_FRAMEWORK=OFF'
|
|
64
|
+
# macOS: avoid the .framework build; we want a plain .dylib.
|
|
65
|
+
opts << '-DCMAKE_INSTALL_LIBDIR=lib'
|
|
66
|
+
opts << '-DCMAKE_BUILD_TYPE=Release'
|
|
67
|
+
opts
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def install
|
|
71
|
+
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?
|
|
76
|
+
|
|
77
|
+
target_dir = ROOT.join('lib', 'libpng')
|
|
78
|
+
FileUtils.mkdir_p(target_dir)
|
|
79
|
+
FileUtils.cp_r(libs, target_dir, verbose: true)
|
|
80
|
+
|
|
81
|
+
verify_libs
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def verify_libs
|
|
85
|
+
each_built_lib do |path|
|
|
86
|
+
out, st = Open3.capture2("file #{path}")
|
|
87
|
+
raise "Failed to query file #{path}: #{out}" unless st.exitstatus.zero?
|
|
88
|
+
|
|
89
|
+
next if target_format.eql?('skip')
|
|
90
|
+
|
|
91
|
+
raise "Invalid file format '#{out}', /#{target_format.source}/ expected" unless target_format.match?(out)
|
|
92
|
+
|
|
93
|
+
message("Verifying #{path} ... OK\n")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def execute(action, command, command_opts = {})
|
|
98
|
+
super(action, command, command_opts.merge(debug: false))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def message(text)
|
|
102
|
+
return super unless text.start_with?("\rDownloading")
|
|
103
|
+
|
|
104
|
+
match = text.match(/(\rDownloading .*)\(\s*\d+%\)/)
|
|
105
|
+
pattern = match ? match[1] : text
|
|
106
|
+
return if @printed[pattern]
|
|
107
|
+
|
|
108
|
+
@printed[pattern] = true
|
|
109
|
+
super
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def port_path
|
|
115
|
+
File.join(@target, 'ports', "#{name}-#{version}") # MiniPortile default
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def shared_lib_glob
|
|
119
|
+
if MiniPortile.windows?
|
|
120
|
+
'libpng16*.dll'
|
|
121
|
+
elsif MiniPortile.darwin?
|
|
122
|
+
'libpng16*.dylib'
|
|
123
|
+
else
|
|
124
|
+
'libpng16.so*'
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def each_built_lib(&block)
|
|
129
|
+
Dir.glob(ROOT.join('lib', 'libpng', shared_lib_glob)).each(&block)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def host_platform
|
|
133
|
+
@host_platform ||=
|
|
134
|
+
case @host
|
|
135
|
+
when /\Ax86_64.*mingw32/
|
|
136
|
+
'x64-mingw32'
|
|
137
|
+
when /\Ax86_64.*linux/
|
|
138
|
+
'x86_64-linux'
|
|
139
|
+
when /\A(arm64|aarch64).*linux/
|
|
140
|
+
'arm64-linux'
|
|
141
|
+
when /\Ax86_64.*(darwin|macos|osx)/
|
|
142
|
+
'x86_64-darwin'
|
|
143
|
+
when /\A(arm64|aarch64).*(darwin|macos|osx)/
|
|
144
|
+
'arm64-darwin'
|
|
145
|
+
else
|
|
146
|
+
@host
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def target_platform
|
|
151
|
+
@target_platform ||=
|
|
152
|
+
case ENV.fetch('target_platform', nil)
|
|
153
|
+
when /\A(arm64|aarch64).*(darwin|macos|osx)/
|
|
154
|
+
'arm64-darwin'
|
|
155
|
+
when /\Ax86_64.*(darwin|macos|osx)/
|
|
156
|
+
'x86_64-darwin'
|
|
157
|
+
when /\A(arm64|aarch64).*linux/
|
|
158
|
+
'aarch64-linux'
|
|
159
|
+
else
|
|
160
|
+
ENV.fetch('target_platform', host_platform)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def target_format
|
|
165
|
+
@target_format ||=
|
|
166
|
+
case target_platform
|
|
167
|
+
when 'arm64-darwin'
|
|
168
|
+
/Mach-O 64-bit dynamically linked shared library arm64/
|
|
169
|
+
when 'x86_64-darwin'
|
|
170
|
+
/Mach-O 64-bit dynamically linked shared library x86_64/
|
|
171
|
+
when 'aarch64-linux'
|
|
172
|
+
/ELF 64-bit LSB shared object, ARM aarch64/
|
|
173
|
+
when 'x86_64-linux'
|
|
174
|
+
/ELF 64-bit LSB shared object, x86-64/
|
|
175
|
+
when /\Ax64-mingw(32|-ucrt)/
|
|
176
|
+
/PE32\+ executable.*\(DLL\).*x86-64/
|
|
177
|
+
else
|
|
178
|
+
'skip'
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Libpng
|
|
4
|
+
# Gem version follows the pattern:
|
|
5
|
+
#
|
|
6
|
+
# {LIBPNG_VERSION}.{LIBPNG_RUBY_ITERATION}
|
|
7
|
+
#
|
|
8
|
+
# where LIBPNG_VERSION is the upstream libpng release this gem is
|
|
9
|
+
# built against, and LIBPNG_RUBY_ITERATION is a counter for Ruby-side
|
|
10
|
+
# changes (recipe bug fixes, CI changes, docs) that bump without a
|
|
11
|
+
# new libpng release. The iteration resets to 0 each time
|
|
12
|
+
# LIBPNG_VERSION bumps.
|
|
13
|
+
LIBPNG_VERSION = '1.6.58'
|
|
14
|
+
LIBPNG_RUBY_ITERATION = 0
|
|
15
|
+
VERSION = "#{LIBPNG_VERSION}.#{LIBPNG_RUBY_ITERATION}"
|
|
16
|
+
end
|
data/lib/libpng.rb
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ffi'
|
|
4
|
+
require 'zlib'
|
|
5
|
+
require_relative 'libpng/version'
|
|
6
|
+
|
|
7
|
+
# Libpng is a Ruby binding for libpng (the official PNG reference library)
|
|
8
|
+
# via FFI. The native library is pre-compiled for each target platform and
|
|
9
|
+
# shipped inside the gem, so no C compiler is required at install time.
|
|
10
|
+
#
|
|
11
|
+
# The API mirrors libpng's "simplified" high-level API:
|
|
12
|
+
#
|
|
13
|
+
# Libpng.encode(width, height, rgba_bytes, pixel_format: "RGBA")
|
|
14
|
+
# Libpng.decode(png_bytes, pixel_format: "RGBA")
|
|
15
|
+
#
|
|
16
|
+
# All encode/decode state is per-call (libpng allocates and frees a
|
|
17
|
+
# png_image internally), so calls from different Ractors do not share
|
|
18
|
+
# state. The module's FFI function table is frozen at load time.
|
|
19
|
+
module Libpng
|
|
20
|
+
class Error < StandardError; end
|
|
21
|
+
|
|
22
|
+
extend FFI::Library
|
|
23
|
+
|
|
24
|
+
ffi_lib_flags :now, :global
|
|
25
|
+
|
|
26
|
+
lib_filename = if FFI::Platform.windows?
|
|
27
|
+
'libpng16.dll'
|
|
28
|
+
elsif FFI::Platform.mac?
|
|
29
|
+
'libpng16.dylib'
|
|
30
|
+
else
|
|
31
|
+
'libpng16.so'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
ffi_lib File.expand_path("libpng/#{lib_filename}", __dir__)
|
|
35
|
+
.gsub('/', File::ALT_SEPARATOR || File::SEPARATOR)
|
|
36
|
+
|
|
37
|
+
# libpng simplified API (png_image / png_image_*). All return int
|
|
38
|
+
# (1 on success, 0 on failure); the png_image's message buffer holds
|
|
39
|
+
# the error string on failure.
|
|
40
|
+
attach_function :png_image_begin_read_from_memory,
|
|
41
|
+
%i[pointer pointer size_t], :int
|
|
42
|
+
attach_function :png_image_finish_read,
|
|
43
|
+
%i[pointer pointer pointer int pointer], :int
|
|
44
|
+
attach_function :png_image_write_to_memory,
|
|
45
|
+
%i[pointer pointer pointer int pointer int pointer], :int
|
|
46
|
+
attach_function :png_image_free, [:pointer], :void
|
|
47
|
+
|
|
48
|
+
# PNG_IMAGE_FORMAT_* bit flags and named formats from png.h.
|
|
49
|
+
FORMAT_FLAG_ALPHA = 0x01
|
|
50
|
+
FORMAT_FLAG_COLOR = 0x02
|
|
51
|
+
FORMAT_FLAG_LINEAR = 0x04
|
|
52
|
+
FORMAT_FLAG_COLORMAP = 0x08
|
|
53
|
+
FORMAT_FLAG_BGR = 0x10
|
|
54
|
+
FORMAT_FLAG_AFIRST = 0x20
|
|
55
|
+
FORMAT_FLAG_ASSOCIATED_ALPHA = 0x40
|
|
56
|
+
|
|
57
|
+
FORMAT_GRAY = 0
|
|
58
|
+
FORMAT_GA = FORMAT_FLAG_ALPHA
|
|
59
|
+
FORMAT_AG = FORMAT_FLAG_ALPHA | FORMAT_FLAG_AFIRST
|
|
60
|
+
FORMAT_RGB = FORMAT_FLAG_COLOR
|
|
61
|
+
FORMAT_BGR = FORMAT_FLAG_COLOR | FORMAT_FLAG_BGR
|
|
62
|
+
FORMAT_RGBA = FORMAT_FLAG_COLOR | FORMAT_FLAG_ALPHA
|
|
63
|
+
FORMAT_ARGB = FORMAT_FLAG_COLOR | FORMAT_FLAG_ALPHA | FORMAT_FLAG_AFIRST
|
|
64
|
+
FORMAT_BGRA = FORMAT_FLAG_COLOR | FORMAT_FLAG_ALPHA | FORMAT_FLAG_BGR
|
|
65
|
+
FORMAT_ABGR = FORMAT_FLAG_COLOR | FORMAT_FLAG_ALPHA | FORMAT_FLAG_AFIRST | FORMAT_FLAG_BGR
|
|
66
|
+
|
|
67
|
+
FORMAT_BY_NAME = {
|
|
68
|
+
'GRAY' => FORMAT_GRAY,
|
|
69
|
+
'GRAYSCALE' => FORMAT_GRAY,
|
|
70
|
+
'GA' => FORMAT_GA,
|
|
71
|
+
'AG' => FORMAT_AG,
|
|
72
|
+
'RGB' => FORMAT_RGB,
|
|
73
|
+
'BGR' => FORMAT_BGR,
|
|
74
|
+
'RGBA' => FORMAT_RGBA,
|
|
75
|
+
'ARGB' => FORMAT_ARGB,
|
|
76
|
+
'BGRA' => FORMAT_BGRA,
|
|
77
|
+
'ABGR' => FORMAT_ABGR
|
|
78
|
+
}.freeze
|
|
79
|
+
|
|
80
|
+
# png_image::version value libpng checks for. Defined in png.h as
|
|
81
|
+
# PNG_IMAGE_VERSION == 1.
|
|
82
|
+
PNG_IMAGE_VERSION = 1
|
|
83
|
+
|
|
84
|
+
# Buffer size for libpng's per-image error message (PNG_IMAGE_MESSAGE_BYTES).
|
|
85
|
+
# The png_image struct stores a fixed-size char buffer of this length.
|
|
86
|
+
PNG_IMAGE_MESSAGE_BYTES = 64
|
|
87
|
+
|
|
88
|
+
# png_image struct field offsets (bytes). png_image is laid out as:
|
|
89
|
+
# void* opaque (pointer-width)
|
|
90
|
+
# png_uint_32 version (uint32)
|
|
91
|
+
# png_uint_32 width (uint32)
|
|
92
|
+
# png_uint_32 height (uint32)
|
|
93
|
+
# png_uint_32 format (uint32)
|
|
94
|
+
# png_uint_32 flags (uint32)
|
|
95
|
+
# png_uint_32 colormap_entries (uint32)
|
|
96
|
+
# png_uint_32 warning_or_error (uint32)
|
|
97
|
+
# char[64] message
|
|
98
|
+
# Total = pointer-size + 7*4 + 64. The fixed width makes it safe to
|
|
99
|
+
# allocate via FFI::MemoryPointer directly so the wrapper stays
|
|
100
|
+
# Ractor-safe (FFI::Struct has class-level state that isn't shareable
|
|
101
|
+
# across non-main Ractors).
|
|
102
|
+
PNG_IMAGE_SIZE = FFI.type_size(:pointer) + (7 * 4) + PNG_IMAGE_MESSAGE_BYTES
|
|
103
|
+
PNG_IMAGE_OFF_OPAQUE = 0
|
|
104
|
+
PNG_IMAGE_OFF_VERSION = FFI.type_size(:pointer)
|
|
105
|
+
PNG_IMAGE_OFF_WIDTH = PNG_IMAGE_OFF_VERSION + 4
|
|
106
|
+
PNG_IMAGE_OFF_HEIGHT = PNG_IMAGE_OFF_WIDTH + 4
|
|
107
|
+
PNG_IMAGE_OFF_FORMAT = PNG_IMAGE_OFF_HEIGHT + 4
|
|
108
|
+
PNG_IMAGE_OFF_FLAGS = PNG_IMAGE_OFF_FORMAT + 4
|
|
109
|
+
PNG_IMAGE_OFF_COLORMAP_ENTRIES = PNG_IMAGE_OFF_FLAGS + 4
|
|
110
|
+
PNG_IMAGE_OFF_WARNING_OR_ERROR = PNG_IMAGE_OFF_COLORMAP_ENTRIES + 4
|
|
111
|
+
PNG_IMAGE_OFF_MESSAGE = PNG_IMAGE_OFF_WARNING_OR_ERROR + 4
|
|
112
|
+
|
|
113
|
+
# Result struct for Libpng.decode (returned as a plain Hash for simplicity).
|
|
114
|
+
DecodedImage = Struct.new(:width, :height, :format, :pixels, keyword_init: true)
|
|
115
|
+
|
|
116
|
+
class << self
|
|
117
|
+
# Encode raw pixel data as a PNG.
|
|
118
|
+
#
|
|
119
|
+
# +width+, +height+ image dimensions in pixels
|
|
120
|
+
# +pixels+ String of raw pixel bytes (row-major, top-down)
|
|
121
|
+
# +pixel_format+ "RGB", "RGBA", "GRAY", "GA", "BGR", "BGRA"
|
|
122
|
+
# (default: "RGBA")
|
|
123
|
+
# +convert_to_8bit+ when true, libpng converts 16-bit input to 8-bit
|
|
124
|
+
# on write (default: false)
|
|
125
|
+
# +strip_colorspace+ when true, removes any sRGB/gAMA/cHRM/iCCP chunks
|
|
126
|
+
# libpng's simplified API emits by default, leaving
|
|
127
|
+
# only IHDR, IDAT, and IEND. This matches the
|
|
128
|
+
# "classic" libpng write path (e.g. libemf2svg) and
|
|
129
|
+
# produces byte-identical output. Default: true.
|
|
130
|
+
#
|
|
131
|
+
# Returns a String containing the PNG file bytes. Raises Libpng::Error
|
|
132
|
+
# on failure (bad dimensions, insufficient input bytes, invalid
|
|
133
|
+
# pixel_format, or libpng-internal error).
|
|
134
|
+
#
|
|
135
|
+
# Ractor-safe: every call allocates and frees its own png_image; no
|
|
136
|
+
# shared mutable state.
|
|
137
|
+
def encode(width, height, pixels, pixel_format: 'RGBA',
|
|
138
|
+
convert_to_8bit: false, strip_colorspace: true)
|
|
139
|
+
raw = encode_ancillary(width, height, pixels,
|
|
140
|
+
pixel_format: pixel_format,
|
|
141
|
+
convert_to_8bit: convert_to_8bit)
|
|
142
|
+
strip_colorspace ? strip_ancillary_chunks(raw) : raw
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Decode a PNG file into raw pixels.
|
|
146
|
+
#
|
|
147
|
+
# +png+ String containing PNG file bytes
|
|
148
|
+
# +pixel_format+ desired output format ("RGB", "RGBA", "GRAY", etc.)
|
|
149
|
+
# (default: "RGBA")
|
|
150
|
+
#
|
|
151
|
+
# Returns a DecodedImage Struct with #width, #height, #format (String),
|
|
152
|
+
# and #pixels (binary String).
|
|
153
|
+
#
|
|
154
|
+
# Ractor-safe: same reason as encode.
|
|
155
|
+
def decode(png, pixel_format: 'RGBA')
|
|
156
|
+
fmt = FORMAT_BY_NAME[pixel_format.to_s.upcase] ||
|
|
157
|
+
raise(Error, "unknown pixel_format #{pixel_format.inspect}")
|
|
158
|
+
|
|
159
|
+
img = FFI::MemoryPointer.new(:uint8, PNG_IMAGE_SIZE)
|
|
160
|
+
img.clear
|
|
161
|
+
img.put_uint32(PNG_IMAGE_OFF_VERSION, PNG_IMAGE_VERSION)
|
|
162
|
+
|
|
163
|
+
begin
|
|
164
|
+
FFI::MemoryPointer.new(:uint8, png.bytesize) do |in_buf|
|
|
165
|
+
in_buf.write_bytes(png)
|
|
166
|
+
ok = png_image_begin_read_from_memory(img, in_buf, png.bytesize)
|
|
167
|
+
if ok.zero?
|
|
168
|
+
msg = read_message(img)
|
|
169
|
+
raise Error, "png_image_begin_read_from_memory failed: #{msg}"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
img.put_uint32(PNG_IMAGE_OFF_FORMAT, fmt)
|
|
173
|
+
width = img.get_uint32(PNG_IMAGE_OFF_WIDTH)
|
|
174
|
+
height = img.get_uint32(PNG_IMAGE_OFF_HEIGHT)
|
|
175
|
+
stride = width * bytes_per_pixel_for_format(fmt)
|
|
176
|
+
out_size = stride * height
|
|
177
|
+
|
|
178
|
+
FFI::MemoryPointer.new(:uint8, out_size) do |out_buf|
|
|
179
|
+
ok = png_image_finish_read(img, nil, out_buf, stride, nil)
|
|
180
|
+
if ok.zero?
|
|
181
|
+
msg = read_message(img)
|
|
182
|
+
raise Error, "png_image_finish_read failed: #{msg}"
|
|
183
|
+
end
|
|
184
|
+
return DecodedImage.new(width: width,
|
|
185
|
+
height: height,
|
|
186
|
+
format: pixel_format.to_s.upcase,
|
|
187
|
+
pixels: out_buf.read_bytes(out_size))
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
ensure
|
|
191
|
+
png_image_free(img)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
private
|
|
196
|
+
|
|
197
|
+
# Lower-level encode that produces the raw libpng output (with sRGB
|
|
198
|
+
# or gAMA chunks depending on libpng's defaults). Public #encode strips
|
|
199
|
+
# these by default to match the classic libpng write path.
|
|
200
|
+
def encode_ancillary(width, height, pixels, pixel_format:, convert_to_8bit:)
|
|
201
|
+
raise Error, 'width must be positive' unless width.positive?
|
|
202
|
+
raise Error, 'height must be positive' unless height.positive?
|
|
203
|
+
|
|
204
|
+
fmt = FORMAT_BY_NAME[pixel_format.to_s.upcase] ||
|
|
205
|
+
raise(Error, "unknown pixel_format #{pixel_format.inspect}")
|
|
206
|
+
|
|
207
|
+
bytes_per_pixel = bytes_per_pixel_for_format(fmt)
|
|
208
|
+
stride = width * bytes_per_pixel
|
|
209
|
+
expected = stride * height
|
|
210
|
+
raise Error, "pixels too short: expected #{expected}, got #{pixels.bytesize}" if pixels.bytesize < expected
|
|
211
|
+
|
|
212
|
+
img = FFI::MemoryPointer.new(:uint8, PNG_IMAGE_SIZE)
|
|
213
|
+
img.clear
|
|
214
|
+
img.put_uint32(PNG_IMAGE_OFF_VERSION, PNG_IMAGE_VERSION)
|
|
215
|
+
img.put_uint32(PNG_IMAGE_OFF_WIDTH, width)
|
|
216
|
+
img.put_uint32(PNG_IMAGE_OFF_HEIGHT, height)
|
|
217
|
+
img.put_uint32(PNG_IMAGE_OFF_FORMAT, fmt)
|
|
218
|
+
|
|
219
|
+
out_len_ptr = FFI::MemoryPointer.new(:size_t, 1)
|
|
220
|
+
ok = png_image_write_to_memory(img, nil, out_len_ptr,
|
|
221
|
+
convert_to_8bit ? 1 : 0,
|
|
222
|
+
pixels, stride, nil)
|
|
223
|
+
if ok.zero?
|
|
224
|
+
msg = read_message(img)
|
|
225
|
+
png_image_free(img)
|
|
226
|
+
raise Error, "png_image_write_to_memory (size query) failed: #{msg}"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
out_len = out_len_ptr.read_uint64
|
|
230
|
+
raise Error, 'libpng reported zero-length PNG output' if out_len.zero?
|
|
231
|
+
|
|
232
|
+
buffer = FFI::MemoryPointer.new(:uint8, out_len)
|
|
233
|
+
ok = png_image_write_to_memory(img, buffer, out_len_ptr,
|
|
234
|
+
convert_to_8bit ? 1 : 0,
|
|
235
|
+
pixels, stride, nil)
|
|
236
|
+
if ok.zero?
|
|
237
|
+
msg = read_message(img)
|
|
238
|
+
png_image_free(img)
|
|
239
|
+
raise Error, "png_image_write_to_memory (write) failed: #{msg}"
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
result = buffer.read_bytes(out_len)
|
|
243
|
+
png_image_free(img)
|
|
244
|
+
result
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Walk the PNG chunk list, keeping only IHDR, IDAT, and IEND. The PNG
|
|
248
|
+
# signature (8 bytes) is preserved. We do NOT need to recompute CRCs —
|
|
249
|
+
# the simplified API's emitted chunks (including the ones we drop) are
|
|
250
|
+
# all correctly CRCd; we only need to walk the chunk list and keep the
|
|
251
|
+
# ones we want, byte-for-byte.
|
|
252
|
+
def strip_ancillary_chunks(png_bytes)
|
|
253
|
+
sig = png_bytes.bytes[0, 8]
|
|
254
|
+
raise Error, 'not a PNG file (bad signature)' unless sig == [137, 80, 78, 71, 13, 10, 26, 10]
|
|
255
|
+
|
|
256
|
+
kept = sig.pack('C*')
|
|
257
|
+
offset = 8
|
|
258
|
+
while offset + 8 <= png_bytes.bytesize
|
|
259
|
+
len = png_bytes.bytes[offset, 4].pack('C*').unpack1('N')
|
|
260
|
+
type = png_bytes.bytes[offset + 4, 4].pack('C*')
|
|
261
|
+
chunk_total = 12 + len
|
|
262
|
+
raise Error, "PNG chunk at offset #{offset} runs past EOF" if offset + chunk_total > png_bytes.bytesize
|
|
263
|
+
|
|
264
|
+
crc_input = png_bytes.bytes[offset + 4, 4 + len].pack('C*')
|
|
265
|
+
crc_actual = png_bytes.bytes[offset + 8 + len, 4].pack('C*').unpack1('N')
|
|
266
|
+
crc_expected = Zlib.crc32(crc_input)
|
|
267
|
+
raise Error, "PNG chunk CRC mismatch at offset #{offset} (#{type})" unless crc_actual == crc_expected
|
|
268
|
+
|
|
269
|
+
kept << png_bytes.bytes[offset, chunk_total].pack('C*') if %w[IHDR IDAT IEND].include?(type)
|
|
270
|
+
offset += chunk_total
|
|
271
|
+
break if type == 'IEND'
|
|
272
|
+
end
|
|
273
|
+
kept.force_encoding('ASCII-8BIT')
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def read_message(img)
|
|
277
|
+
# img[:message] is an Array field (char[64]); FFI returns a
|
|
278
|
+
# CharArray, which doesn't respond to .null? but does support to_ptr
|
|
279
|
+
# and to_s.
|
|
280
|
+
|
|
281
|
+
s = img[:message].to_s
|
|
282
|
+
s.empty? ? '(no message)' : s.force_encoding('UTF-8')
|
|
283
|
+
rescue StandardError
|
|
284
|
+
'(no message)'
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def bytes_per_pixel_for_format(fmt)
|
|
288
|
+
case fmt
|
|
289
|
+
when FORMAT_GRAY then 1
|
|
290
|
+
when FORMAT_GA, FORMAT_AG then 2
|
|
291
|
+
when FORMAT_RGB, FORMAT_BGR then 3
|
|
292
|
+
when FORMAT_RGBA, FORMAT_ARGB, FORMAT_BGRA, FORMAT_ABGR then 4
|
|
293
|
+
else raise Error, "no bytes-per-pixel mapping for format 0x#{fmt.to_s(16)}"
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
data/libpng.gemspec
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/libpng/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'libpng'
|
|
7
|
+
spec.version = Libpng::VERSION
|
|
8
|
+
spec.authors = ['Ribose Inc.']
|
|
9
|
+
spec.email = ['open.source@ribose.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'libpng for Ruby (pre-compiled, FFI-based).'
|
|
12
|
+
spec.description = 'Ruby binding for libpng via FFI. The native ' \
|
|
13
|
+
'libpng16 shared library is pre-compiled for ' \
|
|
14
|
+
'each target platform and shipped inside the ' \
|
|
15
|
+
'gem, so no C compiler is required at install time.'
|
|
16
|
+
spec.homepage = 'https://github.com/claricle/libpng-ruby'
|
|
17
|
+
spec.license = 'BSD-2-Clause'
|
|
18
|
+
spec.required_ruby_version = '>= 2.7.0'
|
|
19
|
+
|
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
21
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
|
22
|
+
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/README.adoc#versioning"
|
|
23
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
24
|
+
|
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
27
|
+
f.match(%r{\A(?:test|spec|features|tmp|ports)/})
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
spec.bindir = 'exe'
|
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
32
|
+
spec.require_paths = ['lib']
|
|
33
|
+
|
|
34
|
+
spec.add_dependency 'ffi', '~> 1.0'
|
|
35
|
+
spec.add_dependency 'mini_portile2', '~> 2.6'
|
|
36
|
+
|
|
37
|
+
spec.extensions = ['ext/extconf.rb']
|
|
38
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: libpng
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.6.58.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ribose Inc.
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ffi
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: mini_portile2
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.6'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.6'
|
|
40
|
+
description: Ruby binding for libpng via FFI. The native libpng16 shared library is
|
|
41
|
+
pre-compiled for each target platform and shipped inside the gem, so no C compiler
|
|
42
|
+
is required at install time.
|
|
43
|
+
email:
|
|
44
|
+
- open.source@ribose.com
|
|
45
|
+
executables: []
|
|
46
|
+
extensions:
|
|
47
|
+
- ext/extconf.rb
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- ".github/workflows/build.yml"
|
|
51
|
+
- ".github/workflows/release.yml"
|
|
52
|
+
- ".gitignore"
|
|
53
|
+
- ".rspec"
|
|
54
|
+
- ".rubocop.yml"
|
|
55
|
+
- Gemfile
|
|
56
|
+
- LICENSE.txt
|
|
57
|
+
- README.adoc
|
|
58
|
+
- Rakefile
|
|
59
|
+
- bin/console
|
|
60
|
+
- bin/setup
|
|
61
|
+
- ext/extconf.rb
|
|
62
|
+
- lib/libpng.rb
|
|
63
|
+
- lib/libpng/recipe.rb
|
|
64
|
+
- lib/libpng/version.rb
|
|
65
|
+
- libpng.gemspec
|
|
66
|
+
homepage: https://github.com/claricle/libpng-ruby
|
|
67
|
+
licenses:
|
|
68
|
+
- BSD-2-Clause
|
|
69
|
+
metadata:
|
|
70
|
+
homepage_uri: https://github.com/claricle/libpng-ruby
|
|
71
|
+
source_code_uri: https://github.com/claricle/libpng-ruby
|
|
72
|
+
changelog_uri: https://github.com/claricle/libpng-ruby/blob/main/README.adoc#versioning
|
|
73
|
+
rubygems_mfa_required: 'true'
|
|
74
|
+
rdoc_options: []
|
|
75
|
+
require_paths:
|
|
76
|
+
- lib
|
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: 2.7.0
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
requirements: []
|
|
88
|
+
rubygems_version: 4.0.16
|
|
89
|
+
specification_version: 4
|
|
90
|
+
summary: libpng for Ruby (pre-compiled, FFI-based).
|
|
91
|
+
test_files: []
|