Skip to content

Commit

Permalink
Add support for Core Image in attachment_fu via a new core_image_proc…
Browse files Browse the repository at this point in the history
…essor class that uses a vendor imported mini core image library.

Updated attachment_fu to automatically use core image when available and fall back to Image Science, Rmagick and MiniMagick when not available.
  • Loading branch information
crafterm committed Feb 21, 2008
1 parent ca63a36 commit d971859
Show file tree
Hide file tree
Showing 13 changed files with 422 additions and 2 deletions.
4 changes: 3 additions & 1 deletion init.rb
Expand Up @@ -11,4 +11,6 @@ def make_tmpname(basename, n)
require 'geometry'
ActiveRecord::Base.send(:extend, Technoweenie::AttachmentFu::ActMethods)
Technoweenie::AttachmentFu.tempfile_path = ATTACHMENT_FU_TEMPFILE_PATH if Object.const_defined?(:ATTACHMENT_FU_TEMPFILE_PATH)
FileUtils.mkdir_p Technoweenie::AttachmentFu.tempfile_path
FileUtils.mkdir_p Technoweenie::AttachmentFu.tempfile_path

$:.unshift(File.dirname(__FILE__) + '/vendor')
2 changes: 1 addition & 1 deletion lib/technoweenie/attachment_fu.rb
@@ -1,6 +1,6 @@
module Technoweenie # :nodoc:
module AttachmentFu # :nodoc:
@@default_processors = %w(ImageScience Rmagick MiniMagick)
@@default_processors = %w(CoreImage ImageScience Rmagick MiniMagick)
@@tempfile_path = File.join(RAILS_ROOT, 'tmp', 'attachment_fu')
@@content_types = ['image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/x-png', 'image/jpg']
mattr_reader :content_types, :tempfile_path, :default_processors
Expand Down
56 changes: 56 additions & 0 deletions lib/technoweenie/attachment_fu/processors/core_image_processor.rb
@@ -0,0 +1,56 @@
require 'red_artisan/core_image/processor'

module Technoweenie # :nodoc:
module AttachmentFu # :nodoc:
module Processors
module CoreImageProcessor
def self.included(base)
base.send :extend, ClassMethods
base.alias_method_chain :process_attachment, :processing
end

module ClassMethods
def with_image(file, &block)
block.call OSX::CIImage.from(file)
end
end

protected
def process_attachment_with_processing
return unless process_attachment_without_processing
with_image do |img|
self.width = img.extent.size.width if respond_to?(:width)
self.height = img.extent.size.height if respond_to?(:height)
resize_image_or_thumbnail! img
callback_with_args :after_resize, img
end if image?
end

# Performs the actual resizing operation for a thumbnail
def resize_image(img, size)
processor = ::RedArtisan::CoreImage::Processor.new(img)
size = size.first if size.is_a?(Array) && size.length == 1
if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
if size.is_a?(Fixnum)
processor.fit(size)
else
processor.resize(size[0], size[1])
end
else
new_size = [img.extent.size.width, img.extent.size.height] / size.to_s
processor.resize(new_size[0], new_size[1])
end

processor.render do |result|
self.width = result.extent.size.width if respond_to?(:width)
self.height = result.extent.size.height if respond_to?(:height)
result.save self.temp_path, OSX::NSJPEGFileType
self.size = File.size(self.temp_path)
end
end
end
end
end
end


10 changes: 10 additions & 0 deletions test/fixtures/attachment.rb
Expand Up @@ -94,6 +94,16 @@ class ImageScienceAttachment < ActiveRecord::Base
puts "no ImageScience"
end

begin
class CoreImageAttachment < ActiveRecord::Base
has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
:processor => :core_image, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55
end
rescue MissingSourceFile
puts $!.message
puts "no CoreImage"
end

begin
class MiniMagickAttachment < ActiveRecord::Base
has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
Expand Down
31 changes: 31 additions & 0 deletions test/processors/core_image_test.rb
@@ -0,0 +1,31 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))

class CoreImageTest < Test::Unit::TestCase
attachment_model CoreImageAttachment

if Object.const_defined?(:OSX)
def test_should_resize_image
attachment = upload_file :filename => '/files/rails.png'
assert_valid attachment
assert attachment.image?
# test core image thumbnail
assert_equal 42, attachment.width
assert_equal 55, attachment.height

thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }

# test exact resize dimensions
assert_equal 50, thumb.width
assert_equal 51, thumb.height

# test geometry string
assert_equal 31, geo.width
assert_equal 41, geo.height
end
else
def test_flunk
puts "CoreImage not loaded, tests not running"
end
end
end
11 changes: 11 additions & 0 deletions test/schema.rb
Expand Up @@ -34,6 +34,17 @@
t.column :type, :string
end

create_table :core_image_attachments, :force => true do |t|
t.column :parent_id, :integer
t.column :thumbnail, :string
t.column :filename, :string, :limit => 255
t.column :content_type, :string, :limit => 255
t.column :size, :integer
t.column :width, :integer
t.column :height, :integer
t.column :type, :string
end

create_table :mini_magick_attachments, :force => true do |t|
t.column :parent_id, :integer
t.column :thumbnail, :string
Expand Down
27 changes: 27 additions & 0 deletions vendor/red_artisan/core_image/filters/color.rb
@@ -0,0 +1,27 @@
module RedArtisan
module CoreImage
module Filters
module Color

