image_pack 0.2.1 → 0.2.3

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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/README.md +75 -16
  4. data/ext/image_pack/extconf.rb +41 -126
  5. data/ext/image_pack/image_pack.c +1151 -596
  6. data/ext/image_pack/mozjpeg_sources.rb +178 -0
  7. data/ext/image_pack/vendor/mozjpeg/BUILDING.md +744 -0
  8. data/ext/image_pack/vendor/mozjpeg/CODE_OF_CONDUCT.md +15 -0
  9. data/ext/image_pack/vendor/mozjpeg/ChangeLog.md +1996 -0
  10. data/lib/image_pack/configuration.rb +54 -8
  11. data/lib/image_pack/version.rb +1 -1
  12. data/lib/image_pack.rb +124 -41
  13. metadata +13 -79
  14. data/ext/image_pack/vendor/mozjpeg/README.ijg +0 -258
  15. data/ext/image_pack/vendor/mozjpeg/cdjpeg.c +0 -156
  16. data/ext/image_pack/vendor/mozjpeg/cjpeg.c +0 -961
  17. data/ext/image_pack/vendor/mozjpeg/djpeg.c +0 -855
  18. data/ext/image_pack/vendor/mozjpeg/jaricom.c +0 -157
  19. data/ext/image_pack/vendor/mozjpeg/jcarith.c +0 -972
  20. data/ext/image_pack/vendor/mozjpeg/jcstest.c +0 -126
  21. data/ext/image_pack/vendor/mozjpeg/jdarith.c +0 -782
  22. data/ext/image_pack/vendor/mozjpeg/jdatadst-tj.c +0 -198
  23. data/ext/image_pack/vendor/mozjpeg/jdatasrc-tj.c +0 -194
  24. data/ext/image_pack/vendor/mozjpeg/jpegtran.c +0 -827
  25. data/ext/image_pack/vendor/mozjpeg/jpegyuv.c +0 -172
  26. data/ext/image_pack/vendor/mozjpeg/rdbmp.c +0 -690
  27. data/ext/image_pack/vendor/mozjpeg/rdcolmap.c +0 -253
  28. data/ext/image_pack/vendor/mozjpeg/rdgif.c +0 -720
  29. data/ext/image_pack/vendor/mozjpeg/rdjpeg.c +0 -160
  30. data/ext/image_pack/vendor/mozjpeg/rdjpgcom.c +0 -494
  31. data/ext/image_pack/vendor/mozjpeg/rdpng.c +0 -194
  32. data/ext/image_pack/vendor/mozjpeg/rdppm.c +0 -781
  33. data/ext/image_pack/vendor/mozjpeg/rdswitch.c +0 -642
  34. data/ext/image_pack/vendor/mozjpeg/rdtarga.c +0 -508
  35. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jccolext-neon.c +0 -148
  36. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jchuff-neon.c +0 -334
  37. data/ext/image_pack/vendor/mozjpeg/simd/arm/aarch32/jsimd.c +0 -976
  38. data/ext/image_pack/vendor/mozjpeg/simd/i386/jsimd.c +0 -1312
  39. data/ext/image_pack/vendor/mozjpeg/simd/mips/jsimd.c +0 -1143
  40. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jccolext-mmi.c +0 -455
  41. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jccolor-mmi.c +0 -148
  42. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcgray-mmi.c +0 -132
  43. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcgryext-mmi.c +0 -374
  44. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jcsample-mmi.c +0 -98
  45. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdcolext-mmi.c +0 -415
  46. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdcolor-mmi.c +0 -139
  47. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdmerge-mmi.c +0 -149
  48. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdmrgext-mmi.c +0 -615
  49. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jdsample-mmi.c +0 -304
  50. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jfdctfst-mmi.c +0 -255
  51. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jfdctint-mmi.c +0 -398
  52. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jidctfst-mmi.c +0 -395
  53. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jidctint-mmi.c +0 -571
  54. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jquanti-mmi.c +0 -124
  55. data/ext/image_pack/vendor/mozjpeg/simd/mips64/jsimd.c +0 -866
  56. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jccolext-altivec.c +0 -269
  57. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jccolor-altivec.c +0 -116
  58. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcgray-altivec.c +0 -111
  59. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcgryext-altivec.c +0 -228
  60. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jcsample-altivec.c +0 -159
  61. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdcolext-altivec.c +0 -276
  62. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdcolor-altivec.c +0 -106
  63. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdmerge-altivec.c +0 -130
  64. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdmrgext-altivec.c +0 -329
  65. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jdsample-altivec.c +0 -400
  66. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jfdctfst-altivec.c +0 -154
  67. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jfdctint-altivec.c +0 -258
  68. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jidctfst-altivec.c +0 -255
  69. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jidctint-altivec.c +0 -357
  70. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jquanti-altivec.c +0 -250
  71. data/ext/image_pack/vendor/mozjpeg/simd/powerpc/jsimd.c +0 -884
  72. data/ext/image_pack/vendor/mozjpeg/strtest.c +0 -170
  73. data/ext/image_pack/vendor/mozjpeg/tjbench.c +0 -1044
  74. data/ext/image_pack/vendor/mozjpeg/tjexample.c +0 -406
  75. data/ext/image_pack/vendor/mozjpeg/tjunittest.c +0 -961
  76. data/ext/image_pack/vendor/mozjpeg/tjutil.c +0 -70
  77. data/ext/image_pack/vendor/mozjpeg/transupp.c +0 -2373
  78. data/ext/image_pack/vendor/mozjpeg/turbojpeg-jni.c +0 -1259
  79. data/ext/image_pack/vendor/mozjpeg/turbojpeg.c +0 -2320
  80. data/ext/image_pack/vendor/mozjpeg/wrbmp.c +0 -552
  81. data/ext/image_pack/vendor/mozjpeg/wrgif.c +0 -580
  82. data/ext/image_pack/vendor/mozjpeg/wrjpgcom.c +0 -577
  83. data/ext/image_pack/vendor/mozjpeg/wrppm.c +0 -366
  84. data/ext/image_pack/vendor/mozjpeg/wrtarga.c +0 -258
  85. data/ext/image_pack/vendor/mozjpeg/yuvjpeg.c +0 -268
  86. data/lib/image_pack/backend.rb +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e741b4071a7d8ef87a2bbfb62f1d3a5fe11e809eac31463ee52f3f919833f148
4
- data.tar.gz: f60b9c3bb2e8ff303099b5339a170640950c1e0928686d19c5a0f9bafe297c30
3
+ metadata.gz: 606688569aac1fa1515edefe270b18a04e5b0042de602b18663964726d8fbb34
4
+ data.tar.gz: 9092fcf9fa3c061edc87ce2a22544acfe52dfa6ce4fe39961c95a1d17220a6b0
5
5
  SHA512:
6
- metadata.gz: 966485e630b9ab72ab0e193aaa588a8d396bbb9f8fd5dba40e0e2a17eaa392cc2d7944134b679ec5e73fdd6902ba9db24dbff6582aaa1071b9f1986c77df8590
7
- data.tar.gz: c1f70b669944ce35139f005cc4ed24db9634c51af8ff90d509eec6c6544c4c664525c27b9eb9045ac40fb3ea9bf81ea36e75ad7ce842427fd8968c289ed03ae6
6
+ metadata.gz: ad6b0db99c56d69c99d9216559e3c114a2079941a4f879e69508126bbf12b070844dc53e806b3da7637783a205528c10136c3cd3bac8f60b5fd6ee2ddcad01eb
7
+ data.tar.gz: f1acfa9f5a7154d1ea201ae66658da704a5ee5d207886b096d284b7b69eca175d668993dba127b0a609d6fb5b68492196edb8d3bb70d12575f129c095c6b7704
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ - Lowered the runtime floor to Ruby >= 3.1.0 while keeping `execution: :offload` Ruby >= 3.4-only and explicitly unsupported on older Rubies.
4
+ ## 0.2.2
5
+
6
+ - 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.
7
+ - Fixed libjpeg `longjmp` cleanup for encode/decode/luma-decode transient buffers.
8
+ - Fixed `max_output_size = 0` semantics; all size/dimension limits now consistently treat `0` as disabled.
9
+ - Fixed `compress_pixels(min_ssim:)`: it now uses the raw pixel buffer as the reference instead of a temporary quality-95 seed JPEG, and it respects the requested execution mode.
10
+ - Fixed metadata preservation drift: `strip_metadata: false` now preserves markers across fast, size, and SSIM paths.
11
+ - Fixed EXIF Orientation stripping: when metadata is stripped, orientation is applied to decoded pixels before output.
12
+ - Fixed `progressive:` for `algo: :mozjpeg`; `progressive: false` no longer silently inherits the max-compression progressive scan script.
13
+ - Added decode/luma-decode cancellation checkpoints.
14
+ - Reduced avoidable copying for explicit `execution: :direct` String/pixel inputs.
15
+ - Batched RGBA→RGB scanline encoding instead of writing one line at a time.
16
+ - Decoded SSIM candidate luma directly as grayscale instead of RGB + manual luma conversion.
17
+ - Made output-path writes safer by writing to a temporary file, checking `fclose`, and renaming into place.
18
+ - Added `compress_bytes`, `compress_file`, `optimize_jpeg`, `optimize_bytes`, `optimize_file`, and `build_info`.
19
+ - Added coefficient-level lossless JPEG optimization through `jpeg_read_coefficients` / `jpeg_write_coefficients`; this avoids decode→pixels→re-encode when only optimizing an existing JPEG.
20
+ - Made private native entrypoints private class methods.
21
+ - Added `-fvisibility=hidden` and `IMAGE_PACK_REQUIRE_SIMD=1` build guard.
22
+
3
23
  ## 0.2.1
