diff --git a/ext/RMagick/rmagick.h b/ext/RMagick/rmagick.h index 8aa6f3faf..f982c8062 100644 --- a/ext/RMagick/rmagick.h +++ b/ext/RMagick/rmagick.h @@ -352,6 +352,7 @@ typedef enum _QuantumExpressionOperator EXTERN VALUE Module_Magick; EXTERN VALUE Class_ImageList; EXTERN VALUE Class_Info; +EXTERN VALUE Class_KernelInfo; EXTERN VALUE Class_Draw; EXTERN VALUE Class_DrawOptions; EXTERN VALUE Class_Image; @@ -417,6 +418,9 @@ EXTERN VALUE Class_StretchType; EXTERN VALUE Class_StyleType; EXTERN VALUE Class_WeightType; EXTERN VALUE Class_VirtualPixelMethod; +EXTERN VALUE Class_GeometryFlags; +EXTERN VALUE Class_MorphologyMethod; +EXTERN VALUE Class_KernelInfoType; /** * Commonly-used IDs @@ -839,6 +843,21 @@ extern VALUE rm_info_new(void); extern DisposeType rm_dispose_to_enum(const char *); extern GravityType rm_gravity_to_enum(const char *); +// rmkinfo.c + +extern VALUE KernelInfo_alloc(VALUE); + +extern VALUE KernelInfo_initialize(VALUE, VALUE); +extern VALUE KernelInfo_zero_nans(VALUE); +extern VALUE KernelInfo_unity_add(VALUE, VALUE); +extern VALUE KernelInfo_show(VALUE); +extern VALUE KernelInfo_scale(VALUE, VALUE, VALUE); +extern VALUE KernelInfo_scale_geometry(VALUE, VALUE); +extern VALUE KernelInfo_clone(VALUE); + +extern VALUE KernelInfo_builtin(VALUE, VALUE, VALUE); + + // rmimage.c ATTR_WRITER(Image, alpha) ATTR_ACCESSOR(Image, background_color) @@ -973,6 +992,8 @@ extern VALUE Image_contrast(int, VALUE *, VALUE); extern VALUE Image_contrast_stretch_channel(int, VALUE *, VALUE); extern VALUE Image_convolve(VALUE, VALUE, VALUE); extern VALUE Image_convolve_channel(int, VALUE *, VALUE); +extern VALUE Image_morphology(VALUE, VALUE, VALUE, VALUE); +extern VALUE Image_morphology_channel(VALUE, VALUE, VALUE, VALUE, VALUE); extern VALUE Image_copy(VALUE); extern VALUE Image_crop(int, VALUE *, VALUE); extern VALUE Image_crop_bang(int, VALUE *, VALUE); @@ -1191,6 +1212,7 @@ extern VALUE Enum_initialize(VALUE, VALUE, VALUE); extern VALUE Enum_to_s(VALUE); extern VALUE Enum_to_i(VALUE); extern VALUE Enum_spaceship(VALUE, VALUE); +extern VALUE Enum_bitwise_or(VALUE, VALUE); extern VALUE Enum_case_eq(VALUE, VALUE); extern VALUE Enum_type_initialize(VALUE, VALUE, VALUE); extern VALUE Enum_type_each(VALUE); diff --git a/ext/RMagick/rmenum.c b/ext/RMagick/rmenum.c index c3ca72b10..a0d44da57 100644 --- a/ext/RMagick/rmenum.c +++ b/ext/RMagick/rmenum.c @@ -193,6 +193,42 @@ Enum_spaceship(VALUE self, VALUE other) return rb_funcall(CLASS_OF(self), rm_ID_spaceship, 1, CLASS_OF(other)); } +/** + * Bitwise OR for enums + * + * Ruby usage: + * - @verbatim Enum1 | Enum2 @endverbatim + * + * Notes: + * - Enums must be instances of the same class. + * + * @param Enum1 this object + * @param Enum2 another enum + * @return new Enum instance + */ +VALUE +Enum_bitwise_or(VALUE self, VALUE another) +{ + VALUE new_enum, cls; + MagickEnum *this, *that, *new_enum_data; + + cls = CLASS_OF(self); + if (CLASS_OF(another) != cls) + { + rb_raise(rb_eArgError, "Expected class %s but got %s", rb_class2name(cls), rb_class2name(CLASS_OF(another))); + } + + new_enum = Enum_alloc(cls); + + Data_Get_Struct(self, MagickEnum, this); + Data_Get_Struct(another, MagickEnum, that); + Data_Get_Struct(new_enum, MagickEnum, new_enum_data); + + new_enum_data->id = rb_to_id(rb_sprintf("%s|%s", rb_id2name(this->id), rb_id2name(that->id))); + new_enum_data->val = this->val | that->val; + + return new_enum; +} /** * Return the name of an enum. diff --git a/ext/RMagick/rmimage.c b/ext/RMagick/rmimage.c index 64de23a30..4dbe3e805 100644 --- a/ext/RMagick/rmimage.c +++ b/ext/RMagick/rmimage.c @@ -4088,6 +4088,78 @@ Image_contrast_stretch_channel(int argc, VALUE *argv, VALUE self) return rm_image_new(new_image); } +/** Apply a user supplied kernel to the image according to the given mophology method. + * + * Ruby Usage: + * - @verbatim Image#morphology(method, iterations, kernel) @endverbatim + * + * @param self this object + * @param method is one of morphology methods defined by Magick::MorphologyMethod + * @param iterations apply the operation this many times (or no change). + * A value of -1 means loop until no change found. + * How this is applied may depend on the morphology method. + * Typically this is a value of 1. + * @param kernel morphology kernel to apply + */ + +VALUE +Image_morphology(VALUE self, VALUE method_v, VALUE iterations, VALUE kernel_v) +{ + static VALUE default_channels_const = 0; + + if(!default_channels_const) + default_channels_const = rb_const_get(Module_Magick, rb_intern("DefaultChannels")); + + return Image_morphology_channel(self, default_channels_const, method_v, iterations, kernel_v); +} + +/** Apply a user supplied kernel to the image channel according to the given mophology method. + * + * Ruby Usage: + * - @verbatim Image#morphology_channel(channel, method, iterations, kernel) @endverbatim + * + * @param self this object + * @param channel is a channel type defined by Magick::ChannelType + * @param method is one of morphology methods defined by Magick::MorphologyMethod + * @param iterations apply the operation this many times (or no change). + * A value of -1 means loop until no change found. + * How this is applied may depend on the morphology method. + * Typically this is a value of 1. + * @param kernel morphology kernel to apply + */ + +VALUE +Image_morphology_channel(VALUE self, VALUE channel_v, VALUE method_v, VALUE iterations, VALUE kernel_v) +{ + Image *image, *new_image; + ExceptionInfo exception; + MorphologyMethod method; + ChannelType channel; + KernelInfo *kernel; + + VALUE_TO_ENUM(method_v, method, MorphologyMethod); + VALUE_TO_ENUM(channel_v, channel, ChannelType); + Check_Type(iterations, T_FIXNUM); + + if (TYPE(kernel_v) == T_STRING) + kernel_v = rb_class_new_instance(1, &kernel_v, Class_KernelInfo); + + if (!rb_obj_is_kind_of(kernel_v, Class_KernelInfo)) + rb_raise(rb_eArgError, "expected String or Magick::KernelInfo"); + + Data_Get_Struct(kernel_v, KernelInfo, kernel); + + image = rm_check_destroyed(self); + GetExceptionInfo(&exception); + + new_image = MorphologyImageChannel(image, channel, method, NUM2LONG(iterations), kernel, &exception); + rm_check_exception(&exception, new_image, DestroyOnError); + DestroyExceptionInfo(&exception); + + rm_ensure_result(new_image); + return rm_image_new(new_image); +} + /** * Apply a custom convolution kernel to the image. * diff --git a/ext/RMagick/rmkinfo.c b/ext/RMagick/rmkinfo.c new file mode 100644 index 000000000..72276dda0 --- /dev/null +++ b/ext/RMagick/rmkinfo.c @@ -0,0 +1,247 @@ +/************************************************************************//** + * KernelInfo class definitions for RMagick. + * + * Copyright © RMagick Project + * + * @file rmkinfo.c + * @version $Id: rmkinfo.c,v 1.0 2011/11/29 15:33:14 naquad Exp $ + * @author Naquad + ****************************************************************************/ + +#include "rmagick.h" + +/** + * If there's a kernel info, delete it before destroying the KernelInfo + * + * No Ruby usage (internal function) + * + * @param kernel pointer to the KernelInfo object associated with instance + */ + +static void +rm_kernel_info_destroy(void *kernel) +{ + if (kernel) + DestroyKernelInfo((KernelInfo*)kernel); +} + +/** + * Create a KernelInfo object. + * + * No Ruby usage (internal function) + * + * @param class the Ruby class to use + * @return a new KernelInfo object + */ +VALUE +KernelInfo_alloc(VALUE class) +{ + return Data_Wrap_Struct(class, NULL, rm_kernel_info_destroy, NULL); +} + +/** + * KernelInfo object constructor + * + * Ruby usage: + * - @verbatim KernelInfo#initialize @endverbatim + * + * @param self this object + * @param kernel_string kernel info string representation to be parsed + * @return self + */ +VALUE +KernelInfo_initialize(VALUE self, VALUE kernel_string) +{ + KernelInfo *kernel; + + Check_Type(kernel_string, T_STRING); + + kernel = AcquireKernelInfo(StringValueCStr(kernel_string)); + + if (kernel == NULL) + rb_raise(rb_eRuntimeError, "failed to parse kernel string"); + + DATA_PTR(self) = kernel; + + return self; +} + +/** + * Zero kerne NaNs. + * + * Ruby usage: + * - @verbatim KernelInfo#zero_nans @endverbatim + * + * @param self this object + */ +VALUE +KernelInfo_zero_nans(VALUE self) +{ + ZeroKernelNans((KernelInfo*)DATA_PTR(self)); + return Qnil; +} + +/** + * Adds a given amount of the 'Unity' Convolution Kernel to the given pre-scaled and normalized Kernel. + * + * Ruby usage: + * - @verbatim KernelInfo#unity_add(scale) @endverbatim + * + * @param self this object + * @param scale scale to add + */ +VALUE +KernelInfo_unity_add(VALUE self, VALUE scale) +{ + if (!FIXNUM_P(scale)) + Check_Type(scale, T_FLOAT); + + UnityAddKernelInfo((KernelInfo*)DATA_PTR(self), NUM2DBL(scale)); + return Qnil; +} + +/** + * Dumps KernelInfo object to stderr + * + * Ruby usage: + * - @verbatim KernelInfo#show @endverbatim + * + * @param self this object + */ +VALUE +KernelInfo_show(VALUE self) +{ + ShowKernelInfo((KernelInfo*)DATA_PTR(self)); + return Qnil; +} + +/** + * Scales the given kernel list by the given amount, with or without normalization + * of the sum of the kernel values (as per given flags). + * + * Ruby usage: + * - @verbatim KernelInfo#scale(scale, flags) @endverbatim + * + * @param scale scale to use + * @param flags one of Magick::NormalizeValue, Magick::CorrelateNormalizeValue, + * and/or Magick::PercentValue + * @param self this object + */ +VALUE +KernelInfo_scale(VALUE self, VALUE scale, VALUE flags) +{ + GeometryFlags geoflags; + + if (!FIXNUM_P(scale)) + Check_Type(scale, T_FLOAT); + + if (rb_obj_is_instance_of(flags, Class_GeometryFlags)) + VALUE_TO_ENUM(flags, geoflags, GeometryFlags); + else + rb_raise(rb_eArgError, "expected Fixnum or Magick::GeometryFlags to specify flags"); + + ScaleKernelInfo((KernelInfo*)DATA_PTR(self), NUM2DBL(scale), geoflags); + return Qnil; +} + +/** + * Takes a geometry argument string, typically provided as a "-set option:convolve:scale {geometry}" user setting, + * and modifies the kernel according to the parsed arguments of that setting. + * + * Ruby usage: + * - @verbatim KernelInfo#scale_geometry(geometry) @endverbatim + * + * @param geometry geometry string to parse and apply + * @param self this object + */ +VALUE +KernelInfo_scale_geometry(VALUE self, VALUE geometry) +{ + Check_Type(geometry, T_STRING); + ScaleGeometryKernelInfo((KernelInfo*)DATA_PTR(self), StringValueCStr(geometry)); + return Qnil; +} + +/** + * Creates a new clone of the object so that its can be modified without effecting the original. + * + * Ruby usage: + * - @verbatim KernelInfo#clone @endverbatim + * + * @param self this object + * @return new KernelInfo instance + */ +VALUE +KernelInfo_clone(VALUE self) +{ + KernelInfo *kernel = CloneKernelInfo((KernelInfo*)DATA_PTR(self)); + return Data_Wrap_Struct(Class_KernelInfo, NULL, rm_kernel_info_destroy, kernel); +} + +/** + * Create new instance of KernelInfo with one of the 'named' built-in types of + * kernels used for special purposes such as gaussian blurring, skeleton + * pruning, and edge distance determination. + * + * Ruby usage: + * - @verbatim KernelInfo.builtin(kernel, geometry = nil) @endverbatim + * + * @parms kernel one of Magick::KernelInfoType enums: + * Magick::UndefinedKernel + * Magick::UnityKernel + * Magick::GaussianKernel + * Magick::DoGKernel + * Magick::LoGKernel + * Magick::BlurKernel + * Magick::CometKernel + * Magick::LaplacianKernel + * Magick::SobelKernel + * Magick::FreiChenKernel + * Magick::RobertsKernel + * Magick::PrewittKernel + * Magick::CompassKernel + * Magick::KirschKernel + * Magick::DiamondKernel + * Magick::SquareKernel + * Magick::RectangleKernel + * Magick::OctagonKernel + * Magick::DiskKernel + * Magick::PlusKernel + * Magick::CrossKernel + * Magick::RingKernel + * Magick::PeaksKernel + * Magick::EdgesKernel + * Magick::CornersKernel + * Magick::DiagonalsKernel + * Magick::LineEndsKernel + * Magick::LineJunctionsKernel + * Magick::RidgesKernel + * Magick::ConvexHullKernel + * Magick::ThinSEKernel + * Magick::SkeletonKernel + * Magick::ChebyshevKernel + * Magick::ManhattanKernel + * Magick::OctagonalKernel + * Magick::EuclideanKernel + * Magick::UserDefinedKernel + * @param geometry geometry to pass to default kernel + * @return KernelInfo instance + */ +VALUE +KernelInfo_builtin(VALUE self, VALUE what, VALUE geometry) +{ + KernelInfo *kernel; + KernelInfoType kernel_type; + GeometryInfo info; + + Check_Type(geometry, T_STRING); + VALUE_TO_ENUM(what, kernel_type, KernelInfoType); + ParseGeometry(StringValueCStr(geometry), &info); + + kernel = AcquireKernelBuiltIn(kernel_type, &info); + + if (!kernel) + rb_raise(rb_eRuntimeError, "failed to acquire builtin kernel"); + + return Data_Wrap_Struct(self, NULL, rm_kernel_info_destroy, kernel); +} diff --git a/ext/RMagick/rmmain.c b/ext/RMagick/rmmain.c index e5ebc92b6..3af750cdb 100644 --- a/ext/RMagick/rmmain.c +++ b/ext/RMagick/rmmain.c @@ -334,6 +334,8 @@ Init_RMagick2(void) rb_define_method(Class_Image, "contrast_stretch_channel", Image_contrast_stretch_channel, -1); rb_define_method(Class_Image, "convolve", Image_convolve, 2); rb_define_method(Class_Image, "convolve_channel", Image_convolve_channel, -1); + rb_define_method(Class_Image, "morphology", Image_morphology, 3); + rb_define_method(Class_Image, "morphology_channel", Image_morphology_channel, 3); rb_define_method(Class_Image, "copy", Image_copy, 0); rb_define_method(Class_Image, "crop", Image_crop, -1); rb_define_method(Class_Image, "crop!", Image_crop_bang, -1); @@ -745,6 +747,25 @@ Init_RMagick2(void) DCL_ATTR_ACCESSOR(Info, units) DCL_ATTR_ACCESSOR(Info, view) + /*-----------------------------------------------------------------------*/ + /* Class Magick::KernelInfo */ + /*-----------------------------------------------------------------------*/ + + Class_KernelInfo = rb_define_class_under(Module_Magick, "KernelInfo", rb_cObject); + + rb_define_alloc_func(Class_KernelInfo, KernelInfo_alloc); + + rb_define_method(Class_KernelInfo, "initialize", KernelInfo_initialize, 1); + rb_define_method(Class_KernelInfo, "zero_nans", KernelInfo_zero_nans, 0); + rb_define_method(Class_KernelInfo, "unity_add", KernelInfo_unity_add, 1); + rb_define_method(Class_KernelInfo, "show", KernelInfo_show, 0); + rb_define_method(Class_KernelInfo, "scale", KernelInfo_scale, 2); + rb_define_method(Class_KernelInfo, "scale_geometry", KernelInfo_scale_geometry, 1); + rb_define_method(Class_KernelInfo, "clone", KernelInfo_clone, 0); + rb_define_method(Class_KernelInfo, "dup", KernelInfo_clone, 0); + + rb_define_singleton_method(Class_KernelInfo, "builtin", KernelInfo_builtin, 2); + /*-----------------------------------------------------------------------*/ /* Class Magick::Image::PolaroidOptions */ @@ -841,6 +862,7 @@ Init_RMagick2(void) rb_define_method(Class_Enum, "to_i", Enum_to_i, 0); rb_define_method(Class_Enum, "<=>", Enum_spaceship, 1); rb_define_method(Class_Enum, "===", Enum_case_eq, 1); + rb_define_method(Class_Enum, "|", Enum_bitwise_or, 1); // AlignType constants DEF_ENUM(AlignType) @@ -1548,6 +1570,79 @@ Init_RMagick2(void) ENUMERATOR(LighterWeight) END_ENUM + // For KernelInfo scaling + DEF_ENUM(GeometryFlags) + ENUMERATOR(NormalizeValue) + ENUMERATOR(CorrelateNormalizeValue) + ENUMERATOR(PercentValue) + END_ENUM + + // Morphology methods + DEF_ENUM(MorphologyMethod) + ENUMERATOR(UndefinedMorphology) + ENUMERATOR(ConvolveMorphology) + ENUMERATOR(CorrelateMorphology) + ENUMERATOR(ErodeMorphology) + ENUMERATOR(DilateMorphology) + ENUMERATOR(ErodeIntensityMorphology) + ENUMERATOR(DilateIntensityMorphology) + ENUMERATOR(DistanceMorphology) + ENUMERATOR(OpenMorphology) + ENUMERATOR(CloseMorphology) + ENUMERATOR(OpenIntensityMorphology) + ENUMERATOR(CloseIntensityMorphology) + ENUMERATOR(SmoothMorphology) + ENUMERATOR(EdgeInMorphology) + ENUMERATOR(EdgeOutMorphology) + ENUMERATOR(EdgeMorphology) + ENUMERATOR(TopHatMorphology) + ENUMERATOR(BottomHatMorphology) + ENUMERATOR(HitAndMissMorphology) + ENUMERATOR(ThinningMorphology) + ENUMERATOR(ThickenMorphology) + ENUMERATOR(VoronoiMorphology) + END_ENUM + + DEF_ENUM(KernelInfoType) + ENUMERATOR(UndefinedKernel) + ENUMERATOR(UnityKernel) + ENUMERATOR(GaussianKernel) + ENUMERATOR(DoGKernel) + ENUMERATOR(LoGKernel) + ENUMERATOR(BlurKernel) + ENUMERATOR(CometKernel) + ENUMERATOR(LaplacianKernel) + ENUMERATOR(SobelKernel) + ENUMERATOR(FreiChenKernel) + ENUMERATOR(RobertsKernel) + ENUMERATOR(PrewittKernel) + ENUMERATOR(CompassKernel) + ENUMERATOR(KirschKernel) + ENUMERATOR(DiamondKernel) + ENUMERATOR(SquareKernel) + ENUMERATOR(RectangleKernel) + ENUMERATOR(OctagonKernel) + ENUMERATOR(DiskKernel) + ENUMERATOR(PlusKernel) + ENUMERATOR(CrossKernel) + ENUMERATOR(RingKernel) + ENUMERATOR(PeaksKernel) + ENUMERATOR(EdgesKernel) + ENUMERATOR(CornersKernel) + ENUMERATOR(DiagonalsKernel) + ENUMERATOR(LineEndsKernel) + ENUMERATOR(LineJunctionsKernel) + ENUMERATOR(RidgesKernel) + ENUMERATOR(ConvexHullKernel) + ENUMERATOR(ThinSEKernel) + ENUMERATOR(SkeletonKernel) + ENUMERATOR(ChebyshevKernel) + ENUMERATOR(ManhattanKernel) + ENUMERATOR(OctagonalKernel) + ENUMERATOR(EuclideanKernel) + ENUMERATOR(UserDefinedKernel) + END_ENUM + /*-----------------------------------------------------------------------*/ /* Struct classes */ /*-----------------------------------------------------------------------*/