lib/sugarcube/uiimage.rb in sugarcube-0.14.0 vs lib/sugarcube/uiimage.rb in sugarcube-0.15.0

- old
+ new

@@ -25,26 +25,197 @@ ##| REALLY HANDY STUFF! ##| many of these methods are translated from: ##| <http://www.catamount.com/blog/uiimage-extensions-for-cutting-scaling-and-rotating-uiimages/> ##| <http://www.catamount.com/forums/viewtopic.php?f=21&t=967> ##| + + # Merges the two images. The target is drawn first, `image` is drawn on top. + def <<(image) + size = self.size + if image.size.width > size.width + size.width = image.size.width + end + if image.size.height > size.height + size.height = image.size.height + end + + UIGraphicsBeginImageContextWithOptions(size, false, self.scale) + self.drawAtPoint([0, 0]) + # draw the border and drop shadow + image.drawAtPoint([0, 0]) + new_image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return new_image + end + + ##| + ##| image scaling + ##| + + # 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] def in_rect(rect) - # not necessary, since we don't modify/examine the rect - # rect = SugarCube::CoreGraphics::Rect(rect) imageRef = CGImageCreateWithImageInRect(self.CGImage, rect) - sub_image = UIImage.imageWithCGImage(imageRef) + sub_image = UIImage.imageWithCGImage(imageRef, scale:self.scale, orientation:self.imageOrientation) return sub_image end + # Delegates to scale_to_fill(position: :center) + def scale_to_fill(new_size) + scale_to_fill(new_size, position: :center) + end + + # 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: `[:topleft, :top, :topright, :left, :center, :right, :bottomleft, + # :bottom, :bottomright]` (if you forget and use an underscore, like + # `top_left`, that'll work, too) + # @return [UIImage] + def scale_to_fill(new_size, position:position) + new_size = SugarCube::CoreGraphics::Size(new_size) + my_size = self.size + + 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 + position = SugarCube::CoreGraphics::Point(min_x, min_y) + when :top + position = SugarCube::CoreGraphics::Point(mid_x, min_y) + when :top_right, :topright + position = SugarCube::CoreGraphics::Point(max_x, min_y) + when :left + position = SugarCube::CoreGraphics::Point(min_x, mid_x) + when :center + position = SugarCube::CoreGraphics::Point(mid_x, mid_x) + when :right + position = SugarCube::CoreGraphics::Point(max_x, mid_x) + when :bottom_left, :bottomleft + position = SugarCube::CoreGraphics::Point(min_x, max_y) + when :bottom + position = SugarCube::CoreGraphics::Point(mid_x, max_y) + when :bottom_right, :bottomright + position = SugarCube::CoreGraphics::Point(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 + + UIGraphicsBeginImageContextWithOptions(new_size, false, self.scale) + thumbnail_rect = CGRectZero + thumbnail_rect.origin = [thumbnail_x, thumbnail_y] + thumbnail_rect.size = my_size + + self.drawInRect(thumbnail_rect) + + new_image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + raise "could not scale image" unless new_image + + return new_image + end + + # 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. + 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 + + # Delegates to scale_to(background:), specifying background color of `nil` def scale_to(new_size) + scale_to(new_size, background:nil) + end + + # Scales an image to fit within the given size. Its current aspect ratio is + # maintained, but the image is padded so that it fills the entire area. If the + # image is too small, it will be scaled up to fit. If you specify a + # background that color will be used, otherwise the background will be + # transparent. + # + # @param new_size [CGSize] Maximum dimensions of desired image. The returned image is + # guaranteed to fit within these dimensions. + # @param background [UIColor] Color to fill padded areas. Default is transparent. + # @return [UIImage] + def scale_to(new_size, background:background) new_size = SugarCube::CoreGraphics::Size(new_size) - sourceImage = self - newImage = nil + image_size = self.size - image_size = sourceImage.size + if CGSizeEqualToSize(image_size, new_size) + return self + end + + new_image = nil width = image_size.width height = image_size.height target_width = new_size.width target_height = new_size.height @@ -52,54 +223,62 @@ scale_factor = 0.0 scaled_width = target_width scaled_height = target_height thumbnail_point = CGPoint.new(0.0, 0.0) + width_factor = target_width / width + height_factor = target_height / height - unless CGSizeEqualToSize(image_size, new_size) - width_factor = target_width / width - heightFactor = target_height / height + if width_factor < height_factor + scale_factor = width_factor + else + scale_factor = height_factor + end - if width_factor < heightFactor - scale_factor = width_factor - else - scale_factor = heightFactor - end + scaled_width = width * scale_factor + scaled_height = height * scale_factor - scaled_width = width * scale_factor - scaled_height = height * scale_factor + # center the image - # center the image - - if width_factor < heightFactor - thumbnail_point.y = (target_height - scaled_height) * 0.5 - elsif width_factor > heightFactor - thumbnail_point.x = (target_width - scaled_width) * 0.5 - end + if width_factor < height_factor + thumbnail_point.y = (target_height - scaled_height) * 0.5 + elsif width_factor > height_factor + thumbnail_point.x = (target_width - scaled_width) * 0.5 end # this is actually the interesting part: - UIGraphicsBeginImageContext(new_size) + UIGraphicsBeginImageContextWithOptions(new_size, false, self.scale) + if background + background = background.uicolor + context = UIGraphicsGetCurrentContext() + background.setFill + CGContextAddRect(context, [[0, 0], new_size]) + CGContextDrawPath(context, KCGPathFill) + end + thumbnail_rect = CGRectZero thumbnail_rect.origin = thumbnail_point thumbnail_rect.size.width = scaled_width thumbnail_rect.size.height = scaled_height - sourceImage.drawInRect(thumbnail_rect) + self.drawInRect(thumbnail_rect) new_image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() raise "could not scale image" unless new_image return new_image end + ##| + ##| image modifications + ##| def rounded(corner_radius=5) - UIGraphicsBeginImageContext(size) + UIGraphicsBeginImageContextWithOptions(size, false, self.scale) path = UIBezierPath.bezierPathWithRoundedRect([[0, 0], size], cornerRadius:corner_radius) path.addClip self.drawInRect([[0, 0], size]) new_image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() @@ -126,11 +305,11 @@ end output = darken_filter.valueForKey('outputImage') context = CIContext.contextWithOptions(nil) cg_output_image = context.createCGImage(output, fromRect:output.extent) - output_image = UIImage.imageWithCGImage(cg_output_image) + output_image = UIImage.imageWithCGImage(cg_output_image, scale:self.scale, orientation:self.imageOrientation) return output_image end ##| @@ -154,11 +333,11 @@ 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 # Create the bitmap context - UIGraphicsBeginImageContext(new_size) + UIGraphicsBeginImageContextWithOptions(new_size, false, self.scale) bitmap = UIGraphicsGetCurrentContext() # Move the origin to the middle of the image so we will rotate and scale around the center. CGContextTranslateCTM(bitmap, new_size.width / 2, new_size.height / 2) @@ -198,13 +377,15 @@ end ##| ##| CGImageCreateWithMask ##| - ## The mask image cannot have ANY transparency. - ## Instead, transparent areas must be white or some value between black and white. - ## The closer towards black a pixel is the less transparent it becomes. + # 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 def masked(mask_image) mask_image = mask_image.CGImage width = CGImageGetWidth(mask_image) height = CGImageGetHeight(mask_image) @@ -215,9 +396,60 @@ mask = CGImageMaskCreate(width, height, component_bits, pixel_bits, row_bytes, data_provider,nil, false) masked = CGImageCreateWithMask(self.CGImage, mask) - UIImage.imageWithCGImage(masked) + UIImage.imageWithCGImage(masked, scale:self.scale, orientation:self.imageOrientation) + end + + # Oddly enough, this method doesn't seem to have retina support + 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 + + 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 + + UIGraphicsBeginImageContextWithOptions(new_size, false, scale) + thumbnail_rect = CGRect.new([0, 0], new_size) + + self.drawInRect(thumbnail_rect) + + new_image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return new_image end end