image_pack 0.2.3 → 0.2.5
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 +4 -4
- data/CHANGELOG.md +35 -0
- data/README.md +110 -132
- data/ext/image_pack/extconf.rb +1 -1
- data/ext/image_pack/image_pack.c +293 -103
- data/lib/image_pack/configuration.rb +1 -1
- data/lib/image_pack/version.rb +1 -1
- data/lib/image_pack.rb +40 -10
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12f5358f1b62d18b5f23c0022bf953ae6efab239645a94329774465b5ff4681a
|
|
4
|
+
data.tar.gz: b0fd8d35a15c5e6ef157c925e0ed967a485755b32f310b5c49bdc10688b58ebf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a3cf71f03cedf98384331e74fdd47d3fa151b432d3af4e059032261a3f4a929d15b9329cbc57aace8f04fda9c73ac1cdad9d99a4b7e499fbda1f7242334c5c6e
|
|
7
|
+
data.tar.gz: 99898ec78f08fe924e5447a514dd37dcd4b141cf6b619fe5713f1050c0ca1ded2e112eda18b407e75b39d3a9c2fe802e1294d02d46ed63a24960944f398603d4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.5
|
|
4
|
+
|
|
5
|
+
**Output and API changes (upgrade notes)**
|
|
6
|
+
|
|
7
|
+
- `algo: :mozjpeg` and `algo: :size` now default to **progressive JPEG** with full scan optimization. Previously the default was baseline JPEG. Pass `progressive: false` to restore the old behavior.
|
|
8
|
+
- Added `mozjpeg_scan_opt:` keyword to `compress` and `compress_pixels` (default `true`). Passing `false` keeps trellis + progressive + Huffman optimization but skips the multi-pass scan search, trading ~0.7% larger files for ~30% faster encode on the MozJPEG path.
|
|
9
|
+
- `algo: :jpeg_turbo` and `algo: :fast` are unchanged: baseline JPEG, same output as 0.2.4.
|
|
10
|
+
|
|
11
|
+
**Performance improvements**
|
|
12
|
+
|
|
13
|
+
- SSIM guard (`min_ssim:`) now uses an interpolation/secant probe instead of pure bisection. Once a failing and a passing sample are known, the crossing quality is predicted by linear interpolation and clamped inside the live bracket. The accept/reject invariant and the minimal passing quality are unchanged; typical inputs converge in fewer encode/decode/SSIM round trips.
|
|
14
|
+
- In size mode, the SSIM guard scores candidates with a cheap "measurement" encode that skips `OPTIMIZE_SCANS` (which only affects entropy coding/scan layout, never the dequantized coefficients). The winning quality is re-encoded once with the full profile, and the true SSIM of the final output is re-checked, so `report[:ssim] >= min_ssim` continues to hold.
|
|
15
|
+
- The `algo: :jpeg_turbo`/`:fast` JPEG→JPEG path now transcodes through YCbCr instead of RGB, skipping one color-space round trip. Grayscale and `compress_pixels` inputs are unaffected.
|
|
16
|
+
|
|
17
|
+
**Other**
|
|
18
|
+
|
|
19
|
+
- Added stricter smoke coverage for legacy aliases, default algorithm behavior, and parser-backed JPEG assertions.
|
|
20
|
+
- Added `rake simd:check` and `rake release:check` so release builds cannot silently ship scalar x86_64 output unless `IMAGE_PACK_ALLOW_SCALAR=1` is set.
|
|
21
|
+
|
|
22
|
+
## 0.2.4
|
|
23
|
+
|
|
24
|
+
- libjpeg/MozJPEG diagnostics are no longer written to `stderr`; instead decode warnings (e.g. "Premature end of JPEG file") are counted and the first message is captured, so a damaged or truncated input is observable rather than silently degraded.
|
|
25
|
+
- Added `strict:` to `compress`, `compress_pixels`, and `optimize_jpeg`. With `strict: true` the first libjpeg warning is promoted to `ImagePack::InvalidImageError` instead of producing a degraded JPEG; default is `false`.
|
|
26
|
+
- Added `report:` to `compress` and `compress_pixels`. With `report: true` the call returns a Hash `{ output:, quality:, ssim:, algo:, bytesize:, input_bytesize:, warning_count:, warning: }`, exposing the SSIM-selected quality, achieved luma SSIM, and decode warning information; `report: false` (default) keeps the previous return value (binary `String` or `true`).
|
|
27
|
+
- Made the no-GVL and offload execution paths interruptible regardless of `cancellable:`. Decode/encode/SSIM checkpoints now always observe the unblock signal, and a pending Ruby interrupt (signal, `Thread#raise`, `Thread#kill`, scheduler) is delivered via `rb_thread_check_ints` after the native section, so the raised exception propagates instead of being masked. Cancellation is expressed by raising on the worker thread; `ImagePack::CancelledError` is the idiomatic choice.
|
|
28
|
+
- Added the `IMAGE_PACK_DISABLE_OFFLOAD=1` runtime opt-out. The vendored MozJPEG SIMD thread-local storage is only an idempotent CPU-feature cache, so offload remains safe with SIMD enabled, but the environment variable forces `offload_safe?` to `false` and prevents `:auto`/`:offload` from offloading.
|
|
29
|
+
- `NATIVE_MOZJPEG_VERSION` is now derived from a dedicated `IMAGE_PACK_MOZJPEG_VERSION` compile define instead of relying on the libjpeg `VERSION` macro being in scope.
|
|
30
|
+
- Removed the unused `IMAGE_PACK_RUBY34_ONLY` compile define.
|
|
31
|
+
- Removed a stale `v0.2.2` reference from the `output:` argument error message.
|
|
32
|
+
- Added tests covering `report:`, `strict:`, warning collection, the cancellation contract, offload availability, and version hygiene.
|
|
33
|
+
- Documentation and message version references updated to 0.2.4.
|
|
34
|
+
|
|
35
|
+
## 0.2.3
|
|
36
|
+
|
|
3
37
|
- Lowered the runtime floor to Ruby >= 3.1.0 while keeping `execution: :offload` Ruby >= 3.4-only and explicitly unsupported on older Rubies.
|
|
38
|
+
|
|
4
39
|
## 0.2.2
|
|
5
40
|
|
|
6
41
|
- Fixed native cleanup safety: native contexts are now released through `rb_ensure`, including Ruby exception paths from argument coercion, config reads, native status errors, output `String` allocation, and file-output failures.
|
data/README.md
CHANGED
|
@@ -1,199 +1,177 @@
|
|
|
1
1
|
# image_pack
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Ruby-native JPEG compression and optimization backed by vendored pure-C MozJPEG/libjpeg.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
This pure-C variant intentionally removes Jpegli and any C++ toolchain requirement.
|
|
8
|
-
The native layer is written in C and links against vendored MozJPEG, which is libjpeg-compatible and based on libjpeg-turbo.
|
|
9
|
-
|
|
10
|
-
## Backends / modes
|
|
5
|
+
No system `libjpeg`, `mozjpeg`, `git`, or `CMake` is required for gem users.
|
|
11
6
|
|
|
12
7
|
```ruby
|
|
13
|
-
|
|
14
|
-
ImagePack.compress(input, algo: :mozjpeg)
|
|
8
|
+
gem "image_pack"
|
|
15
9
|
```
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Both modes produce ordinary `.jpg` files.
|
|
21
|
-
|
|
22
|
-
Important implementation note: this prototype uses one pure-C vendored codec family to avoid static-link symbol conflicts between separate libjpeg-compatible libraries. If later we need exact latest `libjpeg-turbo` and exact MozJPEG in one gem, the correct next step is linker isolation / separate native extensions with hidden symbols.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## Native extension layout
|
|
26
|
-
|
|
27
|
-
For the first prototype the native layer intentionally lives in one file:
|
|
28
|
-
|
|
29
|
-
```text
|
|
30
|
-
ext/image_pack/
|
|
31
|
-
├── extconf.rb
|
|
32
|
-
└── image_pack.c
|
|
11
|
+
```ruby
|
|
12
|
+
require "image_pack"
|
|
33
13
|
```
|
|
34
14
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
## API
|
|
15
|
+
## Quick use
|
|
38
16
|
|
|
39
17
|
```ruby
|
|
40
|
-
require "image_pack"
|
|
41
|
-
|
|
42
18
|
jpeg = File.binread("photo.jpg")
|
|
43
19
|
|
|
44
|
-
ImagePack.
|
|
45
|
-
|
|
20
|
+
small = ImagePack.compress_bytes(jpeg, quality: 82)
|
|
21
|
+
File.binwrite("photo.small.jpg", small)
|
|
46
22
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# Start at quality 75, but raise quality if needed to satisfy min_ssim.
|
|
52
|
-
ImagePack.compress(jpeg, algo: :mozjpeg, quality: 75, min_ssim: 0.985)
|
|
23
|
+
ImagePack.compress_file("photo.jpg", output: "photo.small.jpg")
|
|
24
|
+
ImagePack.optimize_file("photo.jpg", output: "photo.optimized.jpg")
|
|
25
|
+
```
|
|
53
26
|
|
|
54
|
-
|
|
55
|
-
ImagePack.compress_file("photo.jpg", output: "photo.optimized.jpg", algo: :mozjpeg)
|
|
56
|
-
ImagePack.compress_bytes(jpeg, algo: :fast, quality: 82)
|
|
27
|
+
Prefer explicit helpers:
|
|
57
28
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
ImagePack.
|
|
61
|
-
ImagePack.optimize_file("photo.jpg", output: "photo.lossless.jpg")
|
|
29
|
+
```ruby
|
|
30
|
+
ImagePack.compress_bytes(jpeg)
|
|
31
|
+
ImagePack.compress_file("photo.jpg", output: "out.jpg")
|
|
62
32
|
ImagePack.optimize_bytes(jpeg)
|
|
33
|
+
ImagePack.optimize_file("photo.jpg", output: "out.jpg")
|
|
34
|
+
```
|
|
63
35
|
|
|
64
|
-
|
|
65
|
-
width: 1920,
|
|
66
|
-
height: 1080,
|
|
67
|
-
channels: 3,
|
|
68
|
-
algo: :mozjpeg
|
|
69
|
-
)
|
|
36
|
+
## Compression
|
|
70
37
|
|
|
71
|
-
|
|
72
|
-
|
|
38
|
+
```ruby
|
|
39
|
+
ImagePack.compress_bytes(jpeg,
|
|
40
|
+
algo: :size,
|
|
41
|
+
quality: 82,
|
|
42
|
+
strip_metadata: true
|
|
43
|
+
)
|
|
73
44
|
```
|
|
74
45
|
|
|
75
|
-
|
|
46
|
+
Algorithms:
|
|
76
47
|
|
|
77
|
-
`
|
|
48
|
+
- `:size` / `:mozjpeg` — smaller files, default; uses optimized progressive MozJPEG output by default
|
|
49
|
+
- `:fast` / `:jpeg_turbo` — faster baseline mode
|
|
78
50
|
|
|
79
|
-
|
|
80
|
-
- non-binary `String` that points to an existing file — file path;
|
|
81
|
-
- `Pathname` — file path;
|
|
82
|
-
- `IO::Buffer` — JPEG bytes, copied before native no-GVL execution for safety.
|
|
51
|
+
Common options:
|
|
83
52
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
53
|
+
```ruby
|
|
54
|
+
ImagePack.compress_bytes(jpeg, min_ssim: 0.985)
|
|
55
|
+
ImagePack.compress_bytes(jpeg, progressive: false) # force baseline output
|
|
56
|
+
ImagePack.compress_bytes(jpeg, strict: true)
|
|
57
|
+
ImagePack.compress_bytes(jpeg, report: true)
|
|
58
|
+
```
|
|
88
59
|
|
|
89
|
-
`output: nil` returns binary JPEG bytes. `output: String/Pathname` writes through a temporary file and renames it into place.
|
|
90
|
-
Streaming output is intentionally not supported in v0.2.2.
|
|
91
60
|
|
|
92
|
-
|
|
61
|
+
`algo: :size` / `:mozjpeg` now defaults to optimized progressive scans plus scan-aware MozJPEG trellis tuning because that is the strongest built-in size profile used by the gem. Pass `progressive: false` when you explicitly need baseline JPEG output.
|
|
93
62
|
|
|
94
|
-
`
|
|
63
|
+
`algo: :fast` / `:jpeg_turbo` keeps baseline output by default and remains the throughput path.
|
|
95
64
|
|
|
96
|
-
|
|
65
|
+
`min_ssim:` searches for the lowest acceptable quality using a fast native luma SSIM guard.
|
|
97
66
|
|
|
98
|
-
|
|
99
|
-
- `strip_metadata: false` preserves APP/COM metadata by default because this path is meant to be visually/losslessly safe.
|
|
67
|
+
`strict: true` raises `ImagePack::InvalidImageError` on damaged/truncated JPEG warnings.
|
|
100
68
|
|
|
101
|
-
|
|
69
|
+
`report: true` returns a Hash:
|
|
102
70
|
|
|
103
|
-
|
|
71
|
+
```ruby
|
|
72
|
+
{
|
|
73
|
+
output: "\xFF\xD8...",
|
|
74
|
+
quality: 84,
|
|
75
|
+
ssim: 0.9861,
|
|
76
|
+
algo: :mozjpeg,
|
|
77
|
+
bytesize: 50122,
|
|
78
|
+
input_bytesize: 81344,
|
|
79
|
+
warning_count: 0,
|
|
80
|
+
warning: nil
|
|
81
|
+
}
|
|
82
|
+
```
|
|
104
83
|
|
|
105
|
-
`
|
|
84
|
+
With `output: "file.jpg"`, `output` is `true`.
|
|
106
85
|
|
|
107
|
-
|
|
108
|
-
2. encode trial JPEG candidates with the existing MozJPEG/libjpeg backend;
|
|
109
|
-
3. decode each candidate;
|
|
110
|
-
4. compute a native luma SSIM score;
|
|
111
|
-
5. return the smallest quality that satisfies `SSIM >= min_ssim`.
|
|
86
|
+
## Lossless optimize
|
|
112
87
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
88
|
+
```ruby
|
|
89
|
+
ImagePack.optimize_bytes(jpeg)
|
|
90
|
+
ImagePack.optimize_file("photo.jpg", output: "photo.optimized.jpg")
|
|
91
|
+
```
|
|
116
92
|
|
|
117
|
-
|
|
118
|
-
is raised. With `execution: :auto`, guarded compression uses the no-GVL/offload
|
|
119
|
-
path instead of the small-image direct path because it may encode/decode several
|
|
120
|
-
candidates. For `compress_pixels`, the reference is the raw pixel buffer itself, not a seed JPEG. RGBA + `min_ssim` is rejected because JPEG cannot represent alpha.
|
|
93
|
+
This rewrites JPEG coefficients without decoding and re-encoding pixels. It is the right path for existing JPEGs when you only want optimized Huffman tables and optional progressive scans.
|
|
121
94
|
|
|
122
|
-
|
|
95
|
+
Defaults: `progressive: true`, `strip_metadata: false`.
|
|
123
96
|
|
|
124
|
-
|
|
97
|
+
If `strip_metadata: true` would remove EXIF Orientation, `optimize_jpeg` raises `UnsupportedError` instead of silently changing visual orientation.
|
|
125
98
|
|
|
126
|
-
|
|
99
|
+
## Raw pixels
|
|
127
100
|
|
|
128
|
-
|
|
101
|
+
```ruby
|
|
102
|
+
ImagePack.compress_pixels(rgb,
|
|
103
|
+
width: 1920,
|
|
104
|
+
height: 1080,
|
|
105
|
+
channels: 3,
|
|
106
|
+
output: "frame.jpg"
|
|
107
|
+
)
|
|
108
|
+
```
|
|
129
109
|
|
|
130
|
-
|
|
110
|
+
`channels` must be `1`, `3`, or `4`. JPEG cannot store alpha, so RGBA input needs explicit opt-in:
|
|
131
111
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
- explicit `execution: :offload` raises `ImagePack::UnsupportedError`.
|
|
112
|
+
```ruby
|
|
113
|
+
ImagePack.compress_pixels(rgba, width: 100, height: 100, channels: 4, drop_alpha: true)
|
|
114
|
+
```
|
|
136
115
|
|
|
137
|
-
|
|
116
|
+
## Inspect
|
|
138
117
|
|
|
139
|
-
|
|
140
|
-
|
|
118
|
+
```ruby
|
|
119
|
+
ImagePack.inspect_image(jpeg)
|
|
120
|
+
# => { format: :jpeg, width: 1920, height: 1080, channels: 3, bit_depth: 8, decoded_bytes: 6220800 }
|
|
121
|
+
```
|
|
141
122
|
|
|
142
|
-
|
|
123
|
+
## Execution
|
|
143
124
|
|
|
125
|
+
Default mode is `:auto`.
|
|
144
126
|
|
|
145
127
|
```ruby
|
|
146
|
-
ImagePack.
|
|
147
|
-
ImagePack.
|
|
148
|
-
ImagePack.
|
|
149
|
-
ImagePack.
|
|
128
|
+
ImagePack.compress_bytes(jpeg, execution: :auto)
|
|
129
|
+
ImagePack.compress_bytes(jpeg, execution: :direct)
|
|
130
|
+
ImagePack.compress_bytes(jpeg, execution: :nogvl)
|
|
131
|
+
ImagePack.compress_bytes(jpeg, execution: :offload) # Ruby >= 3.4 only
|
|
150
132
|
```
|
|
151
133
|
|
|
152
|
-
|
|
153
|
-
- `:nogvl` — uses `rb_nogvl(..., flags: 0)`.
|
|
154
|
-
- `:offload` — uses `rb_nogvl(..., RB_NOGVL_OFFLOAD_SAFE)` on Ruby 3.4+; raises `ImagePack::UnsupportedError` on Ruby 3.1–3.3.
|
|
155
|
-
- `:auto` — header-first policy chooses direct/nogvl/offload; on Ruby 3.1–3.3 it never selects offload.
|
|
156
|
-
|
|
157
|
-
## Cancellation
|
|
134
|
+
Use `ImagePack.offload_safe?` or `ImagePack.build_info` to inspect runtime support.
|
|
158
135
|
|
|
159
|
-
|
|
160
|
-
Cancellation is cooperative at decode, encode, and SSIM-search checkpoints, not instant.
|
|
136
|
+
Set `IMAGE_PACK_DISABLE_OFFLOAD=1` before loading the gem to disable offload.
|
|
161
137
|
|
|
162
|
-
|
|
138
|
+
Long no-GVL/offload calls can be interrupted by raising into the worker thread:
|
|
163
139
|
|
|
164
|
-
|
|
140
|
+
```ruby
|
|
141
|
+
worker = Thread.new { ImagePack.compress_bytes(jpeg, execution: :nogvl, cancellable: true) }
|
|
142
|
+
worker.raise(ImagePack::CancelledError, "cancelled")
|
|
143
|
+
worker.join
|
|
144
|
+
```
|
|
165
145
|
|
|
166
|
-
##
|
|
146
|
+
## Configuration
|
|
167
147
|
|
|
168
148
|
```ruby
|
|
169
|
-
ImagePack.
|
|
170
|
-
|
|
149
|
+
ImagePack.configure do |config|
|
|
150
|
+
config.execution = :auto
|
|
151
|
+
config.max_input_size = 256 * 1024 * 1024
|
|
152
|
+
config.max_output_size = 256 * 1024 * 1024
|
|
153
|
+
config.max_pixels = 100_000_000
|
|
154
|
+
end
|
|
171
155
|
```
|
|
172
156
|
|
|
173
|
-
##
|
|
174
|
-
|
|
175
|
-
The gem is expected to ship vendored native sources/libs, so end users should not install system libjpeg/mozjpeg.
|
|
176
|
-
For repository preparation:
|
|
157
|
+
## Development
|
|
177
158
|
|
|
178
159
|
```bash
|
|
179
160
|
bundle exec rake vendor
|
|
180
161
|
bundle exec rake compile
|
|
162
|
+
bundle exec rake test
|
|
163
|
+
bundle exec rake release:check
|
|
181
164
|
```
|
|
182
165
|
|
|
183
166
|
`rake vendor` pins MozJPEG `v4.1.5`.
|
|
184
167
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- Pixel-level `compress` rejects CMYK/YCCK JPEG input because it decodes to RGB/gray before re-encoding. Use `optimize_jpeg` for coefficient-level lossless optimization of existing CMYK/YCCK JPEGs.
|
|
188
|
-
- Arithmetic-coded JPEG support is disabled in the vendored `jconfig.h` for v0.2.2.
|
|
189
|
-
- The SSIM guard is a fast 8x8 luma block metric and still assumes quality/SSIM monotonicity during binary search.
|
|
190
|
-
- `compress(input, ...)` still has a legacy path-vs-bytes heuristic for non-binary `String`; prefer `compress_bytes` / `compress_file` and `optimize_bytes` / `optimize_file` in new code.
|
|
168
|
+
`rake release:check` compiles, verifies tests, and fails release builds when SIMD is unavailable. Set `IMAGE_PACK_ALLOW_SCALAR=1` only when intentionally shipping a scalar build.
|
|
191
169
|
|
|
192
|
-
##
|
|
170
|
+
## Limits
|
|
193
171
|
|
|
194
|
-
-
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
-
-
|
|
198
|
-
-
|
|
199
|
-
-
|
|
172
|
+
- JPEG only.
|
|
173
|
+
- Ruby `>= 3.1`; `execution: :offload` requires Ruby `>= 3.4`.
|
|
174
|
+
- Pixel-level `compress` rejects CMYK/YCCK JPEG input; use `optimize_jpeg` for existing CMYK/YCCK JPEGs.
|
|
175
|
+
- Arithmetic-coded JPEG support is disabled in `0.2.5`.
|
|
176
|
+
- Streaming output is not supported; file output uses atomic write-through-temp-file and rename.
|
|
177
|
+
- `ImagePack.compress(input, ...)` keeps a legacy path-vs-bytes heuristic; prefer explicit `*_bytes` / `*_file` helpers.
|
data/ext/image_pack/extconf.rb
CHANGED
|
@@ -416,8 +416,8 @@ else
|
|
|
416
416
|
$CFLAGS += " -O2"
|
|
417
417
|
end
|
|
418
418
|
|
|
419
|
-
$CFLAGS += " -DIMAGE_PACK_RUBY34_ONLY=1"
|
|
420
419
|
$CFLAGS += build_info[:with_simd] ? " -DIMAGE_PACK_HAS_SIMD=1" : " -DIMAGE_PACK_PURE_C=1"
|
|
420
|
+
$CPPFLAGS += " -DIMAGE_PACK_MOZJPEG_VERSION=\\\"#{MOZJPEG_VERSION}\\\""
|
|
421
421
|
$LIBS += " -lm" unless msvc?
|
|
422
422
|
|
|
423
423
|
have_header("ruby/thread.h") or abort "ruby/thread.h not found"
|
data/ext/image_pack/image_pack.c
CHANGED
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
#define TRUE 1
|
|
41
41
|
#endif
|
|
42
42
|
|
|
43
|
+
#ifndef IMAGE_PACK_MOZJPEG_VERSION
|
|
44
|
+
#define IMAGE_PACK_MOZJPEG_VERSION VERSION
|
|
45
|
+
#endif
|
|
46
|
+
|
|
43
47
|
#ifndef FALSE
|
|
44
48
|
#define FALSE 0
|
|
45
49
|
#endif
|
|
@@ -133,6 +137,7 @@ typedef struct {
|
|
|
133
137
|
int progressive;
|
|
134
138
|
int strip_metadata;
|
|
135
139
|
int mozjpeg_trellis_enabled;
|
|
140
|
+
int mozjpeg_scan_opt_enabled;
|
|
136
141
|
ip_algo_t algo;
|
|
137
142
|
ip_execution_t requested_execution;
|
|
138
143
|
ip_execution_t resolved_execution;
|
|
@@ -151,6 +156,9 @@ typedef struct {
|
|
|
151
156
|
|
|
152
157
|
atomic_int cancelled;
|
|
153
158
|
int cancellable_requested;
|
|
159
|
+
int strict;
|
|
160
|
+
int warning_count;
|
|
161
|
+
char first_warning[200];
|
|
154
162
|
|
|
155
163
|
jmp_buf jmpbuf;
|
|
156
164
|
int jmp_armed;
|
|
@@ -166,6 +174,7 @@ typedef struct {
|
|
|
166
174
|
unsigned char *transient_jpeg_buf;
|
|
167
175
|
unsigned char *transient_decode_buf;
|
|
168
176
|
int source_orientation;
|
|
177
|
+
int decoded_as_ycbcr;
|
|
169
178
|
} ip_context_t;
|
|
170
179
|
|
|
171
180
|
typedef struct {
|
|
@@ -174,6 +183,7 @@ typedef struct {
|
|
|
174
183
|
} ip_jpeg_error_mgr;
|
|
175
184
|
|
|
176
185
|
static VALUE rb_mImagePack;
|
|
186
|
+
static int ip_offload_runtime_enabled = 1;
|
|
177
187
|
static VALUE rb_eImagePackError;
|
|
178
188
|
static VALUE rb_eImagePackInvalidArgumentError;
|
|
179
189
|
static VALUE rb_eImagePackInvalidImageError;
|
|
@@ -243,7 +253,8 @@ static int ip_run_context(ip_context_t *ctx);
|
|
|
243
253
|
static void validate_limits_for_pixels(ip_context_t *ctx);
|
|
244
254
|
|
|
245
255
|
static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, int *width,
|
|
246
|
-
int *height, int *channels, int fast_decode_mode
|
|
256
|
+
int *height, int *channels, int fast_decode_mode,
|
|
257
|
+
int allow_ycbcr_transcode);
|
|
247
258
|
static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char *data, size_t size,
|
|
248
259
|
unsigned char **luma, int *width, int *height);
|
|
249
260
|
static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode);
|
|
@@ -255,18 +266,20 @@ static int ip_run_optimize_context(ip_context_t *ctx);
|
|
|
255
266
|
typedef struct {
|
|
256
267
|
VALUE self, input, input_kind, output, output_kind, algo, quality, min_ssim;
|
|
257
268
|
VALUE mozjpeg_trellis, progressive, strip_metadata, execution, cancellable, has_scheduler;
|
|
269
|
+
VALUE report, strict, mozjpeg_scan_opt;
|
|
258
270
|
ip_context_t *ctx;
|
|
259
271
|
} ip_compress_jpeg_call_t;
|
|
260
272
|
|
|
261
273
|
typedef struct {
|
|
262
274
|
VALUE self, buffer, width, height, channels, output, output_kind, algo, quality, min_ssim;
|
|
263
275
|
VALUE mozjpeg_trellis, progressive, exact_size, execution, cancellable, has_scheduler;
|
|
276
|
+
VALUE report, strict, mozjpeg_scan_opt;
|
|
264
277
|
ip_context_t *ctx;
|
|
265
278
|
} ip_compress_pixels_call_t;
|
|
266
279
|
|
|
267
280
|
typedef struct {
|
|
268
281
|
VALUE self, input, input_kind, output, output_kind, progressive, strip_metadata;
|
|
269
|
-
VALUE execution, cancellable, has_scheduler;
|
|
282
|
+
VALUE execution, cancellable, has_scheduler, strict;
|
|
270
283
|
ip_context_t *ctx;
|
|
271
284
|
} ip_optimize_jpeg_call_t;
|
|
272
285
|
|
|
@@ -284,18 +297,12 @@ static VALUE ip_call_cleanup(VALUE ptr) {
|
|
|
284
297
|
return Qnil;
|
|
285
298
|
}
|
|
286
299
|
|
|
287
|
-
static VALUE ip_compress_jpeg_entry(
|
|
288
|
-
|
|
289
|
-
VALUE mozjpeg_trellis, VALUE progressive, VALUE strip_metadata,
|
|
290
|
-
VALUE execution, VALUE cancellable, VALUE has_scheduler);
|
|
291
|
-
static VALUE ip_compress_pixels_entry(VALUE self, VALUE buffer, VALUE width, VALUE height,
|
|
292
|
-
VALUE channels, VALUE output, VALUE output_kind, VALUE algo,
|
|
293
|
-
VALUE quality, VALUE min_ssim, VALUE mozjpeg_trellis,
|
|
294
|
-
VALUE progressive, VALUE exact_size, VALUE execution,
|
|
295
|
-
VALUE cancellable, VALUE has_scheduler);
|
|
300
|
+
static VALUE ip_compress_jpeg_entry(int argc, VALUE *argv, VALUE self);
|
|
301
|
+
static VALUE ip_compress_pixels_entry(int argc, VALUE *argv, VALUE self);
|
|
296
302
|
static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
|
|
297
303
|
VALUE output_kind, VALUE progressive, VALUE strip_metadata,
|
|
298
|
-
VALUE execution, VALUE cancellable, VALUE has_scheduler
|
|
304
|
+
VALUE execution, VALUE cancellable, VALUE has_scheduler,
|
|
305
|
+
VALUE strict);
|
|
299
306
|
|
|
300
307
|
static VALUE ip_status_to_exception(ip_status_t status) {
|
|
301
308
|
switch (status) {
|
|
@@ -440,6 +447,7 @@ static ip_context_t *ip_context_new(void) {
|
|
|
440
447
|
ctx->status = IP_OK;
|
|
441
448
|
ctx->quality = 82;
|
|
442
449
|
ctx->mozjpeg_trellis_enabled = 1;
|
|
450
|
+
ctx->mozjpeg_scan_opt_enabled = 1;
|
|
443
451
|
ctx->selected_quality = 82;
|
|
444
452
|
ctx->requested_execution = IP_EXEC_AUTO;
|
|
445
453
|
ctx->resolved_execution = IP_EXEC_AUTO;
|
|
@@ -543,10 +551,43 @@ static VALUE ip_sym(const char *name) {
|
|
|
543
551
|
return ID2SYM(rb_intern(name));
|
|
544
552
|
}
|
|
545
553
|
|
|
554
|
+
static void ip_jpeg_emit_message_collect(j_common_ptr cinfo, int msg_level) {
|
|
555
|
+
if (msg_level >= 0)
|
|
556
|
+
return;
|
|
557
|
+
|
|
558
|
+
ip_jpeg_error_mgr *err = (ip_jpeg_error_mgr *)cinfo->err;
|
|
559
|
+
ip_context_t *ctx = err->ctx;
|
|
560
|
+
if (!ctx)
|
|
561
|
+
return;
|
|
562
|
+
|
|
563
|
+
ctx->warning_count++;
|
|
564
|
+
if (ctx->first_warning[0] == '\0') {
|
|
565
|
+
char buffer[JMSG_LENGTH_MAX];
|
|
566
|
+
(*cinfo->err->format_message)(cinfo, buffer);
|
|
567
|
+
snprintf(ctx->first_warning, sizeof(ctx->first_warning), "%s", buffer);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (ctx->strict) {
|
|
571
|
+
if (ctx->status == IP_OK) {
|
|
572
|
+
char buffer[JMSG_LENGTH_MAX];
|
|
573
|
+
(*cinfo->err->format_message)(cinfo, buffer);
|
|
574
|
+
ip_context_set_error(ctx, IP_ERR_INVALID_IMAGE, buffer);
|
|
575
|
+
}
|
|
576
|
+
if (ctx->jmp_armed)
|
|
577
|
+
longjmp(ctx->jmpbuf, 1);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
static void ip_jpeg_output_message_silent(j_common_ptr cinfo) {
|
|
582
|
+
(void)cinfo;
|
|
583
|
+
}
|
|
584
|
+
|
|
546
585
|
static struct jpeg_error_mgr *ip_use_error(ip_jpeg_error_mgr *jerr, ip_context_t *ctx,
|
|
547
586
|
void (*handler)(j_common_ptr)) {
|
|
548
587
|
struct jpeg_error_mgr *base = jpeg_std_error(&jerr->pub);
|
|
549
588
|
jerr->pub.error_exit = handler;
|
|
589
|
+
jerr->pub.emit_message = ip_jpeg_emit_message_collect;
|
|
590
|
+
jerr->pub.output_message = ip_jpeg_output_message_silent;
|
|
550
591
|
jerr->ctx = ctx;
|
|
551
592
|
return base;
|
|
552
593
|
}
|
|
@@ -876,6 +917,23 @@ static VALUE ip_finish_output(ip_context_t *ctx, ip_output_kind_t kind) {
|
|
|
876
917
|
return Qtrue;
|
|
877
918
|
}
|
|
878
919
|
|
|
920
|
+
static VALUE ip_build_report(ip_context_t *ctx, VALUE output_value) {
|
|
921
|
+
VALUE hash = rb_hash_new();
|
|
922
|
+
rb_hash_aset(hash, ip_sym("output"), output_value);
|
|
923
|
+
rb_hash_aset(hash, ip_sym("quality"), INT2NUM(ctx->selected_quality));
|
|
924
|
+
rb_hash_aset(hash, ip_sym("ssim"),
|
|
925
|
+
ctx->ssim_guard_enabled ? DBL2NUM(ctx->measured_ssim) : Qnil);
|
|
926
|
+
rb_hash_aset(hash, ip_sym("algo"),
|
|
927
|
+
ctx->algo == IP_ALGO_MOZJPEG ? ip_sym("mozjpeg") : ip_sym("jpeg_turbo"));
|
|
928
|
+
rb_hash_aset(hash, ip_sym("bytesize"), SIZET2NUM(ctx->output_size));
|
|
929
|
+
rb_hash_aset(hash, ip_sym("input_bytesize"),
|
|
930
|
+
SIZET2NUM(ctx->input_size > 0 ? ctx->input_size : ctx->pixel_size));
|
|
931
|
+
rb_hash_aset(hash, ip_sym("warning_count"), INT2NUM(ctx->warning_count));
|
|
932
|
+
rb_hash_aset(hash, ip_sym("warning"),
|
|
933
|
+
ctx->first_warning[0] ? rb_str_new_cstr(ctx->first_warning) : Qnil);
|
|
934
|
+
return hash;
|
|
935
|
+
}
|
|
936
|
+
|
|
879
937
|
static int ip_save_marker(ip_context_t *ctx, int marker, const unsigned char *data,
|
|
880
938
|
unsigned int len) {
|
|
881
939
|
if (ctx->preserved_marker_count == ctx->preserved_marker_capacity) {
|
|
@@ -1219,13 +1277,21 @@ static void configure_mozjpeg_profile_before_defaults(struct jpeg_compress_struc
|
|
|
1219
1277
|
static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struct *cinfo,
|
|
1220
1278
|
int mozjpeg_size_mode,
|
|
1221
1279
|
int progressive_requested,
|
|
1222
|
-
int mozjpeg_trellis_enabled
|
|
1280
|
+
int mozjpeg_trellis_enabled,
|
|
1281
|
+
int scan_opt_enabled, int measurement) {
|
|
1223
1282
|
if (mozjpeg_size_mode) {
|
|
1224
1283
|
cinfo->optimize_coding = TRUE;
|
|
1225
1284
|
|
|
1226
1285
|
if (progressive_requested) {
|
|
1286
|
+
if (mozjpeg_trellis_enabled) {
|
|
1287
|
+
jpeg_c_set_bool_param(cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, TRUE);
|
|
1288
|
+
jpeg_c_set_bool_param(cinfo, JBOOLEAN_TRELLIS_EOB_OPT, TRUE);
|
|
1289
|
+
}
|
|
1290
|
+
jpeg_c_set_int_param(cinfo, JINT_DC_SCAN_OPT_MODE, 2);
|
|
1291
|
+
int run_scan_search = (measurement || !scan_opt_enabled) ? FALSE : TRUE;
|
|
1292
|
+
jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, run_scan_search);
|
|
1227
1293
|
jpeg_simple_progression(cinfo);
|
|
1228
|
-
jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS,
|
|
1294
|
+
jpeg_c_set_bool_param(cinfo, JBOOLEAN_OPTIMIZE_SCANS, run_scan_search);
|
|
1229
1295
|
} else {
|
|
1230
1296
|
cinfo->scan_info = NULL;
|
|
1231
1297
|
cinfo->num_scans = 0;
|
|
@@ -1250,7 +1316,7 @@ static void configure_mozjpeg_features_after_defaults(struct jpeg_compress_struc
|
|
|
1250
1316
|
}
|
|
1251
1317
|
}
|
|
1252
1318
|
|
|
1253
|
-
static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode) {
|
|
1319
|
+
static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode, int measurement) {
|
|
1254
1320
|
struct jpeg_compress_struct cinfo;
|
|
1255
1321
|
ip_jpeg_error_mgr jerr;
|
|
1256
1322
|
unsigned long jpeg_size = 0;
|
|
@@ -1272,14 +1338,19 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
|
|
|
1272
1338
|
cinfo.image_width = (JDIMENSION)ctx->width;
|
|
1273
1339
|
cinfo.image_height = (JDIMENSION)ctx->height;
|
|
1274
1340
|
cinfo.input_components = ctx->channels;
|
|
1275
|
-
|
|
1276
|
-
|
|
1341
|
+
if (ctx->decoded_as_ycbcr && ctx->channels == 3) {
|
|
1342
|
+
cinfo.in_color_space = JCS_YCbCr;
|
|
1343
|
+
} else {
|
|
1344
|
+
cinfo.in_color_space =
|
|
1345
|
+
ctx->channels == 4 ? JCS_EXT_RGBA : color_space_for_channels(ctx->channels);
|
|
1346
|
+
}
|
|
1277
1347
|
|
|
1278
1348
|
configure_mozjpeg_profile_before_defaults(&cinfo, mozjpeg_size_mode);
|
|
1279
1349
|
jpeg_set_defaults(&cinfo);
|
|
1280
1350
|
jpeg_set_quality(&cinfo, ctx->quality, TRUE);
|
|
1281
1351
|
configure_mozjpeg_features_after_defaults(&cinfo, mozjpeg_size_mode, ctx->progressive,
|
|
1282
|
-
ctx->mozjpeg_trellis_enabled
|
|
1352
|
+
ctx->mozjpeg_trellis_enabled,
|
|
1353
|
+
ctx->mozjpeg_scan_opt_enabled, measurement);
|
|
1283
1354
|
|
|
1284
1355
|
jpeg_start_compress(&cinfo, TRUE);
|
|
1285
1356
|
|
|
@@ -1287,7 +1358,7 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
|
|
|
1287
1358
|
ip_write_preserved_markers(ctx, &cinfo);
|
|
1288
1359
|
|
|
1289
1360
|
while (cinfo.next_scanline < cinfo.image_height) {
|
|
1290
|
-
if (
|
|
1361
|
+
if (atomic_load(&ctx->cancelled))
|
|
1291
1362
|
IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG encode cancelled");
|
|
1292
1363
|
|
|
1293
1364
|
JSAMPROW rows[16];
|
|
@@ -1305,7 +1376,7 @@ static int encode_pixels_with_libjpeg(ip_context_t *ctx, int mozjpeg_size_mode)
|
|
|
1305
1376
|
|
|
1306
1377
|
jpeg_finish_compress(&cinfo);
|
|
1307
1378
|
|
|
1308
|
-
if (ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size)
|
|
1379
|
+
if (!measurement && ctx->max_output_size > 0 && (size_t)jpeg_size > ctx->max_output_size)
|
|
1309
1380
|
IP_FAIL_GOTO(ctx, IP_ERR_LIMIT, "output exceeds max_output_size");
|
|
1310
1381
|
|
|
1311
1382
|
jpeg_destroy_compress(&cinfo);
|
|
@@ -1326,7 +1397,8 @@ fail:
|
|
|
1326
1397
|
}
|
|
1327
1398
|
|
|
1328
1399
|
static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, int *width,
|
|
1329
|
-
int *height, int *channels, int fast_decode_mode
|
|
1400
|
+
int *height, int *channels, int fast_decode_mode,
|
|
1401
|
+
int allow_ycbcr_transcode) {
|
|
1330
1402
|
struct jpeg_decompress_struct cinfo;
|
|
1331
1403
|
ip_jpeg_error_mgr jerr;
|
|
1332
1404
|
memset(&cinfo, 0, sizeof(cinfo));
|
|
@@ -1334,6 +1406,7 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
|
|
|
1334
1406
|
cinfo.err = ip_use_error(&jerr, ctx, ip_jpeg_invalid_error_exit);
|
|
1335
1407
|
ctx->transient_decode_buf = NULL;
|
|
1336
1408
|
ctx->source_orientation = 1;
|
|
1409
|
+
ctx->decoded_as_ycbcr = 0;
|
|
1337
1410
|
|
|
1338
1411
|
ctx->jmp_armed = 1;
|
|
1339
1412
|
if (setjmp(ctx->jmpbuf)) {
|
|
@@ -1377,7 +1450,9 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
|
|
|
1377
1450
|
if (ctx->status != IP_OK)
|
|
1378
1451
|
goto fail;
|
|
1379
1452
|
|
|
1380
|
-
|
|
1453
|
+
int use_ycbcr = allow_ycbcr_transcode && ch == 3;
|
|
1454
|
+
cinfo.out_color_space = ch == 1 ? JCS_GRAYSCALE : (use_ycbcr ? JCS_YCbCr : JCS_RGB);
|
|
1455
|
+
ctx->decoded_as_ycbcr = use_ycbcr;
|
|
1381
1456
|
if (fast_decode_mode)
|
|
1382
1457
|
ip_apply_fast_decode(&cinfo);
|
|
1383
1458
|
|
|
@@ -1398,7 +1473,7 @@ static int ip_jpeg_decode_to_pixels(ip_context_t *ctx, unsigned char **pixels, i
|
|
|
1398
1473
|
IP_FAIL_GOTO(ctx, IP_ERR_OOM, "failed to allocate decoded pixel buffer");
|
|
1399
1474
|
|
|
1400
1475
|
while (cinfo.output_scanline < cinfo.output_height) {
|
|
1401
|
-
if (
|
|
1476
|
+
if (atomic_load(&ctx->cancelled))
|
|
1402
1477
|
IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG decode cancelled");
|
|
1403
1478
|
|
|
1404
1479
|
JSAMPROW rows[16];
|
|
@@ -1455,14 +1530,14 @@ fail:
|
|
|
1455
1530
|
|
|
1456
1531
|
static int compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode) {
|
|
1457
1532
|
if (ctx->pixel_data) {
|
|
1458
|
-
return encode_pixels_with_libjpeg(ctx, mozjpeg_size_mode);
|
|
1533
|
+
return encode_pixels_with_libjpeg(ctx, mozjpeg_size_mode, 0);
|
|
1459
1534
|
}
|
|
1460
1535
|
|
|
1461
1536
|
unsigned char *pixels = NULL;
|
|
1462
1537
|
int width = 0;
|
|
1463
1538
|
int height = 0;
|
|
1464
1539
|
int channels = 0;
|
|
1465
|
-
if (!ip_jpeg_decode_to_pixels(ctx, &pixels, &width, &height, &channels, !mozjpeg_size_mode))
|
|
1540
|
+
if (!ip_jpeg_decode_to_pixels(ctx, &pixels, &width, &height, &channels, !mozjpeg_size_mode, 1))
|
|
1466
1541
|
return 0;
|
|
1467
1542
|
|
|
1468
1543
|
ctx->owned_pixel_data = pixels;
|
|
@@ -1473,7 +1548,7 @@ static int compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mod
|
|
|
1473
1548
|
ctx->channels = channels;
|
|
1474
1549
|
ctx->decoded_bytes = ctx->pixel_size;
|
|
1475
1550
|
|
|
1476
|
-
return encode_pixels_with_libjpeg(ctx, mozjpeg_size_mode);
|
|
1551
|
+
return encode_pixels_with_libjpeg(ctx, mozjpeg_size_mode, 0);
|
|
1477
1552
|
}
|
|
1478
1553
|
|
|
1479
1554
|
static void ip_clear_output_buffer(ip_context_t *ctx) {
|
|
@@ -1558,7 +1633,7 @@ static int ip_decode_jpeg_to_luma_buffer(ip_context_t *ctx, const unsigned char
|
|
|
1558
1633
|
IP_FAIL_GOTO(ctx, IP_ERR_OOM, "failed to allocate luma buffer");
|
|
1559
1634
|
|
|
1560
1635
|
while (cinfo.output_scanline < cinfo.output_height) {
|
|
1561
|
-
if (
|
|
1636
|
+
if (atomic_load(&ctx->cancelled))
|
|
1562
1637
|
IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "JPEG luma decode cancelled");
|
|
1563
1638
|
|
|
1564
1639
|
JSAMPROW rows[16];
|
|
@@ -1763,6 +1838,49 @@ static double ip_compute_ssim_luma_buffer(const unsigned char *a, const unsigned
|
|
|
1763
1838
|
return windows > 0 ? total_ssim / (double)windows : 0.0;
|
|
1764
1839
|
}
|
|
1765
1840
|
|
|
1841
|
+
static int ip_guard_score_quality(ip_context_t *ctx, int mozjpeg_size_mode, int measurement,
|
|
1842
|
+
const unsigned char *reference_luma, int reference_width,
|
|
1843
|
+
int reference_height, unsigned char **out_jpeg,
|
|
1844
|
+
size_t *out_jpeg_size, double *out_ssim) {
|
|
1845
|
+
*out_jpeg = NULL;
|
|
1846
|
+
*out_jpeg_size = 0;
|
|
1847
|
+
*out_ssim = 0.0;
|
|
1848
|
+
|
|
1849
|
+
ip_clear_output_buffer(ctx);
|
|
1850
|
+
if (!encode_pixels_with_libjpeg(ctx, mozjpeg_size_mode, measurement))
|
|
1851
|
+
return 0;
|
|
1852
|
+
|
|
1853
|
+
unsigned char *candidate_jpeg = ctx->output_data;
|
|
1854
|
+
size_t candidate_jpeg_size = ctx->output_size;
|
|
1855
|
+
ctx->output_data = NULL;
|
|
1856
|
+
ctx->output_size = 0;
|
|
1857
|
+
ctx->output_owner = IP_OUTPUT_OWNER_NONE;
|
|
1858
|
+
|
|
1859
|
+
unsigned char *candidate_luma = NULL;
|
|
1860
|
+
int candidate_width = 0;
|
|
1861
|
+
int candidate_height = 0;
|
|
1862
|
+
if (!ip_decode_jpeg_to_luma_buffer(ctx, candidate_jpeg, candidate_jpeg_size, &candidate_luma,
|
|
1863
|
+
&candidate_width, &candidate_height)) {
|
|
1864
|
+
free(candidate_jpeg);
|
|
1865
|
+
return 0;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
if (candidate_width != reference_width || candidate_height != reference_height) {
|
|
1869
|
+
free(candidate_luma);
|
|
1870
|
+
free(candidate_jpeg);
|
|
1871
|
+
ip_context_set_error(ctx, IP_ERR_ENCODE,
|
|
1872
|
+
"candidate JPEG dimensions differ from reference image");
|
|
1873
|
+
return 0;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
*out_ssim = ip_compute_ssim_luma_buffer(reference_luma, candidate_luma, reference_width,
|
|
1877
|
+
reference_height);
|
|
1878
|
+
free(candidate_luma);
|
|
1879
|
+
*out_jpeg = candidate_jpeg;
|
|
1880
|
+
*out_jpeg_size = candidate_jpeg_size;
|
|
1881
|
+
return 1;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1766
1884
|
static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_size_mode) {
|
|
1767
1885
|
unsigned char *reference_pixels = NULL;
|
|
1768
1886
|
int reference_width = 0;
|
|
@@ -1776,7 +1894,7 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
|
|
|
1776
1894
|
reference_channels = ctx->channels;
|
|
1777
1895
|
} else {
|
|
1778
1896
|
if (!ip_jpeg_decode_to_pixels(ctx, &reference_pixels, &reference_width, &reference_height,
|
|
1779
|
-
&reference_channels, 1)) {
|
|
1897
|
+
&reference_channels, 1, 0)) {
|
|
1780
1898
|
return 0;
|
|
1781
1899
|
}
|
|
1782
1900
|
|
|
@@ -1791,8 +1909,7 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
|
|
|
1791
1909
|
}
|
|
1792
1910
|
|
|
1793
1911
|
if (reference_channels == 4) {
|
|
1794
|
-
ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
|
|
1795
|
-
"min_ssim is not supported for RGBA input in v0.2.2");
|
|
1912
|
+
ip_context_set_error(ctx, IP_ERR_UNSUPPORTED, "min_ssim is not supported for RGBA input");
|
|
1796
1913
|
return 0;
|
|
1797
1914
|
}
|
|
1798
1915
|
|
|
@@ -1809,59 +1926,45 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
|
|
|
1809
1926
|
size_t best_jpeg_size = 0;
|
|
1810
1927
|
double best_seen_ssim = 0.0;
|
|
1811
1928
|
int best_seen_quality = 0;
|
|
1929
|
+
int probe_measurement = mozjpeg_size_mode ? 1 : 0;
|
|
1930
|
+
int have_lo = 0, have_hi = 0;
|
|
1931
|
+
int q_lo = 0, q_hi = 0;
|
|
1932
|
+
double s_lo = 0.0, s_hi = 0.0;
|
|
1812
1933
|
|
|
1813
1934
|
while (search_low <= search_high) {
|
|
1814
|
-
if (
|
|
1935
|
+
if (atomic_load(&ctx->cancelled)) {
|
|
1815
1936
|
free(reference_luma);
|
|
1816
1937
|
free(best_jpeg);
|
|
1817
1938
|
ip_context_set_error(ctx, IP_ERR_CANCELLED, "SSIM-guarded JPEG encode cancelled");
|
|
1818
1939
|
return 0;
|
|
1819
1940
|
}
|
|
1820
1941
|
|
|
1821
|
-
int trial_quality
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1942
|
+
int trial_quality;
|
|
1943
|
+
if (have_lo && have_hi && s_hi > s_lo) {
|
|
1944
|
+
double t = (ctx->min_ssim - s_lo) / (s_hi - s_lo);
|
|
1945
|
+
double q_est = (double)q_lo + t * (double)(q_hi - q_lo);
|
|
1946
|
+
trial_quality = (int)(q_est + 0.5);
|
|
1947
|
+
if (trial_quality < search_low)
|
|
1948
|
+
trial_quality = search_low;
|
|
1949
|
+
if (trial_quality > search_high)
|
|
1950
|
+
trial_quality = search_high;
|
|
1951
|
+
} else {
|
|
1952
|
+
trial_quality = search_low + ((search_high - search_low) / 2);
|
|
1829
1953
|
}
|
|
1830
1954
|
|
|
1831
|
-
|
|
1832
|
-
size_t candidate_jpeg_size = ctx->output_size;
|
|
1833
|
-
ctx->output_data = NULL;
|
|
1834
|
-
ctx->output_size = 0;
|
|
1835
|
-
ctx->output_owner = IP_OUTPUT_OWNER_NONE;
|
|
1836
|
-
|
|
1837
|
-
unsigned char *candidate_luma = NULL;
|
|
1838
|
-
int candidate_width = 0;
|
|
1839
|
-
int candidate_height = 0;
|
|
1840
|
-
int decoded_ok =
|
|
1841
|
-
ip_decode_jpeg_to_luma_buffer(ctx, candidate_jpeg, candidate_jpeg_size, &candidate_luma,
|
|
1842
|
-
&candidate_width, &candidate_height);
|
|
1843
|
-
|
|
1844
|
-
if (!decoded_ok) {
|
|
1845
|
-
free(reference_luma);
|
|
1846
|
-
free(candidate_jpeg);
|
|
1847
|
-
free(best_jpeg);
|
|
1848
|
-
return 0;
|
|
1849
|
-
}
|
|
1955
|
+
ctx->quality = trial_quality;
|
|
1850
1956
|
|
|
1851
|
-
|
|
1957
|
+
unsigned char *candidate_jpeg = NULL;
|
|
1958
|
+
size_t candidate_jpeg_size = 0;
|
|
1959
|
+
double ssim = 0.0;
|
|
1960
|
+
if (!ip_guard_score_quality(ctx, mozjpeg_size_mode, probe_measurement, reference_luma,
|
|
1961
|
+
reference_width, reference_height, &candidate_jpeg,
|
|
1962
|
+
&candidate_jpeg_size, &ssim)) {
|
|
1852
1963
|
free(reference_luma);
|
|
1853
|
-
free(candidate_luma);
|
|
1854
|
-
free(candidate_jpeg);
|
|
1855
1964
|
free(best_jpeg);
|
|
1856
|
-
ip_context_set_error(ctx, IP_ERR_ENCODE,
|
|
1857
|
-
"candidate JPEG dimensions differ from reference image");
|
|
1858
1965
|
return 0;
|
|
1859
1966
|
}
|
|
1860
1967
|
|
|
1861
|
-
double ssim = ip_compute_ssim_luma_buffer(reference_luma, candidate_luma, reference_width,
|
|
1862
|
-
reference_height);
|
|
1863
|
-
free(candidate_luma);
|
|
1864
|
-
|
|
1865
1968
|
if (ssim > best_seen_ssim) {
|
|
1866
1969
|
best_seen_ssim = ssim;
|
|
1867
1970
|
best_seen_quality = trial_quality;
|
|
@@ -1873,9 +1976,15 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
|
|
|
1873
1976
|
best_jpeg_size = candidate_jpeg_size;
|
|
1874
1977
|
best_quality = trial_quality;
|
|
1875
1978
|
best_ssim = ssim;
|
|
1979
|
+
q_hi = trial_quality;
|
|
1980
|
+
s_hi = ssim;
|
|
1981
|
+
have_hi = 1;
|
|
1876
1982
|
search_high = trial_quality - 1;
|
|
1877
1983
|
} else {
|
|
1878
1984
|
free(candidate_jpeg);
|
|
1985
|
+
q_lo = trial_quality;
|
|
1986
|
+
s_lo = ssim;
|
|
1987
|
+
have_lo = 1;
|
|
1879
1988
|
search_low = trial_quality + 1;
|
|
1880
1989
|
}
|
|
1881
1990
|
}
|
|
@@ -1890,13 +1999,75 @@ static int guarded_compress_jpeg_input_with_mode(ip_context_t *ctx, int mozjpeg_
|
|
|
1890
1999
|
return 0;
|
|
1891
2000
|
}
|
|
1892
2001
|
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
2002
|
+
if (!mozjpeg_size_mode) {
|
|
2003
|
+
ctx->quality = best_quality;
|
|
2004
|
+
ctx->selected_quality = best_quality;
|
|
2005
|
+
ctx->measured_ssim = best_ssim;
|
|
2006
|
+
free(reference_luma);
|
|
2007
|
+
ctx->output_data = best_jpeg;
|
|
2008
|
+
ctx->output_size = best_jpeg_size;
|
|
2009
|
+
ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
|
|
2010
|
+
return 1;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
free(best_jpeg);
|
|
2014
|
+
best_jpeg = NULL;
|
|
2015
|
+
|
|
2016
|
+
int final_quality = best_quality;
|
|
2017
|
+
unsigned char *final_jpeg = NULL;
|
|
2018
|
+
size_t final_jpeg_size = 0;
|
|
2019
|
+
double final_ssim = 0.0;
|
|
2020
|
+
int satisfied = 0;
|
|
2021
|
+
|
|
2022
|
+
while (final_quality <= 100) {
|
|
2023
|
+
if (atomic_load(&ctx->cancelled)) {
|
|
2024
|
+
free(final_jpeg);
|
|
2025
|
+
free(reference_luma);
|
|
2026
|
+
ip_context_set_error(ctx, IP_ERR_CANCELLED, "SSIM-guarded JPEG encode cancelled");
|
|
2027
|
+
return 0;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
ctx->quality = final_quality;
|
|
2031
|
+
unsigned char *jpeg = NULL;
|
|
2032
|
+
size_t jpeg_size = 0;
|
|
2033
|
+
double ssim = 0.0;
|
|
2034
|
+
if (!ip_guard_score_quality(ctx, mozjpeg_size_mode, 0, reference_luma, reference_width,
|
|
2035
|
+
reference_height, &jpeg, &jpeg_size, &ssim)) {
|
|
2036
|
+
free(final_jpeg);
|
|
2037
|
+
free(reference_luma);
|
|
2038
|
+
return 0;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
free(final_jpeg);
|
|
2042
|
+
final_jpeg = jpeg;
|
|
2043
|
+
final_jpeg_size = jpeg_size;
|
|
2044
|
+
final_ssim = ssim;
|
|
2045
|
+
|
|
2046
|
+
if (ssim >= ctx->min_ssim) {
|
|
2047
|
+
satisfied = 1;
|
|
2048
|
+
break;
|
|
2049
|
+
}
|
|
2050
|
+
final_quality++;
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
if (!satisfied) {
|
|
2054
|
+
char message[512];
|
|
2055
|
+
snprintf(message, sizeof(message),
|
|
2056
|
+
"cannot satisfy min_ssim=%.6f; best full-profile SSIM %.6f at quality=100",
|
|
2057
|
+
ctx->min_ssim, final_ssim);
|
|
2058
|
+
free(final_jpeg);
|
|
2059
|
+
free(reference_luma);
|
|
2060
|
+
ip_context_set_error(ctx, IP_ERR_QUALITY, message);
|
|
2061
|
+
return 0;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
1896
2064
|
free(reference_luma);
|
|
1897
2065
|
|
|
1898
|
-
ctx->
|
|
1899
|
-
ctx->
|
|
2066
|
+
ctx->quality = final_quality;
|
|
2067
|
+
ctx->selected_quality = final_quality;
|
|
2068
|
+
ctx->measured_ssim = final_ssim;
|
|
2069
|
+
ctx->output_data = final_jpeg;
|
|
2070
|
+
ctx->output_size = final_jpeg_size;
|
|
1900
2071
|
ctx->output_owner = IP_OUTPUT_OWNER_MALLOC;
|
|
1901
2072
|
return 1;
|
|
1902
2073
|
}
|
|
@@ -1959,7 +2130,7 @@ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
|
|
|
1959
2130
|
jpeg_mem_src(&srcinfo, ctx->input_data, (unsigned long)ctx->input_size);
|
|
1960
2131
|
ip_setup_marker_saving(&srcinfo, ctx->strip_metadata);
|
|
1961
2132
|
|
|
1962
|
-
if (
|
|
2133
|
+
if (atomic_load(&ctx->cancelled))
|
|
1963
2134
|
IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
|
|
1964
2135
|
|
|
1965
2136
|
if (jpeg_read_header(&srcinfo, TRUE) != JPEG_HEADER_OK)
|
|
@@ -1989,7 +2160,7 @@ static int ip_lossless_optimize_jpeg(ip_context_t *ctx) {
|
|
|
1989
2160
|
|
|
1990
2161
|
jpeg_mem_dest(&dstinfo, &ctx->transient_jpeg_buf, &jpeg_size);
|
|
1991
2162
|
|
|
1992
|
-
if (
|
|
2163
|
+
if (atomic_load(&ctx->cancelled))
|
|
1993
2164
|
IP_FAIL_GOTO(ctx, IP_ERR_CANCELLED, "lossless JPEG optimize cancelled");
|
|
1994
2165
|
|
|
1995
2166
|
jpeg_write_coefficients(&dstinfo, coef_arrays);
|
|
@@ -2035,7 +2206,7 @@ static int ip_mozjpeg_compress(ip_context_t *ctx) {
|
|
|
2035
2206
|
|
|
2036
2207
|
static ip_execution_t ip_async_execution(const ip_context_t *ctx) {
|
|
2037
2208
|
#if IMAGE_PACK_HAS_OFFLOAD_SAFE
|
|
2038
|
-
return ctx->has_scheduler ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
|
|
2209
|
+
return (ip_offload_runtime_enabled && ctx->has_scheduler) ? IP_EXEC_OFFLOAD : IP_EXEC_NOGVL;
|
|
2039
2210
|
#else
|
|
2040
2211
|
(void)ctx;
|
|
2041
2212
|
return IP_EXEC_NOGVL;
|
|
@@ -2105,12 +2276,16 @@ static int ip_run_context(ip_context_t *ctx) {
|
|
|
2105
2276
|
rb_nogvl(ip_run_encode_nogvl, ctx, ip_unblock_function, ctx, RB_NOGVL_OFFLOAD_SAFE);
|
|
2106
2277
|
#else
|
|
2107
2278
|
ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
|
|
2108
|
-
"offload execution
|
|
2279
|
+
"offload execution is unavailable in this runtime; it requires Ruby "
|
|
2280
|
+
">= 3.4 and IMAGE_PACK_DISABLE_OFFLOAD must not be set");
|
|
2109
2281
|
#endif
|
|
2110
2282
|
} else {
|
|
2111
2283
|
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "invalid resolved execution mode");
|
|
2112
2284
|
}
|
|
2113
2285
|
|
|
2286
|
+
if (ctx->resolved_execution != IP_EXEC_DIRECT)
|
|
2287
|
+
rb_thread_check_ints();
|
|
2288
|
+
|
|
2114
2289
|
return ctx->status == IP_OK;
|
|
2115
2290
|
}
|
|
2116
2291
|
|
|
@@ -2133,12 +2308,16 @@ static int ip_run_optimize_context(ip_context_t *ctx) {
|
|
|
2133
2308
|
rb_nogvl(ip_run_optimize_nogvl, ctx, ip_unblock_function, ctx, RB_NOGVL_OFFLOAD_SAFE);
|
|
2134
2309
|
#else
|
|
2135
2310
|
ip_context_set_error(ctx, IP_ERR_UNSUPPORTED,
|
|
2136
|
-
"offload execution
|
|
2311
|
+
"offload execution is unavailable in this runtime; it requires Ruby "
|
|
2312
|
+
">= 3.4 and IMAGE_PACK_DISABLE_OFFLOAD must not be set");
|
|
2137
2313
|
#endif
|
|
2138
2314
|
} else {
|
|
2139
2315
|
ip_context_set_error(ctx, IP_ERR_INVALID_ARGUMENT, "invalid resolved execution mode");
|
|
2140
2316
|
}
|
|
2141
2317
|
|
|
2318
|
+
if (ctx->resolved_execution != IP_EXEC_DIRECT)
|
|
2319
|
+
rb_thread_check_ints();
|
|
2320
|
+
|
|
2142
2321
|
return ctx->status == IP_OK;
|
|
2143
2322
|
}
|
|
2144
2323
|
|
|
@@ -2215,11 +2394,13 @@ static VALUE ip_compress_jpeg_entry_body(VALUE ptr) {
|
|
|
2215
2394
|
ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
|
|
2216
2395
|
ip_validate_min_ssim_or_raise(ctx);
|
|
2217
2396
|
ctx->mozjpeg_trellis_enabled = ip_bool_value(call->mozjpeg_trellis);
|
|
2397
|
+
ctx->mozjpeg_scan_opt_enabled = ip_bool_value(call->mozjpeg_scan_opt);
|
|
2218
2398
|
ctx->progressive = ip_bool_value(call->progressive);
|
|
2219
2399
|
ctx->strip_metadata = ip_bool_value(call->strip_metadata);
|
|
2220
2400
|
ctx->requested_execution = ip_parse_execution(call->execution);
|
|
2221
2401
|
ctx->cancellable_requested = ip_bool_value(call->cancellable);
|
|
2222
2402
|
ctx->has_scheduler = ip_bool_value(call->has_scheduler);
|
|
2403
|
+
ctx->strict = ip_bool_value(call->strict);
|
|
2223
2404
|
apply_configuration(call->self, ctx);
|
|
2224
2405
|
|
|
2225
2406
|
ip_input_kind_t in_kind = ip_parse_input_kind(call->input_kind);
|
|
@@ -2242,17 +2423,18 @@ static VALUE ip_compress_jpeg_entry_body(VALUE ptr) {
|
|
|
2242
2423
|
}
|
|
2243
2424
|
|
|
2244
2425
|
ip_run_context(ctx);
|
|
2426
|
+
if (ip_bool_value(call->report)) {
|
|
2427
|
+
VALUE output_value = ip_finish_output(ctx, out_kind);
|
|
2428
|
+
return ip_build_report(ctx, output_value);
|
|
2429
|
+
}
|
|
2245
2430
|
return ip_finish_output(ctx, out_kind);
|
|
2246
2431
|
}
|
|
2247
2432
|
|
|
2248
|
-
static VALUE ip_compress_jpeg_entry(
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
self, input, input_kind, output, output_kind,
|
|
2254
|
-
algo, quality, min_ssim, mozjpeg_trellis, progressive,
|
|
2255
|
-
strip_metadata, execution, cancellable, has_scheduler, NULL};
|
|
2433
|
+
static VALUE ip_compress_jpeg_entry(int argc, VALUE *argv, VALUE self) {
|
|
2434
|
+
rb_check_arity(argc, 16, 16);
|
|
2435
|
+
ip_compress_jpeg_call_t call = {self, argv[0], argv[1], argv[2], argv[3], argv[4],
|
|
2436
|
+
argv[5], argv[6], argv[7], argv[8], argv[9], argv[10],
|
|
2437
|
+
argv[11], argv[12], argv[13], argv[14], argv[15], NULL};
|
|
2256
2438
|
return rb_ensure(ip_compress_jpeg_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
|
|
2257
2439
|
}
|
|
2258
2440
|
|
|
@@ -2272,11 +2454,13 @@ static VALUE ip_compress_pixels_entry_body(VALUE ptr) {
|
|
|
2272
2454
|
ctx->ssim_guard_enabled = ctx->min_ssim > 0.0;
|
|
2273
2455
|
ip_validate_min_ssim_or_raise(ctx);
|
|
2274
2456
|
ctx->mozjpeg_trellis_enabled = ip_bool_value(call->mozjpeg_trellis);
|
|
2457
|
+
ctx->mozjpeg_scan_opt_enabled = ip_bool_value(call->mozjpeg_scan_opt);
|
|
2275
2458
|
ctx->progressive = ip_bool_value(call->progressive);
|
|
2276
2459
|
ctx->strip_metadata = 1;
|
|
2277
2460
|
ctx->requested_execution = ip_parse_execution(call->execution);
|
|
2278
2461
|
ctx->cancellable_requested = ip_bool_value(call->cancellable);
|
|
2279
2462
|
ctx->has_scheduler = ip_bool_value(call->has_scheduler);
|
|
2463
|
+
ctx->strict = ip_bool_value(call->strict);
|
|
2280
2464
|
apply_configuration(call->self, ctx);
|
|
2281
2465
|
|
|
2282
2466
|
if (!ip_prepare_output_path(ctx, call->output, out_kind) ||
|
|
@@ -2297,18 +2481,19 @@ static VALUE ip_compress_pixels_entry_body(VALUE ptr) {
|
|
|
2297
2481
|
}
|
|
2298
2482
|
|
|
2299
2483
|
ip_run_context(ctx);
|
|
2484
|
+
if (ip_bool_value(call->report)) {
|
|
2485
|
+
VALUE output_value = ip_finish_output(ctx, out_kind);
|
|
2486
|
+
return ip_build_report(ctx, output_value);
|
|
2487
|
+
}
|
|
2300
2488
|
return ip_finish_output(ctx, out_kind);
|
|
2301
2489
|
}
|
|
2302
2490
|
|
|
2303
|
-
static VALUE ip_compress_pixels_entry(
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
self, buffer, width, height, channels, output, output_kind,
|
|
2310
|
-
algo, quality, min_ssim, mozjpeg_trellis, progressive, exact_size, execution,
|
|
2311
|
-
cancellable, has_scheduler, NULL};
|
|
2491
|
+
static VALUE ip_compress_pixels_entry(int argc, VALUE *argv, VALUE self) {
|
|
2492
|
+
rb_check_arity(argc, 18, 18);
|
|
2493
|
+
ip_compress_pixels_call_t call = {self, argv[0], argv[1], argv[2], argv[3],
|
|
2494
|
+
argv[4], argv[5], argv[6], argv[7], argv[8],
|
|
2495
|
+
argv[9], argv[10], argv[11], argv[12], argv[13],
|
|
2496
|
+
argv[14], argv[15], argv[16], argv[17], NULL};
|
|
2312
2497
|
return rb_ensure(ip_compress_pixels_entry_body, (VALUE)&call, ip_call_cleanup,
|
|
2313
2498
|
(VALUE)&call.ctx);
|
|
2314
2499
|
}
|
|
@@ -2326,6 +2511,7 @@ static VALUE ip_optimize_jpeg_entry_body(VALUE ptr) {
|
|
|
2326
2511
|
ctx->requested_execution = ip_parse_execution(call->execution);
|
|
2327
2512
|
ctx->cancellable_requested = ip_bool_value(call->cancellable);
|
|
2328
2513
|
ctx->has_scheduler = ip_bool_value(call->has_scheduler);
|
|
2514
|
+
ctx->strict = ip_bool_value(call->strict);
|
|
2329
2515
|
ctx->ssim_guard_enabled = 0;
|
|
2330
2516
|
apply_configuration(call->self, ctx);
|
|
2331
2517
|
|
|
@@ -2354,10 +2540,11 @@ static VALUE ip_optimize_jpeg_entry_body(VALUE ptr) {
|
|
|
2354
2540
|
|
|
2355
2541
|
static VALUE ip_optimize_jpeg_entry(VALUE self, VALUE input, VALUE input_kind, VALUE output,
|
|
2356
2542
|
VALUE output_kind, VALUE progressive, VALUE strip_metadata,
|
|
2357
|
-
VALUE execution, VALUE cancellable, VALUE has_scheduler
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2543
|
+
VALUE execution, VALUE cancellable, VALUE has_scheduler,
|
|
2544
|
+
VALUE strict) {
|
|
2545
|
+
ip_optimize_jpeg_call_t call = {self, input, input_kind, output,
|
|
2546
|
+
output_kind, progressive, strip_metadata, execution,
|
|
2547
|
+
cancellable, has_scheduler, strict, NULL};
|
|
2361
2548
|
return rb_ensure(ip_optimize_jpeg_entry_body, (VALUE)&call, ip_call_cleanup, (VALUE)&call.ctx);
|
|
2362
2549
|
}
|
|
2363
2550
|
|
|
@@ -2403,14 +2590,17 @@ IMAGE_PACK_INIT_EXPORT void Init_image_pack(void) {
|
|
|
2403
2590
|
for (size_t i = 0; i < IP_ARRAY_LEN(exceptions); i++)
|
|
2404
2591
|
*exceptions[i].slot = rb_const_get(rb_mImagePack, rb_intern(exceptions[i].name));
|
|
2405
2592
|
|
|
2406
|
-
rb_define_const(rb_mImagePack, "NATIVE_MOZJPEG_VERSION",
|
|
2593
|
+
rb_define_const(rb_mImagePack, "NATIVE_MOZJPEG_VERSION",
|
|
2594
|
+
rb_str_new_cstr(IMAGE_PACK_MOZJPEG_VERSION));
|
|
2407
2595
|
#if defined(IMAGE_PACK_HAS_SIMD)
|
|
2408
2596
|
rb_define_const(rb_mImagePack, "NATIVE_SIMD", Qtrue);
|
|
2409
2597
|
#else
|
|
2410
2598
|
rb_define_const(rb_mImagePack, "NATIVE_SIMD", Qfalse);
|
|
2411
2599
|
#endif
|
|
2600
|
+
ip_offload_runtime_enabled = (getenv("IMAGE_PACK_DISABLE_OFFLOAD") == NULL) ? 1 : 0;
|
|
2412
2601
|
#if IMAGE_PACK_HAS_OFFLOAD_SAFE
|
|
2413
|
-
rb_define_const(rb_mImagePack, "NATIVE_OFFLOAD_SAFE",
|
|
2602
|
+
rb_define_const(rb_mImagePack, "NATIVE_OFFLOAD_SAFE",
|
|
2603
|
+
ip_offload_runtime_enabled ? Qtrue : Qfalse);
|
|
2414
2604
|
#else
|
|
2415
2605
|
rb_define_const(rb_mImagePack, "NATIVE_OFFLOAD_SAFE", Qfalse);
|
|
2416
2606
|
#endif
|
|
@@ -2419,9 +2609,9 @@ IMAGE_PACK_INIT_EXPORT void Init_image_pack(void) {
|
|
|
2419
2609
|
const char *name;
|
|
2420
2610
|
VALUE (*fn)(ANYARGS);
|
|
2421
2611
|
int arity;
|
|
2422
|
-
} methods[] = {{"__compress_jpeg", (VALUE (*)(ANYARGS))ip_compress_jpeg_entry,
|
|
2423
|
-
{"__compress_pixels", (VALUE (*)(ANYARGS))ip_compress_pixels_entry,
|
|
2424
|
-
{"__optimize_jpeg", (VALUE (*)(ANYARGS))ip_optimize_jpeg_entry,
|
|
2612
|
+
} methods[] = {{"__compress_jpeg", (VALUE (*)(ANYARGS))ip_compress_jpeg_entry, -1},
|
|
2613
|
+
{"__compress_pixels", (VALUE (*)(ANYARGS))ip_compress_pixels_entry, -1},
|
|
2614
|
+
{"__optimize_jpeg", (VALUE (*)(ANYARGS))ip_optimize_jpeg_entry, 10},
|
|
2425
2615
|
{"__inspect_image", (VALUE (*)(ANYARGS))ip_inspect_image_entry, 2}};
|
|
2426
2616
|
for (size_t i = 0; i < IP_ARRAY_LEN(methods); i++) {
|
|
2427
2617
|
rb_define_singleton_method(rb_mImagePack, methods[i].name, methods[i].fn, methods[i].arity);
|
|
@@ -42,7 +42,7 @@ module ImagePack
|
|
|
42
42
|
|
|
43
43
|
if value == :offload && ImagePack.respond_to?(:offload_safe?) && !ImagePack.offload_safe?
|
|
44
44
|
raise UnsupportedError,
|
|
45
|
-
"execution: :offload requires Ruby >= 3.4.0
|
|
45
|
+
"execution: :offload is unavailable in this runtime; it requires Ruby >= 3.4.0 and IMAGE_PACK_DISABLE_OFFLOAD must not be set"
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
@execution = value
|
data/lib/image_pack/version.rb
CHANGED
data/lib/image_pack.rb
CHANGED
|
@@ -90,10 +90,12 @@ module ImagePack
|
|
|
90
90
|
progressive: true,
|
|
91
91
|
strip_metadata: false,
|
|
92
92
|
execution: nil,
|
|
93
|
-
cancellable: false
|
|
93
|
+
cancellable: false,
|
|
94
|
+
strict: false)
|
|
94
95
|
validate_boolean!(:progressive, progressive)
|
|
95
96
|
validate_boolean!(:strip_metadata, strip_metadata)
|
|
96
97
|
validate_boolean!(:cancellable, cancellable)
|
|
98
|
+
validate_boolean!(:strict, strict)
|
|
97
99
|
execution ||= configuration.execution
|
|
98
100
|
validate_execution!(execution)
|
|
99
101
|
validate_execution_supported!(execution)
|
|
@@ -109,7 +111,8 @@ module ImagePack
|
|
|
109
111
|
strip_metadata ? 1 : 0,
|
|
110
112
|
execution,
|
|
111
113
|
cancellable ? 1 : 0,
|
|
112
|
-
has_scheduler ? 1 : 0
|
|
114
|
+
has_scheduler ? 1 : 0,
|
|
115
|
+
strict ? 1 : 0)
|
|
113
116
|
end
|
|
114
117
|
|
|
115
118
|
def compress(input,
|
|
@@ -118,16 +121,23 @@ module ImagePack
|
|
|
118
121
|
quality: nil,
|
|
119
122
|
min_ssim: nil,
|
|
120
123
|
mozjpeg_trellis: true,
|
|
121
|
-
|
|
124
|
+
mozjpeg_scan_opt: true,
|
|
125
|
+
progressive: nil,
|
|
122
126
|
strip_metadata: true,
|
|
123
127
|
execution: nil,
|
|
124
|
-
cancellable: false
|
|
128
|
+
cancellable: false,
|
|
129
|
+
report: false,
|
|
130
|
+
strict: false)
|
|
125
131
|
validate_algo!(algo)
|
|
126
132
|
validate_min_ssim!(min_ssim)
|
|
127
133
|
validate_boolean!(:mozjpeg_trellis, mozjpeg_trellis)
|
|
134
|
+
validate_boolean!(:mozjpeg_scan_opt, mozjpeg_scan_opt)
|
|
135
|
+
progressive = default_progressive_for(algo, progressive)
|
|
128
136
|
validate_boolean!(:progressive, progressive)
|
|
129
137
|
validate_boolean!(:strip_metadata, strip_metadata)
|
|
130
138
|
validate_boolean!(:cancellable, cancellable)
|
|
139
|
+
validate_boolean!(:report, report)
|
|
140
|
+
validate_boolean!(:strict, strict)
|
|
131
141
|
quality_was_given = !quality.nil?
|
|
132
142
|
effective_quality = quality_was_given ? quality : DEFAULT_QUALITY
|
|
133
143
|
effective_quality = 1 if min_ssim && !quality_was_given
|
|
@@ -150,7 +160,10 @@ module ImagePack
|
|
|
150
160
|
strip_metadata ? 1 : 0,
|
|
151
161
|
execution,
|
|
152
162
|
cancellable ? 1 : 0,
|
|
153
|
-
has_scheduler ? 1 : 0
|
|
163
|
+
has_scheduler ? 1 : 0,
|
|
164
|
+
report ? 1 : 0,
|
|
165
|
+
strict ? 1 : 0,
|
|
166
|
+
mozjpeg_scan_opt ? 1 : 0)
|
|
154
167
|
end
|
|
155
168
|
|
|
156
169
|
def compress_pixels(buffer,
|
|
@@ -162,19 +175,26 @@ module ImagePack
|
|
|
162
175
|
quality: nil,
|
|
163
176
|
min_ssim: nil,
|
|
164
177
|
mozjpeg_trellis: true,
|
|
165
|
-
|
|
178
|
+
mozjpeg_scan_opt: true,
|
|
179
|
+
progressive: nil,
|
|
166
180
|
drop_alpha: nil,
|
|
167
181
|
exact_size: false,
|
|
168
182
|
execution: nil,
|
|
169
|
-
cancellable: false
|
|
183
|
+
cancellable: false,
|
|
184
|
+
report: false,
|
|
185
|
+
strict: false)
|
|
170
186
|
validate_pixel_buffer!(buffer)
|
|
171
187
|
validate_algo!(algo)
|
|
172
188
|
validate_min_ssim!(min_ssim)
|
|
173
189
|
validate_boolean!(:mozjpeg_trellis, mozjpeg_trellis)
|
|
190
|
+
validate_boolean!(:mozjpeg_scan_opt, mozjpeg_scan_opt)
|
|
191
|
+
progressive = default_progressive_for(algo, progressive)
|
|
174
192
|
validate_boolean!(:progressive, progressive)
|
|
175
193
|
validate_drop_alpha!(drop_alpha)
|
|
176
194
|
validate_boolean!(:exact_size, exact_size)
|
|
177
195
|
validate_boolean!(:cancellable, cancellable)
|
|
196
|
+
validate_boolean!(:report, report)
|
|
197
|
+
validate_boolean!(:strict, strict)
|
|
178
198
|
quality_was_given = !quality.nil?
|
|
179
199
|
effective_quality = quality_was_given ? quality : DEFAULT_QUALITY
|
|
180
200
|
effective_quality = 1 if min_ssim && !quality_was_given
|
|
@@ -214,7 +234,10 @@ module ImagePack
|
|
|
214
234
|
exact_size ? 1 : 0,
|
|
215
235
|
execution,
|
|
216
236
|
cancellable ? 1 : 0,
|
|
217
|
-
has_scheduler ? 1 : 0
|
|
237
|
+
has_scheduler ? 1 : 0,
|
|
238
|
+
report ? 1 : 0,
|
|
239
|
+
strict ? 1 : 0,
|
|
240
|
+
mozjpeg_scan_opt ? 1 : 0)
|
|
218
241
|
end
|
|
219
242
|
|
|
220
243
|
def inspect_image(input)
|
|
@@ -251,7 +274,7 @@ module ImagePack
|
|
|
251
274
|
return :return_string if output.nil?
|
|
252
275
|
return :path if output.is_a?(String) || output.is_a?(Pathname)
|
|
253
276
|
|
|
254
|
-
raise InvalidArgumentError, "output must be nil, String path, or Pathname
|
|
277
|
+
raise InvalidArgumentError, "output must be nil, String path, or Pathname"
|
|
255
278
|
end
|
|
256
279
|
|
|
257
280
|
def validate_algo!(algo)
|
|
@@ -279,6 +302,13 @@ module ImagePack
|
|
|
279
302
|
raise InvalidArgumentError, "#{name} must be true or false, got: #{value.inspect}"
|
|
280
303
|
end
|
|
281
304
|
|
|
305
|
+
|
|
306
|
+
def default_progressive_for(algo, value)
|
|
307
|
+
return value unless value.nil?
|
|
308
|
+
|
|
309
|
+
ALGO_TO_NATIVE.fetch(algo) == :mozjpeg
|
|
310
|
+
end
|
|
311
|
+
|
|
282
312
|
def validate_drop_alpha!(value)
|
|
283
313
|
return if value.nil? || value == true || value == false
|
|
284
314
|
|
|
@@ -296,7 +326,7 @@ module ImagePack
|
|
|
296
326
|
return if offload_safe?
|
|
297
327
|
|
|
298
328
|
raise UnsupportedError,
|
|
299
|
-
"execution: :offload requires Ruby >= 3.4.0
|
|
329
|
+
"execution: :offload is unavailable in this runtime; it requires Ruby >= 3.4.0 and IMAGE_PACK_DISABLE_OFFLOAD must not be set"
|
|
300
330
|
end
|
|
301
331
|
|
|
302
332
|
def validate_dimensions!(width, height, channels)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: image_pack
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roman Haydarov
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -80,9 +80,9 @@ dependencies:
|
|
|
80
80
|
- - "~>"
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '2.21'
|
|
83
|
-
description: Single API, vendored pure-C MozJPEG/libjpeg codec,
|
|
84
|
-
3.1+ native JPEG execution; Ruby 3.4+ enables Fiber::Scheduler-aware
|
|
85
|
-
vendored C sources — no system libjpeg, git, or CMake required.
|
|
83
|
+
description: Single API, vendored pure-C MozJPEG/libjpeg codec, in-memory compression
|
|
84
|
+
and atomic file output, Ruby 3.1+ native JPEG execution; Ruby 3.4+ enables Fiber::Scheduler-aware
|
|
85
|
+
offload. Ships vendored C sources — no system libjpeg, git, or CMake required.
|
|
86
86
|
email:
|
|
87
87
|
- romnhajdarov@gmail.com
|
|
88
88
|
executables: []
|