magro 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ #ifndef MAGRO_IO_H
2
+ #define MAGRO_IO_H 1
3
+
4
+ #include <stdio.h>
5
+ #include <stdlib.h>
6
+ #include <string.h>
7
+
8
+ #include <setjmp.h>
9
+ #include <png.h>
10
+ #include <jpeglib.h>
11
+
12
+ #include <ruby.h>
13
+ #include <numo/narray.h>
14
+ #include <numo/template.h>
15
+
16
+ void init_io_module();
17
+
18
+ #endif /* MAGRO_IO_H */
@@ -1,403 +1,10 @@
1
1
  #include "magro.h"
2
2
 
3
3
  VALUE mMagro;
4
- VALUE mIO;
5
-
6
- /**
7
- * @!visibility private
8
- */
9
- static
10
- VALUE magro_io_read_png(VALUE self, VALUE filename_)
11
- {
12
- char* filename = StringValuePtr(filename_);
13
- FILE* file_ptr = fopen(filename, "rb");
14
- unsigned char header[8];
15
- png_structp png_ptr;
16
- png_infop info_ptr;
17
- png_bytep* row_ptr_ptr;
18
- png_bytep row_ptr;
19
- png_uint_32 width, height;
20
- int color_type;
21
- int bit_depth;
22
- png_uint_32 y;
23
- int n_dims = 0;
24
- int n_ch;
25
- size_t shape[3] = { 0 };
26
- VALUE nary;
27
- uint8_t* nary_ptr;
28
-
29
- if (file_ptr == NULL) {
30
- return Qnil;
31
- }
32
-
33
- if (fread(header, 1, 8, file_ptr) < 8) {
34
- fclose(file_ptr);
35
- return Qnil;
36
- }
37
-
38
- if (png_sig_cmp(header, 0, 8)) {
39
- fclose(file_ptr);
40
- return Qnil;
41
- }
42
-
43
- png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
44
- if (png_ptr == NULL) {
45
- fclose(file_ptr);
46
- return Qnil;
47
- }
48
- info_ptr = png_create_info_struct(png_ptr);
49
- if (info_ptr == NULL) {
50
- png_destroy_read_struct(&png_ptr, NULL, NULL);
51
- fclose(file_ptr);
52
- return Qnil;
53
- }
54
- if (setjmp(png_jmpbuf(png_ptr))) {
55
- png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
56
- fclose(file_ptr);
57
- return Qnil;
58
- }
59
-
60
- png_init_io(png_ptr, file_ptr);
61
- png_set_sig_bytes(png_ptr, 8);
62
-
63
- png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_16, NULL);
64
- row_ptr_ptr = png_get_rows(png_ptr, info_ptr);
65
- png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
66
-
67
- if (color_type == PNG_COLOR_TYPE_PALETTE) {
68
- png_set_palette_to_rgb(png_ptr);
69
- png_read_update_info(png_ptr, info_ptr);
70
- png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
71
- }
72
-
73
- switch (color_type) {
74
- case PNG_COLOR_TYPE_GRAY:
75
- n_ch = 1;
76
- n_dims = 2;
77
- shape[0] = height;
78
- shape[1] = width;
79
- break;
80
- case PNG_COLOR_TYPE_GRAY_ALPHA:
81
- n_ch = 2;
82
- n_dims = 3;
83
- shape[0] = height;
84
- shape[1] = width;
85
- shape[2] = 2;
86
- break;
87
- case PNG_COLOR_TYPE_RGB:
88
- n_ch = 3;
89
- n_dims = 3;
90
- shape[0] = height;
91
- shape[1] = width;
92
- shape[2] = 3;
93
- break;
94
- case PNG_COLOR_TYPE_RGB_ALPHA:
95
- n_ch = 4;
96
- n_dims = 3;
97
- shape[0] = height;
98
- shape[1] = width;
99
- shape[2] = 4;
100
- break;
101
- default:
102
- n_dims = 0;
103
- break;
104
- }
105
-
106
- if (n_dims == 0) {
107
- fclose(file_ptr);
108
- png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
109
- return Qnil;
110
- }
111
-
112
- nary = rb_narray_new(numo_cUInt8, n_dims, shape);
113
- nary_ptr = (uint8_t*)na_get_pointer_for_write(nary);
114
-
115
- for (y = 0; y < height; y++) {
116
- row_ptr = row_ptr_ptr[y];
117
- memcpy(nary_ptr + y * width * n_ch, row_ptr, width * n_ch);
118
- }
119
-
120
- fclose(file_ptr);
121
- png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
122
-
123
- return nary;
124
- }
125
-
126
- /**
127
- * @!visibility private
128
- */
129
- static
130
- VALUE magro_io_save_png(VALUE self, VALUE filename_, VALUE image)
131
- {
132
- char* filename = StringValuePtr(filename_);
133
- FILE* file_ptr = fopen(filename, "wb");
134
- png_structp png_ptr;
135
- png_infop info_ptr;
136
- png_bytep* row_ptr_ptr;
137
- png_uint_32 width, height;
138
- int color_type;
139
- int bit_depth = 8;
140
- png_uint_32 y;
141
- int n_ch;
142
- int n_dims;
143
- narray_t* image_nary;
144
- uint8_t* image_ptr;
145
-
146
- if (CLASS_OF(image) != numo_cUInt8) {
147
- image = rb_funcall(numo_cUInt8, rb_intern("cast"), 1, image);
148
- }
149
- if (!RTEST(nary_check_contiguous(image))) {
150
- image = nary_dup(image);
151
- }
152
-
153
- GetNArray(image, image_nary);
154
- n_dims = NA_NDIM(image_nary);
155
- height = (png_uint_32)NA_SHAPE(image_nary)[0];
156
- width = (png_uint_32)NA_SHAPE(image_nary)[1];
157
- image_ptr = (uint8_t*)na_get_pointer_for_read(image);
158
-
159
- n_ch = 1;
160
- if (n_dims == 3) {
161
- n_ch = (int)NA_SHAPE(image_nary)[2];
162
- }
163
-
164
- switch (n_ch) {
165
- case 4:
166
- color_type = PNG_COLOR_TYPE_RGBA;
167
- break;
168
- case 3:
169
- color_type = PNG_COLOR_TYPE_RGB;
170
- break;
171
- case 2:
172
- color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
173
- break;
174
- default:
175
- color_type = PNG_COLOR_TYPE_GRAY;
176
- break;
177
- }
178
-
179
- if (file_ptr == NULL) {
180
- return Qfalse;
181
- }
182
- png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
183
- if (png_ptr == NULL) {
184
- fclose(file_ptr);
185
- return Qfalse;
186
- }
187
- info_ptr = png_create_info_struct(png_ptr);
188
- if (info_ptr == NULL) {
189
- png_destroy_read_struct(&png_ptr, NULL, NULL);
190
- fclose(file_ptr);
191
- return Qfalse;
192
- }
193
- if (setjmp(png_jmpbuf(png_ptr))) {
194
- png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
195
- fclose(file_ptr);
196
- return Qfalse;
197
- }
198
-
199
- png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type,
200
- PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
201
-
202
- row_ptr_ptr = png_malloc(png_ptr, height * sizeof(png_bytep));
203
- for (y = 0; y < height; y++) {
204
- row_ptr_ptr[y] = png_malloc(png_ptr, width * n_ch * sizeof(png_byte));
205
- memcpy(row_ptr_ptr[y], image_ptr + y * width * n_ch, width * n_ch);
206
- }
207
-
208
- png_init_io(png_ptr, file_ptr);
209
- png_set_rows(png_ptr, info_ptr, row_ptr_ptr);
210
- png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
211
-
212
- fclose(file_ptr);
213
- for (y = 0; y < height; y++) {
214
- png_free(png_ptr, row_ptr_ptr[y]);
215
- }
216
- png_free(png_ptr, row_ptr_ptr);
217
- png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
218
-
219
- return Qtrue;
220
- }
221
-
222
- struct my_error_mgr {
223
- struct jpeg_error_mgr pub;
224
- jmp_buf setjmp_buffer;
225
- };
226
-
227
- static void
228
- my_error_exit(j_common_ptr cinfo)
229
- {
230
- struct my_error_mgr* my_err = (struct my_error_mgr*)cinfo->err;
231
- (*cinfo->err->output_message)(cinfo);
232
- longjmp(my_err->setjmp_buffer, 1);
233
- }
234
-
235
- /**
236
- * @!visibility private
237
- */
238
- static
239
- VALUE magro_io_read_jpg(VALUE self, VALUE filename_)
240
- {
241
- char* filename = StringValuePtr(filename_);
242
- FILE* file_ptr = fopen(filename, "rb");
243
- struct jpeg_decompress_struct jpeg;
244
- struct my_error_mgr err;
245
- unsigned int width, height;
246
- int n_colors;
247
- size_t shape[3] = { 0 };
248
- int n_dims;
249
- unsigned int y;
250
- VALUE nary;
251
- uint8_t* nary_ptr;
252
- JSAMPLE* tmp;
253
-
254
-
255
- if (file_ptr == NULL) {
256
- return Qnil;
257
- }
258
-
259
- jpeg.err = jpeg_std_error(&err.pub);
260
- err.pub.error_exit = my_error_exit;
261
- if (setjmp(err.setjmp_buffer)) {
262
- return Qnil;
263
- }
264
-
265
- jpeg_create_decompress(&jpeg);
266
- jpeg_stdio_src(&jpeg, file_ptr);
267
- jpeg_read_header(&jpeg, TRUE);
268
- jpeg_start_decompress(&jpeg);
269
-
270
- width = jpeg.output_width;
271
- height = jpeg.output_height;
272
- n_colors = jpeg.out_color_components;
273
-
274
- n_dims = n_colors == 1 ? 2 : 3;
275
- shape[0] = height;
276
- shape[1] = width;
277
- shape[2] = n_colors;
278
- nary = rb_narray_new(numo_cUInt8, n_dims, shape);
279
- nary_ptr = (uint8_t*)na_get_pointer_for_write(nary);
280
-
281
- for (y = 0; y < height; y++) {
282
- tmp = nary_ptr + y * width * n_colors;
283
- jpeg_read_scanlines(&jpeg, &tmp, 1);
284
- }
285
-
286
- fclose(file_ptr);
287
- jpeg_finish_decompress(&jpeg);
288
- jpeg_destroy_decompress(&jpeg);
289
-
290
- return nary;
291
- }
292
-
293
- /**
294
- * @!visibility private
295
- */
296
- static
297
- VALUE magro_io_save_jpg(int argc, VALUE* argv, VALUE self)
298
- {
299
- VALUE filename_;
300
- VALUE image;
301
- VALUE quality_;
302
- char* filename;
303
- FILE* file_ptr;
304
- struct jpeg_compress_struct jpeg;
305
- struct my_error_mgr err;
306
- narray_t* image_nary;
307
- int quality;
308
- int n_dims, n_ch;
309
- unsigned int width, height, y;
310
- uint8_t* image_ptr;
311
- JSAMPLE* tmp;
312
-
313
- rb_scan_args(argc, argv, "21", &filename_, &image, &quality_);
314
-
315
- if (NIL_P(quality_)) {
316
- quality = 95;
317
- } else {
318
- quality = NUM2INT(quality_);
319
- }
320
-
321
- filename = StringValuePtr(filename_);
322
-
323
- if (CLASS_OF(image) != numo_cUInt8) {
324
- image = rb_funcall(numo_cUInt8, rb_intern("cast"), 1, image);
325
- }
326
- if (!RTEST(nary_check_contiguous(image))) {
327
- image = nary_dup(image);
328
- }
329
-
330
- jpeg.err = jpeg_std_error(&err.pub);
331
- err.pub.error_exit = my_error_exit;
332
- if (setjmp(err.setjmp_buffer)) {
333
- return Qfalse;
334
- }
335
-
336
- jpeg_create_compress(&jpeg);
337
-
338
- file_ptr = fopen(filename, "wb");
339
- if (file_ptr == NULL) {
340
- jpeg_destroy_compress(&jpeg);
341
- return Qfalse;
342
- }
343
-
344
- GetNArray(image, image_nary);
345
- n_dims = NA_NDIM(image_nary);
346
- height = (unsigned int)NA_SHAPE(image_nary)[0];
347
- width = (unsigned int)NA_SHAPE(image_nary)[1];
348
- image_ptr = (uint8_t*)na_get_pointer_for_read(image);
349
-
350
- n_ch = 1;
351
- if (n_dims == 3) {
352
- n_ch = (int)NA_SHAPE(image_nary)[2];
353
- }
354
-
355
- jpeg_stdio_dest(&jpeg, file_ptr);
356
-
357
- jpeg.image_height = height;
358
- jpeg.image_width = width;
359
- jpeg.input_components = n_ch;
360
-
361
- switch (n_ch) {
362
- case 3:
363
- jpeg.in_color_space = JCS_RGB;
364
- break;
365
- case 1:
366
- jpeg.in_color_space = JCS_GRAYSCALE;
367
- break;
368
- default:
369
- jpeg.in_color_space = JCS_UNKNOWN;
370
- break;
371
- }
372
-
373
- jpeg_set_defaults(&jpeg);
374
-
375
- jpeg_set_quality(&jpeg, quality, TRUE);
376
-
377
- jpeg_start_compress(&jpeg, TRUE);
378
-
379
- for (y = 0; y < height; y++) {
380
- tmp = image_ptr + y * width * n_ch;
381
- jpeg_write_scanlines(&jpeg, &tmp, 1);
382
- }
383
-
384
- jpeg_finish_compress(&jpeg);
385
- jpeg_destroy_compress(&jpeg);
386
-
387
- fclose(file_ptr);
388
-
389
- return Qtrue;
390
- }
391
4
 
