gd2-ffij 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6aa4ff4f4aa7e5c580dd3c9d464c8c1df57a98a5
4
- data.tar.gz: 211bf9eef3b61bffe85ec4a2e5a2a97affe26bad
2
+ SHA256:
3
+ metadata.gz: 052cd08377d7f0f72c6c17f29430aacc3abef04cd6e9544ed6303b6dd9f4ed0d
4
+ data.tar.gz: a00523e57b7df91beb60ed248c6b9f8a33447fef6c0eeb41bbbbcb9b5aca5d18
5
5
  SHA512:
6
- metadata.gz: b7ed5a027a534a36f2b144392aa8e1f1f56e2a3c2e230d31f4d31409353ab8d7a6d7a9846d12d024ccc9d9b892219e8a4e27a48d2a4b7cc641cdfcc3e7efde88
7
- data.tar.gz: 3b5973311c1080fe04273a0dd4923589f9192b1265fe3e5b2fe607acb648af87001fd7fc54cd72a35a02664a26c23c5db60595182d404f1aabbc9e254092a411
6
+ metadata.gz: 4354f71430291fd6c4b3a986108561d37be95f409dcaa8062f1b0f5b46b19d24ff9d22654210e7b6758a9b7c5db7d22f1ed9b93d06d15863b73b626842a5005f
7
+ data.tar.gz: f741e53c0661bb400b21197a7b08ab04cd94b595cca2b83d5bb7d010f4deb1c2891b8193d5f2b199ba37f6377c5a81ec16e3ff362c52a429da5b64be0a98369c
@@ -3,9 +3,9 @@ cache: bundler
3
3
  sudo: false
4
4
  language: ruby
5
5
  rvm:
6
+ - 2.5.1
6
7
  - 2.4.2
7
8
  - 2.3.5
8
- - 2.2.8
9
9
  - rbx-3
10
10
  - jruby-head
11
11
  matrix:
data/COPYRIGHT CHANGED
@@ -16,7 +16,7 @@ See https://github.com/dark-panda/gd2-ffij/issues/10 ("Consider moving back to
16
16
  BSD now that FFI is back to BSD?") for details.
17
17
 
18
18
 
19
- Subsequent code added and modified to create gd2-ffij copyright (C) 2010-2017
19
+ Subsequent code added and modified to create gd2-ffij copyright (C) 2010-2019
20
20
  J Smith <dark.panda@gmail.com> and published under the MIT license as follows:
21
21
 
22
22
  Permission is hereby granted, free of charge, to any person
@@ -59,6 +59,7 @@ module GD2
59
59
  :gdImageCreateFromGd2Part => [ :pointer, :pointer, :int, :int, :int, :int ],
60
60
  :gdImageCreateFromXbm => [ :pointer, :pointer ],
61
61
  :gdImageCreateFromXpm => [ :pointer, :pointer ],
62
+ :gdImagePaletteCopy => [ :void, :pointer, :pointer ],
62
63
  :gdImageCompare => [ :int, :pointer, :pointer ],
63
64
  :gdImageJpeg => [ :void, :pointer, :pointer, :int ],
64
65
  :gdImageJpegPtr => [ :pointer, :pointer, :pointer, :int ],
@@ -105,6 +106,7 @@ module GD2
105
106
  :gdImageColorExactAlpha => [ :int, :pointer, :int, :int, :int, :int ],
106
107
  :gdImageColorClosestAlpha => [ :int, :pointer, :int, :int, :int, :int ],
107
108
  :gdImageColorClosestHWB => [ :int, :pointer, :int, :int, :int ],
109
+ :gdImageColorAllocate => [ :int, :pointer, :int, :int, :int ],
108
110
  :gdImageColorAllocateAlpha => [ :int, :pointer, :int, :int, :int, :int ],
109
111
  :gdImageColorDeallocate => [ :void, :pointer, :int ],
110
112
  :gdAlphaBlend => [ :int, :int, :int ],
@@ -122,6 +124,12 @@ module GD2
122
124
  :gdImageStringUp => [ :void, :pointer, :pointer, :int, :int, :pointer, :int ],
