fast_resize 1.0.2 → 1.0.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/bindings/ruby/ext/fastresize/extconf.rb +79 -105
  4. data/bindings/ruby/lib/fastresize/platform.rb +102 -56
  5. data/bindings/ruby/lib/fastresize/version.rb +1 -1
  6. data/bindings/ruby/lib/fastresize.rb +321 -6
  7. data/bindings/ruby/prebuilt/linux-aarch64/bin/fast_resize +0 -0
  8. data/bindings/ruby/prebuilt/linux-aarch64.tar.gz +0 -0
  9. data/bindings/ruby/prebuilt/linux-x86_64/bin/fast_resize +0 -0
  10. data/bindings/ruby/prebuilt/linux-x86_64/lib/libfastresize.a +0 -0
  11. data/bindings/ruby/prebuilt/linux-x86_64.tar.gz +0 -0
  12. data/bindings/ruby/prebuilt/macos-arm64/bin/fast_resize +0 -0
  13. data/bindings/ruby/prebuilt/macos-arm64/lib/libfastresize.a +0 -0
  14. data/bindings/ruby/prebuilt/macos-arm64.tar.gz +0 -0
  15. data/bindings/ruby/prebuilt/macos-x86_64/bin/fast_resize +0 -0
  16. data/bindings/ruby/prebuilt/macos-x86_64/lib/libfastresize.a +0 -0
  17. data/bindings/ruby/prebuilt/macos-x86_64.tar.gz +0 -0
  18. metadata +4 -22
  19. data/CMakeLists.txt +0 -311
  20. data/bindings/ruby/ext/fastresize/fastresize_ext.cpp +0 -377
  21. data/include/fastresize.h +0 -189
  22. data/include/stb_image.h +0 -7988
  23. data/include/stb_image_resize2.h +0 -10651
  24. data/include/stb_image_write.h +0 -1724
  25. data/src/cli.cpp +0 -540
  26. data/src/decoder.cpp +0 -647
  27. data/src/encoder.cpp +0 -376
  28. data/src/fastresize.cpp +0 -445
  29. data/src/internal.h +0 -108
  30. data/src/pipeline.cpp +0 -284
  31. data/src/pipeline.h +0 -175
  32. data/src/resizer.cpp +0 -199
  33. data/src/simd_resize.cpp +0 -384
  34. data/src/simd_resize.h +0 -72
  35. data/src/simd_utils.h +0 -127
  36. data/src/thread_pool.cpp +0 -232
@@ -1,13 +1,328 @@
1
- require 'fastresize/fastresize_ext'
1
+ # FastResize - The Fastest Image Resizing Library On The Planet
2
+ # Copyright (C) 2025 Tran Huu Canh (0xTh3OKrypt) and FastResize Contributors
3
+ #
4
+ # Resize 1,000 images in 2 seconds. Up to 2.9x faster than libvips,
5
+ # 3.1x faster than imageflow. Uses 3-4x less RAM than alternatives.
6
+ #
7
+ # Author: Tran Huu Canh (0xTh3OKrypt)
8
+ # Email: tranhuucanh39@gmail.com
9
+ # Homepage: https://github.com/tranhuucanh/fast_resize
10
+ #
11
+ # BSD 3-Clause License
12
+ #
13
+ # Redistribution and use in source and binary forms, with or without
14
+ # modification, are permitted provided that the following conditions are met:
15
+ #
16
+ # 1. Redistributions of source code must retain the above copyright notice,
17
+ # this list of conditions and the following disclaimer.
18
+ #
19
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
20
+ # this list of conditions and the following disclaimer in the documentation
21
+ # and/or other materials provided with the distribution.
22
+ #
23
+ # 3. Neither the name of the copyright holder nor the names of its
24
+ # contributors may be used to endorse or promote products derived from
25
+ # this software without specific prior written permission.
26
+ #
27
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
31
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
37
+ # THE POSSIBILITY OF SUCH DAMAGE.
2
38
 
3
- module FastResize
4
- VERSION = File.read(File.expand_path('../../../VERSION', __dir__)).strip
39
+ require_relative "fastresize/version"
40
+ require_relative "fastresize/platform"
5
41
 
42
+ module FastResize
6
43
  class Error < StandardError; end
7
44
 