392
5
  void Init_magro()
393
6
  {
394
- rb_require("numo/narray");
395
-
396
7
  mMagro = rb_define_module("Magro");
397
8
 
398
- mIO = rb_define_module_under(mMagro, "IO");
399
- rb_define_module_function(mIO, "read_png", magro_io_read_png, 1);
400
- rb_define_module_function(mIO, "save_png", magro_io_save_png, 2);
401
- rb_define_module_function(mIO, "read_jpg", magro_io_read_jpg, 1);
402
- rb_define_module_function(mIO, "save_jpg", magro_io_save_jpg, -1);
9
+ init_io_module();
403
10
  }
@@ -1,15 +1,8 @@
1
1
  #ifndef MAGRO_H
2
2
  #define MAGRO_H 1
3
3
 
4
- #include <math.h>
5
- #include <string.h>
6
-
7
- #include <setjmp.h>
8
- #include <png.h>
9
- #include <jpeglib.h>
10
-
11
4
  #include <ruby.h>
12
- #include <numo/narray.h>
13
- #include <numo/template.h>
5
+
6
+ #include "imgrw.h"
14
7
 
15
8
  #endif /* MAGRO_H */
@@ -5,4 +5,6 @@ require 'numo/narray'
5
5
  require 'magro/version'
6
6
  require 'magro/magro'
7
7
 
8
+ require 'magro/filter'
8
9
  require 'magro/io'
10
+ require 'magro/transform'
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Magro
4
+ # Filter module provides functions for image filtering.
5
+ module Filter
6
+ module_function
7
+
8
+ # Applies box filter to image.
9
+ # This method performs zero padding as a preprocessing.
10
+ #
11
+ # @example
12
+ # image = Magro::IO.imread('foo.png')
13
+ # kernel = Numo::DFloat[
14
+ # [1, 1, 1],
15
+ # [1, 1, 1],
16
+ # [1, 1, 1]
17
+ # ]
18
+ # blured_image = Magro::Filter.filter2d(image, kernel)
19
+ # Magro::IO.imsave('bar.png', blured_image)
20
+ #
21
+ # @param image [Numo::UInt8] (shape: [height, width, n_channels]) Input image to be filtered.
22
+ # @param kernel [Numo::DFloat] (shape: [kernel_height, kernel_width]) Box filter.
23
+ # @param scale [Float/Nil] Scale parameter for box filter. If nil is given, the box filter is normalized with sum of filter values.
24
+ # @param offset [Integer] Offset value of filtered image.
25
+ # @raise [ArgumentError] This error is raised when class of input image is not Numo::NArray.
26
+ # @return [Numo::UInt8] (shape: [height, width, n_channels]) Filtered image.
27
+ def filter2d(image, kernel, scale: nil, offset: 0)
28
+ raise ArgumentError, 'Expect class of image to be Numo::NArray.' unless image.is_a?(Numo::NArray)
29
+ filter_h, filter_w = kernel.shape
30
+ padded = zero_padding(image, filter_h, filter_w)
31
+ n_channels = image.shape[2]
32
+ if n_channels.nil?
33
+ filter1ch(padded, kernel, scale, offset)
34
+ else
35
+ image.class.zeros(*image.shape).tap do |filtered|
36
+ n_channels.times do |c|
37
+ filtered[true, true, c] = filter1ch(padded[true, true, c], kernel, scale, offset)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ # Convolve two 2-dimensional arrays.
44
+ #
45
+ # @param arr1 [Numo::NArray] (shape: [row1, col1]) First input array.
46
+ # @param arr2 [Numo::NArray] (shape: [row2, col2]) Second input array.
47
+ # @raise [ArgumentError] This error is raised when class of input array is not Numo::NArray.
48
+ # @return [Numo::NArray] (shape: [row1 - row2 + 1, col1 - col2 + 1]) Convolution of arr1 with arr2.
49
+ def convolve2d(arr1, arr2)
50
+ raise ArgumentError, 'Expect class of first input array to be Numo::NArray.' unless arr1.is_a?(Numo::NArray)
51
+ raise ArgumentError, 'Expect class of second input array to be Numo::NArray.' unless arr2.is_a?(Numo::NArray)
52
+ raise ArgumentError, 'Expect first input array to be 2-dimensional array.' unless arr1.ndim == 2
53
+ raise ArgumentError, 'Expect second input array to be 2-dimensional array.' unless arr2.ndim == 2
54
+ row1, col1 = arr1.shape
55
+ row2, col2 = arr2.shape
56
+ # FIXME: lib/numo/narray/extra.rb:1098: warning: Using the last argument as keyword parameters is deprecated
57
+ # convolved = im2col(arr1, row2, col2).dot(arr2.flatten)
58
+ convolved = arr2.flatten.dot(im2col(arr1, row2, col2).transpose)
59
+ convolved.reshape(row1 - row2 + 1, col1 - col2 + 1)
60
+ end
61
+
62
+ # private
63
+
64
+ def zero_padding(image, filter_h, filter_w)
65
+ image_h, image_w, n_channels = image.shape
66
+ pad_h = filter_h / 2
67
+ pad_w = filter_w / 2
68
+ out_h = image_h + pad_h * 2
69
+ out_w = image_w + pad_w * 2
70
+ if n_channels.nil?
71
+ image.class.zeros(out_h, out_w).tap do |padded|
72
+ padded[pad_h...(pad_h + image_h), pad_w...(pad_w + image_w)] = image
73
+ end
74
+ else
75
+ image.class.zeros(out_h, out_w, n_channels).tap do |padded|
76
+ n_channels.times do |c|
77
+ padded[pad_h...(pad_h + image_h), pad_w...(pad_w + image_w), c] = image[true, true, c]
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def filter1ch(image, kernel, scale, offset)
84
+ scale ||= kernel.sum
85
+ kernel *= scale.zero? ? 1.0 : 1.fdiv(scale)
86
+ filtered = convolve2d(image, kernel)
87
+ filtered = (filtered + offset).round.clip(image.class::MIN, image.class::MAX) if integer_narray?(image)
88
+ filtered = image.class.cast(filtered) unless filtered.is_a?(image.class)
89
+ filtered
90
+ end
91
+
92
+ def im2col(image, filter_h, filter_w)
93
+ height, width = image.shape
94
+ rows = height - filter_h + 1
95
+ cols = width - filter_w + 1
96
+ mat = image.class.zeros(filter_h, filter_w, rows, cols)
97
+ filter_h.times do |y|
98
+ y_end = y + rows
99
+ filter_w.times do |x|
100
+ x_end = x + cols
101
+ mat[y, x, true, true] = image[y...y_end, x...x_end]
102
+ end
103
+ end
104
+ mat.transpose(2, 3, 0, 1).reshape(rows * cols, filter_h * filter_w)
105
+ end
106
+
107
+ INTEGER_NARRAY = %w[Numo::Int8 Numo::Int16 Numo::Int32 Numo::Int64
108
+ Numo::UInt8 Numo::UInt16 Numo::UInt32 Numo::UInt64].freeze
109
+
110
+ private_constant :INTEGER_NARRAY
111
+
112
+ def integer_narray?(image)
113
+ INTEGER_NARRAY.include?(image.class.to_s)
114
+ end
115
+
116
+ private_class_method :zero_padding, :filter1ch, :im2col, :integer_narray?
117
+ end
118
+ end