123
125
  :gdImageStringFTEx => [ :pointer, :pointer, :pointer, :int, :pointer, :double, :double, :int, :int, :pointer, :pointer ],
124
126
  :gdImageStringFTCircle => [ :pointer, :pointer, :int, :int, :double, :double, :double, :pointer, :double, :pointer, :pointer, :int ],
127
+ :gdImageGifAnimBeginPtr => [ :pointer, :pointer, :pointer, :int, :int ],
128
+ :gdImageGifAnimBegin => [ :void, :pointer, :pointer, :int, :int ],
129
+ :gdImageGifAnimAddPtr => [ :pointer, :pointer, :pointer, :int, :int, :int, :int, :int, :pointer ],
130
+ :gdImageGifAnimAdd => [ :void, :pointer, :pointer, :int, :int, :int, :int, :int, :pointer ],
131
+ :gdImageGifAnimEnd => [ :void, :pointer ],
132
+ :gdImageGifAnimEndPtr => [ :pointer, :pointer ],
125
133
  :gdFontGetSmall => [ :pointer ],
126
134
  :gdFontGetLarge => [ :pointer ],
127
135
  :gdFontGetMediumBold => [ :pointer ],
@@ -179,14 +187,24 @@ module GD2
179
187
  ALPHA_TRANSPARENT = 127
180
188
 
181
189
  class LibraryError < StandardError; end
182
- end
183
190
 
184
- require 'gd2/image'
185
- require 'gd2/color'
186
- require 'gd2/palette'
187
- require 'gd2/canvas'
188
- require 'gd2/font'
189
- require 'gd2/ffi_struct'
191
+ GD2_BASE = File.join(File.dirname(__FILE__), 'gd2')
192
+
193
+ autoload :Image,
194
+ File.join(GD2_BASE, 'image')
195
+ autoload :Color,
196
+ File.join(GD2_BASE, 'color')
197
+ autoload :Palette,
198
+ File.join(GD2_BASE, 'palette')
199
+ autoload :Canvas,
200
+ File.join(GD2_BASE, 'canvas')
201
+ autoload :Font,
202
+ File.join(GD2_BASE, 'font')
203
+ autoload :FFIStruct,
204
+ File.join(GD2_BASE, 'ffi_struct')
205
+ autoload :AnimatedGif,
206
+ File.join(GD2_BASE, 'animated_gif')
207
+ end
190
208
 
191
209
  class Numeric
192
210
  if not self.instance_methods.include? 'degrees'
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true; encoding: ASCII-8BIT
2
+ #
3
+ # See COPYRIGHT for license details.
4
+
5
+ class GD2::AnimatedGif
6
+ class Frame < Struct.new(:ptr, :size)
7
+ def read
8
+ ptr.get_bytes(0, size.get_int(0))
9
+ end
10
+ end
11
+
12
+ def add(ptr, options = {})
13
+ size = FFI::MemoryPointer.new(:int)
14
+
15
+ frame_ptr = if frames.empty?
16
+ ::GD2::GD2FFI.send(:gdImageGifAnimBeginPtr, ptr.image_ptr, size, -1, 0)
17
+ else
18
+ previous_frame_ptr = if options[:previous_frame]
19
+ options[:previous_frame].image_ptr
20
+ else
21
+ FFI::Pointer::NULL
22
+ end
23
+
24
+ ::GD2::GD2FFI.send(:gdImageGifAnimAddPtr, ptr.image_ptr, size, 0, 0, 0, options[:delay].to_i, 1, previous_frame_ptr)
25
+ end
26
+
27
+ raise LibraryError if frame_ptr.null?
28
+
29
+ frames << Frame.new(frame_ptr, size)
30
+ end
31
+
32
+ def end
33
+ size = FFI::MemoryPointer.new(:int)
34
+
35
+ frame_ptr = ::GD2::GD2FFI.send(:gdImageGifAnimEndPtr, size)
36
+
37
+ raise LibraryError if frame_ptr.null?
38
+
39
+ frames << Frame.new(frame_ptr, size)
40
+ end
41
+
42
+ def export(filename_or_io)
43
+ output = case filename_or_io
44
+ when String
45
+ File.open(filename_or_io, 'wb')
46
+ else
47
+ filename_or_io
48
+ end
49
+
50
+ frames.each do |frame|
51
+ output.write(frame.read)
52
+ end
53
+
54
+ output.flush
55
+ output.rewind
56
+
57
+ output
58
+ end
59
+
60
+ private
61
+
62
+ def frames
63
+ return @frames if defined?(@frames)
64
+
65
+ @frames = []
66
+
67
+ ObjectSpace.define_finalizer(@frames, frames_finalizer)
68
+
69
+ @frames
70
+ end
71
+
72
+ def frames_finalizer
73
+ proc do
74
+ each do |frame|
75
+ ::GD2::GD2FFI.send(:gdFree, frame[:frame_ptr])
76
+ end
77
+ end
78
+ end
79
+ end
@@ -126,10 +126,31 @@ module GD2
126
126
  :wbmp