def greyscale(color = nil, intensity = 1.00)
create_core_image_context(@original.extent.size.width, @original.extent.size.height)

color = OSX::CIColor.colorWithString("1.0 1.0 1.0 1.0") unless color

@original.color_monochrome :inputColor => color, :inputIntensity => intensity do |greyscale|
@target = greyscale
end
end

def sepia(intensity = 1.00)
create_core_image_context(@original.extent.size.width, @original.extent.size.height)

@original.sepia_tone :inputIntensity => intensity do |sepia|
@target = sepia
end
end

end
end
end
end
31 changes: 31 additions & 0 deletions vendor/red_artisan/core_image/filters/effects.rb
@@ -0,0 +1,31 @@
module RedArtisan
module CoreImage
module Filters
module Effects

def spotlight(position, points_at, brightness, concentration, color)
create_core_image_context(@original.extent.size.width, @original.extent.size.height)

@original.spot_light :inputLightPosition => vector3(*position), :inputLightPointsAt => vector3(*points_at),
:inputBrightness => brightness, :inputConcentration => concentration, :inputColor => color do |spot|
@target = spot
end
end

def edges(intensity = 1.00)
create_core_image_context(@original.extent.size.width, @original.extent.size.height)

@original.edges :inputIntensity => intensity do |edged|
@target = edged
end
end

private

def vector3(x, y, w)
OSX::CIVector.vectorWithX_Y_Z(x, y, w)
end
end
end
end
end
25 changes: 25 additions & 0 deletions vendor/red_artisan/core_image/filters/perspective.rb
@@ -0,0 +1,25 @@
module RedArtisan
module CoreImage
module Filters
module Perspective

def perspective(top_left, top_right, bottom_left, bottom_right)
create_core_image_context(@original.extent.size.width, @original.extent.size.height)

@original.perspective_transform :inputTopLeft => top_left, :inputTopRight => top_right, :inputBottomLeft => bottom_left, :inputBottomRight => bottom_right do |transformed|
@target = transformed
end
end

def perspective_tiled(top_left, top_right, bottom_left, bottom_right)
create_core_image_context(@original.extent.size.width, @original.extent.size.height)

@original.perspective_tile :inputTopLeft => top_left, :inputTopRight => top_right, :inputBottomLeft => bottom_left, :inputBottomRight => bottom_right do |tiled|
@target = tiled
end
end

end
end
end
end
25 changes: 25 additions & 0 deletions vendor/red_artisan/core_image/filters/quality.rb
@@ -0,0 +1,25 @@
module RedArtisan
module CoreImage
module Filters
module Quality

def reduce_noise(level = 0.02, sharpness = 0.4)
create_core_image_context(@original.extent.size.width, @original.extent.size.height)

@original.noise_reduction :inputNoiseLevel => level, :inputSharpness => sharpness do |noise_reduced|
@target = noise_reduced
end
end

def adjust_exposure(input_ev = 0.5)
create_core_image_context(@original.extent.size.width, @original.extent.size.height)

@original.exposure_adjust :inputEV => input_ev do |adjusted|
@target = adjusted
end
end

end
end
end
end
47 changes: 47 additions & 0 deletions vendor/red_artisan/core_image/filters/scale.rb
@@ -0,0 +1,47 @@
module RedArtisan
module CoreImage
module Filters
module Scale

def resize(width, height)
create_core_image_context(width, height)

scale_x, scale_y = scale(width, height)

@original.affine_clamp :inputTransform => OSX::NSAffineTransform.transform do |clamped|
clamped.lanczos_scale_transform :inputScale => scale_x > scale_y ? scale_x : scale_y, :inputAspectRatio => scale_x / scale_y do |scaled|
scaled.crop :inputRectangle => vector(0, 0, width, height) do |cropped|
@target = cropped
end
end
end
end

def thumbnail(width, height)
create_core_image_context(width, height)

transform = OSX::NSAffineTransform.transform
transform.scaleXBy_yBy *scale(width, height)

@original.affine_transform :inputTransform => transform do |scaled|
@target = scaled
end
end

def fit(size)
original_size = @original.extent.size
scale = size.to_f / (original_size.width > original_size.height ? original_size.width : original_size.height)
resize (original_size.width * scale).to_i, (original_size.height * scale).to_i
end

private

def scale(width, height)
original_size = @original.extent.size
return width.to_f / original_size.width.to_f, height.to_f / original_size.height.to_f
end

end
end
end
end
32 changes: 32 additions & 0 deletions vendor/red_artisan/core_image/filters/watermark.rb
@@ -0,0 +1,32 @@
module RedArtisan
module CoreImage
module Filters
module Watermark

def watermark(watermark_image, tile = false, strength = 0.1)
create_core_image_context(@original.extent.size.width, @original.extent.size.height)

if watermark_image.respond_to? :to_str
watermark_image = OSX::CIImage.from(watermark_image.to_str)
end

if tile
tile_transform = OSX::NSAffineTransform.transform
tile_transform.scaleXBy_yBy 1.0, 1.0

watermark_image.affine_tile :inputTransform => tile_transform do |tiled|
tiled.crop :inputRectangle => vector(0, 0, @original.extent.size.width, @original.extent.size.height) do |tiled_watermark|
watermark_image = tiled_watermark
end
end
end

@original.dissolve_transition :inputTargetImage => watermark_image, :inputTime => strength do |watermarked|
@target = watermarked
end
end

end
end
end
end

0 comments on commit d971859

Please sign in to comment.