class UIImage

| | REALLY HANDY STUFF! | many of these methods are translated from: | <www.catamount.com/blog/uiimage-extensions-for-cutting-scaling-and-rotating-uiimages/> | <www.catamount.com/forums/viewtopic.php?f=21&t=967> |

Constants

SugarCube1024_TallSuffix
SugarCube568_TallSuffix
SugarCube667_TallSuffix
SugarCube736_TallSuffix

Public Class Methods

canvas(options_or_size={}, more_options={}, &block) click to toggle source

Easily create a UIImage by using this factory method, and do your drawing in a block. The core graphics context will be passed to the block you provide. To create a canvas based on an image, use the instance method.

@example

white_square = UIImage.canvas([100, 100]) do |context|
  :white.uicolor.set
  CGContextAddRect(context, [[0, 0], [100, 100]])
  CGContextDrawPath(context, KCGPathFill)
end

:size is required, :scale defaults to the screen scale, and :opaque is false by default.

The first argument can be a size, or an options dict

# File lib/ios/sugarcube-image/uiimage.rb, line 25
def canvas(options_or_size={}, more_options={}, &block)
  if options_or_size.is_a?(NSDictionary)
    options = options_or_size
    size = options[:size]
  else
    options = more_options
    size = options_or_size
  end
  raise ":size is required in #{self.name}##canvas" unless size
  scale = options.fetch(:scale, 0.0)  # not a typo, 0.0 indicates "use default"
  opaque = options.fetch(:opaque, false)

  UIGraphicsBeginImageContextWithOptions(size, opaque, scale)
  block.call(UIGraphicsGetCurrentContext()) if block
  new_image = UIGraphicsGetImageFromCurrentImageContext()
  UIGraphicsEndImageContext()
  return new_image
end
imageNamed(name) click to toggle source

now we've got sugarcube568_imageNamed and imageNamed_old to load the respective versions

# File lib/ios/sugarcube-568/uiimage.rb, line 40
def imageNamed(name)
  sugarcube568_imageNamed(name) || imageNamed_old(name)
end
Also aliased as: imageNamed_old
imageNamed_old(name)
Alias for: imageNamed
sugarcube568_imageNamed(name) click to toggle source
# File lib/ios/sugarcube-568/uiimage.rb, line 8
def sugarcube568_imageNamed(name)
  case UIScreen.mainScreen.bounds.size.height
  when 568
    taller_image_ext = SugarCube568_TallSuffix
  when 667
    taller_image_ext = SugarCube667_TallSuffix
  when 736
    taller_image_ext = SugarCube736_TallSuffix
  when 1024
    taller_image_ext = SugarCube1024_TallSuffix
  else
    taller_image_ext = false
  end

  if ( taller_image_ext && name.length > 0 && name.rangeOfString(taller_image_ext).location == NSNotFound )
    # Check if is there a path extension or not
    test_name = name
    if test_name.pathExtension.length > 0
      test_name = test_name.stringByDeletingPathExtension.stringByAppendingString(taller_image_ext).stringByAppendingPathExtension(name.pathExtension)
    else
      test_name = test_name.stringByAppendingString(taller_image_ext)
    end

    return imageNamed_old(test_name)
  end

  return nil
end

Public Instance Methods

<<(image) click to toggle source

Merges the two images. The target is drawn first, `image` is drawn on top. The two images are centered, and the maximum size is used so that both images fit on the canvas.

# File lib/ios/sugarcube-image/uiimage.rb, line 49
def <<(image)
  self.merge(image, at: :center, stretch: true)
end
alignment_rect(insets=UIEdgeInsetsZero) click to toggle source

| | imageWithAlignmentRectInsets |

# File lib/ios/sugarcube-image/uiimage.rb, line 517
def alignment_rect(insets=UIEdgeInsetsZero)
  imageWithAlignmentRectInsets(insets)
end
apply_filter(filter) click to toggle source

Returns a CIImage with the filter applied to the receiver. The return value is a CIImage object, which also defines `|` to work the same way, so filters can be chained.

@example

image = 'test'.uiimage
new_image = image.apply_filter(CIFilter.gaussian_blur)
# File lib/ios/sugarcube-image/uiimage.rb, line 389
def apply_filter(filter)
  filter.setValue(self.ciimage, forKey: 'inputImage')
  return filter.valueForKey('outputImage')