8
- class << self
9
- def version
10
- VERSION
45
+ # Get library version
46
+ #
47
+ # @return [String] Version string
48
+ def self.version
49
+ cli_path = Platform.find_binary
50
+ output = `#{cli_path} --version 2>&1`.strip
51
+ output.sub('FastResize v', '')
52
+ rescue => e
53
+ VERSION
54
+ end
55
+
56
+ # Get image information
57
+ #
58
+ # @param path [String] Path to the image file
59
+ # @return [Hash] Image info with :width, :height, :channels, :format
60
+ #
61
+ # @example
62
+ # FastResize.image_info("photo.jpg")
63
+ # # => { width: 1920, height: 1080, channels: 3, format: "JPEG" }
64
+ def self.image_info(path)
65
+ raise Error, "Image path cannot be empty" if path.nil? || path.empty?
66
+ raise Error, "Image file not found: #{path}" unless File.exist?(path)
67
+
68
+ cli_path = Platform.find_binary
69
+ output = `#{cli_path} info '#{path}' 2>&1`
70
+ raise Error, "Failed to get image info: #{output}" unless $?.success?
71
+
72
+ info = {}
73
+ output.each_line do |line|
74
+ case line
75
+ when /Format:\s*(\S+)/
76
+ info[:format] = $1
77
+ when /Size:\s*(\d+)x(\d+)/
78
+ info[:width] = $1.to_i
79
+ info[:height] = $2.to_i
80
+ when /Channels:\s*(\d+)/
81
+ info[:channels] = $1.to_i
82
+ end
83
+ end
84
+
85
+ info
86
+ end
87
+
88
+ # Resize a single image
89
+ #
90
+ # @param input_path [String] Path to input image
91
+ # @param output_path [String] Path to save resized image
92
+ # @param options [Hash] Resize options
93
+ # @option options [Integer] :width Target width in pixels
94
+ # @option options [Integer] :height Target height in pixels
95
+ # @option options [Float] :scale Scale factor (e.g., 0.5 = 50%, 2.0 = 200%)
96
+ # @option options [Integer] :quality JPEG/WebP quality 1-100 (default: 85)
97
+ # @option options [Symbol] :filter Resize filter: :mitchell, :catmull_rom, :box, :triangle
98
+ # @option options [Boolean] :keep_aspect_ratio Maintain aspect ratio (default: true)
99
+ # @option options [Boolean] :overwrite Overwrite input file (default: false)
100
+ # @return [Boolean] true if successful
101
+ #
102
+ # @example Basic resize by width
103
+ # FastResize.resize("input.jpg", "output.jpg", width: 800)
104
+ #
105
+ # @example Resize to exact dimensions
106
+ # FastResize.resize("input.jpg", "output.jpg", width: 800, height: 600)
107
+ #
108
+ # @example Scale to 50%
109
+ # FastResize.resize("input.jpg", "output.jpg", scale: 0.5)
110
+ #
111
+ # @example With quality and filter
112
+ # FastResize.resize("input.jpg", "output.jpg",
113
+ # width: 800,
114
+ # quality: 95,
115
+ # filter: :catmull_rom
116
+ # )
117
+ def self.resize(input_path, output_path, options = {})
118
+ raise Error, "Input path cannot be empty" if input_path.nil? || input_path.empty?
119
+ raise Error, "Output path cannot be empty" if output_path.nil? || output_path.empty?
120
+ raise Error, "Input file not found: #{input_path}" unless File.exist?(input_path)
121
+
122
+ cli_path = Platform.find_binary
123
+ args = build_resize_args(input_path, output_path, options)
124
+
125
+ result = system(cli_path, *args, out: File::NULL, err: File::NULL)
126
+ raise Error, "Failed to resize image" unless result
127
+
128
+ true
129
+ end
130
+
131
+ # Resize with format conversion
132
+ #
133
+ # @param input_path [String] Path to input image
134
+ # @param output_path [String] Path to save resized image
135
+ # @param format [String] Output format: 'jpg', 'png', 'webp'
136
+ # @param options [Hash] Resize options (same as resize)
137
+ # @return [Boolean] true if successful
138
+ #
139
+ # @example Convert to WebP
140
+ # FastResize.resize_with_format("input.jpg", "output.webp", "webp", width: 800)
141
+ def self.resize_with_format(input_path, output_path, format, options = {})
142
+ raise Error, "Input path cannot be empty" if input_path.nil? || input_path.empty?
143
+ raise Error, "Output path cannot be empty" if output_path.nil? || output_path.empty?
144
+ raise Error, "Format cannot be empty" if format.nil? || format.empty?
145
+ raise Error, "Input file not found: #{input_path}" unless File.exist?(input_path)
146
+
147
+ # The CLI handles format based on output extension
148
+ # Ensure output path has the correct extension
149
+ ext = File.extname(output_path).downcase
150
+ expected_ext = ".#{format.downcase}"
151
+
152
+ if ext != expected_ext
153
+ output_path = output_path.sub(/\.[^.]+$/, expected_ext)
154
+ end
155
+
156
+ resize(input_path, output_path, options)
157
+ end
158
+
159
+ # Batch resize images to a directory with same options
160
+ #
161
+ # @param input_paths [Array<String>] Array of input image paths
162
+ # @param output_dir [String] Output directory
163
+ # @param options [Hash] Resize options plus batch options
164
+ # @option options [Integer] :threads Number of threads (default: auto)
165
+ # @option options [Boolean] :stop_on_error Stop on first error (default: false)
166
+ # @option options [Boolean] :max_speed Enable pipeline mode (default: false)
167
+ # @return [Hash] Result with :total, :success, :failed, :errors
168
+ #
169
+ # @example Batch resize
170
+ # files = Dir["photos/*.jpg"]
171
+ # result = FastResize.batch_resize(files, "thumbnails/", width: 300)
172
+ # # => { total: 100, success: 100, failed: 0, errors: [] }
173
+ def self.batch_resize(input_paths, output_dir, options = {})
174
+ raise Error, "Input paths cannot be empty" if input_paths.nil? || input_paths.empty?
175
+ raise Error, "Output directory cannot be empty" if output_dir.nil? || output_dir.empty?
176
+
177
+ # Create output directory if it doesn't exist
178
+ require 'fileutils'
179
+ FileUtils.mkdir_p(output_dir)
180
+
181
+ cli_path = Platform.find_binary
182
+
183
+ # Create temp file with input paths for batch mode
184
+ require 'tempfile'
185
+ temp_dir = Tempfile.new(['fastresize_batch', ''])
186
+ temp_dir_path = temp_dir.path
187
+ temp_dir.close
188
+ temp_dir.unlink
189
+
190
+ # Copy input files to temp directory (simulate batch input)
191
+ # Actually, use the batch command directly
192
+ args = ['batch']
193
+ args += build_batch_args(options)
194
+
195
+ # Get the input directory from the first file
196
+ input_dir = File.dirname(input_paths.first)
197
+ args << input_dir
198
+ args << output_dir
199
+
200
+ output = `#{cli_path} #{args.map { |a| "'#{a}'" }.join(' ')} 2>&1`
201
+ success = $?.success?
202
+
203
+ # Parse output
204
+ result = {
205
+ total: input_paths.length,
206
+ success: 0,
207
+ failed: 0,
208
+ errors: []
209
+ }
210
+
211
+ if success
212
+ # Parse success/failed from output
213
+ if output =~ /(\d+) success/
214
+ result[:success] = $1.to_i
215
+ end
216
+ if output =~ /(\d+) failed/
217
+ result[:failed] = $1.to_i
218
+ end
219
+ else
220
+ result[:failed] = input_paths.length
221
+ result[:errors] << output.strip
222
+ end
223
+
224
+ result
225
+ end
226
+
227
+ # Batch resize with custom options per image
228
+ #
229
+ # @param items [Array<Hash>] Array of items, each with :input, :output, and resize options
230
+ # @param options [Hash] Batch options (:threads, :stop_on_error, :max_speed)
231
+ # @return [Hash] Result with :total, :success, :failed, :errors
232
+ #
233
+ # @example Custom batch resize
234
+ # items = [
235
+ # { input: "photo1.jpg", output: "thumb1.jpg", width: 300 },
236
+ # { input: "photo2.jpg", output: "thumb2.jpg", width: 400, quality: 90 }
237
+ # ]
238
+ # result = FastResize.batch_resize_custom(items)
239
+ def self.batch_resize_custom(items, options = {})
240
+ raise Error, "Items cannot be empty" if items.nil? || items.empty?
241
+
242
+ result = {
243
+ total: items.length,
244
+ success: 0,
245
+ failed: 0,
246
+ errors: []
247
+ }
248
+
249
+ items.each do |item|
250
+ input = item[:input]
251
+ output = item[:output]
252
+ item_options = item.reject { |k, _| [:input, :output].include?(k) }
253
+
254
+ begin
255
+ resize(input, output, item_options)
256
+ result[:success] += 1
257
+ rescue Error => e
258
+ result[:failed] += 1
259
+ result[:errors] << "#{input}: #{e.message}"
260
+ break if options[:stop_on_error]
261
+ end
262
+ end
263
+
264
+ result
265
+ end
266
+
267
+ private
268
+
269
+ # Build CLI arguments for single resize
270
+ def self.build_resize_args(input_path, output_path, options)
271
+ args = [input_path, output_path]
272
+
273
+ if options[:scale]
274
+ # Scale factor: 0.5 = 50%, 2.0 = 200%
275
+ args += ['-s', options[:scale].to_s]
276
+ elsif options[:width] && options[:height]
277
+ args += ['-w', options[:width].to_s]
278
+ args += ['-h', options[:height].to_s]
279
+ elsif options[:width]
280
+ args += ['-w', options[:width].to_s]
281
+ elsif options[:height]
282
+ args += ['-h', options[:height].to_s]
11
283
  end
