fast_resize 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CMakeLists.txt +308 -0
- data/LICENSE +29 -0
- data/README.md +426 -0
- data/VERSION +1 -0
- data/bindings/ruby/ext/fastresize/extconf.rb +152 -0
- data/bindings/ruby/ext/fastresize/fastresize_ext.cpp +377 -0
- data/bindings/ruby/lib/fastresize/platform.rb +106 -0
- data/bindings/ruby/lib/fastresize/version.rb +5 -0
- data/bindings/ruby/lib/fastresize.rb +13 -0
- data/bindings/ruby/prebuilt/linux-aarch64/bin/fast_resize +0 -0
- data/bindings/ruby/prebuilt/linux-aarch64/include/fastresize.h +189 -0
- data/bindings/ruby/prebuilt/linux-aarch64/include/stb_image.h +7988 -0
- data/bindings/ruby/prebuilt/linux-aarch64/include/stb_image_resize2.h +10651 -0
- data/bindings/ruby/prebuilt/linux-aarch64/include/stb_image_write.h +1724 -0
- data/bindings/ruby/prebuilt/linux-aarch64/lib/libfastresize.a +0 -0
- data/bindings/ruby/prebuilt/linux-aarch64.tar.gz +0 -0
- data/bindings/ruby/prebuilt/linux-x86_64/bin/fast_resize +0 -0
- data/bindings/ruby/prebuilt/linux-x86_64/include/fastresize.h +189 -0
- data/bindings/ruby/prebuilt/linux-x86_64/include/stb_image.h +7988 -0
- data/bindings/ruby/prebuilt/linux-x86_64/include/stb_image_resize2.h +10651 -0
- data/bindings/ruby/prebuilt/linux-x86_64/include/stb_image_write.h +1724 -0
- data/bindings/ruby/prebuilt/linux-x86_64/lib/libfastresize.a +0 -0
- data/bindings/ruby/prebuilt/linux-x86_64.tar.gz +0 -0
- data/bindings/ruby/prebuilt/macos-arm64/bin/fast_resize +0 -0
- data/bindings/ruby/prebuilt/macos-arm64/include/fastresize.h +189 -0
- data/bindings/ruby/prebuilt/macos-arm64/include/stb_image.h +7988 -0
- data/bindings/ruby/prebuilt/macos-arm64/include/stb_image_resize2.h +10651 -0
- data/bindings/ruby/prebuilt/macos-arm64/include/stb_image_write.h +1724 -0
- data/bindings/ruby/prebuilt/macos-arm64/lib/libfastresize.a +0 -0
- data/bindings/ruby/prebuilt/macos-arm64.tar.gz +0 -0
- data/bindings/ruby/prebuilt/macos-x86_64/bin/fast_resize +0 -0
- data/bindings/ruby/prebuilt/macos-x86_64/include/fastresize.h +189 -0
- data/bindings/ruby/prebuilt/macos-x86_64/include/stb_image.h +7988 -0
- data/bindings/ruby/prebuilt/macos-x86_64/include/stb_image_resize2.h +10651 -0
- data/bindings/ruby/prebuilt/macos-x86_64/include/stb_image_write.h +1724 -0
- data/bindings/ruby/prebuilt/macos-x86_64/lib/libfastresize.a +0 -0
- data/bindings/ruby/prebuilt/macos-x86_64.tar.gz +0 -0
- data/include/fastresize.h +189 -0
- data/include/stb_image.h +7988 -0
- data/include/stb_image_resize2.h +10651 -0
- data/include/stb_image_write.h +1724 -0
- data/src/cli.cpp +540 -0
- data/src/decoder.cpp +647 -0
- data/src/encoder.cpp +376 -0
- data/src/fastresize.cpp +445 -0
- data/src/internal.h +108 -0
- data/src/pipeline.cpp +284 -0
- data/src/pipeline.h +175 -0
- data/src/resizer.cpp +199 -0
- data/src/simd_resize.cpp +384 -0
- data/src/simd_resize.h +72 -0
- data/src/simd_utils.h +127 -0
- data/src/thread_pool.cpp +232 -0
- metadata +158 -0
data/src/pipeline.cpp
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* FastResize - The Fastest Image Resizing Library On The Planet
|
|
3
|
+
* Copyright (C) 2025 Tran Huu Canh (0xTh3OKrypt) and FastResize Contributors
|
|
4
|
+
*
|
|
5
|
+
* Resize 1,000 images in 2 seconds. Up to 2.9x faster than libvips,
|
|
6
|
+
* 3.1x faster than imageflow. Uses 3-4x less RAM than alternatives.
|
|
7
|
+
*
|
|
8
|
+
* Author: Tran Huu Canh (0xTh3OKrypt)
|
|
9
|
+
* Email: tranhuucanh39@gmail.com
|
|
10
|
+
* Homepage: https://github.com/tranhuucanh/fast_resize
|
|
11
|
+
*
|
|
12
|
+
* BSD 3-Clause License
|
|
13
|
+
*
|
|
14
|
+
* Redistribution and use in source and binary forms, with or without
|
|
15
|
+
* modification, are permitted provided that the following conditions are met:
|
|
16
|
+
*
|
|
17
|
+
* 1. Redistributions of source code must retain the above copyright notice,
|
|
18
|
+
* this list of conditions and the following disclaimer.
|
|
19
|
+
*
|
|
20
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
21
|
+
* this list of conditions and the following disclaimer in the documentation
|
|
22
|
+
* and/or other materials provided with the distribution.
|
|
23
|
+
*
|
|
24
|
+
* 3. Neither the name of the copyright holder nor the names of its
|
|
25
|
+
* contributors may be used to endorse or promote products derived from
|
|
26
|
+
* this software without specific prior written permission.
|
|
27
|
+
*
|
|
28
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
29
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
30
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
31
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
32
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
33
|
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
34
|
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
35
|
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
36
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
37
|
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
38
|
+
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
#include "pipeline.h"
|
|
42
|
+
#include <thread>
|
|
43
|
+
|
|
44
|
+
namespace fastresize {
|
|
45
|
+
namespace internal {
|
|
46
|
+
|
|
47
|
+
PipelineProcessor::PipelineProcessor(
|
|
48
|
+
size_t decode_threads,
|
|
49
|
+
size_t resize_threads,
|
|
50
|
+
size_t encode_threads,
|
|
51
|
+
size_t queue_capacity
|
|
52
|
+
)
|
|
53
|
+
: decode_pool_(nullptr)
|
|
54
|
+
, resize_pool_(nullptr)
|
|
55
|
+
, encode_pool_(nullptr)
|
|
56
|
+
, decode_queue_(queue_capacity)
|
|
57
|
+
, resize_queue_(queue_capacity)
|
|
58
|
+
, success_count_(0)
|
|
59
|
+
, failed_count_(0)
|
|
60
|
+
{
|
|
61
|
+
decode_pool_ = create_thread_pool(decode_threads);
|
|
62
|
+
resize_pool_ = create_thread_pool(resize_threads);
|
|
63
|
+
encode_pool_ = create_thread_pool(encode_threads);
|
|
64
|
+
|
|
65
|
+
for (size_t i = 0; i < encode_threads; ++i) {
|
|
66
|
+
encode_buffer_pools_.push_back(create_buffer_pool());
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
PipelineProcessor::~PipelineProcessor() {
|
|
71
|
+
if (decode_pool_) destroy_thread_pool(decode_pool_);
|
|
72
|
+
if (resize_pool_) destroy_thread_pool(resize_pool_);
|
|
73
|
+
if (encode_pool_) destroy_thread_pool(encode_pool_);
|
|
74
|
+
|
|
75
|
+
for (BufferPool* pool : encode_buffer_pools_) {
|
|
76
|
+
destroy_buffer_pool(pool);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
void PipelineProcessor::decode_stage(const std::vector<BatchItem>& items) {
|
|
81
|
+
for (size_t i = 0; i < items.size(); ++i) {
|
|
82
|
+
thread_pool_enqueue(decode_pool_, [this, &items, i]() {
|
|
83
|
+
const auto& item = items[i];
|
|
84
|
+
|
|
85
|
+
DecodeResult result;
|
|
86
|
+
result.task_id = i;
|
|
87
|
+
result.output_path = item.output_path;
|
|
88
|
+
result.options = item.options;
|
|
89
|
+
result.success = false;
|
|
90
|
+
|
|
91
|
+
ImageFormat fmt = detect_format(item.input_path);
|
|
92
|
+
if (fmt == FORMAT_UNKNOWN) {
|
|
93
|
+
result.error_message = "Unknown format: " + item.input_path;
|
|
94
|
+
decode_queue_.push(std::move(result));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
result.image = decode_image(item.input_path, fmt);
|
|
99
|
+
if (result.image.pixels == nullptr) {
|
|
100
|
+
result.error_message = "Decode failed: " + item.input_path;
|
|
101
|
+
decode_queue_.push(std::move(result));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
result.success = true;
|
|
106
|
+
decode_queue_.push(std::move(result));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
thread_pool_wait(decode_pool_);
|
|
111
|
+
decode_queue_.set_done();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
void PipelineProcessor::resize_stage() {
|
|
115
|
+
std::vector<std::thread> workers;
|
|
116
|
+
for (size_t i = 0; i < 8; ++i) {
|
|
117
|
+
workers.emplace_back([this]() {
|
|
118
|
+
DecodeResult decode_result;
|
|
119
|
+
|
|
120
|
+
while (decode_queue_.pop(decode_result)) {
|
|
121
|
+
ResizeResult resize_result;
|
|
122
|
+
resize_result.task_id = decode_result.task_id;
|
|
123
|
+
resize_result.output_path = decode_result.output_path;
|
|
124
|
+
resize_result.options = decode_result.options;
|
|
125
|
+
resize_result.success = false;
|
|
126
|
+
|
|
127
|
+
if (!decode_result.success) {
|
|
128
|
+
resize_result.error_message = decode_result.error_message;
|
|
129
|
+
resize_result.pixels = nullptr;
|
|
130
|
+
resize_result.width = 0;
|
|
131
|
+
resize_result.height = 0;
|
|
132
|
+
resize_result.channels = 0;
|
|
133
|
+
resize_queue_.push(std::move(resize_result));
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
int out_w, out_h;
|
|
138
|
+
calculate_dimensions(
|
|
139
|
+
decode_result.image.width,
|
|
140
|
+
decode_result.image.height,
|
|
141
|
+
decode_result.options,
|
|
142
|
+
out_w, out_h
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
unsigned char* resized_pixels = nullptr;
|
|
146
|
+
bool resize_ok = resize_image(
|
|
147
|
+
decode_result.image.pixels,
|
|
148
|
+
decode_result.image.width,
|
|
149
|
+
decode_result.image.height,
|
|
150
|
+
decode_result.image.channels,
|
|
151
|
+
&resized_pixels,
|
|
152
|
+
out_w, out_h,
|
|
153
|
+
decode_result.options
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
free_image_data(decode_result.image);
|
|
157
|
+
|
|
158
|
+
if (!resize_ok || resized_pixels == nullptr) {
|
|
159
|
+
resize_result.error_message = "Resize failed";
|
|
160
|
+
resize_result.pixels = nullptr;
|
|
161
|
+
resize_result.width = 0;
|
|
162
|
+
resize_result.height = 0;
|
|
163
|
+
resize_result.channels = 0;
|
|
164
|
+
resize_queue_.push(std::move(resize_result));
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
resize_result.pixels = resized_pixels;
|
|
169
|
+
resize_result.width = out_w;
|
|
170
|
+
resize_result.height = out_h;
|
|
171
|
+
resize_result.channels = decode_result.image.channels;
|
|
172
|
+
resize_result.success = true;
|
|
173
|
+
resize_queue_.push(std::move(resize_result));
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for (auto& worker : workers) {
|
|
179
|
+
worker.join();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
resize_queue_.set_done();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
void PipelineProcessor::encode_stage() {
|
|
186
|
+
std::vector<std::thread> workers;
|
|
187
|
+
for (size_t i = 0; i < encode_buffer_pools_.size(); ++i) {
|
|
188
|
+
workers.emplace_back([this, i]() {
|
|
189
|
+
BufferPool* buffer_pool = encode_buffer_pools_[i];
|
|
190
|
+
ResizeResult resize_result;
|
|
191
|
+
|
|
192
|
+
while (resize_queue_.pop(resize_result)) {
|
|
193
|
+
if (!resize_result.success) {
|
|
194
|
+
failed_count_.fetch_add(1);
|
|
195
|
+
if (!resize_result.error_message.empty()) {
|
|
196
|
+
std::lock_guard<std::mutex> lock(errors_mutex_);
|
|
197
|
+
errors_.push_back(resize_result.error_message);
|
|
198
|
+
}
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
ImageFormat out_fmt = FORMAT_UNKNOWN;
|
|
203
|
+
size_t dot_pos = resize_result.output_path.find_last_of('.');
|
|
204
|
+
if (dot_pos != std::string::npos) {
|
|
205
|
+
std::string ext = resize_result.output_path.substr(dot_pos + 1);
|
|
206
|
+
for (char& c : ext) {
|
|
207
|
+
if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a';
|
|
208
|
+
}
|
|
209
|
+
out_fmt = string_to_format(ext);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (out_fmt == FORMAT_UNKNOWN) {
|
|
213
|
+
out_fmt = FORMAT_JPEG;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (resize_result.pixels == nullptr || resize_result.width <= 0 ||
|
|
217
|
+
resize_result.height <= 0 || resize_result.channels <= 0) {
|
|
218
|
+
failed_count_.fetch_add(1);
|
|
219
|
+
std::lock_guard<std::mutex> lock(errors_mutex_);
|
|
220
|
+
errors_.push_back("Invalid resize data for: " + resize_result.output_path);
|
|
221
|
+
if (resize_result.pixels) delete[] resize_result.pixels;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
ImageData img_data;
|
|
226
|
+
img_data.pixels = resize_result.pixels;
|
|
227
|
+
img_data.width = resize_result.width;
|
|
228
|
+
img_data.height = resize_result.height;
|
|
229
|
+
img_data.channels = resize_result.channels;
|
|
230
|
+
|
|
231
|
+
bool encode_ok = encode_image(
|
|
232
|
+
resize_result.output_path,
|
|
233
|
+
img_data,
|
|
234
|
+
out_fmt,
|
|
235
|
+
resize_result.options.quality,
|
|
236
|
+
buffer_pool
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
delete[] resize_result.pixels;
|
|
240
|
+
|
|
241
|
+
if (encode_ok) {
|
|
242
|
+
success_count_.fetch_add(1);
|
|
243
|
+
} else {
|
|
244
|
+
failed_count_.fetch_add(1);
|
|
245
|
+
std::lock_guard<std::mutex> lock(errors_mutex_);
|
|
246
|
+
char buf[512];
|
|
247
|
+
snprintf(buf, sizeof(buf), "Encode failed: %s (fmt=%d, %dx%d, %dch)",
|
|
248
|
+
resize_result.output_path.c_str(), (int)out_fmt,
|
|
249
|
+
resize_result.width, resize_result.height, resize_result.channels);
|
|
250
|
+
errors_.push_back(buf);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
for (auto& worker : workers) {
|
|
257
|
+
worker.join();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
BatchResult PipelineProcessor::process_batch(const std::vector<BatchItem>& items) {
|
|
262
|
+
success_count_ = 0;
|
|
263
|
+
failed_count_ = 0;
|
|
264
|
+
errors_.clear();
|
|
265
|
+
|
|
266
|
+
std::thread decode_thread([this, &items]() { decode_stage(items); });
|
|
267
|
+
std::thread resize_thread([this]() { resize_stage(); });
|
|
268
|
+
std::thread encode_thread([this]() { encode_stage(); });
|
|
269
|
+
|
|
270
|
+
decode_thread.join();
|
|
271
|
+
resize_thread.join();
|
|
272
|
+
encode_thread.join();
|
|
273
|
+
|
|
274
|
+
BatchResult result;
|
|
275
|
+
result.total = items.size();
|
|
276
|
+
result.success = success_count_.load();
|
|
277
|
+
result.failed = failed_count_.load();
|
|
278
|
+
result.errors = errors_;
|
|
279
|
+
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
}
|
|
284
|
+
}
|
data/src/pipeline.h
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* FastResize - The Fastest Image Resizing Library On The Planet
|
|
3
|
+
* Copyright (C) 2025 Tran Huu Canh (0xTh3OKrypt) and FastResize Contributors
|
|
4
|
+
*
|
|
5
|
+
* Resize 1,000 images in 2 seconds. Up to 2.9x faster than libvips,
|
|
6
|
+
* 3.1x faster than imageflow. Uses 3-4x less RAM than alternatives.
|
|
7
|
+
*
|
|
8
|
+
* Author: Tran Huu Canh (0xTh3OKrypt)
|
|
9
|
+
* Email: tranhuucanh39@gmail.com
|
|
10
|
+
* Homepage: https://github.com/tranhuucanh/fast_resize
|
|
11
|
+
*
|
|
12
|
+
* BSD 3-Clause License
|
|
13
|
+
*
|
|
14
|
+
* Redistribution and use in source and binary forms, with or without
|
|
15
|
+
* modification, are permitted provided that the following conditions are met:
|
|
16
|
+
*
|
|
17
|
+
* 1. Redistributions of source code must retain the above copyright notice,
|
|
18
|
+
* this list of conditions and the following disclaimer.
|
|
19
|
+
*
|
|
20
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
21
|
+
* this list of conditions and the following disclaimer in the documentation
|
|
22
|
+
* and/or other materials provided with the distribution.
|
|
23
|
+
*
|
|
24
|
+
* 3. Neither the name of the copyright holder nor the names of its
|
|
25
|
+
* contributors may be used to endorse or promote products derived from
|
|
26
|
+
* this software without specific prior written permission.
|
|
27
|
+
*
|
|
28
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
29
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
30
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
31
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
32
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
33
|
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
34
|
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
35
|
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
36
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
37
|
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
38
|
+
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
#pragma once
|
|
42
|
+
|
|
43
|
+
#include "internal.h"
|
|
44
|
+
#include <queue>
|
|
45
|
+
#include <mutex>
|
|
46
|
+
#include <condition_variable>
|
|
47
|
+
#include <thread>
|
|
48
|
+
#include <atomic>
|
|
49
|
+
|
|
50
|
+
namespace fastresize {
|
|
51
|
+
namespace internal {
|
|
52
|
+
|
|
53
|
+
template<typename T>
|
|
54
|
+
class BoundedQueue {
|
|
55
|
+
public:
|
|
56
|
+
BoundedQueue(size_t capacity)
|
|
57
|
+
: capacity_(capacity)
|
|
58
|
+
, done_(false)
|
|
59
|
+
{}
|
|
60
|
+
|
|
61
|
+
bool push(T&& item) {
|
|
62
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
63
|
+
|
|
64
|
+
cv_not_full_.wait(lock, [this]() {
|
|
65
|
+
return queue_.size() < capacity_ || done_;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (done_) return false;
|
|
69
|
+
|
|
70
|
+
queue_.push(std::move(item));
|
|
71
|
+
lock.unlock();
|
|
72
|
+
cv_not_empty_.notify_one();
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
bool pop(T& item) {
|
|
77
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
78
|
+
|
|
79
|
+
cv_not_empty_.wait(lock, [this]() {
|
|
80
|
+
return !queue_.empty() || done_;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (queue_.empty() && done_) return false;
|
|
84
|
+
|
|
85
|
+
item = std::move(queue_.front());
|
|
86
|
+
queue_.pop();
|
|
87
|
+
lock.unlock();
|
|
88
|
+
cv_not_full_.notify_one();
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
void set_done() {
|
|
93
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
94
|
+
done_ = true;
|
|
95
|
+
lock.unlock();
|
|
96
|
+
cv_not_empty_.notify_all();
|
|
97
|
+
cv_not_full_.notify_all();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
size_t size() const {
|
|
101
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
102
|
+
return queue_.size();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private:
|
|
106
|
+
size_t capacity_;
|
|
107
|
+
std::queue<T> queue_;
|
|
108
|
+
mutable std::mutex mutex_;
|
|
109
|
+
std::condition_variable cv_not_empty_;
|
|
110
|
+
std::condition_variable cv_not_full_;
|
|
111
|
+
bool done_;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
struct DecodeTask {
|
|
115
|
+
std::string input_path;
|
|
116
|
+
std::string output_path;
|
|
117
|
+
ResizeOptions options;
|
|
118
|
+
int task_id;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
struct DecodeResult {
|
|
122
|
+
ImageData image;
|
|
123
|
+
std::string output_path;
|
|
124
|
+
ResizeOptions options;
|
|
125
|
+
int task_id;
|
|
126
|
+
bool success;
|
|
127
|
+
std::string error_message;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
struct ResizeResult {
|
|
131
|
+
unsigned char* pixels;
|
|
132
|
+
int width;
|
|
133
|
+
int height;
|
|
134
|
+
int channels;
|
|
135
|
+
std::string output_path;
|
|
136
|
+
ResizeOptions options;
|
|
137
|
+
int task_id;
|
|
138
|
+
bool success;
|
|
139
|
+
std::string error_message;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
class PipelineProcessor {
|
|
143
|
+
public:
|
|
144
|
+
PipelineProcessor(
|
|
145
|
+
size_t decode_threads = 4,
|
|
146
|
+
size_t resize_threads = 8,
|
|
147
|
+
size_t encode_threads = 4,
|
|
148
|
+
size_t queue_capacity = 32
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
~PipelineProcessor();
|
|
152
|
+
BatchResult process_batch(const std::vector<BatchItem>& items);
|
|
153
|
+
|
|
154
|
+
private:
|
|
155
|
+
ThreadPool* decode_pool_;
|
|
156
|
+
ThreadPool* resize_pool_;
|
|
157
|
+
ThreadPool* encode_pool_;
|
|
158
|
+
|
|
159
|
+
std::vector<BufferPool*> encode_buffer_pools_;
|
|
160
|
+
|
|
161
|
+
BoundedQueue<DecodeResult> decode_queue_;
|
|
162
|
+
BoundedQueue<ResizeResult> resize_queue_;
|
|
163
|
+
|
|
164
|
+
std::atomic<int> success_count_;
|
|
165
|
+
std::atomic<int> failed_count_;
|
|
166
|
+
std::mutex errors_mutex_;
|
|
167
|
+
std::vector<std::string> errors_;
|
|
168
|
+
|
|
169
|
+
void decode_stage(const std::vector<BatchItem>& items);
|
|
170
|
+
void resize_stage();
|
|
171
|
+
void encode_stage();
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
}
|
|
175
|
+
}
|
data/src/resizer.cpp
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* FastResize - The Fastest Image Resizing Library On The Planet
|
|
3
|
+
* Copyright (C) 2025 Tran Huu Canh (0xTh3OKrypt) and FastResize Contributors
|
|
4
|
+
*
|
|
5
|
+
* Resize 1,000 images in 2 seconds. Up to 2.9x faster than libvips,
|
|
6
|
+
* 3.1x faster than imageflow. Uses 3-4x less RAM than alternatives.
|
|
7
|
+
*
|
|
8
|
+
* Author: Tran Huu Canh (0xTh3OKrypt)
|
|
9
|
+
* Email: tranhuucanh39@gmail.com
|
|
10
|
+
* Homepage: https://github.com/tranhuucanh/fast_resize
|
|
11
|
+
*
|
|
12
|
+
* BSD 3-Clause License
|
|
13
|
+
*
|
|
14
|
+
* Redistribution and use in source and binary forms, with or without
|
|
15
|
+
* modification, are permitted provided that the following conditions are met:
|
|
16
|
+
*
|
|
17
|
+
* 1. Redistributions of source code must retain the above copyright notice,
|
|
18
|
+
* this list of conditions and the following disclaimer.
|
|
19
|
+
*
|
|
20
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
21
|
+
* this list of conditions and the following disclaimer in the documentation
|
|
22
|
+
* and/or other materials provided with the distribution.
|
|
23
|
+
*
|
|
24
|
+
* 3. Neither the name of the copyright holder nor the names of its
|
|
25
|
+
* contributors may be used to endorse or promote products derived from
|
|
26
|
+
* this software without specific prior written permission.
|
|
27
|
+
*
|
|
28
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
29
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
30
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
31
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
32
|
+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
33
|
+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
34
|
+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
35
|
+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
36
|
+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
37
|
+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
38
|
+
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
#include "internal.h"
|
|
42
|
+
#include "simd_resize.h"
|
|
43
|
+
#include <cmath>
|
|
44
|
+
|
|
45
|
+
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
|
46
|
+
#include "stb_image_resize2.h"
|
|
47
|
+
|
|
48
|
+
namespace fastresize {
|
|
49
|
+
namespace internal {
|
|
50
|
+
|
|
51
|
+
void calculate_dimensions(
|
|
52
|
+
int in_w, int in_h,
|
|
53
|
+
const ResizeOptions& opts,
|
|
54
|
+
int& out_w, int& out_h
|
|
55
|
+
) {
|
|
56
|
+
switch (opts.mode) {
|
|
57
|
+
case ResizeOptions::SCALE_PERCENT:
|
|
58
|
+
out_w = static_cast<int>(std::round(in_w * opts.scale_percent));
|
|
59
|
+
out_h = static_cast<int>(std::round(in_h * opts.scale_percent));
|
|
60
|
+
break;
|
|
61
|
+
|
|
62
|
+
case ResizeOptions::FIT_WIDTH:
|
|
63
|
+
out_w = opts.target_width;
|
|
64
|
+
if (opts.keep_aspect_ratio) {
|
|
65
|
+
float ratio = static_cast<float>(out_w) / in_w;
|
|
66
|
+
out_h = static_cast<int>(std::round(in_h * ratio));
|
|
67
|
+
} else {
|
|
68
|
+
out_h = in_h;
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
case ResizeOptions::FIT_HEIGHT:
|
|
73
|
+
out_h = opts.target_height;
|
|
74
|
+
if (opts.keep_aspect_ratio) {
|
|
75
|
+
float ratio = static_cast<float>(out_h) / in_h;
|
|
76
|
+
out_w = static_cast<int>(std::round(in_w * ratio));
|
|
77
|
+
} else {
|
|
78
|
+
out_w = in_w;
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
case ResizeOptions::EXACT_SIZE:
|
|
83
|
+
out_w = opts.target_width;
|
|
84
|
+
out_h = opts.target_height;
|
|
85
|
+
if (opts.keep_aspect_ratio) {
|
|
86
|
+
float ratio_w = static_cast<float>(out_w) / in_w;
|
|
87
|
+
float ratio_h = static_cast<float>(out_h) / in_h;
|
|
88
|
+
float ratio = std::min(ratio_w, ratio_h);
|
|
89
|
+
out_w = static_cast<int>(std::round(in_w * ratio));
|
|
90
|
+
out_h = static_cast<int>(std::round(in_h * ratio));
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (out_w < 1) out_w = 1;
|
|
96
|
+
if (out_h < 1) out_h = 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
bool resize_image(
|
|
100
|
+
const unsigned char* input_pixels,
|
|
101
|
+
int input_w, int input_h, int channels,
|
|
102
|
+
unsigned char** output_pixels,
|
|
103
|
+
int output_w, int output_h,
|
|
104
|
+
const ResizeOptions& opts
|
|
105
|
+
) {
|
|
106
|
+
if (!input_pixels || input_w <= 0 || input_h <= 0 ||
|
|
107
|
+
output_w <= 0 || output_h <= 0 || channels <= 0) {
|
|
108
|
+
set_last_error(RESIZE_ERROR, "Invalid input parameters for resize");
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
size_t output_size = static_cast<size_t>(output_w) * output_h * channels;
|
|
113
|
+
*output_pixels = new unsigned char[output_size];
|
|
114
|
+
|
|
115
|
+
float downscale_ratio_w = static_cast<float>(input_w) / output_w;
|
|
116
|
+
float downscale_ratio_h = static_cast<float>(input_h) / output_h;
|
|
117
|
+
float max_downscale = (downscale_ratio_w > downscale_ratio_h) ? downscale_ratio_w : downscale_ratio_h;
|
|
118
|
+
|
|
119
|
+
stbir_filter stb_filter;
|
|
120
|
+
|
|
121
|
+
if (max_downscale >= 3.0f && opts.filter == ResizeOptions::MITCHELL) {
|
|
122
|
+
stb_filter = STBIR_FILTER_TRIANGLE;
|
|
123
|
+
} else {
|
|
124
|
+
switch (opts.filter) {
|
|
125
|
+
case ResizeOptions::MITCHELL:
|
|
126
|
+
stb_filter = STBIR_FILTER_MITCHELL;
|
|
127
|
+
break;
|
|
128
|
+
case ResizeOptions::CATMULL_ROM:
|
|
129
|
+
stb_filter = STBIR_FILTER_CATMULLROM;
|
|
130
|
+
break;
|
|
131
|
+
case ResizeOptions::BOX:
|
|
132
|
+
stb_filter = STBIR_FILTER_BOX;
|
|
133
|
+
break;
|
|
134
|
+
case ResizeOptions::TRIANGLE:
|
|
135
|
+
stb_filter = STBIR_FILTER_TRIANGLE;
|
|
136
|
+
break;
|
|
137
|
+
default:
|
|
138
|
+
stb_filter = STBIR_FILTER_MITCHELL;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
bool use_simd = (output_w < input_w && output_h < input_h) &&
|
|
143
|
+
(stb_filter == STBIR_FILTER_TRIANGLE ||
|
|
144
|
+
(stb_filter == STBIR_FILTER_MITCHELL && max_downscale < 3.0f)) &&
|
|
145
|
+
(channels == 3 || channels == 4 || channels == 1);
|
|
146
|
+
|
|
147
|
+
if (use_simd) {
|
|
148
|
+
bool simd_ok = simd_resize(
|
|
149
|
+
input_pixels, input_w, input_h, channels,
|
|
150
|
+
*output_pixels, output_w, output_h,
|
|
151
|
+
ResizeQuality::FAST
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (simd_ok) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
stbir_pixel_layout pixel_layout;
|
|
160
|
+
switch (channels) {
|
|
161
|
+
case 1:
|
|
162
|
+
pixel_layout = STBIR_1CHANNEL;
|
|
163
|
+
break;
|
|
164
|
+
case 2:
|
|
165
|
+
pixel_layout = STBIR_2CHANNEL;
|
|
166
|
+
break;
|
|
167
|
+
case 3:
|
|
168
|
+
pixel_layout = STBIR_RGB;
|
|
169
|
+
break;
|
|
170
|
+
case 4:
|
|
171
|
+
pixel_layout = STBIR_RGBA;
|
|
172
|
+
break;
|
|
173
|
+
default:
|
|
174
|
+
delete[] *output_pixels;
|
|
175
|
+
*output_pixels = nullptr;
|
|
176
|
+
set_last_error(RESIZE_ERROR, "Unsupported number of channels");
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
void* result = stbir_resize(
|
|
181
|
+
input_pixels, input_w, input_h, 0,
|
|
182
|
+
*output_pixels, output_w, output_h, 0,
|
|
183
|
+
pixel_layout, STBIR_TYPE_UINT8,
|
|
184
|
+
STBIR_EDGE_CLAMP,
|
|
185
|
+
stb_filter
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
if (!result) {
|
|
189
|
+
delete[] *output_pixels;
|
|
190
|
+
*output_pixels = nullptr;
|
|
191
|
+
set_last_error(RESIZE_ERROR, "stb_image_resize2 failed");
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
}
|
|
199
|
+
}
|