diff --git a/.ruby-version b/.ruby-version index bc4abe8..6a6a3d8 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.3.8 +2.6.1 diff --git a/Rakefile b/Rakefile index a8de4c0..df43bbd 100644 --- a/Rakefile +++ b/Rakefile @@ -9,3 +9,9 @@ begin rescue LoadError # no rspec available end + +require 'rake/extensiontask' + +Rake::ExtensionTask.new('rszr') do |ext| + ext.lib_dir = 'lib/rszr' +end diff --git a/benchmark/memory.rb b/benchmark/memory.rb new file mode 100644 index 0000000..3ceee10 --- /dev/null +++ b/benchmark/memory.rb @@ -0,0 +1,40 @@ +require 'memory_profiler' +require 'rszr' +require 'mini_magick' +require 'gd2-ffij' + +ITERATIONS = 100 + +original = Pathname.new(__FILE__).dirname.join('../spec/images/test.jpg') +resized = Pathname.new(__FILE__).dirname.join('output.jpg') + +mini_magick = MemoryProfiler.report do + ITERATIONS.times do + image = MiniMagick::Image.open(original.to_s) + image.resize '800x532' + image.write resized.to_s + image = nil + end +end + +mini_magick.pretty_print(scale_bytes: true) + +gd2 = MemoryProfiler.report do + image = GD2::Image.import(original.to_s) + image.resize! 800, 532 + image.export resized.to_s + image = nil +end + +gd2.pretty_print(scale_bytes: true) + +rszr = MemoryProfiler.report do + ITERATIONS.times do + image = Rszr::Image.load(original.to_s) + image.resize! 800, 532 + image.save resized.to_s + image = nil + end +end + +rszr.pretty_print(scale_bytes: true) diff --git a/benchmark/benchmark.rb b/benchmark/speed.rb similarity index 93% rename from benchmark/benchmark.rb rename to benchmark/speed.rb index 539a4e8..8230d90 100644 --- a/benchmark/benchmark.rb +++ b/benchmark/speed.rb @@ -14,14 +14,16 @@ image = MiniMagick::Image.open(original.to_s) image.resize '800x532' image.write resized.to_s + image = nil end end - + x.report 'GD2' do ITERATIONS.times do image = GD2::Image.import(original.to_s) image.resize! 800, 532 image.export resized.to_s + image = nil end end @@ -30,6 +32,7 @@ image = Rszr::Image.load(original.to_s) image.resize! 800, 532 image.save resized.to_s + image = nil end end end diff --git a/ext/rszr/errors.c b/ext/rszr/errors.c new file mode 100644 index 0000000..c1714e0 --- /dev/null +++ b/ext/rszr/errors.c @@ -0,0 +1,63 @@ +#ifndef RUBY_RSZR_ERRORS +#define RUBY_RSZR_ERRORS + +#include "rszr.h" +#include "errors.h" + +VALUE eRszrError = Qnil; +VALUE eRszrFileNotFound = Qnil; +VALUE eRszrTransformationError = Qnil; +VALUE eRszrErrorWithMessage = Qnil; +VALUE eRszrLoadError = Qnil; +VALUE eRszrSaveError = Qnil; + +static const char * const sRszrErrorMessages[] = +{ + "File does not exist", + "File is a directory", + "Read permission denied", + "Unsupported format", + "Path too long", + "Non-existant path component", + "Path component is not a directory", + "Path outside address space", + "Too many symbolic links", + "Out of memory", + "Out of file descriptors", + "Write permission denied", + "Out of disk space", + "Unknown error" +}; +const int RSZR_MAX_ERROR_INDEX = 13; + +void Init_rszr_errors() +{ + eRszrError = rb_define_class_under(mRszr, "Error", rb_eStandardError); + eRszrFileNotFound = rb_define_class_under(mRszr, "FileNotFound", eRszrError); + eRszrTransformationError = rb_define_class_under(mRszr, "TransformationError", eRszrError); + eRszrErrorWithMessage = rb_define_class_under(mRszr, "ErrorWithMessage", eRszrError); + eRszrLoadError = rb_define_class_under(mRszr, "LoadError", eRszrErrorWithMessage); + eRszrSaveError = rb_define_class_under(mRszr, "SaveError", eRszrErrorWithMessage); +} + +static void rszr_raise_error_with_message(VALUE rb_error_class, Imlib_Load_Error error) +{ + int error_index = (int) error - 1; + if (error_index < 1 || error_index > RSZR_MAX_ERROR_INDEX) + error_index = 13; + VALUE rb_error = rb_exc_new2(rb_error_class, sRszrErrorMessages[error_index]); + rb_exc_raise(rb_error); +} + +void rszr_raise_load_error(Imlib_Load_Error error) +{ + rszr_raise_error_with_message(eRszrLoadError, error); +} + +void rszr_raise_save_error(Imlib_Load_Error error) +{ + rszr_raise_error_with_message(eRszrSaveError, error); +} + + +#endif \ No newline at end of file diff --git a/ext/rszr/errors.h b/ext/rszr/errors.h new file mode 100644 index 0000000..872b68c --- /dev/null +++ b/ext/rszr/errors.h @@ -0,0 +1,15 @@ +#ifndef RUBY_RSZR_ERRORS_H +#define RUBY_RSZR_ERRORS_H + +void Init_rszr_errors(); +void rszr_raise_load_error(Imlib_Load_Error error); +void rszr_raise_save_error(Imlib_Load_Error error); + +extern VALUE eRszrError; +extern VALUE eRszrFileNotFound; +extern VALUE eRszrTransformationError; +extern VALUE eRszrErrorWithMessage; +extern VALUE eRszrLoadError; +extern VALUE eRszrSaveError; + +#endif diff --git a/ext/rszr/extconf.rb b/ext/rszr/extconf.rb new file mode 100644 index 0000000..c00700b --- /dev/null +++ b/ext/rszr/extconf.rb @@ -0,0 +1,37 @@ +require 'mkmf' +require 'rbconfig' +=begin +LIBDIR = RbConfig::CONFIG['libdir'] +INCLUDEDIR = RbConfig::CONFIG['includedir'] + +HEADER_DIRS = [ + '/opt/local/include', + '/usr/local/include', + INCLUDEDIR, + '/usr/include', +] + +LIB_DIRS = [ + '/opt/local/lib', + '/usr/local/lib', + LIBDIR, + '/usr/lib', +] + +dir_config('Imlib2', HEADER_DIRS, LIB_DIRS) +=end +imlib2_config = with_config('imlib2-config', 'imlib2-config') + +$CFLAGS << ' -DX_DISPLAY_MISSING ' << `#{imlib2_config} --cflags`.chomp +$LDFLAGS << ' ' << `#{imlib2_config} --libs`.chomp +$LDFLAGS.gsub!(/\ -lX11\ -lXext/, '') if RUBY_PLATFORM =~ /darwin/ + +unless find_header('Imlib2.h') + abort 'imlib2 development headers are missing' +end + +unless find_library('imlib2', 'imlib_set_cache_size') + abort 'imlib2 is missing' +end + +create_makefile 'rszr' diff --git a/ext/rszr/image.c b/ext/rszr/image.c new file mode 100644 index 0000000..7c38591 --- /dev/null +++ b/ext/rszr/image.c @@ -0,0 +1,245 @@ +#ifndef RUBY_RSZR_IMAGE +#define RUBY_RSZR_IMAGE + +#include "rszr.h" +#include "image.h" +#include "errors.h" + +VALUE cImage = Qnil; + +static void rszr_free_image(Imlib_Image image) +{ + imlib_context_set_image(image); + imlib_free_image(); +} + +static void rszr_image_deallocate(rszr_image_handle * handle) +{ + // fprintf(stderr, "rszr_image_deallocate"); + if (handle->image) { + // fprintf(stderr, ": freeing"); + rszr_free_image(handle->image); + handle->image = NULL; + } + free(handle); + // fprintf(stderr, "\n"); +} + +static VALUE rszr_image_s_allocate(VALUE klass) +{ + rszr_image_handle * handle = calloc(1, sizeof(rszr_image_handle)); + return Data_Wrap_Struct(klass, NULL, rszr_image_deallocate, handle); +} + +static VALUE rszr_image_initialize(VALUE self, VALUE rb_width, VALUE rb_height) +{ + rszr_image_handle * handle; + + Check_Type(rb_width, T_FIXNUM); + Check_Type(rb_height, T_FIXNUM); + + Data_Get_Struct(self, rszr_image_handle, handle); + + handle->image = imlib_create_image(FIX2INT(rb_width), FIX2INT(rb_height)); + + return self; +} + +static VALUE rszr_image_s__load(VALUE klass, VALUE rb_path) +{ + rszr_image_handle * handle; + Imlib_Image image; + char * path; + Imlib_Load_Error error; + VALUE oImage; + + path = StringValueCStr(rb_path); + + imlib_set_cache_size(0); + image = imlib_load_image_with_error_return(path, &error); + + if (!image) { + rszr_raise_load_error(error); + return Qnil; + } + + oImage = rszr_image_s_allocate(cImage); + Data_Get_Struct(oImage, rszr_image_handle, handle); + handle->image = image; + return oImage; +} + +static VALUE rszr_image_format(VALUE self) +{ + rszr_image_handle * handle; + char * format; + + Data_Get_Struct(self, rszr_image_handle, handle); + + imlib_context_set_image(handle->image); + format = imlib_image_format(); + + if (format) { + return rb_str_new2(format); + } else { + return Qnil; + } +} + +static VALUE rszr_image_width(VALUE self) +{ + rszr_image_handle * handle; + int width; + + Data_Get_Struct(self, rszr_image_handle, handle); + + imlib_context_set_image(handle->image); + width = imlib_image_get_width(); + + return INT2NUM(width); +} + +static VALUE rszr_image_height(VALUE self) +{ + rszr_image_handle * handle; + int height; + + Data_Get_Struct(self, rszr_image_handle, handle); + + imlib_context_set_image(handle->image); + height = imlib_image_get_height(); + + return INT2NUM(height); +} + +/* +static VALUE rszr_image_replace(VALUE self, VALUE rb_other) +{ + rszr_image_handle * handle; + rszr_image_handle * other_handle; + + if (CLASS_OF(rb_other) != cImage) { + rb_raise(rb_eArgError, "Rszr::Image required, got %s", rb_obj_classname(rb_other)); + } + + Data_Get_Struct(self, rszr_image_handle, handle); + Data_Get_Struct(rb_other, rszr_image_handle, other_handle); + + return self; +} +*/ + +/* +static VALUE rszr_image_turn(VALUE self, VALUE orientation) +{ + // void imlib_image_orientate(int orientation) + return self; +} +*/ + +static Imlib_Image rszr_create_cropped_scaled_image(VALUE self, VALUE rb_src_x, VALUE rb_src_y, VALUE rb_src_w, VALUE rb_src_h, VALUE rb_dst_w, VALUE rb_dst_h) +{ + rszr_image_handle * handle; + Imlib_Image resized_image; + + int src_x = NUM2INT(rb_src_x); + int src_y = NUM2INT(rb_src_y); + int src_w = NUM2INT(rb_src_w); + int src_h = NUM2INT(rb_src_h); + int dst_w = NUM2INT(rb_dst_w); + int dst_h = NUM2INT(rb_dst_h); + + // TODO: raise if <= 0 + + Data_Get_Struct(self, rszr_image_handle, handle); + + imlib_context_set_image(handle->image); + imlib_context_set_anti_alias(1); + resized_image = imlib_create_cropped_scaled_image(src_x, src_y, src_w, src_h, dst_w, dst_h); + + if (!resized_image) { + rb_raise(eRszrTransformationError, "error resizing image"); + return NULL; + } + + return resized_image; +} + +static VALUE rszr_image__resize_bang(VALUE self, VALUE rb_src_x, VALUE rb_src_y, VALUE rb_src_w, VALUE rb_src_h, VALUE rb_dst_w, VALUE rb_dst_h) +{ + rszr_image_handle * handle; + Imlib_Image resized_image; + + Data_Get_Struct(self, rszr_image_handle, handle); + + resized_image = rszr_create_cropped_scaled_image(self, rb_src_x, rb_src_y, rb_src_w, rb_src_h, rb_dst_w, rb_dst_h); + if (!resized_image) return Qfalse; + + rszr_free_image(handle->image); + handle->image = resized_image; + + return self; +} + +static VALUE rszr_image__resize(VALUE self, VALUE rb_src_x, VALUE rb_src_y, VALUE rb_src_w, VALUE rb_src_h, VALUE rb_dst_w, VALUE rb_dst_h) +{ + rszr_image_handle * handle; + Imlib_Image resized_image; + rszr_image_handle * resized_handle; + VALUE oResizedImage; + + Data_Get_Struct(self, rszr_image_handle, handle); + + resized_image = rszr_create_cropped_scaled_image(self, rb_src_x, rb_src_y, rb_src_w, rb_src_h, rb_dst_w, rb_dst_h); + if (!resized_image) return Qfalse; + + oResizedImage = rszr_image_s_allocate(cImage); + Data_Get_Struct(oResizedImage, rszr_image_handle, resized_handle); + resized_handle->image = resized_image; + + return oResizedImage; +} + +static VALUE rszr_image__save(VALUE self, VALUE rb_path, VALUE rb_format) +{ + rszr_image_handle * handle; + char * path; + char * format; + Imlib_Load_Error save_error; + + path = StringValueCStr(rb_path); + format = StringValueCStr(rb_format); + + Data_Get_Struct(self, rszr_image_handle, handle); + + imlib_context_set_image(handle->image); + imlib_image_set_format(format); + imlib_save_image_with_error_return(path, &save_error); + + if (save_error) { + rszr_raise_save_error(save_error); + return Qfalse; + } + + return Qtrue; +} + +void Init_rszr_image() +{ + cImage = rb_define_class_under(mRszr, "Image", rb_cObject); + rb_define_alloc_func(cImage, rszr_image_s_allocate); + + // Class methods + rb_define_private_method(rb_singleton_class(cImage), "_load", rszr_image_s__load, 1); + + // Instance methods + rb_define_method(cImage, "initialize", rszr_image_initialize, 2); + rb_define_method(cImage, "width", rszr_image_width, 0); + rb_define_method(cImage, "height", rszr_image_height, 0); + rb_define_method(cImage, "format", rszr_image_format, 0); + rb_define_private_method(cImage, "_resize!", rszr_image__resize, 6); + rb_define_private_method(cImage, "_resize", rszr_image__resize, 6); + rb_define_private_method(cImage, "_save", rszr_image__save, 2); +} + +#endif diff --git a/ext/rszr/image.h b/ext/rszr/image.h new file mode 100644 index 0000000..77f6369 --- /dev/null +++ b/ext/rszr/image.h @@ -0,0 +1,12 @@ +#ifndef RUBY_RSZR_IMAGE_H +#define RUBY_RSZR_IMAGE_H + +typedef struct { + Imlib_Image image; +} rszr_image_handle; + +extern VALUE cImage; + +void Init_rszr_image(); + +#endif diff --git a/ext/rszr/rszr.c b/ext/rszr/rszr.c new file mode 100644 index 0000000..0fe4196 --- /dev/null +++ b/ext/rszr/rszr.c @@ -0,0 +1,17 @@ +#ifndef RUBY_RSZR +#define RUBY_RSZR + +#include "rszr.h" +#include "image.h" +#include "errors.h" + +VALUE mRszr = Qnil; + +void Init_rszr() +{ + mRszr = rb_define_module("Rszr"); + Init_rszr_errors(); + Init_rszr_image(); +} + +#endif diff --git a/ext/rszr/rszr.h b/ext/rszr/rszr.h new file mode 100644 index 0000000..796945f --- /dev/null +++ b/ext/rszr/rszr.h @@ -0,0 +1,10 @@ +#ifndef RUBY_RSZR_H +#define RUBY_RSZR_H + +#include "ruby.h" +#include + +extern VALUE mRszr; +void Init_rszr(); + +#endif diff --git a/lib/rszr.rb b/lib/rszr.rb index d05cf90..a8afbfd 100644 --- a/lib/rszr.rb +++ b/lib/rszr.rb @@ -1,12 +1,6 @@ -require 'fiddle' -require 'fiddle/import' require 'rbconfig' require 'pathname' +require 'rszr/rszr' require 'rszr/version' -require 'rszr/errors' -require 'rszr/lib' -require 'rszr/lock' -require 'rszr/handle' -require 'rszr/base' require 'rszr/image' diff --git a/lib/rszr/base.rb b/lib/rszr/base.rb deleted file mode 100644 index 8ad2ca2..0000000 --- a/lib/rszr/base.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Rszr - module Base - - def self.included(base) - Rszr::Lib.delegate(base) - Rszr::Lib.delegate(base.singleton_class) - base.include(Lock) - base.extend(Lock) - end - - protected - - def handle - @handle - end - - def ptr - @handle.ptr - end - - private - - def assert_valid_keys(hsh, *valid_keys) - if unknown_key = (hsh.keys - valid_keys).first - raise ArgumentError.new("Unknown key: #{unknown_key.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}") - end - end - end -end diff --git a/lib/rszr/errors.rb b/lib/rszr/errors.rb deleted file mode 100644 index 1b08d7d..0000000 --- a/lib/rszr/errors.rb +++ /dev/null @@ -1,42 +0,0 @@ -module Rszr - class Error < StandardError; end - - class ErrorWithMessage < Error - MESSAGES = [nil, - 'File does not exist', - 'File is a directory', - 'Read permission denied', - 'Unsupported format', - 'Path too long', - 'Non-existant path component', - 'Path component is not a directory', - 'Path outside address space', - 'Too many symbolic links', - 'Out of memory', - 'Out of file descriptors', - 'Write permission denied', - 'Out of disk space', - 'Unknown error'] - - attr_reader :ptr - - def initialize - @ptr = Fiddle::Pointer.malloc(Rszr::Lib.sizeof('Imlib_Load_Error')) - end - - def error? - return if ptr.null? - 0 != ptr[0] - end - - def message - MESSAGES[ptr[0]] unless ptr.null? - end - end - - - class FileNotFound < Error; end - class LoadError < ErrorWithMessage; end - class TransformationError < Error; end - class SaveError < ErrorWithMessage; end -end \ No newline at end of file diff --git a/lib/rszr/handle.rb b/lib/rszr/handle.rb deleted file mode 100644 index 87ec565..0000000 --- a/lib/rszr/handle.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Rszr - class Handle - attr_reader :ptr, :klass - - def initialize(obj, ptr) - @ptr = ptr - raise ArgumentError, "#{obj.class.inspect} does not define the finalize class method" unless obj.class.respond_to?(:finalize, true) - @klass = obj.class - ObjectSpace.define_finalizer(obj, self) - end - - def replace!(other_ptr) - raise ArgumentError, "Cannot replace pointer with itself" if ptr == other_ptr - finalize! - @ptr = other_ptr - end - - def finalize!(*args) - #puts "Finalizing #{args.inspect} #{ptr.inspect}" - if !ptr.null? && klass - #puts " calling #{args.inspect}" - klass.send(:finalize, ptr) - @ptr = Fiddle::Pointer.new(0) - #else - #puts " skipping #{args.inspect}" - end - end - - def release! - replace! Fiddle::Pointer.new(0) - end - - def call(object_id) - finalize!(object_id) - end - end -end diff --git a/lib/rszr/image.rb b/lib/rszr/image.rb index 6fa46a0..c257b3a 100644 --- a/lib/rszr/image.rb +++ b/lib/rszr/image.rb @@ -1,95 +1,46 @@ module Rszr class Image - include Base class << self - alias_method :instantiate, :new - protected :instantiate - - def new(width, height) - ptr = with_lock { imlib_create_image(width, height) } - raise Error, 'Could not instantiate image' if ptr.null? - instantiate(ptr) - end - def load(path, options = {}) path = path.to_s raise FileNotFound unless File.exist?(path) - load_error = LoadError.new - ptr = with_lock do - imlib_set_cache_size(0) - imlib_load_image_with_error_return(path, load_error.ptr) - end - raise load_error, load_error.message if ptr.null? - return instantiate(ptr) + _load(path) end alias :open :load - protected - - def finalize(ptr) - with_lock do - imlib_context_set_image(ptr) - imlib_free_image - end - end - - end - - def initialize(ptr) - @handle = Handle.new(self, ptr) - end - - def width - with_image { imlib_image_get_width } end - - def height - with_image { imlib_image_get_height } - end - + def dimensions [width, height] end - - def format - str_ptr = with_image { imlib_image_format } - return if str_ptr.null? - str_ptr.to_s + + def inspect + fmt = format + fmt = " #{fmt.upcase}" if fmt + "#<#{self.class.name}:0x#{object_id.to_s(16)} #{width}x#{height}#{fmt}>" end - + def resize(*args) - instantiate(create_resized_image(*args)) + _resize(*calculate_size(*args)) end - + def resize!(*args) - handle.replace!(create_resized_image(*args)) - self - end - - def crop(*args) - instantiate(create_cropped_image(*args)) - end - - def crop!(*args) - handle.replace!(create_cropped_image(*args)) - self + _resize!(*calculate_size(*args)) end + + #def crop(*args) + # #instantiate(create_cropped_image(*args)) + #end + + #def crop!(*args) + # #replace!(create_cropped_image(*args)) + #end def save(path, format = nil) - with_image do - format ||= format_from_filename(path) || 'jpg' - imlib_image_set_format(format) - save_error = SaveError.new - imlib_save_image_with_error_return(path, save_error.ptr) - raise save_error, save_error.message if save_error.error? - true - end - end - - def inspect - "#<#{self.class.name}:0x#{object_id.to_s(16)} width=#{width} height=#{height} format=#{format.inspect}>" + format ||= format_from_filename(path) || 'jpg' + _save(path.to_s, format.to_s) end private @@ -102,7 +53,7 @@ def inspect # 400, 300, background: rgba # 400, 300, aspect: false - def create_resized_image(*args) + def calculate_size(*args) options = args.last.is_a?(Hash) ? args.pop : {} assert_valid_keys options, :crop, :background, :skew #:extend, :width, :height, :max_width, :max_height, :box original_width, original_height = width, height @@ -142,38 +93,24 @@ def create_resized_image(*args) else raise ArgumentError, "wrong number of arguments (#{args.size} for 1..2)" end - resized_ptr = with_image do - imlib_context_set_anti_alias(1) - imlib_create_cropped_scaled_image(x, y, imlib_image_get_width, imlib_image_get_height, new_width.round, new_height.round) - end - raise TransformationError, "error resizing image" if resized_ptr.null? - resized_ptr - end - - def create_cropped_image(x, y, width, height) - cropped_ptr = with_image { imlib_create_cropped_image(x, y, width, height) } - raise TransformationError, 'error cropping image' if cropped_ptr.null? - cropped_ptr + [x, y, original_width, original_height, new_width.round, new_height.round] end def format_from_filename(path) File.extname(path)[1..-1] end - - def context_set_image - imlib_context_set_image(ptr) - end - - def with_image - with_lock do - context_set_image - yield + + def assert_valid_keys(hsh, *valid_keys) + if unknown_key = (hsh.keys - valid_keys).first + raise ArgumentError.new("Unknown key: #{unknown_key.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}") end end - def instantiate(ptr) - self.class.send(:instantiate, ptr) - end - + #def create_cropped_image(x, y, width, height) + # cropped_ptr = with_image { imlib_create_cropped_image(x, y, width, height) } + # raise TransformationError, 'error cropping image' if cropped_ptr.null? + # cropped_ptr + #end + end end diff --git a/rszr.gemspec b/rszr.gemspec index 5d11ae5..62a8712 100644 --- a/rszr.gemspec +++ b/rszr.gemspec @@ -1,4 +1,3 @@ -# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'rszr/version' @@ -11,17 +10,19 @@ Gem::Specification.new do |s| s.summary = %q{Fast image resizer} s.description = %q{Fast image resizer - do one thing and do it fast.} - s.licenses = ['MIT'] - s.homepage = "https://github.com/mtgrosser/rszr" + s.licenses = %w[MIT] + s.homepage = 'https://github.com/mtgrosser/rszr' s.files = Dir['{lib}/**/*.rb', 'LICENSE', 'README.md', 'Rakefile'] - s.require_paths = ["lib"] + s.require_paths = %w[lib ext] - s.add_development_dependency "bundler", "~> 1.9" - s.add_development_dependency "rake", "~> 10.0" - s.add_development_dependency "byebug" - s.add_development_dependency "rspec" - s.add_development_dependency "simplecov" - s.add_development_dependency "gd2-ffij" - s.add_development_dependency "mini_magick" + s.add_development_dependency 'bundler', '~> 1.9' + s.add_development_dependency 'rake', '~> 10.0' + s.add_development_dependency 'rake-compiler' + s.add_development_dependency 'byebug' + s.add_development_dependency 'rspec' + s.add_development_dependency 'simplecov' + s.add_development_dependency 'gd2-ffij' + s.add_development_dependency 'mini_magick' + s.add_development_dependency 'memory_profiler' end diff --git a/spec/rszr_spec.rb b/spec/rszr_spec.rb index df050dd..817e87c 100644 --- a/spec/rszr_spec.rb +++ b/spec/rszr_spec.rb @@ -142,11 +142,11 @@ context 'Garbage collection' do it 'releases instances' do - GC.start + 10.times { GC.start(full_mark: true, immediate_sweep: true); sleep 0.5; print '.' } expect(ObjectSpace.each_object(Rszr::Image).count).to eq(0) 20.times { Rszr::Image.load(RSpec.root.join('images/bacon.png')) } expect(ObjectSpace.each_object(Rszr::Image).count).to be > 0 - GC.start + 5.times { GC.start(full_mark: true, immediate_sweep: true); sleep 0.5; print '.' } expect(ObjectSpace.each_object(Rszr::Image).count).to eq(0) end @@ -159,26 +159,26 @@ def data end def resize - image = nil - Tempfile.open('src') do |file| - file.binmode - file.write data - image = Rszr::Image.load(file.path) - file.close(true) - end - image.resize!(200, :auto) - Tempfile.open('dst') do |file| - image.save(file.path) - file.close(true) + Tempfile.open('src') do |src_file| + src_file.binmode + src_file.write data + Rszr::Image.open(src_file.path) do |image| + image.resize!(200, :auto) + Tempfile.open('dst') do |dst_file| + image.save(dst_file.path) + dst_file.close(true) + end + end + src_file.close(true) end end - it 'synchronizes access to imlib2 context' do + it 'synchronizes access to imlib2 context by GIL' do threads = [] 10.times do |t| threads << Thread.new do - 100.times do |i| - print '.' # puts " #{t}/#{i}" + 1000.times do |i| + print '.' resize end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 45bd4c2..ba950b6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -88,4 +88,4 @@ def self.root @spec_root ||= Pathname.new(__FILE__).dirname end -end \ No newline at end of file +end