# ImageVoodoo is an ImageScience-API-compatible image manipulation library for JRuby. # # Example of usage: # # ImageVoodoo.with_image(ARGV[0]) do |img| # img.cropped_thumbnail(100) { |img2| img2.save "CTH.jpg" } # img.with_crop(100, 200, 400, 600) { |img2| img2.save "CR.jpg" } # img.thumbnail(50) { |img2| img2.save "TH.jpg" } # img.resize(100, 150) do |img2| # img2.save "HEH.jpg" # img2.save "HEH.png" # end # end class ImageVoodoo include Java import java.awt.RenderingHints import java.awt.color.ColorSpace import java.awt.geom.AffineTransform import java.awt.image.ByteLookupTable import java.awt.image.ColorConvertOp import java.awt.image.LookupOp import java.awt.image.RescaleOp import java.awt.image.BufferedImage JFile = java.io.File BAIS = java.io.ByteArrayInputStream BAOS = java.io.ByteArrayOutputStream import javax.imageio.ImageIO import javax.swing.JFrame NEGATIVE_OP = LookupOp.new(ByteLookupTable.new(0, (0...254).to_a.reverse.to_java(:byte)), nil) GREY_OP = ColorConvertOp.new(ColorSpace.getInstance(ColorSpace::CS_GRAY), nil) class JImagePanel < javax.swing.JPanel def initialize(image, x=0, y=0) super() @image, @x, @y = image, x, y end def image=(image) @image = image invalidate end def getPreferredSize java.awt.Dimension.new(@image.width, @image.height) end def paintComponent(graphics) graphics.drawImage(@image.to_java, @x, @y, nil) end end def initialize(src) @src = src end # Foreach pixel new_pixel = pixel * scale + offset def adjust_brightness(scale, offset) image = ImageVoodoo.new internal_transform(RescaleOp.new(scale, offset, nil)) block_given? ? yield(image) : image end def cropped_thumbnail(size) l, t, r, b, half = 0, 0, width, height, (width - height).abs / 2 l, r = half, half + height if width > height t, b = half, half + width if height > width with_crop(l, t, r, b) do |image| image.thumbnail(size) do |thumb| target = ImageVoodoo.new target block_given? ? yield(target) : target end end end def height @src.height end def width @src.width end def to_java @src end def negative image = ImageVoodoo.new internal_transform(NEGATIVE_OP) block_given? ? yield(image) : image end def resize(width, height) target = BufferedImage.new(width, height, color_type) graphics = target.graphics graphics.set_rendering_hint(RenderingHints::KEY_INTERPOLATION, RenderingHints::VALUE_INTERPOLATION_BICUBIC) w_scale = width.to_f / @src.width h_scale = height.to_f / @src.height transform = AffineTransform.get_scale_instance w_scale, h_scale graphics.draw_rendered_image @src, transform graphics.dispose target = ImageVoodoo.new target block_given? ? yield(target) : target rescue NativeException => ne raise ArgumentError, ne.message end def greyscale image = ImageVoodoo.new internal_transform(GREY_OP) block_given? ? yield(image) : image end def save(file) format = File.extname(file) return false if format == "" format = format[1..-1].downcase ImageIO.write(@src, format, JFile.new(file)) true end class WindowClosed def initialize(block = nil) @block = block || proc { java.lang.System.exit(0) } end def method_missing(meth,*args); end def windowClosing(event); @block.call; end end def preview(&block) frame = JFrame.new("Preview") frame.add_window_listener WindowClosed.new(block) frame.set_bounds 0, 0, width + 20, height + 40 frame.add JImagePanel.new(self, 10, 10) frame.visible = true end def bytes(format) out = BAOS.new ImageIO.write(@src, format, out) String.from_java_bytes(out.to_byte_array) end def scale(ratio) new_width = (width * ratio).to_i new_height = (height * ratio).to_i resize(new_width, new_height) do |image| target = ImageVoodoo.new image block_given? ? yield(target) : target end end def thumbnail(size) scale(size.to_f / (width > height ? width : height)) end def with_crop(left, top, right, bottom) image = ImageVoodoo.new(@src.get_subimage(left, top, right-left, bottom-top)) block_given? ? yield(image) : image end # TODO: Figure out how to determine whether source has alpha or not def self.from_url(source) url = java.net.URL.new(source) image = java.awt.Toolkit.default_toolkit.create_image(url) tracker = java.awt.MediaTracker.new(java.awt.Label.new("")) tracker.addImage(image, 0); tracker.waitForID(0) target = BufferedImage.new(image.getWidth, image.getHeight, BufferedImage::TYPE_INT_RGB) graphics = target.graphics graphics.drawImage(image, 0, 0, nil) graphics.dispose target = ImageVoodoo.new target block_given? ? yield(target) : target rescue java.io.IOException, java.net.MalformedURLException raise ArgumentError.new "Trouble retrieving image: #{$!.message}" end def self.with_image(file) readers = ImageIO.getImageReadersBySuffix(File.extname(file)[1..-1]) raise TypeError, "unrecognized format for #{file}" unless readers.hasNext image = ImageVoodoo.new ImageIO.read(JFile.new(file)) block_given? ? yield(image) : image rescue NativeException => ne nil end def self.with_bytes(bytes) bytes = bytes.to_java_bytes if String === bytes image = ImageVoodoo.new ImageIO.read(BAIS.new(bytes)) block_given? ? yield(image) : image end private def color_type return BufferedImage::TYPE_INT_ARGB if @src.color_model.has_alpha BufferedImage::TYPE_INT_RGB end def internal_transform(operation, target=BufferedImage.new(width, height, color_type)) graphics = target.graphics graphics.drawImage(@src, 0, 0, nil) graphics.drawImage(operation.filter(target, nil), 0, 0, nil) graphics.dispose target end end