127
127
  when /^gd2/
128
128
  :gd2
129
+ when /^\xff\xff/
130
+ :gd
129
131
  end
130
132
  end
131
133
  private_class_method :data_type
132
134
 
135
+ def self.extract_format(filename_or_io, options)
136
+ format = options[:format]
137
+
138
+ if !format
139
+ case filename_or_io
140
+ when String
141
+ md = filename_or_io.match(/\.([^.]+)\z/)
142
+ format = md ? md[1].downcase : nil
143
+ else
144
+ magic = filename_or_io.read(4)
145
+ filename_or_io.seek(-magic.bytes.length, IO::SEEK_CUR)
146
+ format = data_type(magic.strip)
147
+ end
148
+ end
149
+
150
+ format = format.to_sym if format
151
+ format
152
+ end
153
+
133
154
  # Import an image from a file with the given +filename+. The :format option
134
155
  # or the file extension is used to determine the image type (jpeg, png,
135
156
  # gif, wbmp, gd, gd2, xbm, or xpm). The resulting image will be either of
@@ -138,21 +159,17 @@ module GD2
138
159
  # If the file format is gd2, it is optionally possible to extract only a
139
160
  # part of the image. Use options :x, :y, :width, and :height to specify the
140
161
  # part of the image to import.
141
- def self.import(filename, options = {})
142
- raise Errno::ENOENT.new(filename) unless File.exist?(filename)
143
-
144
- unless format = options.delete(:format)
145
- md = filename.match(/\.([^.]+)\z/)
146
- format = md ? md[1].downcase : nil
147
- end
148
- format = format.to_sym if format
162
+ def self.import(filename_or_io, options = {})
163
+ format = extract_format(filename_or_io, options)
149
164
 
150
165
  ptr = # TODO: implement xpm and xbm imports
166
+
151
167
  #if format == :xpm
152
168
  #raise ArgumentError, "Unexpected options #{options.inspect}" unless options.empty?
153
169
  #::GD2::GD2FFI.send(:gdImageCreateFromXpm, filename)
154
170
  #elsif format == :xbm
155
171
  #::GD2::GD2FFI.send(:gdImageCreateFromXbm, filename)
172
+
156
173
  if format == :gd2 && !options.empty?
157
174
  x, y, width, height =
158
175
  options.delete(:x) || 0, options.delete(:y) || 0,
@@ -181,12 +198,18 @@ module GD2
181
198
  raise UnrecognizedImageTypeError,
182
199
  'Format (or file extension) is not recognized' unless create_sym
183
200
 
184
- file = File.open(filename, 'rb').read
185
- file = file.force_encoding("ASCII-8BIT") if file.respond_to? :force_encoding
186
- file_ptr = FFI::MemoryPointer.new(file.size, 1, false)
187
- file_ptr.put_bytes(0, file)
201
+ output = case filename_or_io
202
+ when String
203
+ File.open(filename_or_io, 'rb').read
204
+ else
205
+ filename_or_io.read
206
+ end
207
+
208
+ output = output.force_encoding("ASCII-8BIT") if output.respond_to? :force_encoding
209
+ file_ptr = FFI::MemoryPointer.new(output.size, 1, false)
210
+ file_ptr.put_bytes(0, output)
188
211
 
189
- ::GD2::GD2FFI.send(create_sym, file.size, file_ptr)
212
+ ::GD2::GD2FFI.send(create_sym, output.size, file_ptr)
190
213
  end
191
214
  raise LibraryError if ptr.null?
192
215
 
@@ -425,17 +448,13 @@ module GD2
425
448
  # implemented by subclass
426
449
  end
427
450
 
428
- # Export this image to a file with the given +filename+. The image format
429
- # is determined by the :format option, or by the file extension (jpeg, png,
430
- # gif, wbmp, gd, or gd2). Returns the size of the written image data.
431
- # Additional +options+ are as arguments for the Image#jpeg, Image#png,
432
- # Image#wbmp, or Image#gd2 methods.
433
- def export(filename, options = {})
434
- unless format = options.delete(:format)
435
- md = filename.match(/\.([^.]+)\z/)
436
- format = md ? md[1].downcase : nil
437
- end
438
- format = format.to_sym if format
451
+ # Export this image to a file or stream with the given +filename+. The
452
+ # image format is determined by the :format option, or by the file
453
+ # extension (jpeg, png, gif, wbmp, gd, or gd2). Returns the size of the
454
+ # written image data. Additional +options+ are as arguments for the
455
+ # Image#jpeg, Image#png, Image#wbmp, or Image#gd2 methods.
456
+ def export(filename_or_io, options = {})
457
+ format = self.class.extract_format(filename_or_io, options)
439
458
 
440
459
  size = FFI::MemoryPointer.new(:pointer)
441
460
 
@@ -465,17 +484,24 @@ module GD2
465
484
  'Format (or file extension) is not recognized'
466
485
  end
467
486
 
468
- raise ArgumentError, "Unrecognized options #{options.inspect}" unless
469
- options.empty?
487
+ output = case filename_or_io
488
+ when String
489
+ File.open(filename_or_io, 'wb')
490
+ else
491
+ filename_or_io
492
+ end
470
493
 
471
- File.open(filename, 'wb') do |file|
472
- begin
473
- img = ::GD2::GD2FFI.send(write_sym, image_ptr, *args)
474
- file.write(img.get_bytes(0, size.get_int(0)))
475
- ensure
476
- ::GD2::GD2FFI.gdFree(img)
477
- end
494
+ begin
495
+ img = ::GD2::GD2FFI.send(write_sym, image_ptr, *args)
496
+ output.write(img.get_bytes(0, size.get_int(0)))
497
+ ensure
498
+ ::GD2::GD2FFI.gdFree(img)
478
499
  end
500
+
501
+ output.flush
502
+ output.rewind
503
+
504
+ output
479
505
  end
480
506
 
481
507
  # Encode and return data for this image in JPEG format. The +quality+
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true; encoding: ASCII-8BIT
2
2
 
3
3
  module GD2
4
- VERSION = '0.3.0'.freeze
4
+ VERSION = '0.4.0'.freeze
5
5
  end
6
6
 
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true; encoding: ASCII-8BIT
2
+
3
+ require './test/test_helper'
4
+
5
+ class AnimatedGifTest < Minitest::Test
6
+ include TestHelper
7
+
8
+ def test_animation
9
+ image1 = GD2::Image::IndexedColor.new(256, 256)
10
+
11
+ black = image1.palette.allocate(GD2::Color[0, 0, 0])
12
+ white = image1.palette.allocate(GD2::Color[255, 255, 255])
13
+ lime = image1.palette.allocate(GD2::Color[0, 255, 0])
14
+
15
+ image1.draw do |pen|
16
+ pen.color = white
17
+ pen.line(64, 64, 192, 192)
18
+ end
19
+
20
+ image2 = GD2::Image::IndexedColor.new(256, 256)
21
+ black = image2.palette.allocate(GD2::Color[0, 0, 0])
22
+ white = image2.palette.allocate(GD2::Color[255, 255, 255])
23
+ lime = image2.palette.allocate(GD2::Color[0, 255, 0])
24
+
25
+ image2.draw do |pen|
26
+ pen.color = lime
27
+ pen.rectangle(32, 64, 192, 192)
28
+ end
29
+
30
+ anim = GD2::AnimatedGif.new
31
+ anim.add(image1)
32
+ anim.add(image2, delay: 50)
33
+ anim.add(image1, delay: 50)
34
+ anim.end
35
+
36
+ output = StringIO.new
37
+
38
+ anim.export(output)
39
+
40
+ assert(output.read == File.read('test/images/test_animated_gif.gif'))
41
+ end
42
+ end
@@ -77,6 +77,17 @@ class ImageTest < Minitest::Test
77
77
  File.unlink(out)
78
78
  end
79
79
 
80
+ def test_export_and_import_#{ext}_io
81
+ img = GD2::Image.import(File.join(PATH_TO_IMAGES, 'test.gd2'))
82
+ out = StringIO.new
83
+ img.export(out, format: :#{ext})
84
+
85
+ imgA = GD2::Image.import(out)
86
+ imgB = GD2::Image.import(File.join(PATH_TO_IMAGES, 'test.#{ext}'))
87
+
88
+ assert(imgA == imgB)
89
+ end
90
+
80
91
  def test_compare_#{ext}
81
92
  imgA = GD2::Image.import(File.join(PATH_TO_IMAGES, 'test.#{ext}'))
82
93
  imgB = GD2::Image.import(File.join(PATH_TO_IMAGES, 'test.#{ext}'))
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gd2-ffij
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - J Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-09 00:00:00.000000000 Z
11
+ date: 2019-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi
@@ -40,6 +40,7 @@ files:
40
40
  - Rakefile
41
41
  - gd2-ffij.gemspec
42
42
  - lib/gd2-ffij.rb
43
+ - lib/gd2/animated_gif.rb
43
44
  - lib/gd2/canvas.rb
44
45
  - lib/gd2/color.rb
45
46
  - lib/gd2/ffi_struct.rb
@@ -47,6 +48,7 @@ files:
47
48
  - lib/gd2/image.rb
48
49
  - lib/gd2/palette.rb
49
50
  - lib/gd2/version.rb
51
+ - test/animated_gif_tests.rb
50
52
  - test/canvas_tests.rb
51
53
  - test/image_tests.rb
52
54
  - test/images/test.bmp
@@ -59,6 +61,7 @@ files:
59
61
  - test/images/test.xbm
60
62
  - test/images/test.xcf
61
63
  - test/images/test.xpm
64
+ - test/images/test_animated_gif.gif
62
65
  - test/images/test_arc.gd2
63
66
  - test/images/test_canvas_filled_polygon.gd2
64
67
  - test/images/test_canvas_filled_rectangle.gd2
@@ -108,12 +111,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
111
  - !ruby/object:Gem::Version
109
112
  version: '0'
110
113
  requirements: []
111
- rubyforge_project:
112
- rubygems_version: 2.6.13
114
+ rubygems_version: 3.0.3
113
115
  signing_key:
114
116
  specification_version: 4
115
117
  summary: gd2-ffij is a refactoring of the Ruby/GD2 library implemented with FFI
116
118
  test_files:
119
+ - test/animated_gif_tests.rb
117
120
  - test/canvas_tests.rb
118
121
  - test/image_tests.rb
119
122
  - test/images/test.bmp
@@ -126,6 +129,7 @@ test_files:
126
129
  - test/images/test.xbm
127
130
  - test/images/test.xcf
128
131
  - test/images/test.xpm
132
+ - test/images/test_animated_gif.gif
129
133
  - test/images/test_arc.gd2
130
134
  - test/images/test_canvas_filled_polygon.gd2
131
135
  - test/images/test_canvas_filled_rectangle.gd2