diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e7a3a0..8d2a505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Rsze 1.5.0 (Feb 6, 2024) + +* Image desaturation (grayscale) + ## Rszr 1.4.0 (Jan 11, 2024) * Fix `load_data` (@mantas) diff --git a/README.md b/README.md index 96daa5c..9971028 100644 --- a/README.md +++ b/README.md @@ -238,6 +238,14 @@ image.contrast(0.5) # gamma image.gamma(1.1) + +# convert to grayscale (automatic mode) +image.desaturate + +# convert to grayscale with mode +image.desaturate(:lightness) +image.desaturate(:luminosity) +image.desaturate(:average) ``` ### Image auto orientation diff --git a/ext/rszr/image.c b/ext/rszr/image.c index b2fd038..4a33932 100644 --- a/ext/rszr/image.c +++ b/ext/rszr/image.c @@ -394,6 +394,54 @@ static VALUE rszr_image__sharpen_bang(VALUE self, VALUE rb_radius) } +static void rszr_desaturate_pixel(rszr_raw_pixel * pixel, int mode) +{ + uint8_t grey; + if (mode == 2 || (mode == 0 && (pixel->blue > pixel->red && pixel->blue > pixel->green))) { + // lightness + grey = (pixel->blue + (pixel->red > pixel->green ? pixel->green : pixel->red)) / 2; + } else if (mode == 1 || mode == 0) { + // luminosity + grey = 0.21 * pixel->red + 0.72 * pixel->green + 0.07 * pixel->blue; + } else { + // average + grey = (pixel->red + pixel->green + pixel->blue) / 3; + } + pixel->red = grey; + pixel->green = grey; + pixel->blue = grey; +} + +static VALUE rszr_image__desaturate_bang(VALUE self, VALUE rb_mode) +{ + rszr_image_handle * handle; + rszr_raw_pixel * pixels; + uint64_t size; + int mode; + + mode = NUM2INT(rb_mode); + + Data_Get_Struct(self, rszr_image_handle, handle); + + imlib_context_set_image(handle->image); + + pixels = (rszr_raw_pixel *) imlib_image_get_data(); + if (pixels == NULL) { + rb_raise(eRszrTransformationError, "error desaturating image"); + return Qnil; + } + + size = imlib_image_get_width() * imlib_image_get_height(); + for (uint64_t i = 0; i < size; i++) { + rszr_desaturate_pixel(&pixels[i], mode); + } + + imlib_image_put_back_data((uint32_t *) pixels); + + return self; +} + + static Imlib_Image rszr_create_cropped_scaled_image(const Imlib_Image image, VALUE rb_src_x, VALUE rb_src_y, VALUE rb_src_w, VALUE rb_src_h, VALUE rb_dst_w, VALUE rb_dst_h) { Imlib_Image resized_image; @@ -688,15 +736,16 @@ void Init_rszr_image() rb_define_protected_method(cImage, "_format", rszr_image__format_get, 0); rb_define_protected_method(cImage, "_format=", rszr_image__format_set, 1); - rb_define_private_method(cImage, "_initialize", rszr_image__initialize, 2); - rb_define_private_method(cImage, "_resize", rszr_image__resize, 7); - rb_define_private_method(cImage, "_crop", rszr_image__crop, 5); - rb_define_private_method(cImage, "_turn!", rszr_image__turn_bang, 1); - rb_define_private_method(cImage, "_rotate", rszr_image__rotate, 2); - rb_define_private_method(cImage, "_sharpen!", rszr_image__sharpen_bang, 1); - rb_define_private_method(cImage, "_pixel", rszr_image__pixel_get, 2); - rb_define_private_method(cImage, "_blend", rszr_image__blend, 11); - rb_define_private_method(cImage, "_rectangle!", rszr_image__rectangle_bang, 5); + rb_define_private_method(cImage, "_initialize", rszr_image__initialize, 2); + rb_define_private_method(cImage, "_resize", rszr_image__resize, 7); + rb_define_private_method(cImage, "_crop", rszr_image__crop, 5); + rb_define_private_method(cImage, "_turn!", rszr_image__turn_bang, 1); + rb_define_private_method(cImage, "_rotate", rszr_image__rotate, 2); + rb_define_private_method(cImage, "_sharpen!", rszr_image__sharpen_bang, 1); + rb_define_private_method(cImage, "_desaturate!", rszr_image__desaturate_bang, 1); + rb_define_private_method(cImage, "_pixel", rszr_image__pixel_get, 2); + rb_define_private_method(cImage, "_blend", rszr_image__blend, 11); + rb_define_private_method(cImage, "_rectangle!", rszr_image__rectangle_bang, 5); rb_define_private_method(cImage, "_save", rszr_image__save, 4); } diff --git a/ext/rszr/image.h b/ext/rszr/image.h index 77f6369..730dbf0 100644 --- a/ext/rszr/image.h +++ b/ext/rszr/image.h @@ -5,6 +5,10 @@ typedef struct { Imlib_Image image; } rszr_image_handle; +typedef struct { + uint8_t blue, green, red, alpha; //alpha, red, green, blue; +} rszr_raw_pixel; + extern VALUE cImage; void Init_rszr_image(); diff --git a/lib/rszr/image.rb b/lib/rszr/image.rb index d00a268..4d649ac 100644 --- a/lib/rszr/image.rb +++ b/lib/rszr/image.rb @@ -2,6 +2,7 @@ module Rszr class Image GRAVITIES = [true, :center, :n, :nw, :w, :sw, :s, :se, :e, :ne].freeze BLENDING_MODES = %i[copy add subtract reshade].freeze + DESATURATION_MODES = %i[dynamic luminosity lightness average].freeze extend Identification include Buffered @@ -119,6 +120,16 @@ def blur!(radius) _sharpen!(-radius) end + def desaturate!(mode = :dynamic) + _mode = DESATURATION_MODES.index(mode) + raise ArgumentError, 'illegal mode' unless _mode + _desaturate!(_mode); + end + + def desaturate(*args, **opts) + dup.desaturate!(*args, **opts) + end + def filter(filter_expr) dup.filter!(filter_expr) end diff --git a/lib/rszr/version.rb b/lib/rszr/version.rb index 12c1405..285fc90 100644 --- a/lib/rszr/version.rb +++ b/lib/rszr/version.rb @@ -1,3 +1,3 @@ module Rszr - VERSION = '1.4.0' + VERSION = '1.5.0' end diff --git a/spec/rszr_spec.rb b/spec/rszr_spec.rb index fb64a0c..1ea979b 100644 --- a/spec/rszr_spec.rb +++ b/spec/rszr_spec.rb @@ -318,6 +318,16 @@ it 'filters' do expect(@image.filter('bump_map( map=tint(red=50,tint=200), blue=10 );').dimensions).to eq(@image.dimensions) end + + %i[dynamic luminosity lightness average].each do |mode| + it "desaturates in mode #{mode}" do + @image.desaturate!(mode) + pixel = @image[500, 500] + expect(pixel.red).to eq(pixel.green) + expect(pixel.blue).to eq(pixel.red) + end + end + end end