magro 0.4.2 → 0.6.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 +4 -4
- data/CHANGELOG.md +17 -0
- data/LICENSE.txt +1 -1
- data/README.md +2 -2
- data/ext/magro/extconf.rb +7 -31
- data/ext/magro/magro.c +456 -4
- data/ext/magro/magro.h +41 -1
- data/lib/magro/filter.rb +1 -1
- data/lib/magro/io.rb +32 -5
- data/lib/magro/version.rb +1 -1
- data/sig/magro/filter.rbs +20 -0
- data/sig/magro/io.rbs +14 -0
- data/sig/magro/transform.rbs +12 -0
- data/sig/magro.rbs +9 -0
- metadata +9 -18
- data/.coveralls.yml +0 -1
- data/.github/workflows/build.yml +0 -23
- data/.github/workflows/coverage.yml +0 -29
- data/.gitignore +0 -22
- data/.rspec +0 -3
- data/CODE_OF_CONDUCT.md +0 -74
- data/Gemfile +0 -11
- data/Rakefile +0 -14
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/ext/magro/imgrw.c +0 -421
- data/ext/magro/imgrw.h +0 -18
- data/magro.gemspec +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1c544837f96597012ff6cf938a4148b7fcc640237daf460ae26c64f91588767
|
4
|
+
data.tar.gz: '08915ee5d2d4ed4b535963684b160e2ab4707d7823b56578f5574099ca33d7ad'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d5cd134c942319d0ecb83fb39b89cf8a42c3cacc9c4336eaf592b0170a4257e597ce5c62e4fdb35b4641f2446e206c79da4b012c009516547ff7ae464745975
|
7
|
+
data.tar.gz: cd6cdb5059adf3ed20ea6b9625d314f0519c338b0f01b8abb1ea73ecb8c5cb183b0288e1cdbdb22482d8fb71945b26f4761911c606bb81ce62008cb3ee984742
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
# 0.6.0
|
2
|
+
- Reorganize native exntesion codes.
|
3
|
+
- Remove dependent gem's type declaration file from installation files.
|
4
|
+
- Update documentations.
|
5
|
+
- Introduce conventional commits.
|
6
|
+
|
7
|
+
# 0.5.1
|
8
|
+
- Fix version specifier of runtime dependencies.
|
9
|
+
|
10
|
+
# 0.5.0
|
11
|
+
- Add type declaration files.
|
12
|
+
- Refactor some codes.
|
13
|
+
|
14
|
+
# 0.4.3
|
15
|
+
- Fix imread method to read image from URL.
|
16
|
+
- Fix some configulation files.
|
17
|
+
|
1
18
|
# 0.4.2
|
2
19
|
- Add GC guard to reading and writing image methods.
|
3
20
|
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -61,6 +61,6 @@ Or install it yourself as:
|
|
61
61
|
Bug reports and pull requests are welcome on GitHub at https://github.com/yoshoku/magro.
|
62
62
|
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
63
63
|
|
64
|
-
##
|
64
|
+
## License
|
65
65
|
|
66
|
-
|
66
|
+
The gem is available as open source under the terms of the [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause).
|
data/ext/magro/extconf.rb
CHANGED
@@ -8,10 +8,7 @@ $LOAD_PATH.each do |lp|
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
unless have_header('numo/narray.h')
|
12
|
-
puts 'numo/narray.h not found.'
|
13
|
-
exit(1)
|
14
|
-
end
|
11
|
+
abort 'numo/narray.h not found.' unless have_header('numo/narray.h')
|
15
12
|
|
16
13
|
if RUBY_PLATFORM =~ /mswin|cygwin|mingw/
|
17
14
|
$LOAD_PATH.each do |lp|
|
@@ -20,35 +17,14 @@ if RUBY_PLATFORM =~ /mswin|cygwin|mingw/
|
|
20
17
|
break
|
21
18
|
end
|
22
19
|
end
|
23
|
-
unless have_library('narray', 'nary_new')
|
24
|
-
puts 'libnarray.a not found.'
|
25
|
-
exit(1)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
unless have_header('setjmp.h')
|
30
|
-
puts 'setjmp.h not found.'
|
31
|
-
exit(1)
|
32
|
-
end
|
33
|
-
|
34
|
-
unless have_header('png.h')
|
35
|
-
puts 'png.h not found.'
|
36
|
-
exit(1)
|
20
|
+
abort 'libnarray.a not found.' unless have_library('narray', 'nary_new')
|
37
21
|
end
|
38
22
|
|
39
|
-
unless have_header('
|
40
|
-
|
41
|
-
|
42
|
-
end
|
23
|
+
abort 'setjmp.h not found.' unless have_header('setjmp.h')
|
24
|
+
abort 'png.h not found.' unless have_header('png.h')
|
25
|
+
abort 'libpng not found.' unless have_library('png')
|
43
26
|
|
44
|
-
unless
|
45
|
-
|
46
|
-
exit(1)
|
47
|
-
end
|
48
|
-
|
49
|
-
unless have_library('jpeg')
|
50
|
-
puts 'libjpeg not found.'
|
51
|
-
exit(1)
|
52
|
-
end
|
27
|
+
abort 'jpeglib.h not found.' unless have_header('jpeglib.h')
|
28
|
+
abort 'libjpeg not found.' unless have_library('jpeg')
|
53
29
|
|
54
30
|
create_makefile('magro/magro')
|
data/ext/magro/magro.c
CHANGED
@@ -1,10 +1,462 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) 2019-2022 Atsushi Tatsuma
|
3
|
+
* All rights reserved.
|
4
|
+
*
|
5
|
+
* Redistribution and use in source and binary forms, with or without
|
6
|
+
* modification, are permitted provided that the following conditions are met:
|
7
|
+
*
|
8
|
+
* * Redistributions of source code must retain the above copyright notice, this
|
9
|
+
* list of conditions and the following disclaimer.
|
10
|
+
*
|
11
|
+
* * Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
* this list of conditions and the following disclaimer in the documentation
|
13
|
+
* and/or other materials provided with the distribution.
|
14
|
+
*
|
15
|
+
* * Neither the name of the copyright holder nor the names of its
|
16
|
+
* contributors may be used to endorse or promote products derived from
|
17
|
+
* this software without specific prior written permission.
|
18
|
+
*
|
19
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
22
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
23
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
24
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
25
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
26
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
27
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
*/
|
30
|
+
|
1
31
|
#include "magro.h"
|
2
32
|
|
3
33
|
VALUE mMagro;
|
34
|
+
VALUE mMagroIO;
|
4
35
|
|
5
|
-
|
6
|
-
|
7
|
-
|
36
|
+
static VALUE magro_io_read_png(VALUE self, VALUE filename_) {
|
37
|
+
char* filename = StringValuePtr(filename_);
|
38
|
+
FILE* file_ptr = fopen(filename, "rb");
|
39
|
+
unsigned char header[8];
|
40
|
+
png_structp png_ptr;
|
41
|
+
png_infop info_ptr;
|
42
|
+
png_bytep* row_ptr_ptr;
|
43
|
+
png_bytep row_ptr;
|
44
|
+
png_uint_32 width, height;
|
45
|
+
int color_type;
|
46
|
+
int bit_depth;
|
47
|
+
png_uint_32 y;
|
48
|
+
int n_dims = 0;
|
49
|
+
int n_ch;
|
50
|
+
size_t shape[3] = {0};
|
51
|
+
VALUE nary;
|
52
|
+
uint8_t* nary_ptr;
|
53
|
+
|
54
|
+
if (file_ptr == NULL) {
|
55
|
+
rb_raise(rb_eIOError, "Failed to open file '%s'", filename);
|
56
|
+
return Qnil;
|
57
|
+
}
|
58
|
+
|
59
|
+
if (fread(header, 1, 8, file_ptr) < 8) {
|
60
|
+
fclose(file_ptr);
|
61
|
+
rb_raise(rb_eIOError, "Failed to read header info '%s'", filename);
|
62
|
+
return Qnil;
|
63
|
+
}
|
64
|
+
|
65
|
+
if (png_sig_cmp(header, 0, 8)) {
|
66
|
+
fclose(file_ptr);
|
67
|
+
rb_raise(rb_eIOError, "Failed to read header info '%s'", filename);
|
68
|
+
return Qnil;
|
69
|
+
}
|
70
|
+
|
71
|
+
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
72
|
+
if (png_ptr == NULL) {
|
73
|
+
fclose(file_ptr);
|
74
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory.");
|
75
|
+
return Qnil;
|
76
|
+
}
|
77
|
+
info_ptr = png_create_info_struct(png_ptr);
|
78
|
+
if (info_ptr == NULL) {
|
79
|
+
png_destroy_read_struct(&png_ptr, NULL, NULL);
|
80
|
+
fclose(file_ptr);
|
81
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory.");
|
82
|
+
return Qnil;
|
83
|
+
}
|
84
|
+
if (setjmp(png_jmpbuf(png_ptr))) {
|
85
|
+
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
86
|
+
fclose(file_ptr);
|
87
|
+
rb_raise(rb_eIOError, "Error happened while reading file '%s'", filename);
|
88
|
+
return Qnil;
|
89
|
+
}
|
90
|
+
|
91
|
+
png_init_io(png_ptr, file_ptr);
|
92
|
+
png_set_sig_bytes(png_ptr, 8);
|
93
|
+
|
94
|
+
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_16, NULL);
|
95
|
+
row_ptr_ptr = png_get_rows(png_ptr, info_ptr);
|
96
|
+
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
|
97
|
+
|
98
|
+
if (color_type == PNG_COLOR_TYPE_PALETTE) {
|
99
|
+
png_set_palette_to_rgb(png_ptr);
|
100
|
+
png_read_update_info(png_ptr, info_ptr);
|
101
|
+
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
|
102
|
+
}
|
103
|
+
|
104
|
+
switch (color_type) {
|
105
|
+
case PNG_COLOR_TYPE_GRAY:
|
106
|
+
n_ch = 1;
|
107
|
+
n_dims = 2;
|
108
|
+
shape[0] = height;
|
109
|
+
shape[1] = width;
|
110
|
+
break;
|
111
|
+
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
112
|
+
n_ch = 2;
|
113
|
+
n_dims = 3;
|
114
|
+
shape[0] = height;
|
115
|
+
shape[1] = width;
|
116
|
+
shape[2] = 2;
|
117
|
+
break;
|
118
|
+
case PNG_COLOR_TYPE_RGB:
|
119
|
+
n_ch = 3;
|
120
|
+
n_dims = 3;
|
121
|
+
shape[0] = height;
|
122
|
+
shape[1] = width;
|
123
|
+
shape[2] = 3;
|
124
|
+
break;
|
125
|
+
case PNG_COLOR_TYPE_RGB_ALPHA:
|
126
|
+
n_ch = 4;
|
127
|
+
n_dims = 3;
|
128
|
+
shape[0] = height;
|
129
|
+
shape[1] = width;
|
130
|
+
shape[2] = 4;
|
131
|
+
break;
|
132
|
+
default:
|
133
|
+
n_dims = 0;
|
134
|
+
break;
|
135
|
+
}
|
136
|
+
|
137
|
+
if (n_dims == 0) {
|
138
|
+
fclose(file_ptr);
|
139
|
+
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
140
|
+
rb_raise(rb_eIOError, "Unsupported color type of input file '%s'", filename);
|
141
|
+
return Qnil;
|
142
|
+
}
|
143
|
+
|
144
|
+
nary = rb_narray_new(numo_cUInt8, n_dims, shape);
|
145
|
+
nary_ptr = (uint8_t*)na_get_pointer_for_write(nary);
|
146
|
+
|
147
|
+
for (y = 0; y < height; y++) {
|
148
|
+
row_ptr = row_ptr_ptr[y];
|
149
|
+
memcpy(nary_ptr + y * width * n_ch, row_ptr, width * n_ch);
|
150
|
+
}
|
151
|
+
|
152
|
+
fclose(file_ptr);
|
153
|
+
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
154
|
+
|
155
|
+
RB_GC_GUARD(filename_);
|
156
|
+
|
157
|
+
return nary;
|
158
|
+
}
|
159
|
+
|
160
|
+
static VALUE magro_io_save_png(VALUE self, VALUE filename_, VALUE image) {
|
161
|
+
char* filename = StringValuePtr(filename_);
|
162
|
+
FILE* file_ptr = fopen(filename, "wb");
|
163
|
+
png_structp png_ptr;
|
164
|
+
png_infop info_ptr;
|
165
|
+
png_bytep* row_ptr_ptr;
|
166
|
+
png_uint_32 width, height;
|
167
|
+
int color_type;
|
168
|
+
int bit_depth = 8;
|
169
|
+
png_uint_32 y;
|
170
|
+
int n_ch;
|
171
|
+
int n_dims;
|
172
|
+
narray_t* image_nary;
|
173
|
+
uint8_t* image_ptr;
|
174
|
+
|
175
|
+
if (file_ptr == NULL) {
|
176
|
+
rb_raise(rb_eIOError, "Failed to open file '%s'", filename);
|
177
|
+
return Qfalse;
|
178
|
+
}
|
179
|
+
|
180
|
+
if (CLASS_OF(image) != numo_cUInt8) image = rb_funcall(numo_cUInt8, rb_intern("cast"), 1, image);
|
181
|
+
if (!RTEST(nary_check_contiguous(image))) image = nary_dup(image);
|
182
|
+
|
183
|
+
GetNArray(image, image_nary);
|
184
|
+
n_dims = NA_NDIM(image_nary);
|
185
|
+
height = (png_uint_32)NA_SHAPE(image_nary)[0];
|
186
|
+
width = (png_uint_32)NA_SHAPE(image_nary)[1];
|
187
|
+
image_ptr = (uint8_t*)na_get_pointer_for_read(image);
|
188
|
+
n_ch = n_dims == 3 ? (int)NA_SHAPE(image_nary)[2] : 1;
|
189
|
+
|
190
|
+
switch (n_ch) {
|
191
|
+
case 4:
|
192
|
+
color_type = PNG_COLOR_TYPE_RGBA;
|
193
|
+
break;
|
194
|
+
case 3:
|
195
|
+
color_type = PNG_COLOR_TYPE_RGB;
|
196
|
+
break;
|
197
|
+
case 2:
|
198
|
+
color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
|
199
|
+
break;
|
200
|
+
default:
|
201
|
+
color_type = PNG_COLOR_TYPE_GRAY;
|
202
|
+
break;
|
203
|
+
}
|
204
|
+
|
205
|
+
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
206
|
+
if (png_ptr == NULL) {
|
207
|
+
fclose(file_ptr);
|
208
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory.");
|
209
|
+
return Qfalse;
|
210
|
+
}
|
211
|
+
info_ptr = png_create_info_struct(png_ptr);
|
212
|
+
if (info_ptr == NULL) {
|
213
|
+
png_destroy_read_struct(&png_ptr, NULL, NULL);
|
214
|
+
fclose(file_ptr);
|
215
|
+
rb_raise(rb_eNoMemError, "Failed to allocate memory.");
|
216
|
+
return Qfalse;
|
217
|
+
}
|
218
|
+
if (setjmp(png_jmpbuf(png_ptr))) {
|
219
|
+
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
220
|
+
fclose(file_ptr);
|
221
|
+
return Qfalse;
|
222
|
+
}
|
223
|
+
|
224
|
+
png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
225
|
+
PNG_FILTER_TYPE_DEFAULT);
|
226
|
+
|
227
|
+
row_ptr_ptr = png_malloc(png_ptr, height * sizeof(png_bytep));
|
228
|
+
for (y = 0; y < height; y++) {
|
229
|
+
row_ptr_ptr[y] = png_malloc(png_ptr, width * n_ch * sizeof(png_byte));
|
230
|
+
memcpy(row_ptr_ptr[y], image_ptr + y * width * n_ch, width * n_ch);
|
231
|
+
}
|
232
|
+
|
233
|
+
png_init_io(png_ptr, file_ptr);
|
234
|
+
png_set_rows(png_ptr, info_ptr, row_ptr_ptr);
|
235
|
+
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
|
236
|
+
|
237
|
+
fclose(file_ptr);
|
238
|
+
for (y = 0; y < height; y++) png_free(png_ptr, row_ptr_ptr[y]);
|
239
|
+
png_free(png_ptr, row_ptr_ptr);
|
240
|
+
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
241
|
+
|
242
|
+
RB_GC_GUARD(image);
|
243
|
+
RB_GC_GUARD(filename_);
|
244
|
+
|
245
|
+
return Qtrue;
|
246
|
+
}
|
247
|
+
|
248
|
+
struct my_error_mgr {
|
249
|
+
struct jpeg_error_mgr pub;
|
250
|
+
jmp_buf setjmp_buffer;
|
251
|
+
};
|
252
|
+
|
253
|
+
static void my_error_exit(j_common_ptr cinfo) {
|
254
|
+
struct my_error_mgr* my_err = (struct my_error_mgr*)cinfo->err;
|
255
|
+
(*cinfo->err->output_message)(cinfo);
|
256
|
+
longjmp(my_err->setjmp_buffer, 1);
|
257
|
+
}
|
8
258
|
|
9
|
-
|
259
|
+
static VALUE magro_io_read_jpg(VALUE self, VALUE filename_) {
|
260
|
+
char* filename = StringValuePtr(filename_);
|
261
|
+
FILE* file_ptr = fopen(filename, "rb");
|
262
|
+
struct jpeg_decompress_struct jpeg;
|
263
|
+
struct my_error_mgr err;
|
264
|
+
unsigned int width, height;
|
265
|
+
int n_colors;
|
266
|
+
size_t shape[3] = {0};
|
267
|
+
int n_dims;
|
268
|
+
unsigned int y;
|
269
|
+
VALUE nary;
|
270
|
+
uint8_t* nary_ptr;
|
271
|
+
JSAMPLE* tmp;
|
272
|
+
|
273
|
+
if (file_ptr == NULL) {
|
274
|
+
rb_raise(rb_eIOError, "Failed to open file '%s'", filename);
|
275
|
+
return Qnil;
|
276
|
+
}
|
277
|
+
|
278
|
+
jpeg.err = jpeg_std_error(&err.pub);
|
279
|
+
err.pub.error_exit = my_error_exit;
|
280
|
+
if (setjmp(err.setjmp_buffer)) {
|
281
|
+
rb_raise(rb_eIOError, "Error happened while reading file '%s'", filename);
|
282
|
+
return Qnil;
|
283
|
+
}
|
284
|
+
|
285
|
+
jpeg_create_decompress(&jpeg);
|
286
|
+
jpeg_stdio_src(&jpeg, file_ptr);
|
287
|
+
jpeg_read_header(&jpeg, TRUE);
|
288
|
+
jpeg_start_decompress(&jpeg);
|
289
|
+
|
290
|
+
width = jpeg.output_width;
|
291
|
+
height = jpeg.output_height;
|
292
|
+
n_colors = jpeg.out_color_components;
|
293
|
+
|
294
|
+
n_dims = n_colors == 1 ? 2 : 3;
|
295
|
+
shape[0] = height;
|
296
|
+
shape[1] = width;
|
297
|
+
shape[2] = n_colors;
|
298
|
+
nary = rb_narray_new(numo_cUInt8, n_dims, shape);
|
299
|
+
nary_ptr = (uint8_t*)na_get_pointer_for_write(nary);
|
300
|
+
|
301
|
+
for (y = 0; y < height; y++) {
|
302
|
+
tmp = nary_ptr + y * width * n_colors;
|
303
|
+
jpeg_read_scanlines(&jpeg, &tmp, 1);
|
304
|
+
}
|
305
|
+
|
306
|
+
fclose(file_ptr);
|
307
|
+
jpeg_finish_decompress(&jpeg);
|
308
|
+
jpeg_destroy_decompress(&jpeg);
|
309
|
+
|
310
|
+
RB_GC_GUARD(filename_);
|
311
|
+
|
312
|
+
return nary;
|
313
|
+
}
|
314
|
+
|
315
|
+
static VALUE magro_io_save_jpg(int argc, VALUE* argv, VALUE self) {
|
316
|
+
VALUE filename_;
|
317
|
+
VALUE image;
|
318
|
+
VALUE quality_;
|
319
|
+
char* filename;
|
320
|
+
FILE* file_ptr;
|
321
|
+
struct jpeg_compress_struct jpeg;
|
322
|
+
struct my_error_mgr err;
|
323
|
+
narray_t* image_nary;
|
324
|
+
int quality;
|
325
|
+
int n_dims, n_ch;
|
326
|
+
unsigned int width, height, y;
|
327
|
+
uint8_t* image_ptr;
|
328
|
+
JSAMPLE* tmp;
|
329
|
+
|
330
|
+
rb_scan_args(argc, argv, "21", &filename_, &image, &quality_);
|
331
|
+
|
332
|
+
quality = NIL_P(quality_) ? 95 : NUM2INT(quality_);
|
333
|
+
|
334
|
+
filename = StringValuePtr(filename_);
|
335
|
+
|
336
|
+
if (CLASS_OF(image) != numo_cUInt8) image = rb_funcall(numo_cUInt8, rb_intern("cast"), 1, image);
|
337
|
+
if (!RTEST(nary_check_contiguous(image))) image = nary_dup(image);
|
338
|
+
|
339
|
+
jpeg.err = jpeg_std_error(&err.pub);
|
340
|
+
err.pub.error_exit = my_error_exit;
|
341
|
+
if (setjmp(err.setjmp_buffer)) return Qfalse;
|
342
|
+
|
343
|
+
jpeg_create_compress(&jpeg);
|
344
|
+
|
345
|
+
file_ptr = fopen(filename, "wb");
|
346
|
+
if (file_ptr == NULL) {
|
347
|
+
rb_raise(rb_eIOError, "Failed to open file '%s'", filename);
|
348
|
+
jpeg_destroy_compress(&jpeg);
|
349
|
+
return Qfalse;
|
350
|
+
}
|
351
|
+
|
352
|
+
GetNArray(image, image_nary);
|
353
|
+
n_dims = NA_NDIM(image_nary);
|
354
|
+
height = (unsigned int)NA_SHAPE(image_nary)[0];
|
355
|
+
width = (unsigned int)NA_SHAPE(image_nary)[1];
|
356
|
+
image_ptr = (uint8_t*)na_get_pointer_for_read(image);
|
357
|
+
n_ch = n_dims == 3 ? (int)NA_SHAPE(image_nary)[2] : 1;
|
358
|
+
|
359
|
+
jpeg_stdio_dest(&jpeg, file_ptr);
|
360
|
+
|
361
|
+
jpeg.image_height = height;
|
362
|
+
jpeg.image_width = width;
|
363
|
+
jpeg.input_components = n_ch;
|
364
|
+
|
365
|
+
switch (n_ch) {
|
366
|
+
case 3:
|
367
|
+
jpeg.in_color_space = JCS_RGB;
|
368
|
+
break;
|
369
|
+
case 1:
|
370
|
+
jpeg.in_color_space = JCS_GRAYSCALE;
|
371
|
+
break;
|
372
|
+
default:
|
373
|
+
jpeg.in_color_space = JCS_UNKNOWN;
|
374
|
+
break;
|
375
|
+
}
|
376
|
+
|
377
|
+
jpeg_set_defaults(&jpeg);
|
378
|
+
|
379
|
+
jpeg_set_quality(&jpeg, quality, TRUE);
|
380
|
+
|
381
|
+
jpeg_start_compress(&jpeg, TRUE);
|
382
|
+
|
383
|
+
for (y = 0; y < height; y++) {
|
384
|
+
tmp = image_ptr + y * width * n_ch;
|
385
|
+
jpeg_write_scanlines(&jpeg, &tmp, 1);
|
386
|
+
}
|
387
|
+
|
388
|
+
jpeg_finish_compress(&jpeg);
|
389
|
+
jpeg_destroy_compress(&jpeg);
|
390
|
+
|
391
|
+
fclose(file_ptr);
|
392
|
+
|
393
|
+
RB_GC_GUARD(image);
|
394
|
+
RB_GC_GUARD(filename_);
|
395
|
+
|
396
|
+
return Qtrue;
|
397
|
+
}
|
398
|
+
|
399
|
+
void Init_magro() {
|
400
|
+
/**
|
401
|
+
* Document-module: Magro
|
402
|
+
* Magro is an image processing library in Ruby.
|
403
|
+
*/
|
404
|
+
mMagro = rb_define_module("Magro");
|
405
|
+
/**
|
406
|
+
* Document-module: Magro::IO
|
407
|
+
* IO module provides functions for input and output of image file.
|
408
|
+
*/
|
409
|
+
mMagroIO = rb_define_module_under(mMagro, "IO");
|
410
|
+
/**
|
411
|
+
* @!visibility private
|
412
|
+
*
|
413
|
+
* Loads PNG image file.
|
414
|
+
*
|
415
|
+
* @overload read_png(filename) -> Numo::UInt8
|
416
|
+
* @param filename [String] File path of PNG image file.
|
417
|
+
*
|
418
|
+
* @raise [IOError] This error is raised when failed to read image file.
|
419
|
+
* @raise [NoMemoryError] If memory allocation of image data fails, this error is raised.
|
420
|
+
* @return [Numo::UInt8] (shape: [height, width, n_channels]) Loaded image.
|
421
|
+
*/
|
422
|
+
rb_define_module_function(mMagroIO, "read_png", RUBY_METHOD_FUNC(magro_io_read_png), 1);
|
423
|
+
/**
|
424
|
+
* @!visibility private
|
425
|
+
*
|
426
|
+
* Saves PNG image file.
|
427
|
+
*
|
428
|
+
* @overload save_png(filename, image) -> Boolean
|
429
|
+
* @param filename [String] Path to image file to be saved.
|
430
|
+
* @param image [Numo::UInt8] (shape: [height, width, n_channels]) Image data to be saved.
|
431
|
+
*
|
432
|
+
* @raise [IOError] This error is raised when failed to write image file.
|
433
|
+
* @raise [NoMemoryError] If memory allocation of image data fails, this error is raised.
|
434
|
+
* @return [Boolean] true if file save is successful.
|
435
|
+
*/
|
436
|
+
rb_define_module_function(mMagroIO, "save_png", RUBY_METHOD_FUNC(magro_io_save_png), 2);
|
437
|
+
/**
|
438
|
+
* @!visibility private
|
439
|
+
*
|
440
|
+
* Loads JPEG image file.
|
441
|
+
*
|
442
|
+
* @overload read_jpg(filename) -> Numo::UInt8
|
443
|
+
* @param filename [String] File path of JPEG image file.
|
444
|
+
*
|
445
|
+
* @raise [IOError] This error is raised when failed to read image file.
|
446
|
+
* @return [Numo::UInt8] (shape: [height, width, n_channels]) Loaded image.
|
447
|
+
*/
|
448
|
+
rb_define_module_function(mMagroIO, "read_jpg", RUBY_METHOD_FUNC(magro_io_read_jpg), 1);
|
449
|
+
/**
|
450
|
+
* @!visibility private
|
451
|
+
*
|
452
|
+
* Saves JPEG image file.
|
453
|
+
*
|
454
|
+
* @overload save_jpg(filename, image, quality) -> Boolean
|
455
|
+
* @param filename [String] Path to image file to be saved.
|
456
|
+
* @param image [Numo::UInt8] (shape: [height, width, n_channels]) Image data to be saved.
|
457
|
+
* @param quality [Integer/Nil] Quality parameter of jpeg image that takes a value between 0 to 100.
|
458
|
+
*
|
459
|
+
* @return [Boolean] true if file save is successful.
|
460
|
+
*/
|
461
|
+
rb_define_module_function(mMagroIO, "save_jpg", RUBY_METHOD_FUNC(magro_io_save_jpg), -1);
|
10
462
|
}
|
data/ext/magro/magro.h
CHANGED
@@ -1,8 +1,48 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) 2019-2022 Atsushi Tatsuma
|
3
|
+
* All rights reserved.
|
4
|
+
*
|
5
|
+
* Redistribution and use in source and binary forms, with or without
|
6
|
+
* modification, are permitted provided that the following conditions are met:
|
7
|
+
*
|
8
|
+
* * Redistributions of source code must retain the above copyright notice, this
|
9
|
+
* list of conditions and the following disclaimer.
|
10
|
+
*
|
11
|
+
* * Redistributions in binary form must reproduce the above copyright notice,
|
12
|
+
* this list of conditions and the following disclaimer in the documentation
|
13
|
+
* and/or other materials provided with the distribution.
|
14
|
+
*
|
15
|
+
* * Neither the name of the copyright holder nor the names of its
|
16
|
+
* contributors may be used to endorse or promote products derived from
|
17
|
+
* this software without specific prior written permission.
|
18
|
+
*
|
19
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
20
|
+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
21
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
22
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
23
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
24
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
25
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
26
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
27
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
|
+
*/
|
30
|
+
|
1
31
|
#ifndef MAGRO_H
|
2
32
|
#define MAGRO_H 1
|
3
33
|
|
34
|
+
#include <stdio.h>
|
35
|
+
#include <stdlib.h>
|
36
|
+
#include <string.h>
|
37
|
+
|
38
|
+
#include <setjmp.h>
|
39
|
+
|
40
|
+
#include <jpeglib.h>
|
41
|
+
#include <png.h>
|
42
|
+
|
4
43
|
#include <ruby.h>
|
5
44
|
|
6
|
-
#include
|
45
|
+
#include <numo/narray.h>
|
46
|
+
#include <numo/template.h>
|
7
47
|
|
8
48
|
#endif /* MAGRO_H */
|
data/lib/magro/filter.rb
CHANGED
@@ -32,7 +32,7 @@ module Magro
|
|
32
32
|
if n_channels.nil?
|
33
33
|
filter1ch(padded, kernel, scale, offset)
|
34
34
|
else
|
35
|
-
image.class.zeros(
|
35
|
+
image.class.zeros(image.shape).tap do |filtered|
|
36
36
|
n_channels.times do |c|
|
37
37
|
filtered[true, true, c] = filter1ch(padded[true, true, c], kernel, scale, offset)
|
38
38
|
end
|
data/lib/magro/io.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'open-uri'
|
4
|
+
require 'tempfile'
|
5
|
+
|
3
6
|
module Magro
|
4
7
|
# IO module provides functions for input and output of image file.
|
5
8
|
module IO
|
6
9
|
module_function
|
7
10
|
|
8
11
|
# Loads an image from file.
|
9
|
-
# @param filename [String]
|
12
|
+
# @param filename [String] File path or URL of image file to be loaded.
|
10
13
|
# Currently, the following file formats are support:
|
11
14
|
# Portbale Network Graphics (*.png) and JPEG files (*.jpeg, *.jpg, *jpe).
|
12
15
|
# @raise [ArgumentError] This error is raised when filename is not String.
|
@@ -15,8 +18,27 @@ module Magro
|
|
15
18
|
# @return [Numo::UInt8] (shape: [height, width, n_channels]) Loaded image.
|
16
19
|
def imread(filename)
|
17
20
|
raise ArgumentError, 'Expect class of filename to be String.' unless filename.is_a?(String)
|
18
|
-
|
19
|
-
|
21
|
+
|
22
|
+
unless url?(filename)
|
23
|
+
return case filename.downcase
|
24
|
+
when /\.(jpeg|jpg|jpe)$/
|
25
|
+
read_jpg(filename)
|
26
|
+
when /\.png$/
|
27
|
+
read_png(filename)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
uri = URI.parse(filename)
|
32
|
+
ext = File.extname(uri.path).downcase
|
33
|
+
raise IOError, 'Failed to detect file extension from given URL.' unless ext =~ /\.(jpeg|jpg|jpe|png)$/
|
34
|
+
|
35
|
+
uri.open do |file|
|
36
|
+
temp = Tempfile.new(['magro_', ext])
|
37
|
+
temp.binmode
|
38
|
+
temp.write(file.read)
|
39
|
+
temp.rewind
|
40
|
+
imread(temp.path)
|
41
|
+
end
|
20
42
|
end
|
21
43
|
|
22
44
|
# Saves an image to file.
|
@@ -26,7 +48,7 @@ module Magro
|
|
26
48
|
# @param image [Numo::UInt8] (shape: [height, width, n_channels]) Image data to be saved.
|
27
49
|
# @param quality [Integer] Quality parameter of jpeg image that takes a value between 0 to 100.
|
28
50
|
# @raise [ArgumentError] If filename is not String or image is not Numo::NArray, this error is raised.
|
29
|
-
# @raise [IOError] This error is raised when failed to
|
51
|
+
# @raise [IOError] This error is raised when failed to write image file.
|
30
52
|
# @raise [NoMemoryError] If memory allocation of image data fails, this error is raised.
|
31
53
|
# @return [Boolean] true if file save is successful.
|
32
54
|
def imsave(filename, image, quality: nil)
|
@@ -46,6 +68,11 @@ module Magro
|
|
46
68
|
false
|
47
69
|
end
|
48
70
|
|
49
|
-
|
71
|
+
def url?(str)
|
72
|
+
uri = URI.parse(str)
|
73
|
+
uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
|
74
|
+
end
|
75
|
+
|
76
|
+
private_class_method :read_jpg, :read_png, :save_jpg, :save_png, :url?
|
50
77
|
end
|
51
78
|
end
|