end
at_scale(scale) click to toggle source
# File lib/ios/sugarcube-image/uiimage.rb, line 600
def at_scale(scale)
  if scale == self.scale
    return self
  end

  new_size = self.size
  new_size.width = new_size.width * self.scale / scale
  new_size.height = new_size.height * self.scale / scale

  return self.canvas(size: new_size, scale: scale) do
    thumbnail_rect = CGRect.new([0, 0], new_size)
    self.drawInRect(thumbnail_rect)
  end
end
avg_color() click to toggle source
# File lib/ios/sugarcube-image/uiimage.rb, line 578
def avg_color
  colorSpace = CGColorSpaceCreateDeviceRGB()
  rgba = Pointer.new(:uchar, 4)
  context = CGBitmapContextCreate(rgba, 1, 1, 8, 4, colorSpace, KCGImageAlphaPremultipliedLast | KCGBitmapByteOrder32Big)

  CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), self.CGImage)

  if rgba[3] > 0
      alpha = rgba[3] / 255.0
      multiplier = alpha / 255.0
      return UIColor.colorWithRed(rgba[0] * multiplier,
                            green:rgba[1] * multiplier,
                             blue:rgba[2] * multiplier,
                            alpha:alpha)
  else
      return UIColor.colorWithRed(rgba[0] / 255.0,
                            green:rgba[1] / 255.0,
                             blue:rgba[2] / 255.0,
                            alpha:rgba[3] / 255.0)
  end
end
canvas(options_or_size={}, more_options={}, &block) click to toggle source

the first argument can be a size, or an options dict

# File lib/ios/sugarcube-image/uiimage.rb, line 626
def canvas(options_or_size={}, more_options={}, &block)
  if options_or_size.is_a?(NSDictionary)
    options = options_or_size
  else
    options = more_options
    options[:size] = options_or_size
  end

  unless options[:size]
    options[:size] = self.size
  end

  unless options[:scale]
    options[:scale] = self.scale
  end

  self.class.canvas(options) do |context|
    block.call(context) if block
  end
end
cgcolor(alpha=nil) click to toggle source
# File lib/ios/sugarcube-color/uiimage.rb, line 13
def cgcolor(alpha=nil)
  uicolor(alpha).CGColor
end
cgimage() click to toggle source

Returns a CGImageRef. Alias for `CGImage`.

# File lib/ios/sugarcube-image/uiimage.rb, line 395
def cgimage
  return self.CGImage
end
ciimage() click to toggle source

Returns a CIImage.

# File lib/ios/sugarcube-image/uiimage.rb, line 400
def ciimage
  return CIImage.imageWithCGImage(self.CGImage)
end
color_at(point) click to toggle source

Oddly enough, this method doesn't seem to have retina support

# File lib/ios/sugarcube-image/uiimage.rb, line 547
def color_at(point)
  point = SugarCube::CoreGraphics::Point(point)
  point.x *= self.scale
  point.y *= self.scale

  # First get the image into your data buffer
  cgimage = self.CGImage
  width = CGImageGetWidth(cgimage)
  height = CGImageGetHeight(cgimage)
  bytes_per_pixel = 4
  bits_per_component = 8
  bytes_per_row = bytes_per_pixel * width
  @raw_data || begin
    color_space = CGColorSpaceCreateDeviceRGB()
    @raw_data = Pointer.new(:uchar, height * width * 4)
    context = CGBitmapContextCreate(@raw_data, width, height, bits_per_component, bytes_per_row, color_space, KCGImageAlphaPremultipliedLast | KCGBitmapByteOrder32Big)

    CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgimage)
  end

  # Now @raw_data contains the image data in the RGBA8888 pixel format.
  xx = point.x.round
  yy = point.y.round
  byte_index = (bytes_per_row * yy) + xx * bytes_per_pixel
  red = @raw_data[byte_index]
  green = @raw_data[byte_index + 1]
  blue = @raw_data[byte_index + 2]
  alpha = @raw_data[byte_index + 3]
  return [red, green, blue].uicolor(alpha / 255.0)
end
crop(rect) click to toggle source

Returns a cropped UIImage. The easiest way is to check for a CGImage backing, but if this image uses a CIImage backing, we draw a new (cropped) image.

# File lib/ios/sugarcube-image/uiimage.rb, line 121
def crop(rect)
  rect = SugarCube::CoreGraphics::Rect(rect)
  if self.CGImage
    if self.scale > 1.0
      rect = CGRectMake(rect.origin.x * self.scale,
                        rect.origin.y * self.scale,
                        rect.size.width * self.scale,
                        rect.size.height * self.scale)
    end

    cgimage = CGImageCreateWithImageInRect(self.CGImage, rect)
    return UIImage.imageWithCGImage(cgimage, scale:self.scale, orientation:self.imageOrientation)
  else
    return self.canvas(size: rect.size) do |context|
      self.drawAtPoint(CGPoint.new(-rect.origin.x, -rect.origin.y))
    end
  end
end
darken(options={}) click to toggle source

Accepts two options: brightness (default: 0.0) and saturation (default: 0.0) Returns a darkened version of the image.

# File lib/ios/sugarcube-image/uiimage.rb, line 406
def darken(options={})
  filter_options = {
    'inputSaturation' => options[:saturation] || 0,
    'inputBrightness' => options[:brightness] || 0,
  }

  darken_filter = CIFilter.color_controls(filter_options)
  output = self.apply_filter(darken_filter)
  return UIImage.imageWithCIImage(output, scale:self.scale, orientation:self.imageOrientation)
end
draw(options={}, &block) click to toggle source

Using the image as the background, you can use this method to draw anything on top, like text or other images.

# File lib/ios/sugarcube-image/uiimage.rb, line 617
def draw(options={}, &block)
  at = options[:at] || [0, 0]
  return self.canvas(options) do |context|
    self.drawAtPoint(at)
    block.call(context) if block
  end
end
gaussian_blur(*args) click to toggle source

Apply a gaussian filter @options radius, default: 10

@example

image.gaussian_blur(radius: 5)
image.gaussian_blur(5)  # :radius is the default option
# File lib/ios/sugarcube-image/uiimage.rb, line 435
def gaussian_blur(*args)
  output = self.apply_filter(CIFilter.gaussian_blur(*args))
  return UIImage.imageWithCIImage(output, scale:self.scale, orientation:self.imageOrientation)
end
height() click to toggle source
# File lib/ios/sugarcube-ui/uiimage.rb, line 13
def height
  self.size.height
end
in_rect(rect) click to toggle source

This method is used to crop an image. Scale (retina or non-retina) is preserved.

@param rect [CGRect] the portion of the image to return @return [UIImage]

# File lib/ios/sugarcube-image/uiimage.rb, line 148
def in_rect(rect)
  imageRef = CGImageCreateWithImageInRect(self.CGImage, rect)
  sub_image = UIImage.imageWithCGImage(imageRef, scale:self.scale, orientation:self.imageOrientation)

  return sub_image
end
inverted() click to toggle source

Invert the image @options - none

@example

image.inverted

similar to (image | CIFilter.color_invert).uiimage

# File lib/ios/sugarcube-image/uiimage.rb, line 424
def inverted
  output = self.apply_filter(CIFilter.color_invert)
  return UIImage.imageWithCIImage(output, scale:self.scale, orientation:self.imageOrientation)
end
masked(mask_image) click to toggle source

| | CGImageCreateWithMask | The mask image cannot have ANY transparency. Instead, transparent areas must be white or some value between black and white. The more white a pixel is the more transparent it becomes.

black  .. white
opaque .. transparent
# File lib/ios/sugarcube-image/uiimage.rb, line 529
def masked(mask_image)
  mask_image = mask_image.CGImage

  width = CGImageGetWidth(mask_image)
  height = CGImageGetHeight(mask_image)
  component_bits = CGImageGetBitsPerComponent(mask_image)
  pixel_bits = CGImageGetBitsPerPixel(mask_image)
  row_bytes = CGImageGetBytesPerRow(mask_image)
  data_provider = CGImageGetDataProvider(mask_image)

  mask = CGImageMaskCreate(width, height, component_bits,
    pixel_bits, row_bytes, data_provider,nil, false)

  masked = CGImageCreateWithMask(self.CGImage, mask)
  UIImage.imageWithCGImage(masked, scale:self.scale, orientation:self.imageOrientation)
end
merge(image, options={}) click to toggle source