4
24
 
5
25
  - `ip_compute_ssim_luma_buffer`: rewrote inner accumulators from `double` to
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # image_pack
2
2
 
3
- `image_pack` is a Ruby-native JPEG compressor prototype for Ruby `>= 3.4.0`.
3
+ `image_pack` is a Ruby-native JPEG compressor prototype for Ruby `>= 3.1.0`.
4
4
 
5
- Current version: `0.2.0`.
5
+ Current version: `0.2.2`.
6
6
 
7
7
  This pure-C variant intentionally removes Jpegli and any C++ toolchain requirement.
8
8
  The native layer is written in C and links against vendored MozJPEG, which is libjpeg-compatible and based on libjpeg-turbo.
@@ -14,8 +14,8 @@ ImagePack.compress(input, algo: :jpeg_turbo)
14
14
  ImagePack.compress(input, algo: :mozjpeg)
15
15
  ```
16
16
 
17
- - `:jpeg_turbo` — fast libjpeg-compatible mode with MozJPEG-specific size optimizations disabled.
18
- - `:mozjpeg` — size-oriented mode using progressive/optimized Huffman coding through the MozJPEG/libjpeg API.
17
+ - `:jpeg_turbo` / `:fast` — fast libjpeg-compatible mode with MozJPEG-specific size optimizations disabled.
18
+ - `:mozjpeg` / `:size` — size-oriented mode using optimized Huffman coding and optional progressive output through the MozJPEG/libjpeg API.
19
19
 
20
20
  Both modes produce ordinary `.jpg` files.
21
21
 
@@ -52,6 +52,14 @@ ImagePack.compress(jpeg, algo: :mozjpeg, min_ssim: 0.985)
52
52
  ImagePack.compress(jpeg, algo: :mozjpeg, quality: 75, min_ssim: 0.985)
53
53
 
54
54
  ImagePack.compress("photo.jpg", output: "photo.optimized.jpg", algo: :mozjpeg)
55
+ ImagePack.compress_file("photo.jpg", output: "photo.optimized.jpg", algo: :mozjpeg)
56
+ ImagePack.compress_bytes(jpeg, algo: :fast, quality: 82)
57
+
58
+ # Lossless coefficient-level JPEG optimization/transcode.
59
+ # This rewrites JPEG coefficients without decoding/re-encoding pixels.
60
+ ImagePack.optimize_jpeg(jpeg, progressive: true, strip_metadata: false)
61
+ ImagePack.optimize_file("photo.jpg", output: "photo.lossless.jpg")
62
+ ImagePack.optimize_bytes(jpeg)
55
63
 
56
64
  ImagePack.compress_pixels(rgb_buffer,
57
65
  width: 1920,
@@ -69,19 +77,32 @@ ImagePack.inspect_image(jpeg)
69
77
  `compress(input, ...)` accepts JPEG input only:
70
78
 
71
79
  - binary `String` (`ASCII-8BIT`) — JPEG bytes;
72
- - non-binary `String` — file path;
80
+ - non-binary `String` that points to an existing file — file path;
73
81
  - `Pathname` — file path;
74
82
  - `IO::Buffer` — JPEG bytes, copied before native no-GVL execution for safety.
75
83
 
84
+ For non-ambiguous call sites prefer `compress_bytes(bytes, ...)` or `compress_file(path, ...)`.
85
+
76
86
  `compress_pixels(buffer, ...)` accepts raw pixels as binary `String` or `IO::Buffer`.
77
- `channels` must be `1`, `3`, or `4`. Alpha in RGBA input is dropped in v0.2.0.
87
+ `channels` must be `1`, `3`, or `4`. Alpha in RGBA input is dropped in v0.2.2; pass `drop_alpha: true` to make that explicit, or `drop_alpha: false` to reject it.
88
+
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
+
92
+ ## Lossless JPEG optimize
93
+
94
+ `optimize_jpeg`, `optimize_bytes`, and `optimize_file` are coefficient-level JPEG transcode helpers. They use `jpeg_read_coefficients` / `jpeg_write_coefficients`, so pixels are not decoded and re-encoded. This is the preferred path when you only want to rewrite an existing JPEG with optimized Huffman tables and optional progressive scans.
95
+
96
+ Defaults are intentionally conservative:
78
97
 
79
- `output: nil` returns binary JPEG bytes. `output: String/Pathname` writes to path and returns `true`.
80
- Streaming output is intentionally not supported in v0.2.0.
98
+ - `progressive: true` creates a progressive optimized JPEG;
99
+ - `strip_metadata: false` preserves APP/COM metadata by default because this path is meant to be visually/losslessly safe.
100
+
101
+ If `strip_metadata: true` is requested and the source JPEG has EXIF Orientation, `optimize_jpeg` raises `UnsupportedError` instead of silently removing the orientation tag and changing how viewers display the image. Use `strip_metadata: false` for coefficient-level optimization, or `compress(..., strip_metadata: true)` if you want pixel-level orientation normalization.
81
102
 
82
103
  ## SSIM guard
83
104
 
84
- `compress` accepts `min_ssim:` for JPEG input. This enables a native guarded path:
105
+ `compress` and `compress_pixels` accept `min_ssim:`. This enables a native guarded path:
85
106
 
86
107
  1. decode the original JPEG to reference pixels;
87
108
  2. encode trial JPEG candidates with the existing MozJPEG/libjpeg backend;
@@ -96,11 +117,31 @@ the encoder raises quality only if the candidate violates the SSIM floor.
96
117
  If no quality can satisfy the requested floor, `ImagePack::QualityConstraintError`
97
118
  is raised. With `execution: :auto`, guarded compression uses the no-GVL/offload
98
119
  path instead of the small-image direct path because it may encode/decode several
99
- candidates. `min_ssim` is intentionally not supported by `compress_pixels` in
100
- this release.
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.
121
+
122
+ The metric is a fast native luma SSIM-like guard based on 8x8 blocks, not a full Wang-style Gaussian-window reference implementation. It is intended as a compression guard, not as a general-purpose image quality benchmark.
101
123
 
102
124
  ## Execution modes
103
125
 
126
+ ### Ruby compatibility for offload
127
+
128
+ ImagePack supports Ruby `>= 3.1.0`. Ruby `>= 3.4.0` is required only for the scheduler-aware `execution: :offload` path.
129
+
130
+ On Ruby 3.1–3.3:
131
+
132
+ - `execution: :direct` works.
133
+ - `execution: :nogvl` works.
134
+ - `execution: :auto` works, but never selects `:offload`.
135
+ - explicit `execution: :offload` raises `ImagePack::UnsupportedError`.
136
+
137
+ On Ruby 3.4+:
138
+
139
+ - `execution: :offload` is available.
140
+ - `execution: :auto` may select `:offload` when a Fiber scheduler is active.
141
+
142
+ Use `ImagePack.offload_safe?` or `ImagePack.build_info[:offload_safe]` to check the native capability at runtime.
143
+
144
+
104
145
  ```ruby
