magro 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0b0bad63e73de3bde86fdd749536f8949fd84e8c
4
- data.tar.gz: a6c86139fe5cc7c401a9090a34400bf2387f5cc6
2
+ SHA256:
3
+ metadata.gz: a8bfcf8b7886c7fa5d89556642be97b077f6bf96d7a1725fd2cb8cf1b15c0aef
4
+ data.tar.gz: b0ab5bff6959f1264ed584dcefc25fb7a64fc866164f73dcf11b1556e7cf6377
5
5
  SHA512:
6
- metadata.gz: 872b26cc6825cd1bb7954e5104d6b196944798126d2d3ae66f08380d7017e747a8eb4748b6fbe2d688feff6ed79fbdb0d082ec29ed188b6cab38601125d7543e
7
- data.tar.gz: 53de5a2b240ce63404a8831f61b6285bf3009d3647c487ac6fb7fa32cada5372dced92d839297af911ee4a6b4c6e062bef88a692aac8d4ccdfa7ca1c7f5f45ea
6
+ metadata.gz: f40b650588ec98b33aa8d88bd5c0fb11d4dc8b14faaa197751275f478c2c316e7348b5bd1ff4cc55d33e451036b6b6411fc4f60d9d374c787c91d30ff661abc4
7
+ data.tar.gz: 7df4bfc3e747f2e8d8fb2dad307df3a0b6370fae1b6c53df66797252629851cf57ab256fdcc64088950ce9177ee2d6ca3a694e498588dc3b4baeef17c771d928
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.gitignore CHANGED
@@ -7,6 +7,10 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ /spec/files/tmp.jpeg
11
+ /spec/files/tmp.png
12
+ /spec/files/tmp_UC.JPEG
13
+ /spec/files/tmp_UC.PNG
10
14
 
11
15
  # rspec failure tracking
12
16
  .rspec_status
@@ -1,5 +1,4 @@
1
1
  ---
2
- sudo: true
3
2
  os: linux
4
3
  dist: bionic
5
4
  language: ruby
@@ -8,7 +7,13 @@ rvm:
8
7
  - '2.4'
9
8
  - '2.5'
10
9
  - '2.6'
10
+ - '2.7'
11
+
12
+ addons:
13
+ apt:
14
+ packages:
15
+ - libpng-dev
16
+ - libjpeg-dev
11
17
 
12
18
  before_install:
13
- - sudo apt-get install -y libpng-dev libjpeg-dev
14
- - gem install bundler -v 2.0.2
19
+ - gem install bundler -v 2.1.4
@@ -1,2 +1,23 @@
1
+ # 0.4.0
2
+ - Rename extension file for reading and writing image file.
3
+ - Update documentations.
4
+
5
+ # 0.3.0
6
+ - Add [filter module](https://yoshoku.github.io/magro/doc/Magro/Filter.html) consists of image filtering methods.
7
+ - Change to use round instead of ceil in [quantization of resize method](https://github.com/yoshoku/magro/commit/1b3308ddfb98a650889483af3cd2045aaf6b8837) when given integer image.
8
+
9
+ # 0.2.0
10
+ - Add [transform module](https://yoshoku.github.io/magro/doc/Magro/Transform.html) and resize method.
11
+ - Fix some configulation files.
12
+
13
+ # 0.1.2
14
+ - Fix bug that fails to read and save file with upper case file extension.
15
+
16
+ # 0.1.1
17
+ - Refactor extension codes.
18
+ - Fix to raise IOError when occured file reading / writing error.
19
+ - Fix to raise NoMemoryError when occured memory allocation error.
20
+ - Several documentation improvements.
21
+
1
22
  # 0.1.0
2
23
  - First release.
data/Gemfile CHANGED
@@ -1,4 +1,10 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in magro.gemspec
4
4
  gemspec
5
+
6
+ gem 'coveralls', '~> 0.8'
7
+ gem 'bundler', '~> 2.0'
8
+ gem 'rake', '~> 12.0'
9
+ gem 'rake-compiler', '~> 1.0'
10
+ gem 'rspec', '~> 3.0'
@@ -1,4 +1,4 @@
1
- Copyright (c) 2019 Atsushi Tatsuma
1
+ Copyright (c) 2019-2020 Atsushi Tatsuma
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without
data/README.md CHANGED
@@ -1,20 +1,27 @@
1
1
  # Magro
2
2
 
3
3
  [![Build Status](https://travis-ci.org/yoshoku/magro.svg?branch=master)](https://travis-ci.org/yoshoku/magro)
4
+ [![Coverage Status](https://coveralls.io/repos/github/yoshoku/magro/badge.svg?branch=master)](https://coveralls.io/github/yoshoku/magro?branch=master)
5
+ [![Gem Version](https://badge.fury.io/rb/magro.svg)](https://badge.fury.io/rb/magro)
4
6
  [![BSD 3-Clause License](https://img.shields.io/badge/License-BSD%203--Clause-orange.svg)](https://github.com/yoshoku/numo-liblinear/blob/master/LICENSE.txt)
7
+ [![Documentation](http://img.shields.io/badge/api-reference-blue.svg)](https://yoshoku.github.io/magro/doc/)
5
8
 
6
- Magro is an image processing library in Ruby.
7
- Magro uses [Numo::NArray](https://github.com/ruby-numo/numo-narray) arrays as image objects.
9
+ Magro is a minimal image processing library in Ruby.
10
+ Magro uses [Numo::NArray](https://github.com/ruby-numo/numo-narray) arrays as image objects and
11
+ provides basic image processing functions.
12
+ Current supporting features are reading and writing JPEG and PNG images,
13
+ image resizing with bilinear interpolation method, and image filtering.
8
14
 
9
15
  ## Installation
10
16
 
11
- Magro dependents some external libraries to provides functions loading image file.
17
+ Magro dependents libpng and libjpeg to provides functions reading and writing image file.
18
+ Moreover, it is recommended that using libpng version 1.6 or later.
12
19
 
13
20
  macOS:
14
21
 
15
22
  $ brew install libpng libjpeg
16
23
 
17
- Ubuntu:
24
+ Ubuntu (bionic):
18
25
 
19
26
  $ sudo apt-get install libpng-dev libjpeg-dev
20
27
 
@@ -32,25 +39,23 @@ Or install it yourself as:
32
39
 
33
40
  $ gem install magro
34
41
 
42
+ ## Documentation
43
+
44
+ - [Magro API Documentation](https://yoshoku.github.io/magro/doc/)
45
+
35
46
  ## Usage
36
47
 
37
48
  ```ruby
38
49
  > require 'magro'
39
50
  => true
40
- > image = Magro::IO.imread('lena.png')
51
+ > image = Magro::IO.imread('foo.png')
41
52
  => Numo::UInt8#shape=[512,512,3]
42
53
  > grayscale = image.median(axis: 2)
43
54
  => Numo::UInt8#shape=[512,512]
44
- > Magro::IO.imsave('lena_gray.png', grayscale)
55
+ > Magro::IO.imsave('foo_gray.png', grayscale)
45
56
  => true
46
57
  ```
47
58
 
48
- ## Development
49
-
50
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
51
-
52
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
53
-
54
59
  ## Contributing
55
60
 
56
61
  Bug reports and pull requests are welcome on GitHub at https://github.com/yoshoku/magro.
@@ -0,0 +1,411 @@
1
+ #include "imgrw.h"
2
+
3
+ RUBY_EXTERN VALUE mMagro;
4
+
5
+ /**
6
+ * @!visibility private
7
+ */
8
+ static
9
+ VALUE magro_io_read_png(VALUE self, VALUE filename_)
10
+ {
11
+ char* filename = StringValuePtr(filename_);
12
+ FILE* file_ptr = fopen(filename, "rb");
13
+ unsigned char header[8];
14
+ png_structp png_ptr;
15
+ png_infop info_ptr;
16
+ png_bytep* row_ptr_ptr;
17
+ png_bytep row_ptr;
18
+ png_uint_32 width, height;
19
+ int color_type;
20
+ int bit_depth;
21
+ png_uint_32 y;
22
+ int n_dims = 0;
23
+ int n_ch;
24
+ size_t shape[3] = { 0 };
25
+ VALUE nary;
26
+ uint8_t* nary_ptr;
27
+
28
+ if (file_ptr == NULL) {
29
+ rb_raise(rb_eIOError, "Failed to open file '%s'", filename);
30
+ return Qnil;
31
+ }
32
+
33
+ if (fread(header, 1, 8, file_ptr) < 8) {
34
+ fclose(file_ptr);
35
+ rb_raise(rb_eIOError, "Failed to read header info '%s'", filename);
36
+ return Qnil;
37
+ }
38
+
39
+ if (png_sig_cmp(header, 0, 8)) {
40
+ fclose(file_ptr);
41
+ rb_raise(rb_eIOError, "Failed to read header info '%s'", filename);
42
+ return Qnil;
43
+ }
44
+
45
+ png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
46
+ if (png_ptr == NULL) {
47
+ fclose(file_ptr);
48
+ rb_raise(rb_eNoMemError, "Failed to allocate memory.");
49
+ return Qnil;
50
+ }
51
+ info_ptr = png_create_info_struct(png_ptr);
52
+ if (info_ptr == NULL) {
53
+ png_destroy_read_struct(&png_ptr, NULL, NULL);
54
+ fclose(file_ptr);
55
+ rb_raise(rb_eNoMemError, "Failed to allocate memory.");
56
+ return Qnil;
57
+ }
58
+ if (setjmp(png_jmpbuf(png_ptr))) {
59
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
60
+ fclose(file_ptr);
61
+ rb_raise(rb_eIOError, "Error happened while reading file '%s'", filename);
62
+ return Qnil;
63
+ }
64
+
65
+ png_init_io(png_ptr, file_ptr);
66
+ png_set_sig_bytes(png_ptr, 8);
67
+
68
+ png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_16, NULL);
69
+ row_ptr_ptr = png_get_rows(png_ptr, info_ptr);
70
+ png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
71
+
72
+ if (color_type == PNG_COLOR_TYPE_PALETTE) {
73
+ png_set_palette_to_rgb(png_ptr);
74
+ png_read_update_info(png_ptr, info_ptr);
75
+ png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);
76
+ }
77
+
78
+ switch (color_type) {
79
+ case PNG_COLOR_TYPE_GRAY:
80
+ n_ch = 1;
81
+ n_dims = 2;
82
+ shape[0] = height;
83
+ shape[1] = width;
84
+ break;
85
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
86
+ n_ch = 2;
87
+ n_dims = 3;
88
+ shape[0] = height;
89
+ shape[1] = width;
90
+ shape[2] = 2;
91
+ break;
92
+ case PNG_COLOR_TYPE_RGB:
93
+ n_ch = 3;
94
+ n_dims = 3;
95
+ shape[0] = height;
96
+ shape[1] = width;
97
+ shape[2] = 3;
98
+ break;
99
+ case PNG_COLOR_TYPE_RGB_ALPHA:
100
+ n_ch = 4;
101
+ n_dims = 3;
102
+ shape[0] = height;
103
+ shape[1] = width;
104
+ shape[2] = 4;
105
+ break;
106
+ default:
107
+ n_dims = 0;
108
+ break;
109
+ }
110
+
111
+ if (n_dims == 0) {
112
+ fclose(file_ptr);
113
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
114
+ rb_raise(rb_eIOError, "Unsupported color type of input file '%s'", filename);
115
+ return Qnil;
116
+ }
117
+
118
+ nary = rb_narray_new(numo_cUInt8, n_dims, shape);
119
+ nary_ptr = (uint8_t*)na_get_pointer_for_write(nary);
120
+
121
+ for (y = 0; y < height; y++) {
122
+ row_ptr = row_ptr_ptr[y];
123
+ memcpy(nary_ptr + y * width * n_ch, row_ptr, width * n_ch);
124
+ }
125
+
126
+ fclose(file_ptr);
127
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
128
+
129
+ return nary;
130
+ }
131
+
132
+ /**
133
+ * @!visibility private
134
+ */
135
+ static
136
+ VALUE magro_io_save_png(VALUE self, VALUE filename_, VALUE image)
137
+ {
138
+ char* filename = StringValuePtr(filename_);
139
+ FILE* file_ptr = fopen(filename, "wb");
140
+ png_structp png_ptr;
141
+ png_infop info_ptr;
142
+ png_bytep* row_ptr_ptr;
143
+ png_uint_32 width, height;
144
+ int color_type;
145
+ int bit_depth = 8;
146
+ png_uint_32 y;
147
+ int n_ch;
148
+ int n_dims;
149
+ narray_t* image_nary;
150
+ uint8_t* image_ptr;
151
+
152
+ if (file_ptr == NULL) {
153
+ rb_raise(rb_eIOError, "Failed to open file '%s'", filename);
154
+ return Qfalse;
155
+ }
156
+
157
+ if (CLASS_OF(image) != numo_cUInt8) {
158
+ image = rb_funcall(numo_cUInt8, rb_intern("cast"), 1, image);
159
+ }
160
+ if (!RTEST(nary_check_contiguous(image))) {
161
+ image = nary_dup(image);
162
+ }
163
+
164
+ GetNArray(image, image_nary);
165
+ n_dims = NA_NDIM(image_nary);
166
+ height = (png_uint_32)NA_SHAPE(image_nary)[0];
167
+ width = (png_uint_32)NA_SHAPE(image_nary)[1];
168
+ image_ptr = (uint8_t*)na_get_pointer_for_read(image);
169
+
170
+ n_ch = 1;
171
+ if (n_dims == 3) {
172
+ n_ch = (int)NA_SHAPE(image_nary)[2];
173
+ }
174
+
175
+ switch (n_ch) {
176
+ case 4:
177
+ color_type = PNG_COLOR_TYPE_RGBA;
178
+ break;
179
+ case 3:
180
+ color_type = PNG_COLOR_TYPE_RGB;
181
+ break;
182
+ case 2:
183
+ color_type = PNG_COLOR_TYPE_GRAY_ALPHA;
184
+ break;
185
+ default:
186
+ color_type = PNG_COLOR_TYPE_GRAY;
187
+ break;
188
+ }
189
+
190
+ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
191
+ if (png_ptr == NULL) {
192
+ fclose(file_ptr);
193
+ rb_raise(rb_eNoMemError, "Failed to allocate memory.");
194
+ return Qfalse;
195
+ }
196
+ info_ptr = png_create_info_struct(png_ptr);
197
+ if (info_ptr == NULL) {
198
+ png_destroy_read_struct(&png_ptr, NULL, NULL);
199
+ fclose(file_ptr);
200
+ rb_raise(rb_eNoMemError, "Failed to allocate memory.");
201
+ return Qfalse;
202
+ }
203
+ if (setjmp(png_jmpbuf(png_ptr))) {
204
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
205
+ fclose(file_ptr);
206
+ return Qfalse;
207
+ }
208
+
209
+ png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type,
210
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
211
+
212
+ row_ptr_ptr = png_malloc(png_ptr, height * sizeof(png_bytep));
213
+ for (y = 0; y < height; y++) {
214
+ row_ptr_ptr[y] = png_malloc(png_ptr, width * n_ch * sizeof(png_byte));
215
+ memcpy(row_ptr_ptr[y], image_ptr + y * width * n_ch, width * n_ch);
216
+ }
217
+
218
+ png_init_io(png_ptr, file_ptr);
219
+ png_set_rows(png_ptr, info_ptr, row_ptr_ptr);
220
+ png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
221
+
222
+ fclose(file_ptr);
223
+ for (y = 0; y < height; y++) {
224
+ png_free(png_ptr, row_ptr_ptr[y]);
225
+ }
226
+ png_free(png_ptr, row_ptr_ptr);
227
+ png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
228
+
229
+ return Qtrue;
230
+ }
231
+
232
+ struct my_error_mgr {
233
+ struct jpeg_error_mgr pub;
234
+ jmp_buf setjmp_buffer;
235
+ };
236
+
237
+ static void
238
+ my_error_exit(j_common_ptr cinfo)
239
+ {
240
+ struct my_error_mgr* my_err = (struct my_error_mgr*)cinfo->err;
241
+ (*cinfo->err->output_message)(cinfo);
242
+ longjmp(my_err->setjmp_buffer, 1);
243
+ }
244
+
245
+ /**
246
+ * @!visibility private
247
+ */
248
+ static
249
+ VALUE magro_io_read_jpg(VALUE self, VALUE filename_)
250
+ {
251
+ char* filename = StringValuePtr(filename_);
252
+ FILE* file_ptr = fopen(filename, "rb");
253
+ struct jpeg_decompress_struct jpeg;
254
+ struct my_error_mgr err;
255
+ unsigned int width, height;
256
+ int n_colors;
257
+ size_t shape[3] = { 0 };
258
+ int n_dims;
259
+ unsigned int y;
260
+ VALUE nary;
261
+ uint8_t* nary_ptr;
262
+ JSAMPLE* tmp;
263
+
264
+ if (file_ptr == NULL) {
265
+ rb_raise(rb_eIOError, "Failed to open file '%s'", filename);
266
+ return Qnil;
267
+ }
268
+
269
+ jpeg.err = jpeg_std_error(&err.pub);
270
+ err.pub.error_exit = my_error_exit;
271
+ if (setjmp(err.setjmp_buffer)) {
272
+ rb_raise(rb_eIOError, "Error happened while reading file '%s'", filename);
273
+ return Qnil;
274
+ }
275
+
276
+ jpeg_create_decompress(&jpeg);
277
+ jpeg_stdio_src(&jpeg, file_ptr);
278
+ jpeg_read_header(&jpeg, TRUE);
279
+ jpeg_start_decompress(&jpeg);
280
+
281
+ width = jpeg.output_width;
282
+ height = jpeg.output_height;
283
+ n_colors = jpeg.out_color_components;
284
+
285
+ n_dims = n_colors == 1 ? 2 : 3;
286
+ shape[0] = height;
287
+ shape[1] = width;
288
+ shape[2] = n_colors;
289
+ nary = rb_narray_new(numo_cUInt8, n_dims, shape);
290
+ nary_ptr = (uint8_t*)na_get_pointer_for_write(nary);
291
+
292
+ for (y = 0; y < height; y++) {
293
+ tmp = nary_ptr + y * width * n_colors;
294
+ jpeg_read_scanlines(&jpeg, &tmp, 1);
295
+ }
296
+
297
+ fclose(file_ptr);
298
+ jpeg_finish_decompress(&jpeg);
299
+ jpeg_destroy_decompress(&jpeg);
300
+
301
+ return nary;
302
+ }
303
+
304
+ /**
305
+ * @!visibility private
306
+ */
307
+ static
308
+ VALUE magro_io_save_jpg(int argc, VALUE* argv, VALUE self)
309
+ {
310
+ VALUE filename_;
311
+ VALUE image;
312
+ VALUE quality_;
313
+ char* filename;
314
+ FILE* file_ptr;
315
+ struct jpeg_compress_struct jpeg;
316
+ struct my_error_mgr err;
317
+ narray_t* image_nary;
318
+ int quality;
319
+ int n_dims, n_ch;
320
+ unsigned int width, height, y;
321
+ uint8_t* image_ptr;
322
+ JSAMPLE* tmp;
323
+
324
+ rb_scan_args(argc, argv, "21", &filename_, &image, &quality_);
325
+
326
+ if (NIL_P(quality_)) {
327
+ quality = 95;
328
+ } else {
329
+ quality = NUM2INT(quality_);
330
+ }
331
+
332
+ filename = StringValuePtr(filename_);
333
+
334
+ if (CLASS_OF(image) != numo_cUInt8) {
335
+ image = rb_funcall(numo_cUInt8, rb_intern("cast"), 1, image);
336
+ }
337
+ if (!RTEST(nary_check_contiguous(image))) {
338
+ image = nary_dup(image);
339
+ }
340
+
341
+ jpeg.err = jpeg_std_error(&err.pub);
342
+ err.pub.error_exit = my_error_exit;
343
+ if (setjmp(err.setjmp_buffer)) {
344
+ return Qfalse;
345
+ }
346
+
347
+ jpeg_create_compress(&jpeg);
348
+
349
+ file_ptr = fopen(filename, "wb");
350
+ if (file_ptr == NULL) {
351
+ rb_raise(rb_eIOError, "Failed to open file '%s'", filename);
352
+ jpeg_destroy_compress(&jpeg);
353
+ return Qfalse;
354
+ }
355
+
356
+ GetNArray(image, image_nary);
357
+ n_dims = NA_NDIM(image_nary);
358
+ height = (unsigned int)NA_SHAPE(image_nary)[0];
359
+ width = (unsigned int)NA_SHAPE(image_nary)[1];
360
+ image_ptr = (uint8_t*)na_get_pointer_for_read(image);
361
+
362
+ n_ch = 1;
363
+ if (n_dims == 3) {
364
+ n_ch = (int)NA_SHAPE(image_nary)[2];
365
+ }
366
+
367
+ jpeg_stdio_dest(&jpeg, file_ptr);
368
+
369
+ jpeg.image_height = height;
370
+ jpeg.image_width = width;
371
+ jpeg.input_components = n_ch;
372
+
373
+ switch (n_ch) {
374
+ case 3:
375
+ jpeg.in_color_space = JCS_RGB;
376
+ break;
377
+ case 1:
378
+ jpeg.in_color_space = JCS_GRAYSCALE;
379
+ break;
380
+ default:
381
+ jpeg.in_color_space = JCS_UNKNOWN;
382
+ break;
383
+ }
384
+
385
+ jpeg_set_defaults(&jpeg);
386
+
387
+ jpeg_set_quality(&jpeg, quality, TRUE);
388
+
389
+ jpeg_start_compress(&jpeg, TRUE);
390
+
391
+ for (y = 0; y < height; y++) {
392
+ tmp = image_ptr + y * width * n_ch;
393
+ jpeg_write_scanlines(&jpeg, &tmp, 1);
394
+ }
395
+
396
+ jpeg_finish_compress(&jpeg);
397
+ jpeg_destroy_compress(&jpeg);
398
+
399
+ fclose(file_ptr);
400
+
401
+ return Qtrue;
402
+ }
403
+
404
+ void init_io_module()
405
+ {
406
+ VALUE mIO = rb_define_module_under(mMagro, "IO");
407
+ rb_define_module_function(mIO, "read_png", magro_io_read_png, 1);
408
+ rb_define_module_function(mIO, "save_png", magro_io_save_png, 2);
409
+ rb_define_module_function(mIO, "read_jpg", magro_io_read_jpg, 1);
410
+ rb_define_module_function(mIO, "save_jpg", magro_io_save_jpg, -1);
411
+ }