Draw an image on top of the receiver. The `:at` option provides either an absolute location (Array or CGPoint) or relative location (Symbol, one of :top_left, :top, :top_right, :left, :center (default), :right, :bottom_left, :bottom, :bottom_right). The `:stretch` option increases the canvas so there is room for both images, otherwise the target image's size is used.

# File lib/ios/sugarcube-image/uiimage.rb, line 58
def merge(image, options={})
  image_position = options.fetch(:at, :center)
  stretch = options.fetch(:stretch, false)

  size = self.size
  if stretch
    if image.size.width > size.width
      size.width = image.size.width
    end
    if image.size.height > size.height
      size.height = image.size.height
    end
  end

  my_left = image_left = 0
  my_top = image_top = 0
  my_right = (size.width - self.size.width)
  my_bottom = (size.height - self.size.height)
  image_right = (size.width - image.size.width)
  image_bottom = (size.height - image.size.height)
  my_cx = my_right / 2.0
  my_cy = my_bottom / 2.0
  image_cx = image_right / 2.0
  image_cy = image_bottom / 2.0

  case image_position
  when :top_left, :topleft, :tl
    my_position = [my_right, my_bottom]
    image_position = [image_left, image_top]
  when :top, :t
    my_position = [my_cx, my_bottom]
    image_position = [image_cx, image_top]
  when :top_right, :topright, :tr
    my_position = [my_left, my_bottom]
    image_position = [image_right, image_top]
  when :left, :l
    my_position = [my_right, my_cy]
    image_position = [image_left, image_cy]
  when :center, :c
    my_position = [my_cx, my_cy]
    image_position = [image_cx, image_cy]
  when :right, :r
    my_position = [my_left, my_cy]
    image_position = [image_right, image_cy]
  when :bottom_left, :bottomleft, :bl
    my_position = [my_right, my_top]
    image_position = [image_left, image_bottom]
  when :bottom, :b
    my_position = [my_cx, my_top]
    image_position = [image_cx, image_bottom]
  when :bottom_right, :bottomright, :br
    my_position = [my_left, my_top]
    image_position = [image_right, image_bottom]
  end

  return self.draw(size: size, at: my_position) do
    image.drawAtPoint(image_position)
  end
end
nsdata(format=:png, compression=0.9) click to toggle source

@return [NSData] an NSData object in PNG or JPG format

# File lib/ios/sugarcube-nsdata/uiimage.rb, line 4
def nsdata(format=:png, compression=0.9)
  case format
  when :png, :PNG
    UIImagePNGRepresentation(self)
  when :jpg, :JPG
    UIImageJPEGRepresentation(self, compression)
  end
end
overlay(color) click to toggle source

Apply a color overlay to the image (very practical with PNG button images)

@example

image.overlay(UIColor.redColor)
# File lib/ios/sugarcube-image/uiimage.rb, line 444
def overlay(color)
  image_rect = CGRectMake(0, 0, self.size.width, self.size.height)
  new_image = nil

  UIImage.canvas(size: self.size, scale: self.scale) do |ctx|
    self.drawInRect(image_rect)

    CGContextSetFillColorWithColor(ctx, color.uicolor.CGColor)
    CGContextSetAlpha(ctx, 1)
    CGContextSetBlendMode(ctx, KCGBlendModeSourceAtop)
    CGContextFillRect(ctx, image_rect)

    image_ref = CGBitmapContextCreateImage(ctx)
    new_image = UIImage.imageWithCGImage(image_ref, scale:self.scale, orientation:self.imageOrientation)
  end

  return new_image
end
rotate(angle_or_direction) click to toggle source

| | rotate images |

# File lib/ios/sugarcube-image/uiimage.rb, line 466
def rotate(angle_or_direction)
  case angle_or_direction
  when :left
    radian = -90.degrees
  when :right
    radian = 90.degrees
  when :flip
    radian = 180.degrees
  when Numeric
    radian = angle_or_direction
  else
    raise "Unknown angle/direction #{angle_or_direction.inspect}"
  end

  w = (self.size.width * Math.cos(radian)).abs + (self.size.height * Math.sin(radian)).abs
  h = (self.size.height * Math.cos(radian)).abs + (self.size.width * Math.sin(radian)).abs
  new_size = CGSize.new(w, h)
  new_size = self.size

  return self.canvas(size: new_size) do |context|
    # Move the origin to the middle of the image so we will rotate and scale around the center.
    CGContextTranslateCTM(context, new_size.width / 2, new_size.height / 2)

    # Rotate the image context
    CGContextRotateCTM(context, radian)

    # otherwise it'll be upside down:
    CGContextScaleCTM(context, 1.0, -1.0)
    # Now, draw the rotated/scaled image into the context
    CGContextDrawImage(context, CGRectMake(-new_size.width / 2, -new_size.height / 2, new_size.width, new_size.height), self.CGImage)
  end