105
146
  ImagePack.compress(jpeg, execution: :direct)
106
147
  ImagePack.compress(jpeg, execution: :nogvl)
@@ -110,13 +151,24 @@ ImagePack.compress(jpeg, execution: :auto)
110
151
 
111
152
  - `:direct` — executes under GVL, intended for small images.
112
153
  - `:nogvl` — uses `rb_nogvl(..., flags: 0)`.
113
- - `:offload` — uses `rb_nogvl(..., RB_NOGVL_OFFLOAD_SAFE)` on Ruby 3.4+.
114
- - `:auto` — header-first policy chooses direct/nogvl/offload.
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.
115
156
 
116
157
  ## Cancellation
117
158
 
118
- `cancellable: true` is supported only for `algo: :mozjpeg` and `execution: :nogvl`, `:offload`, or `:auto`.
119
- Cancellation is cooperative at encoder scanline checkpoints, not instant.
159
+ `cancellable: true` is supported for `execution: :nogvl`, `:offload`, or `:auto`.
160
+ Cancellation is cooperative at decode, encode, and SSIM-search checkpoints, not instant.
161
+
162
+ ## Metadata / EXIF orientation
163
+
164
+ `strip_metadata: true` removes metadata. If the source JPEG contains EXIF Orientation, `image_pack` applies that orientation to decoded pixels before stripping metadata, so the visual orientation is preserved. With `strip_metadata: false`, APP/COM markers are preserved across both fast and size-oriented paths.
165
+
166
+ ## Build info
167
+
168
+ ```ruby
169
+ ImagePack.build_info
170
+ # => { version: "0.2.2", mozjpeg: "4.1.5", simd: true, offload_safe: true }
171
+ ```
120
172
 
