lib/chunky_png/canvas/operations.rb in chunky_png-1.0.0.beta2 vs lib/chunky_png/canvas/operations.rb in chunky_png-1.0.0.rc1

- old
+ new

@@ -9,52 +9,97 @@ # a new canvas and leave the original intact. # # @see ChunkyPNG::Canvas module Operations - # Composes another image onto this image using alpha blending. + # Composes another image onto this image using alpha blending. This will modify + # the current canvas. # # If you simply want to replace pixels or when the other image does not have - # transparency, it is faster to use {#replace}. + # transparency, it is faster to use {#replace!}. # # @param [ChunkyPNG::Canvas] other The foreground canvas to compose on the # current canvas, using alpha compositing. # @param [Integer] offset_x The x-offset to apply the new forgeround on. # @param [Integer] offset_y The y-offset to apply the new forgeround on. # @return [ChunkyPNG::Canvas] Returns itself, but with the other canvas composed onto it. # @raise [ChunkyPNG::OutOfBounds] when the other canvas doesn't fit on this one, - # given the offset and size of the other canavs. - # @see #replace - def compose(other, offset_x = 0, offset_y = 0) + # given the offset and size of the other canvas. + # @see #replace! + # @see #compose + def compose!(other, offset_x = 0, offset_y = 0) check_size_constraints!(other, offset_x, offset_y) for y in 0...other.height do for x in 0...other.width do set_pixel(x + offset_x, y + offset_y, ChunkyPNG::Color.compose(other.get_pixel(x, y), get_pixel(x + offset_x, y + offset_y))) end end self end - + + # Composes another image onto this image using alpha blending. This will return + # a new canvas and leave the original intact. + # + # If you simply want to replace pixels or when the other image does not have + # transparency, it is faster to use {#replace}. + # + # @param (see #compose!) + # @return [ChunkyPNG::Canvas] Returns the new canvas, composed of the other 2. + # @raise [ChunkyPNG::OutOfBounds] when the other canvas doesn't fit on this one, + # given the offset and size of the other canvas. + # + # @note API changed since 1.0 - This method now no longer is in place, but returns + # a new canvas and leaves the original intact. Use {#compose!} if you want to + # compose on the canvas in place. + # @see #replace + def compose(other, offset_x = 0, offset_y = 0) + dup.compose!(other, offset_x, offset_y) + end + # Replaces pixels on this image by pixels from another pixels, on a given offset. + # This method will modify the current canvas. # # This will completely replace the pixels of the background image. If you want to blend - # them with semi-transparent pixels from the foreground image, see {#compose}. + # them with semi-transparent pixels from the foreground image, see {#compose!}. # - # @return [ChunkyPNG::Canvas] Returns itself, but with the other canvas composed onto it. + # @param [ChunkyPNG::Canvas] other The foreground canvas to get the pixels from. + # @param [Integer] offset_x The x-offset to apply the new forgeround on. + # @param [Integer] offset_y The y-offset to apply the new forgeround on. + # @return [ChunkyPNG::Canvas] Returns itself, but with the other canvas placed onto it. # @raise [ChunkyPNG::OutOfBounds] when the other canvas doesn't fit on this one, - # given the offset and size of the other canavs. - # @see #compose - def replace(other, offset_x = 0, offset_y = 0) + # given the offset and size of the other canvas. + # @see #compose! + # @see #replace + def replace!(other, offset_x = 0, offset_y = 0) check_size_constraints!(other, offset_x, offset_y) for y in 0...other.height do pixels[(y + offset_y) * width + offset_x, other.width] = other.pixels[y * other.width, other.width] end self end + # Replaces pixels on this image by pixels from another pixels, on a given offset. + # This method will modify the current canvas. + # + # This will completely replace the pixels of the background image. If you want to blend + # them with semi-transparent pixels from the foreground image, see {#compose!}. + # + # @param (see #replace!) + # @return [ChunkyPNG::Canvas] Returns a new, combined canvas. + # @raise [ChunkyPNG::OutOfBounds] when the other canvas doesn't fit on this one, + # given the offset and size of the other canvas. + # + # @note API changed since 1.0 - This method now no longer is in place, but returns + # a new canvas and leaves the original intact. Use {#replace!} if you want to + # replace pixels on the canvas in place. + # @see #compose + def replace(other, offset_x = 0, offset_y = 0) + dup.replace!(other, offset_x, offset_y) + end + # Crops an image, given the coordinates and size of the image that needs to be cut out. # This will leave the original image intact and return a new, cropped image with pixels # copied from the original image. # # @param [Integer] x The x-coordinate of the top left corner of the image to be cropped. @@ -74,154 +119,138 @@ new_pixels += pixels.slice((cy + y) * width + x, crop_width) end ChunkyPNG::Canvas.new(crop_width, crop_height, new_pixels) end - # Creates a new image, based on the current image but with a new theme color. + # Crops an image, given the coordinates and size of the image that needs to be cut out. # - # This method will replace one color in an image with another image. This is done by - # first extracting the pixels with a color close to the original theme color as a mask - # image, changing the color of this mask image and then apply it on the original image. + # This will change the size and content of the current canvas. Use {#crop} if you want to + # have a new canvas returned instead, leaving the current canvas intact. # - # Mask extraction works best when the theme colored pixels are clearly distinguishable - # from a background color (preferably white). You can set a tolerance level to influence - # the extraction process. - # - # @param [Integer] old_theme_color The original theme color in this image. - # @param [Integer] new_theme_color The color to replace the old theme color with. - # @param [Integer] The backrgound color opn which the theme colored pixels are placed. - # @param [Integer] tolerance The tolerance level to use when extracting the mask image. Five is - # the default; increase this if the masked image does not extract all the required pixels, - # decrease it if too many pixels get extracted. - # @return [ChunkyPNG::Canvas] Returns itself, but with the theme colored pixels changed. - # @see #change_theme_color! - # @see #change_mask_color! - def change_theme_color!(old_theme_color, new_theme_color, bg_color = ChunkyPNG::Color::WHITE, tolerance = 5) - base, mask = extract_mask(old_theme_color, bg_color, tolerance) - mask.change_mask_color!(new_theme_color) - self.replace(base.compose(mask)) - end - - # Creates a base image and a mask image from an original image that has a particular theme color. - # This can be used to easily change a theme color in an image. - # - # It will extract all the pixels that look like the theme color (with a tolerance level) and put - # these in a mask image. All the other pixels will be stored in a base image. Both images will be - # of the exact same size as the original image. The original image will be left untouched. - # - # The color of the mask image can be changed with {#change_mask_color!}. This new mask image can - # then be composed upon the base image to create an image with a new theme color. A call to - # {#change_theme_color!} will perform this in one go. - # - # @param [Integer] mask_color The current theme color. - # @param [Integer] bg_color The background color on which the theme colored pxiels are applied. - # @param [Integer] tolerance The tolerance level to use when extracting the mask image. Five is - # the default; increase this if the masked image does not extract all the required pixels, - # decrease it if too many pixels get extracted. - # @return [Array<ChunkyPNG::Canvas, ChunkyPNG::Canvas>] An array with the base canvas and the mask - # canvas as elements. - # @see #change_theme_color! - # @see #change_mask_color! - def extract_mask(mask_color, bg_color = ChunkyPNG::Color::WHITE, tolerance = 5) - base_pixels = [] - mask_pixels = [] - - pixels.each do |pixel| - if ChunkyPNG::Color.alpha_decomposable?(pixel, mask_color, bg_color, tolerance) - mask_pixels << ChunkyPNG::Color.decompose_color(pixel, mask_color, bg_color, tolerance) - base_pixels << bg_color - else - mask_pixels << (mask_color & 0xffffff00) - base_pixels << pixel - end - end + # @param [Integer] x The x-coordinate of the top left corner of the image to be cropped. + # @param [Integer] y The y-coordinate of the top left corner of the image to be cropped. + # @param [Integer] crop_width The width of the image to be cropped. + # @param [Integer] crop_height The height of the image to be cropped. + # @return [ChunkyPNG::Canvas] Returns itself, but cropped. + # @raise [ChunkyPNG::OutOfBounds] when the crop dimensions plus the given coordinates + # are bigger then the original image. + def crop!(x, y, crop_width, crop_height) - [ self.class.new(width, height, base_pixels), self.class.new(width, height, mask_pixels) ] + raise ChunkyPNG::OutOfBounds, "Image width is too small!" if crop_width + x > width + raise ChunkyPNG::OutOfBounds, "Image width is too small!" if crop_height + y > height + + new_pixels = [] + for cy in 0...crop_height do + new_pixels += pixels.slice((cy + y) * width + x, crop_width) + end + replace_canvas!(crop_width, crop_height, new_pixels) end - # Changes the color of a mask image. + # Flips the image horizontally, leaving the original intact. # - # This method works on acanavs extracte out of another image using the {#extract_mask} method. - # It can then be applied on the extracted base image. See {#change_theme_color!} to perform - # these operations in one go. + # This will flip the image on its horizontal axis, e.g. pixels on the top will now + # be pixels on the bottom. Chaining this method twice will return the original canvas. + # This method will leave the original object intact and return a new canvas. # - # @param [Integer] new_color The color to replace the original mask color with. - # @raise [ChunkyPNG::ExpectationFailed] when this canvas is not a mask image, i.e. its palette - # has more than once color, disregarding transparency. - # @see #change_theme_color! - # @see #extract_mask - def change_mask_color!(new_color) - raise ChunkyPNG::ExpectationFailed, "This is not a mask image!" if palette.opaque_palette.size != 1 - pixels.map! { |pixel| (new_color & 0xffffff00) | ChunkyPNG::Color.a(pixel) } - self + # @return [ChunkyPNG::Canvas] The flipped image + # @see #flip_horizontally! + def flip_horizontally + dup.flip_horizontally! end - # Flips the image horizontally. + # Flips the image horizontally in place. # # This will flip the image on its horizontal axis, e.g. pixels on the top will now # be pixels on the bottom. Chaining this method twice will return the original canvas. # This method will leave the original object intact and return a new canvas. # - # @return [ChunkyPNG::Canvas] The flipped image - def flip_horizontally - self.class.new(width, height).tap do |flipped| - for y in 0...height do - flipped.replace_row!(height - (y + 1), row(y)) - end + # @return [ChunkyPNG::Canvas] Itself, but flipped + # @see #flip_horizontally + def flip_horizontally! + for y in 0..((height - 1) >> 1) do + other_y = height - (y + 1) + other_row = row(other_y) + replace_row!(other_y, row(y)) + replace_row!(y, other_row) end + return self end - # Flips the image horizontally. + alias_method :flip!, :flip_horizontally! + alias_method :flip, :flip_horizontally + + # Flips the image vertically, leaving the orginial intact. # # This will flip the image on its vertical axis, e.g. pixels on the left will now # be pixels on the right. Chaining this method twice will return the original canvas. # This method will leave the original object intact and return a new canvas. # # @return [ChunkyPNG::Canvas] The flipped image + # @see #flip_vertically! def flip_vertically - self.class.new(width, height).tap do |flipped| - for x in 0...width do - flipped.replace_column!(width - (x + 1), column(x)) - end + dup.flip_vertically! + end + + # Flips the image vertically in place. + # + # This will flip the image on its vertical axis, e.g. pixels on the left will now + # be pixels on the right. Chaining this method twice will return the original canvas. + # This method will leave the original object intact and return a new canvas. + # + # @return [ChunkyPNG::Canvas] Itself, but flipped + # @see #flip_vertically + def flip_vertically! + for y in 0...height do + replace_row!(y, row(y).reverse) end + return self end + + alias_method :mirror!, :flip_vertically! + alias_method :mirror, :flip_vertically # Rotates the image 90 degrees clockwise. # This method will leave the original object intact and return a new canvas. # # @return [ChunkyPNG::Canvas] The rotated image def rotate_right - self.class.new(height, width).tap do |rotated| - for i in 0...width do - rotated.replace_row!(i, column(i).reverse) - end + rotated = self.class.new(height, width) + for i in 0...width do + rotated.replace_row!(i, column(i).reverse) end + return rotated end # Rotates the image 90 degrees counter-clockwise. # This method will leave the original object intact and return a new canvas. # # @return [ChunkyPNG::Canvas] The rotated image. def rotate_left - self.class.new(height, width).tap do |rotated| - for i in 0...width do - rotated.replace_row!(width - (i + 1), column(i)) - end + rotated = self.class.new(height, width) + for i in 0...width do + rotated.replace_row!(width - (i + 1), column(i)) end + return rotated end - + # Rotates the image 180 degrees. # This method will leave the original object intact and return a new canvas. # # @return [ChunkyPNG::Canvas] The rotated image. + # @see #rotate_180! def rotate_180 - self.class.new(width, height).tap do |flipped| - for y in 0...height do - flipped.replace_row!(height - (y + 1), row(y).reverse) - end - end + dup.rotate_180! end - + + # Rotates the image 180 degrees in place. + # + # @return [ChunkyPNG::Canvas] Itself, but rotated 180 degrees. + # @see #rotate_180 + def rotate_180! + pixels.reverse! + return self + end + protected # Checks whether another image has the correct dimension to be used for an operation # on the current image, given an offset coordinate to work with. # @param [ChunkyPNG::Canvas] other The other canvas