end
rounded(corner_radius=5) click to toggle source

| | image modifications |

# File lib/ios/sugarcube-image/uiimage.rb, line 374
def rounded(corner_radius=5)
  return self.canvas do
    path = UIBezierPath.bezierPathWithRoundedRect([[0, 0], size], cornerRadius:corner_radius)
    path.addClip
    self.drawInRect([[0, 0], size])
  end
end
scale_to(new_size) click to toggle source

Delegates to scale_to(background:), specifying background color of `nil`

# File lib/ios/sugarcube-image/uiimage.rb, line 294
def scale_to(new_size)
  scale_to(new_size, background:nil)
end
scale_to_fill(new_size, options={}) click to toggle source

Scales an image to fit within the given size, stretching one or both dimensions so that it completely fills the area. The current aspect ratio is maintained. If you want to place an image inside a container image, this is the method to use.

You can specify a `position` property, which can be a symbol or a point. It specifies where you want the image located if it has to be cropped. Specifying the top-left corner will display the top-left corner of the image, likewise specifing the bottom-right corner will display that corner. If you want the image centered, you can use the 'position-less' version of this method (`scale_to_fit()`) or specify the point at the center of the image (`scale_to_fit(size, position:[w/2, h/2])`), or use a symbol (`scale_to_fit(size, position: :center)`).

@param new_size [CGSize] Minimum dimensions of desired image. The returned image is

guaranteed to fit within these dimensions.