121
173
  ## Vendoring
122
174
 
@@ -130,11 +182,18 @@ bundle exec rake compile
130
182
 
131
183
  `rake vendor` pins MozJPEG `v4.1.5`.
132
184
 
185
+ ## Current limitations
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.
191
+
133
192
  ## What is intentionally not included
134
193
 
135
194
  - Jpegli / C++ code
136
195
  - AVIF/WebP/PNG
137
196
  - FFI
138
197
  - shell-out
139
- - tempfiles by default
198
+ - external tempfiles for in-memory output
140
199
  - byte-size targets / max-bytes policy
@@ -4,127 +4,19 @@ require "mkmf"
4
4
  require "fileutils"
5
5
  require "rbconfig"
6
6
 
7
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.4.0")
8
- abort "image_pack requires Ruby >= 3.4.0; got #{RUBY_VERSION}"
7
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.1.0")
8
+ abort "image_pack requires Ruby >= 3.1.0; got #{RUBY_VERSION}"
9
9
  end
10
10
 
11
- MOZJPEG_CORE_SOURCES = %w[
12
- jcapimin.c
13
- jcapistd.c
14
- jccoefct.c
15
- jccolor.c
16
- jcdctmgr.c
17
- jchuff.c
18
- jcext.c
19
- jcicc.c
20
- jcinit.c
21
- jcmainct.c
22
- jcmarker.c
23
- jcmaster.c
24
- jcomapi.c
25
- jcparam.c
26
- jcphuff.c
27
- jcprepct.c
28
- jcsample.c
29
- jctrans.c
30
- jdapimin.c
31
- jdapistd.c
32
- jdatadst.c
33
- jdatasrc.c
34
- jdcoefct.c
35
- jdcolor.c
36
- jddctmgr.c
37
- jdhuff.c
38
- jdicc.c
39
- jdinput.c
40
- jdmainct.c
41
- jdmarker.c
42
- jdmaster.c
43
- jdmerge.c
44
- jdphuff.c
45
- jdpostct.c
46
- jdsample.c
47
- jdtrans.c
48
- jerror.c
49
- jfdctflt.c
50
- jfdctfst.c
51
- jfdctint.c
52
- jidctflt.c
53
- jidctfst.c
54
- jidctint.c
55
- jidctred.c
56
- jquant1.c
57
- jquant2.c
58
- jutils.c
59
- jmemmgr.c
60
- jmemnobs.c
61
- ].freeze
62
-
63
- NEON_AARCH64_SOURCES = [
64
- "simd/arm/aarch64/jsimd.c",
65
- "simd/arm/aarch64/jchuff-neon.c",
66
- "simd/arm/jccolor-neon.c",
67
- "simd/arm/jcgray-neon.c",
68
- "simd/arm/jcphuff-neon.c",
69
- "simd/arm/jcsample-neon.c",
70
- "simd/arm/jdcolor-neon.c",
71
- "simd/arm/jdmerge-neon.c",
72
- "simd/arm/jdsample-neon.c",
73
- "simd/arm/jfdctfst-neon.c",
74
- "simd/arm/jfdctint-neon.c",
75
- "simd/arm/jidctfst-neon.c",
76
- "simd/arm/jidctint-neon.c",
77
- "simd/arm/jidctred-neon.c",
78
- "simd/arm/jquanti-neon.c",
79
- ].freeze
80
-
81
- X86_64_C_SOURCES = [
82
- "simd/x86_64/jsimd.c",
83
- ].freeze
84
-
85
- X86_64_ASM_SOURCES = %w[
86
- simd/x86_64/jsimdcpu.asm
87
- simd/x86_64/jfdctflt-sse.asm
88
- simd/x86_64/jccolor-sse2.asm
89
- simd/x86_64/jcgray-sse2.asm
90
- simd/x86_64/jchuff-sse2.asm
91
- simd/x86_64/jcphuff-sse2.asm
92
- simd/x86_64/jcsample-sse2.asm
93
- simd/x86_64/jdcolor-sse2.asm
94
- simd/x86_64/jdmerge-sse2.asm
95
- simd/x86_64/jdsample-sse2.asm
96
- simd/x86_64/jfdctfst-sse2.asm
97
- simd/x86_64/jfdctint-sse2.asm
98
- simd/x86_64/jidctflt-sse2.asm
99
- simd/x86_64/jidctfst-sse2.asm
100
- simd/x86_64/jidctint-sse2.asm
101
- simd/x86_64/jidctred-sse2.asm
102
- simd/x86_64/jquantf-sse2.asm
103
- simd/x86_64/jquanti-sse2.asm
104
- simd/x86_64/jccolor-avx2.asm
105
- simd/x86_64/jcgray-avx2.asm
106
- simd/x86_64/jcsample-avx2.asm
107
- simd/x86_64/jdcolor-avx2.asm
108
- simd/x86_64/jdmerge-avx2.asm
109
- simd/x86_64/jdsample-avx2.asm
110
- simd/x86_64/jfdctint-avx2.asm
111
- simd/x86_64/jidctint-avx2.asm
112
- simd/x86_64/jquanti-avx2.asm
113
- ].freeze
114
-
115
- X86_64_ASM_TEMPLATE_SOURCES = %w[
116
- simd/x86_64/jccolext-sse2.asm
117
- simd/x86_64/jcgryext-sse2.asm
118
- simd/x86_64/jdcolext-sse2.asm
119
- simd/x86_64/jdmrgext-sse2.asm
120
- simd/x86_64/jccolext-avx2.asm
121
- simd/x86_64/jcgryext-avx2.asm
122
- simd/x86_64/jdcolext-avx2.asm
123
- simd/x86_64/jdmrgext-avx2.asm
124
- ].freeze
125
-
126
- MOZJPEG_VERSION = "4.1.5"
127
- MOZJPEG_VERSION_NUMBER = "4001005"
11
+ require_relative "mozjpeg_sources"
12
+
13
+ MOZJPEG_CORE_SOURCES = ImagePackMozjpegSources::TOPLEVEL_C_SOURCES
14
+ MOZJPEG_INCLUDE_TEMPLATES = ImagePackMozjpegSources::TOPLEVEL_INCLUDE_TEMPLATES
15
+ NEON_AARCH64_SOURCES = ImagePackMozjpegSources::NEON_AARCH64_C_SOURCES
16
+ X86_64_C_SOURCES = ImagePackMozjpegSources::X86_64_C_SOURCES
17
+ X86_64_ASM_SOURCES = ImagePackMozjpegSources::X86_64_ASM_SOURCES
18
+ MOZJPEG_VERSION = ImagePackMozjpegSources::VERSION
19
+ MOZJPEG_VERSION_NUMBER = ImagePackMozjpegSources::VERSION_NUMBER
128
20
 
129
21
  def find_vendor_dir
130
22
  candidates = [
@@ -285,8 +177,14 @@ def select_simd_backend(mozjpeg_dir, arch)
285
177
  else
286
178
  missing = NEON_AARCH64_SOURCES.reject { |rel| File.exist?(File.join(mozjpeg_dir, rel)) }
287
179
  missing << "simd/arm/neon-compat.h.in" unless template_present
288
- warn "image_pack: ARM64 detected but NEON sources missing under #{mozjpeg_dir}:"
289
- missing.each { |m| warn " - #{m}" }
180
+ message = "image_pack: ARM64 detected but NEON sources are incomplete under #{mozjpeg_dir}:
181
+ " "#{missing.map { |m| " - #{m}" }.join("
182
+ ")}"
183
+ if ENV["IMAGE_PACK_REQUIRE_SIMD"] == "1"
184
+ abort "#{message}
185
+ image_pack: refusing scalar fallback because IMAGE_PACK_REQUIRE_SIMD=1."
186
+ end
187
+ warn message
290
188
  warn "image_pack: re-run `bundle exec rake vendor` after updating script/vendor_libs.rb."
291
189
  warn "image_pack: falling back to scalar."
292
190
  { kind: :none }
@@ -294,16 +192,32 @@ def select_simd_backend(mozjpeg_dir, arch)
294
192
 
295
193
  when :x86_64
296
194
  if find_nasm.nil?
297
- warn "image_pack: x86_64 detected but NASM/YASM not found on PATH. Falling back to scalar; install nasm for ~3-4x speedup."
195
+ message = "image_pack: x86_64 detected but NASM/YASM not found on PATH."
196
+ if ENV["IMAGE_PACK_REQUIRE_SIMD"] == "1"
197
+ abort "#{message} Install nasm/yasm or unset IMAGE_PACK_REQUIRE_SIMD."
198
+ end
199
+ warn "#{message} Falling back to scalar; install nasm for ~3-4x speedup. Set IMAGE_PACK_REQUIRE_SIMD=1 to fail instead."
298
200
  { kind: :none }
299
201
  elsif !X86_64_C_SOURCES.all? { |rel| File.exist?(File.join(mozjpeg_dir, rel)) }
300
- warn "image_pack: x86_64 SIMD sources missing under #{mozjpeg_dir}/simd/x86_64/. Falling back to scalar."
202
+ missing = X86_64_C_SOURCES.reject { |rel| File.exist?(File.join(mozjpeg_dir, rel)) }
203
+ message = "image_pack: x86_64 SIMD C sources missing under #{mozjpeg_dir}: #{missing.join(', ')}"
204
+ ENV["IMAGE_PACK_REQUIRE_SIMD"] == "1" ? abort(message) : warn("#{message}. Falling back to scalar.")
301
205
  { kind: :none }
302
206
  else
207
+ missing_asm = X86_64_ASM_SOURCES.reject { |rel| File.exist?(File.join(mozjpeg_dir, rel)) }
208
+ unless missing_asm.empty?
209
+ message = "image_pack: x86_64 SIMD ASM sources missing under #{mozjpeg_dir}: #{missing_asm.join(', ')}"
210
+ if ENV["IMAGE_PACK_REQUIRE_SIMD"] == "1"
211
+ abort message
212
+ end
213
+ warn "#{message}. Falling back to scalar."
214
+ return { kind: :none }
215
+ end
216
+
303
217
  {
304
218
  kind: :x86_64_simd,
305
219
  c_sources: X86_64_C_SOURCES,
306
- asm_sources: X86_64_ASM_SOURCES.select { |rel| File.exist?(File.join(mozjpeg_dir, rel)) },
220
+ asm_sources: X86_64_ASM_SOURCES,
307
221
  extra_includes: [
308
222
  File.join(mozjpeg_dir, "simd"),
309
223
  File.join(mozjpeg_dir, "simd", "nasm"),
@@ -363,7 +277,8 @@ def configure_vendored_mozjpeg(vendor_dir)
363
277
 
364
278
  abort "vendored MozJPEG sources not found in #{mozjpeg_dir}" unless Dir.exist?(mozjpeg_dir)
365
279
 
366
- missing_sources = MOZJPEG_CORE_SOURCES.reject { |source| File.exist?(File.join(mozjpeg_dir, source)) }
280
+ required_sources = MOZJPEG_CORE_SOURCES + MOZJPEG_INCLUDE_TEMPLATES
281
+ missing_sources = required_sources.reject { |source| File.exist?(File.join(mozjpeg_dir, source)) }
367
282
  abort "vendored MozJPEG sources are incomplete: #{missing_sources.join(', ')}" unless missing_sources.empty?
368
283
 
369
284
  arch = detect_simd_arch
@@ -489,7 +404,7 @@ $warnflags = ""
489
404
 
490
405
  unless msvc?
491
406
  $CFLAGS += " -O3 -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare -std=gnu11"
492
- $CFLAGS += " -fno-math-errno -fno-trapping-math"
407
+ $CFLAGS += " -fno-math-errno -fno-trapping-math -fvisibility=hidden"
493
408
 
494
409
  march = ENV["IMAGE_PACK_MARCH"].to_s
495
410
  unless march.empty?