rack_resize 0.1.3 → 0.1.6
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/.github/workflows/test.yml +1 -1
- data/.ruby-version +1 -1
- data/Gemfile +3 -0
- data/README.md +185 -24
- data/benchmark/http_benchmark.rb +151 -0
- data/benchmark/processor_benchmark.rb +84 -0
- data/config.ru +1 -1
- data/lib/rack_resize/color_utils.rb +200 -0
- data/lib/rack_resize/configuration.rb +3 -2
- data/lib/rack_resize/input_parsers/cloudflare.rb +1 -1
- data/lib/rack_resize/input_parsers/imgproxy.rb +71 -0
- data/lib/rack_resize/input_parsers/query_string.rb +30 -0
- data/lib/rack_resize/processing.rb +25 -5
- data/lib/rack_resize/processors/bun_image.js +34 -0
- data/lib/rack_resize/processors/bun_image.rb +42 -0
- data/lib/rack_resize/processors/imlib2.rb +40 -3
- data/lib/rack_resize/processors/mini_magick.rb +17 -25
- data/lib/rack_resize/processors/sips.rb +55 -23
- data/lib/rack_resize/processors/vips.rb +18 -3
- data/lib/rack_resize/rack_app.rb +11 -7
- data/lib/rack_resize.rb +8 -5
- data/rack_resize.gemspec +1 -1
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d7491afc343d0a0a7959e94542b99579cc48e4c9fbd0697a7f06a6658e3d28c0
|
|
4
|
+
data.tar.gz: d682ff508ee8ae6c75520f060b832addb33dc0db8e0a4be0966e962ea97a996c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c2265999fb7762371c4c075748709d89e6f303646f0e0a919ae4af225714e1b2af47f6015b66d4df88b8e5b74881f64d253a100097f1e05c9e22f79a8679ce4e
|
|
7
|
+
data.tar.gz: 4e5b7c9100e44e3477654b4cc004e2fc1f29f3ae687a3c6dd176e44f05ee81393ed4eee24349b98f6b8e9c83852ca8168e10b214efd4cd1fa1d513508fcf3f04
|
data/.github/workflows/test.yml
CHANGED
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
- name: Install package
|
|
20
20
|
run: |
|
|
21
21
|
sudo apt-get update
|
|
22
|
-
sudo apt-get install -y libvips42 libvips-tools imagemagick libimlib2 libimlib2-dev
|
|
22
|
+
sudo apt-get install -y libvips42 libvips-tools imagemagick libimlib2 libimlib2-dev libheif-plugin-aomenc libavif-dev
|
|
23
23
|
|
|
24
24
|
- uses: ruby/setup-ruby@v1
|
|
25
25
|
with:
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.0.
|
|
1
|
+
4.0.6
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -13,7 +13,7 @@ end
|
|
|
13
13
|
How to use with Rack
|
|
14
14
|
|
|
15
15
|
```ruby
|
|
16
|
-
use RackResize::RackApp, processor: :
|
|
16
|
+
use RackResize::RackApp, processor: :mini_magick, assets_folders: ["samples"]
|
|
17
17
|
use Rack::Static, urls: [""], root: "samples", index: "index.html" # optional
|
|
18
18
|
```
|
|
19
19
|
|
|
@@ -23,17 +23,107 @@ Cloudflare format: (can be used with helpers from `carrierwave-cloudflare` gem)
|
|
|
23
23
|
```
|
|
24
24
|
/cdn-cgi/image/width=426,format=auto/assets/pets/dog.jpg
|
|
25
25
|
```
|
|
26
|
-
Fastly
|
|
26
|
+
Fastly / bunny.net format (query string params):
|
|
27
27
|
```
|
|
28
|
-
/assets/pets/dog.jpg?width=300
|
|
28
|
+
/assets/pets/dog.jpg?width=300&height=200&fit=cover&format=webp&quality=85
|
|
29
|
+
```
|
|
30
|
+
Imgproxy format:
|
|
31
|
+
```
|
|
32
|
+
/insecure/rs:fill:400:300/q:85/f:webp/plain/local:///assets/pets/dog.jpg
|
|
33
|
+
/insecure/rs:fit:200:150/plain/local:///assets/banner.jpg@webp
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Supported Parameters:
|
|
37
|
+
|
|
38
|
+
| Parameter | Shortcut | Imgproxy Option | Type | Description |
|
|
39
|
+
|------------|----------|-----------------|------|-------------|
|
|
40
|
+
| `width` | `w` | `w:<val>` or in `rs:<mode>:<w>:<h>` | integer | Target width in pixels. Scales proportionally if `height` is omitted. |
|
|
41
|
+
| `height` | `h` | `h:<val>` or in `rs:<mode>:<w>:<h>` | integer | Target height in pixels. Scales proportionally if `width` is omitted. |
|
|
42
|
+
| `fit` | — | `rs:fill` (cover), `rs:fit` (contain) | string | Resize mode: `contain` (default), `cover`, `crop`. See below. |
|
|
43
|
+
| `format` | `f` | `f:<fmt>` or `@<fmt>` suffix | string | Output format: `jpeg`, `png`, `webp`, `avif`, `gif`. Use `auto` to keep original. |
|
|
44
|
+
| `quality` | `q` | `q:<val>` | integer (1–100) | Compression quality. Defaults to `default_quality` config value (95). |
|
|
45
|
+
| `dpr` | — | `dpr:<val>` | float (0.1–10) | Device pixel ratio. Multiplies `width` and `height` before processing. |
|
|
46
|
+
| `bg-color` | `bg` | `bg:<color>` | color | Background color for flattening transparency. Also aliased as `background`. See formats below. |
|
|
47
|
+
|
|
48
|
+
**Background color formats:**
|
|
49
|
+
- CSS named color: `white`, `red`, `cornflowerblue` (all 148 CSS colors supported)
|
|
50
|
+
- 3-digit hex: `#a84`
|
|
51
|
+
- 6-digit hex: `#aa8844`
|
|
52
|
+
- 8-digit hex: `#aa884480` (last two digits = alpha 0–255)
|
|
53
|
+
- Decimal RGB: `0,255,0`
|
|
54
|
+
- Decimal RGBA: `0,255,0,0.5` (alpha 0.0–1.0)
|
|
55
|
+
|
|
56
|
+
Supported by: `vips`, `mini_magick`. Accepted but ignored by `sips` and `imlib2`.
|
|
57
|
+
|
|
58
|
+
**Fit modes:**
|
|
59
|
+
- `contain` (`rs:fit`) — resizes to fit within the given box, preserving aspect ratio (default)
|
|
60
|
+
- `cover` / `crop` (`rs:fill`) — resizes and center-crops to fill the exact box
|
|
61
|
+
|
|
62
|
+
**Example URLs (query string format):**
|
|
63
|
+
```
|
|
64
|
+
# Resize to width only
|
|
65
|
+
/samples/image_1.jpeg?w=400
|
|
66
|
+
|
|
67
|
+
# Resize to fit within 400×300 box
|
|
68
|
+
/samples/image_1.jpeg?width=400&height=300
|
|
69
|
+
|
|
70
|
+
# Cover-crop to exact 400×300
|
|
71
|
+
/samples/image_1.jpeg?width=400&height=300&fit=cover
|
|
72
|
+
|
|
73
|
+
# Convert to WebP at 80% quality
|
|
74
|
+
/samples/image_1.jpeg?w=400&f=webp&q=80
|
|
75
|
+
|
|
76
|
+
# Retina (2×) resize
|
|
77
|
+
/samples/image_1.jpeg?w=200&dpr=2
|
|
78
|
+
|
|
79
|
+
# Flatten transparency with a background color
|
|
80
|
+
/samples/image.png?w=400&f=jpeg&bg-color=white
|
|
81
|
+
/samples/image.png?w=400&f=jpeg&bg=%23ff0000
|
|
82
|
+
/samples/image.png?w=400&f=jpeg&background=0,255,0,0.5
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Example URLs (Cloudflare format):**
|
|
86
|
+
```
|
|
87
|
+
# Resize to width
|
|
88
|
+
/cdn-cgi/image/width=400/samples/image_1.jpeg
|
|
89
|
+
|
|
90
|
+
# Cover-crop with format conversion
|
|
91
|
+
/cdn-cgi/image/width=400,height=300,fit=cover,format=webp/samples/image_1.jpeg
|
|
92
|
+
|
|
93
|
+
# Quality + format
|
|
94
|
+
/cdn-cgi/image/width=400,format=avif,quality=80/samples/image_1.jpeg
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Example URLs (Imgproxy format):**
|
|
98
|
+
```
|
|
99
|
+
# Resize to width and height with cover crop (rs:fill)
|
|
100
|
+
/insecure/rs:fill:400:300/plain/local:///samples/image_1.jpeg
|
|
101
|
+
|
|
102
|
+
# Resize to fit within 400×300 box (rs:fit)
|
|
103
|
+
/insecure/rs:fit:400:300/plain/local:///samples/image_1.jpeg
|
|
104
|
+
|
|
105
|
+
# Convert format and adjust quality
|
|
106
|
+
/insecure/rs:fill:400:300/q:85/f:webp/plain/local:///samples/image_1.jpeg
|
|
107
|
+
|
|
108
|
+
# Specify format via extension suffix (@webp)
|
|
109
|
+
/insecure/rs:fit:200:150/plain/samples/banner.jpg@webp
|
|
110
|
+
|
|
111
|
+
# Using separate width, height, and DPR
|
|
112
|
+
/insecure/w:400/h:300/dpr:2/plain/samples/image_1.jpeg
|
|
113
|
+
|
|
114
|
+
# Flatten transparency with background color
|
|
115
|
+
/insecure/rs:fill:200:200/bg:white/plain/samples/logo.png
|
|
116
|
+
|
|
117
|
+
# Rails fingerprint stripping (fingerprinted URL automatically resolves to original file)
|
|
118
|
+
/insecure/w:200/plain/assets/photo-1a2b3c4d.jpg
|
|
29
119
|
```
|
|
30
120
|
|
|
31
121
|
### Configuration:
|
|
32
122
|
|
|
33
123
|
```ruby
|
|
34
124
|
RackResize.configure do |config|
|
|
35
|
-
config.assets_folders = {
|
|
36
|
-
config.processor = :sips / :vips / :mini_magick / :imlib2
|
|
125
|
+
config.assets_folders = { assets: Rails.root.join('app', 'assets', 'images') }
|
|
126
|
+
config.processor = :sips / :vips / :mini_magick / :imlib2 / :bun_image
|
|
37
127
|
config.default_quality = 95
|
|
38
128
|
config.save_resized = false
|
|
39
129
|
config.cache_folder = Rails.root.join('tmp', 'rack_resize_cache') # used if save_resized enabled
|
|
@@ -41,16 +131,44 @@ RackResize.configure do |config|
|
|
|
41
131
|
end
|
|
42
132
|
```
|
|
43
133
|
|
|
134
|
+
### Development vs. Production Architecture:
|
|
135
|
+
|
|
136
|
+
`rack_resize` lets you write standard CDN / imgproxy URLs in your application code without needing external services running in development:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
[ Local Development ]
|
|
140
|
+
Browser Request --> Rails / Rack App --> RackResize Middleware --> Ruby Processor (:vips, :mini_magick, etc.)
|
|
141
|
+
|
|
142
|
+
[ Production ]
|
|
143
|
+
Browser Request --> Nginx / CDN Cache --> imgproxy (Go service) --> Original Image Storage (S3 / disk)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- **In Development**: Include `rack_resize` in your `:development` Gemfile group. It intercepts `/insecure/...`, `/cdn-cgi/image/...`, or query string parameters, processes images on the fly in Ruby, and serves them immediately.
|
|
147
|
+
- **In Production**: Route image paths directly at your reverse proxy (e.g. Nginx with `proxy_cache`) to your production `imgproxy` container or CDN edge, keeping your web application servers focused on dynamic application traffic.
|
|
148
|
+
|
|
44
149
|
### Supported Processing Backends:
|
|
45
150
|
|
|
46
151
|
<table>
|
|
47
152
|
<tr>
|
|
48
|
-
<th>
|
|
49
|
-
<th>dependency</th>
|
|
153
|
+
<th>processor</th>
|
|
50
154
|
<th>config</th>
|
|
155
|
+
<th>system library</th>
|
|
156
|
+
<th>gems</th>
|
|
157
|
+
</tr>
|
|
158
|
+
<tr>
|
|
159
|
+
<td>sips (macOS only)</td>
|
|
160
|
+
<td><code>processor: :sips</code></td>
|
|
161
|
+
<td>built-in on macOS</td>
|
|
162
|
+
<td>none</td>
|
|
51
163
|
</tr>
|
|
52
164
|
<tr>
|
|
53
165
|
<td>mini_magick</td>
|
|
166
|
+
<td><code>processor: :mini_magick</code></td>
|
|
167
|
+
<td>
|
|
168
|
+
|
|
169
|
+
ImageMagick — `brew install imagemagick`
|
|
170
|
+
|
|
171
|
+
</td>
|
|
54
172
|
<td>
|
|
55
173
|
|
|
56
174
|
```ruby
|
|
@@ -59,14 +177,15 @@ gem "mini_magick"
|
|
|
59
177
|
```
|
|
60
178
|
|
|
61
179
|
</td>
|
|
180
|
+
</tr>
|
|
181
|
+
<tr>
|
|
182
|
+
<td>vips</td>
|
|
183
|
+
<td><code>processor: :vips</code></td>
|
|
62
184
|
<td>
|
|
63
185
|
|
|
64
|
-
`
|
|
186
|
+
libvips — `brew install vips`
|
|
65
187
|
|
|
66
188
|
</td>
|
|
67
|
-
</tr>
|
|
68
|
-
<tr>
|
|
69
|
-
<td>vips</td>
|
|
70
189
|
<td>
|
|
71
190
|
|
|
72
191
|
```ruby
|
|
@@ -74,35 +193,77 @@ gem "image_processing"
|
|
|
74
193
|
gem "ruby-vips"
|
|
75
194
|
```
|
|
76
195
|
|
|
77
|
-
</td>
|
|
78
|
-
<td>
|
|
79
|
-
|
|
80
|
-
`processor: :vips`
|
|
81
|
-
|
|
82
196
|
</td>
|
|
83
197
|
</tr>
|
|
84
198
|
<tr>
|
|
85
|
-
<td>
|
|
86
|
-
<td>
|
|
199
|
+
<td>imlib2</td>
|
|
200
|
+
<td><code>processor: :imlib2</code></td>
|
|
87
201
|
<td>
|
|
88
202
|
|
|
89
|
-
`
|
|
203
|
+
Imlib2 — `brew install imlib2`
|
|
90
204
|
|
|
91
205
|
</td>
|
|
92
|
-
</tr>
|
|
93
|
-
<tr>
|
|
94
|
-
<td>imlib2</td>
|
|
95
206
|
<td>
|
|
96
207
|
|
|
97
208
|
```ruby
|
|
98
|
-
gem
|
|
209
|
+
gem "rszr"
|
|
99
210
|
```
|
|
100
211
|
|
|
101
212
|
</td>
|
|
213
|
+
</tr>
|
|
214
|
+
<tr>
|
|
215
|
+
<td>bun_image</td>
|
|
216
|
+
<td><code>processor: :bun_image</code></td>
|
|
102
217
|
<td>
|
|
103
218
|
|
|
104
|
-
`
|
|
219
|
+
Bun ≥ 1.3.14 — `brew install oven-sh/bun/bun`
|
|
105
220
|
|
|
106
221
|
</td>
|
|
222
|
+
<td>none (shells out to <code>bun</code>)</td>
|
|
107
223
|
</tr>
|
|
108
224
|
</table>
|
|
225
|
+
|
|
226
|
+
Note: <code>fit=cover</code>/<code>fit=crop</code> falls back to stretch (fill) with <code>bun_image</code> — Bun.Image has no crop yet.
|
|
227
|
+
|
|
228
|
+
### Supported Image Formats:
|
|
229
|
+
|
|
230
|
+
| Format | sips | mini_magick | vips | imlib2 | bun_image |
|
|
231
|
+
|--------|:----:|:-----------:|:----:|:------:|:---------:|
|
|
232
|
+
| JPEG | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
233
|
+
| PNG | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
234
|
+
| GIF | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
235
|
+
| WebP | ❌ | ✅ ¹ | ✅ ¹ | ❌ | ✅ |
|
|
236
|
+
| AVIF | ✅ ² | ✅ ³ | ✅ ⁴ | ❌ | ✅ ⁷ |
|
|
237
|
+
| HEIC | ✅ | ✅ ³ | ✅ ⁴ | ❌ | ✅ ⁷ |
|
|
238
|
+
| SVG | ❌ | ✅ ⁵ | ✅ ⁶ | ❌ | ❌ |
|
|
239
|
+
|
|
240
|
+
¹ Requires ImageMagick / libvips built with **libwebp** support (`brew install webp`)
|
|
241
|
+
² macOS 13 (Ventura) or later
|
|
242
|
+
³ Requires ImageMagick built with **libheif** support (`brew install libheif`)
|
|
243
|
+
⁴ Requires libvips built with **libheif** support (`brew install libheif`)
|
|
244
|
+
⁵ Requires **Inkscape** or **librsvg** (`brew install librsvg`)
|
|
245
|
+
⁶ Requires libvips built with **librsvg** support (`brew install librsvg`)
|
|
246
|
+
⁷ AVIF/HEIC encode is macOS/Windows only in Bun.Image
|
|
247
|
+
|
|
248
|
+
### Performance Benchmarks:
|
|
249
|
+
|
|
250
|
+
Measured on an Apple M1 Pro with Ruby 4.0.5. Output size: 300×200 px.
|
|
251
|
+
Run with `ruby benchmark/processor_benchmark.rb`
|
|
252
|
+
|
|
253
|
+
**JPEG — image_1.jpeg (51 KB)**
|
|
254
|
+
|
|
255
|
+
| Processor | i/s | ms/i | vs fastest |
|
|
256
|
+
|-------------|------:|------:|:----------:|
|
|
257
|
+
| imlib2 | 613.0 | 1.63 | — |
|
|
258
|
+
| vips | 216.9 | 4.61 | 2.83× |
|
|
259
|
+
| mini_magick | 36.4 | 27.49 | 16.85× |
|
|
260
|
+
| sips | 20.1 | 49.68 | 30.45× |
|
|
261
|
+
|
|
262
|
+
**PNG — sample.png (2 KB)**
|
|
263
|
+
|
|
264
|
+
| Processor | i/s | ms/i | vs fastest |
|
|
265
|
+
|-------------|------:|-----:|:----------:|
|
|
266
|
+
| vips | 431.1 | 2.32 | — |
|
|
267
|
+
| imlib2 | 221.7 | 4.51 | 1.94× |
|
|
268
|
+
| mini_magick | 36.9 | 27.12| 11.69× |
|
|
269
|
+
| sips | 19.4 | 51.62| 22.25× |
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# HTTP benchmark: starts a WEBrick server in a background thread, mounts one
|
|
4
|
+
# RackResize::RackApp per available processor at a distinct path prefix, then
|
|
5
|
+
# measures throughput with benchmark-ips over persistent HTTP connections.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# bundle exec ruby benchmark/http_benchmark.rb
|
|
9
|
+
|
|
10
|
+
require 'bundler/setup'
|
|
11
|
+
require 'rack'
|
|
12
|
+
require 'webrick'
|
|
13
|
+
require 'net/http'
|
|
14
|
+
require 'benchmark/ips'
|
|
15
|
+
require 'logger'
|
|
16
|
+
require 'stringio'
|
|
17
|
+
require 'timeout'
|
|
18
|
+
require_relative '../lib/rack_resize'
|
|
19
|
+
|
|
20
|
+
SAMPLES_DIR = File.expand_path('../samples', __dir__)
|
|
21
|
+
BENCH_PORT = 14_569
|
|
22
|
+
BENCH_IMAGE = 'image_1.jpeg'
|
|
23
|
+
BENCH_PARAMS = 'width=300&height=200'
|
|
24
|
+
NULL_LOGGER = Logger.new(IO::NULL)
|
|
25
|
+
|
|
26
|
+
# ── One RackApp per available processor ──────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
available_apps = {}
|
|
29
|
+
|
|
30
|
+
puts "Loading processors..."
|
|
31
|
+
%i[sips vips mini_magick imlib2 bun_image].each do |name|
|
|
32
|
+
begin
|
|
33
|
+
app = RackResize::RackApp.new(
|
|
34
|
+
processor: name,
|
|
35
|
+
assets_folders: { '/' => SAMPLES_DIR },
|
|
36
|
+
save_resized: false,
|
|
37
|
+
logger: NULL_LOGGER,
|
|
38
|
+
)
|
|
39
|
+
available_apps[name] = app
|
|
40
|
+
puts " ✓ #{name}"
|
|
41
|
+
rescue => e
|
|
42
|
+
puts " ✗ #{name}: #{e.message}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
abort "\nNo processors available." if available_apps.empty?
|
|
47
|
+
|
|
48
|
+
# ── URLMap: /sips → sips_app, /vips → vips_app, etc. ────────────────────────
|
|
49
|
+
|
|
50
|
+
rack_app = Rack::Builder.new do
|
|
51
|
+
available_apps.each do |name, app|
|
|
52
|
+
map("/#{name}") { run app }
|
|
53
|
+
end
|
|
54
|
+
end.to_app
|
|
55
|
+
|
|
56
|
+
# ── WEBrick with a minimal Rack env bridge ───────────────────────────────────
|
|
57
|
+
|
|
58
|
+
webrick = WEBrick::HTTPServer.new(
|
|
59
|
+
Port: BENCH_PORT,
|
|
60
|
+
Logger: WEBrick::Log.new(IO::NULL),
|
|
61
|
+
AccessLog: [],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
webrick.mount_proc('/') do |req, res|
|
|
65
|
+
env = {
|
|
66
|
+
'REQUEST_METHOD' => req.request_method,
|
|
67
|
+
'PATH_INFO' => req.path,
|
|
68
|
+
'QUERY_STRING' => req.query_string.to_s,
|
|
69
|
+
'SERVER_NAME' => 'localhost',
|
|
70
|
+
'SERVER_PORT' => BENCH_PORT.to_s,
|
|
71
|
+
'HTTP_HOST' => "localhost:#{BENCH_PORT}",
|
|
72
|
+
'SCRIPT_NAME' => '',
|
|
73
|
+
'rack.version' => Rack::VERSION,
|
|
74
|
+
'rack.input' => StringIO.new(''),
|
|
75
|
+
'rack.errors' => $stderr,
|
|
76
|
+
'rack.multithread' => true,
|
|
77
|
+
'rack.multiprocess' => false,
|
|
78
|
+
'rack.run_once' => false,
|
|
79
|
+
'rack.url_scheme' => 'http',
|
|
80
|
+
}
|
|
81
|
+
req.header.each do |key, values|
|
|
82
|
+
env["HTTP_#{key.upcase.tr('-', '_')}"] = values.join(', ')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
status, headers, body = rack_app.call(env)
|
|
86
|
+
|
|
87
|
+
res.status = status
|
|
88
|
+
headers.each { |k, v| res[k] = v }
|
|
89
|
+
body_str = +''
|
|
90
|
+
body.each { |chunk| body_str << chunk }
|
|
91
|
+
res.body = body_str
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
server_thread = Thread.new { webrick.start }
|
|
95
|
+
|
|
96
|
+
Timeout.timeout(10) do
|
|
97
|
+
loop do
|
|
98
|
+
TCPSocket.new('localhost', BENCH_PORT).close
|
|
99
|
+
break
|
|
100
|
+
rescue Errno::ECONNREFUSED
|
|
101
|
+
sleep 0.05
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
puts "\nServer listening on port #{BENCH_PORT}"
|
|
106
|
+
|
|
107
|
+
# ── Smoke-test each endpoint before benchmarking ─────────────────────────────
|
|
108
|
+
|
|
109
|
+
puts "\nVerifying endpoints..."
|
|
110
|
+
available_apps.keys.each do |name|
|
|
111
|
+
uri = URI("http://localhost:#{BENCH_PORT}/#{name}/#{BENCH_IMAGE}?#{BENCH_PARAMS}")
|
|
112
|
+
resp = Net::HTTP.get_response(uri)
|
|
113
|
+
if resp.is_a?(Net::HTTPOK)
|
|
114
|
+
puts " ✓ /#{name}/#{BENCH_IMAGE} → 200 (#{resp.body.bytesize} B)"
|
|
115
|
+
else
|
|
116
|
+
warn " ✗ /#{name}/#{BENCH_IMAGE} → #{resp.code}: #{resp.body}"
|
|
117
|
+
available_apps.delete(name)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
abort "\nNo processors passed smoke test." if available_apps.empty?
|
|
122
|
+
|
|
123
|
+
# ── Persistent HTTP connections, one per processor ───────────────────────────
|
|
124
|
+
|
|
125
|
+
connections = available_apps.keys.to_h do |name|
|
|
126
|
+
http = Net::HTTP.new('localhost', BENCH_PORT)
|
|
127
|
+
http.start
|
|
128
|
+
[name, http]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
puts "\n── HTTP rack server benchmark ───────────────────────────────────────────────"
|
|
132
|
+
puts " image : #{BENCH_IMAGE}"
|
|
133
|
+
puts " params: #{BENCH_PARAMS}"
|
|
134
|
+
puts
|
|
135
|
+
|
|
136
|
+
Benchmark.ips do |x|
|
|
137
|
+
x.config(time: 10, warmup: 3)
|
|
138
|
+
|
|
139
|
+
connections.each do |name, http|
|
|
140
|
+
path = "/#{name}/#{BENCH_IMAGE}?#{BENCH_PARAMS}"
|
|
141
|
+
x.report(name.to_s) { http.get(path) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
x.compare!
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# ── Cleanup ───────────────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
connections.each_value(&:finish)
|
|
150
|
+
webrick.shutdown
|
|
151
|
+
server_thread.join
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# Direct processor benchmark: instantiates each available RackResize processor
|
|
4
|
+
# and calls resize() directly on images from the samples/ folder.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# bundle exec ruby benchmark/processor_benchmark.rb
|
|
8
|
+
|
|
9
|
+
require 'bundler/setup'
|
|
10
|
+
require 'benchmark/ips'
|
|
11
|
+
require_relative '../lib/rack_resize'
|
|
12
|
+
|
|
13
|
+
SAMPLES_DIR = File.expand_path('../samples', __dir__)
|
|
14
|
+
BENCH_IMAGES = Dir[File.join(SAMPLES_DIR, '{image_1,sample}.{jpg,jpeg,png,webp,heic,avif}')].sort
|
|
15
|
+
BENCH_WIDTH = 300
|
|
16
|
+
BENCH_HEIGHT = 200
|
|
17
|
+
|
|
18
|
+
abort 'No sample images found in samples/.' if BENCH_IMAGES.empty?
|
|
19
|
+
|
|
20
|
+
# ── Collect available processor instances ────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
PROCESSOR_CLASSES = {
|
|
23
|
+
sips: RackResize::Processors::Sips,
|
|
24
|
+
vips: RackResize::Processors::Vips,
|
|
25
|
+
mini_magick: RackResize::Processors::MiniMagick,
|
|
26
|
+
imlib2: RackResize::Processors::Imlib2,
|
|
27
|
+
bun_image: RackResize::Processors::BunImage,
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
puts "Loading processors..."
|
|
31
|
+
processors = {}
|
|
32
|
+
|
|
33
|
+
PROCESSOR_CLASSES.each do |name, klass|
|
|
34
|
+
begin
|
|
35
|
+
instance = klass.new
|
|
36
|
+
# Verify the processor actually works before including it
|
|
37
|
+
instance.resize(
|
|
38
|
+
source_file: BENCH_IMAGES.first,
|
|
39
|
+
target_file: nil,
|
|
40
|
+
target_width: BENCH_WIDTH,
|
|
41
|
+
target_height: BENCH_HEIGHT,
|
|
42
|
+
)
|
|
43
|
+
processors[name] = instance
|
|
44
|
+
puts " ✓ #{name}"
|
|
45
|
+
rescue => e
|
|
46
|
+
puts " ✗ #{name}: #{e.message}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
abort "\nNo processors available." if processors.empty?
|
|
51
|
+
|
|
52
|
+
puts "\nSample images: #{BENCH_IMAGES.map { |f| File.basename(f) }.join(', ')}"
|
|
53
|
+
|
|
54
|
+
# ── Benchmark each sample image independently ─────────────────────────────────
|
|
55
|
+
|
|
56
|
+
BENCH_IMAGES.each do |image_path|
|
|
57
|
+
image_name = File.basename(image_path)
|
|
58
|
+
image_size = File.size(image_path)
|
|
59
|
+
|
|
60
|
+
puts "\n── Direct processor benchmark ───────────────────────────────────────────────"
|
|
61
|
+
puts " image : #{image_name} (#{image_size / 1024} KB)"
|
|
62
|
+
puts " output: #{BENCH_WIDTH}x#{BENCH_HEIGHT}"
|
|
63
|
+
puts
|
|
64
|
+
|
|
65
|
+
Benchmark.ips do |x|
|
|
66
|
+
x.config(time: 5, warmup: 2)
|
|
67
|
+
|
|
68
|
+
processors.each do |name, processor|
|
|
69
|
+
next if name.to_s == "imlib2" && image_name =~ /heic|webp|avif/
|
|
70
|
+
next if name.to_s == "sips" && image_name =~ /webp/
|
|
71
|
+
|
|
72
|
+
x.report(name.to_s) do
|
|
73
|
+
processor.resize(
|
|
74
|
+
source_file: image_path,
|
|
75
|
+
target_file: nil,
|
|
76
|
+
target_width: BENCH_WIDTH,
|
|
77
|
+
target_height: BENCH_HEIGHT,
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
x.compare!
|
|
83
|
+
end
|
|
84
|
+
end
|
data/config.ru
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
require_relative "lib/rack_resize"
|
|
8
8
|
require "rack/static"
|
|
9
9
|
|
|
10
|
-
use RackResize::RackApp, processor: :
|
|
10
|
+
use RackResize::RackApp, processor: :vips, assets_folders: ["samples"]
|
|
11
11
|
|
|
12
12
|
use Rack::Static, urls: [""], root: "samples", index: "index.html"
|
|
13
13
|
|