@param position [Symbol, CGPoint] Where to position the resized image. Valid symbols

 are: `[:top_left, :top, :top_right, :left, :center, :right, :bottom_left,
 :bottom, :bottom_right]` (if you forget the underscore, like
 `topleft`, that'll work, too)
@param scale [Numeric] image scale

@return [UIImage]

# File lib/ios/sugarcube-image/uiimage.rb, line 177
def scale_to_fill(new_size, options={})
  new_size = SugarCube::CoreGraphics::Size(new_size)
  position = options[:position] || :center
  scale = options[:scale] || self.scale

  my_size = self.size
  if new_size.width == my_size.width && new_size.height == my_size.height && self.scale == scale
    return self
  end

  # first, scale down; then we'll scale back up if we went too far
  if my_size.width > new_size.width
    my_size.height *= new_size.width / my_size.width
    my_size.width = new_size.width
  end

  if my_size.height > new_size.height
    my_size.width *= new_size.height / my_size.height
    my_size.height = new_size.height
  end

  if my_size.width < new_size.width
    my_size.height *= new_size.width / my_size.width
    my_size.width = new_size.width
  end

  if my_size.height < new_size.height
    my_size.width *= new_size.height / my_size.height
    my_size.height = new_size.height
  end

  if self.size.width == my_size.width && self.size.height == my_size.height
    return self
  end

  if position.is_a?(Symbol)
    min_x = 0
    min_y = 0
    max_x = my_size.width
    max_y = my_size.height
    mid_x = max_x / 2
    mid_y = max_y / 2
    case position
    when :top_left, :topleft, :tl
      position = CGPoint.new(min_x, min_y)
    when :top, :t
      position = CGPoint.new(mid_x, min_y)
    when :top_right, :topright, :tr
      position = CGPoint.new(max_x, min_y)
    when :left, :l
      position = CGPoint.new(min_x, mid_x)
    when :center, :c
      position = CGPoint.new(mid_x, mid_x)
    when :right, :r
      position = CGPoint.new(max_x, mid_x)
    when :bottom_left, :bottomleft, :bl
      position = CGPoint.new(min_x, max_y)
    when :bottom, :b
      position = CGPoint.new(mid_x, max_y)
    when :bottom_right, :bottomright, :br
      position = CGPoint.new(max_x, max_y)
    else
      raise "Unknown position #{position.inspect}"
    end
  else
    position = SugarCube::CoreGraphics::Point(position)
  end
  thumbnail_x = position.x * (new_size.width - my_size.width) / my_size.width
  thumbnail_y = position.y * (new_size.height - my_size.height) / my_size.height

  new_image = self.canvas(size: new_size) do
    thumbnail_rect = CGRect.new([0, 0], [0, 0])
    thumbnail_rect.origin = [thumbnail_x, thumbnail_y]
    thumbnail_rect.size  = my_size

    self.drawInRect(thumbnail_rect)
  end

  raise "could not scale image" unless new_image

  return new_image
end
scale_within(new_size) click to toggle source

This method is similar to `scale_to`, except it doesn't pad the image, it just scales the image so that it will fit inside the new bounds.

# File lib/ios/sugarcube-image/uiimage.rb, line 262
def scale_within(new_size)
  target_size = SugarCube::CoreGraphics::Size(new_size)
  image_size = self.size

  if CGSizeEqualToSize(target_size, self.size)
    return self
  end

  width = image_size.width
  height = image_size.height

  target_width = target_size.width
  target_height = target_size.height

  width_factor = target_width / width
  height_factor = target_height / height

  if width_factor < height_factor
    scale_factor = width_factor
  else
    scale_factor = height_factor
  end

  if scale_factor == 1
    return self
  end

  scaled_size = CGSize.new(width * scale_factor, height * scale_factor)
  return scale_to(scaled_size)
end
skcolor(alpha=nil) click to toggle source
# File lib/ios/sugarcube-color/uiimage.rb, line 17
def skcolor(alpha=nil)
  uicolor(alpha)
end
stretchable(insets=UIEdgeInsetsZero) click to toggle source
# File lib/ios/sugarcube-image/uiimage.rb, line 508
def stretchable(insets=UIEdgeInsetsZero)
  # not necessary, since we don't modify/examine the insets
  # insets = SugarCube::CoreGraphics::EdgeInsets(insets)
  resizableImageWithCapInsets(insets, resizingMode:UIImageResizingModeStretch)
end
tileable(insets=UIEdgeInsetsZero) click to toggle source

| | resizableImageWithCapInsets |

# File lib/ios/sugarcube-image/uiimage.rb, line 502
def tileable(insets=UIEdgeInsetsZero)
  # not necessary, since we don't modify/examine the insets
  # insets = SugarCube::CoreGraphics::EdgeInsets(insets)
  resizableImageWithCapInsets(insets, resizingMode:UIImageResizingModeTile)
end
uicolor(alpha=nil) click to toggle source

@return [UIColor]

# File lib/ios/sugarcube-color/uiimage.rb, line 4
def uicolor(alpha=nil)
  color = UIColor.colorWithPatternImage(self)
  if alpha
    color = color.colorWithAlphaComponent(alpha.to_f)
  end

  color
end
uiimage() click to toggle source
# File lib/ios/sugarcube-ui/uiimage.rb, line 2
def uiimage ; self ; end
uiimageview() click to toggle source

@return [UIImageView]

# File lib/ios/sugarcube-ui/uiimage.rb, line 5
def uiimageview
  UIImageView.alloc.initWithImage(self)
end
width() click to toggle source
# File lib/ios/sugarcube-ui/uiimage.rb, line 9
def width
  self.size.width
end
|(filter) click to toggle source

Operator shorthand for `apply_filter`, or coerces the image into other formats.

@example

image = 'test'.uiimage
new_image = image | CIFilter.gaussian_blur  # => returns a CIImage
new_image = (image | CIFilter.gaussian_blur).uiimage  # => coerce to UIImage
new_image = image | CIFilter.gaussian_blur | UIImage  # => coerce to UIImage via chaining
# File lib/ios/sugarcube-pipes/pipes.rb, line 11
def |(filter)
  if CIFilter === filter
    apply_filter(filter)
  elsif filter == UIImage
    self
  elsif filter == UIView || filter == UIImageView
    self.uiimageview
  elsif filter == CIImage
    ciimage
  elsif filter == UIColor
    uicolor
  elsif filter == NSData
    nsdata
  else
    raise "The `|` operator is not supported for the #{filter.is_a?(Class) ? filter.name : filter.class.to_s} class"
  end
end