284
+
285
+ args += ['-q', options[:quality].to_s] if options[:quality]
286
+
287
+ if options[:filter]
288
+ filter_name = options[:filter].to_s.gsub('_', '-')
289
+ args += ['-f', filter_name]
290
+ end
291
+
292
+ args << '--no-aspect-ratio' if options[:keep_aspect_ratio] == false
293
+ args << '-o' if options[:overwrite]
294
+
295
+ args
296
+ end
297
+
298
+ # Build CLI arguments for batch operations
299
+ def self.build_batch_args(options)
300
+ args = []
301
+
302
+ if options[:scale]
303
+ # Scale factor: 0.5 = 50%, 2.0 = 200%
304
+ args += ['-s', options[:scale].to_s]
305
+ elsif options[:width] && options[:height]
306
+ args += ['-w', options[:width].to_s]
307
+ args += ['-h', options[:height].to_s]
308
+ elsif options[:width]
309
+ args += ['-w', options[:width].to_s]
310
+ elsif options[:height]
311
+ args += ['-h', options[:height].to_s]
312
+ end
313
+
314
+ args += ['-q', options[:quality].to_s] if options[:quality]
315
+
316
+ if options[:filter]
317
+ filter_name = options[:filter].to_s.gsub('_', '-')
318
+ args += ['-f', filter_name]
319
+ end
320
+
321
+ args << '--no-aspect-ratio' if options[:keep_aspect_ratio] == false
322
+ args += ['-t', options[:threads].to_s] if options[:threads]
323
+ args << '--stop-on-error' if options[:stop_on_error]
324
+ args << '--max-speed' if options[:max_speed]
325
+
326
+ args
12
327
  end
13
328
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fast_resize
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tran Huu Canh (0xTh3OKrypt)
@@ -75,12 +75,10 @@ extensions:
75
75
  - bindings/ruby/ext/fastresize/extconf.rb
76
76
  extra_rdoc_files: []
77
77
  files:
78
- - CMakeLists.txt
79
78
  - LICENSE
80
79
  - README.md
81
80
  - VERSION
82
81
  - bindings/ruby/ext/fastresize/extconf.rb
83
- - bindings/ruby/ext/fastresize/fastresize_ext.cpp
84
82
  - bindings/ruby/lib/fastresize.rb
85
83
  - bindings/ruby/lib/fastresize/platform.rb
86
84
  - bindings/ruby/lib/fastresize/version.rb
@@ -112,30 +110,14 @@ files:
112
110
  - bindings/ruby/prebuilt/macos-x86_64/include/stb_image_resize2.h
113
111
  - bindings/ruby/prebuilt/macos-x86_64/include/stb_image_write.h
114
112
  - bindings/ruby/prebuilt/macos-x86_64/lib/libfastresize.a
115
- - include/fastresize.h
116
- - include/stb_image.h
117
- - include/stb_image_resize2.h
118
- - include/stb_image_write.h
119
- - src/cli.cpp
120
- - src/decoder.cpp
121
- - src/encoder.cpp
122
- - src/fastresize.cpp
123
- - src/internal.h
124
- - src/pipeline.cpp
125
- - src/pipeline.h
126
- - src/resizer.cpp
127
- - src/simd_resize.cpp
128
- - src/simd_resize.h
129
- - src/simd_utils.h
130
- - src/thread_pool.cpp
131
113
  homepage: https://github.com/tranhuucanh/fast_resize
132
114
  licenses:
133
115
  - BSD-3-Clause
134
116
  metadata:
135
- bug_tracker_uri: https://github.com/tranhuucanh/fast_resize/issues
136
- changelog_uri: https://github.com/tranhuucanh/fast_resize/blob/master/CHANGELOG.md
137
- documentation_uri: https://github.com/tranhuucanh/fast_resize
117
+ homepage_uri: https://github.com/tranhuucanh/fast_resize
138
118
  source_code_uri: https://github.com/tranhuucanh/fast_resize
119
+ changelog_uri: https://github.com/tranhuucanh/fast_resize/blob/master/CHANGELOG.md
120
+ bug_tracker_uri: https://github.com/tranhuucanh/fast_resize/issues
139
121
  post_install_message:
140
122
  rdoc_options: []
141
123
  require_paths: