Arthur
Arthur

Reputation: 145

How to create thumbnail for first pdf page with carrierwave

I'm processing thumbnail for PDF file in this way:

  version :thumb do
    process :resize_to_limit => [260, 192]
    process :convert => :jpg
    process :set_content_type
  end

  def set_content_type(*args)
    self.file.instance_variable_set(:@content_type, "image/jpeg")
  end

But when PDF file is multipage it produces thumbnail for all pages in one jpg file. Is there any way to produce thumbnail only for first page?

Upvotes: 12

Views: 6532

Answers (4)

Thomas
Thomas

Reputation: 2952

Conclusion

The current solution that @tanzeeb came up with is working great for a majority of Image/PDF files and the manipulate function is a useful and dynamic way to manipulate the entire pdf, but it is too broad to be considered a viable method when generating thumbnails, whose main goal is to manipulate only the first page of a pdf.

Why?

This manipulate! code forces the entire file to be read before continuing, and also iterates over that entire file a little later. That's counterintuitive to generating thumbnails and can be a resource problem.

Other Solutions

Inside of manipulate there is a &read_block that is being passed into ::Magic::Image.read/1, and that could possibly be used to alter the image data passed into read itself, with one of the read_options specifying a certain bit of data from the current_path.

Though, the CarrierWave documentation is too shallow in describing it's possible usages and actually wrong in describing it's code usage. So, this was a dead-end for me.

Code

I wound up sourcing a bunch of code from the manipulate! function to utilize inside of my own uploader, where I've removed some functionality not used in the generation of the thumbnail.

 # @doc Returns the first page of a file
  version :thumb, :if => :thumbable? do
    process :cover
    process convert: "jpg"
    def full_filename(filename = model.file.filename) = "#{filename.split(".").first}.jpg"
    def style = :thumb

    # @doc This is an overridden function from the `manipulate!` function defined in CarrierWaves library
    # @doc I removed the ability to pass in options, or a block to the function to use in processing the image.
    # @doc This seemed to be the route to go because there isn't many maintainers available on CarrierWave and the documentation is trash.
    def cover
      cache_file
      push_frames(get_frames)
    rescue ::Magick::ImageMagickError
      raise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.processing_error")
    end

    private

    # @doc This will store the file available on the uploader in the cache, unless it is already cached.
    def cache_file
      cache_stored_file! unless cached?
    end

    # @doc This will utilize RMagick to create an image from an altered current_path variable
    # @doc Create a new ImageList, and store the image created as the first frame
    def get_frames
      path = "#{current_path}[0]"
      image = ::Magick::Image.read(path)
      frames = ::Magick::ImageList.new
      frames << image.first
    end

    # @doc This will persist the frames created as an ImageList into the file on the uploader, essentially replacing the original pdf with the modified pdf.
    def push_frames(frames)
      frames.write(current_path)
    end

    # @doc This will destroy the ImageList that is allocating memory.
    def destroy_image(frames)
      frames.try(:destroy!)
    end
  end

Upvotes: 0

everyman
everyman

Reputation: 3407

Great solution by Tanzeeb! Thank you.

So i could do something like this:

 def cover 
    manipulate! do |frame, index|
      frame if index.zero?
    end
  end   

and used this for the thumb generation

  version :thumb do
    process :cover    
    process :resize_to_fill => [50, 50, Magick::NorthGravity]
    process :convert => 'png'
  end

great!

Upvotes: 8

Jason Crump
Jason Crump

Reputation: 445

I ran across this post when searching for a solution to this same problem. When you convert a pdf to jpeg it creates a long pdf with all the pages attached end to end, so you need to crop the image to the aspect ratio that you want and discard the rest. Below is what I ended up using:

version :thumb_safari do #special version for safari and ios
  process :resize_to_fit => [200,200]
  process :convert => 'jpg'
  process :paper_shape
  def full_filename (for_file = model.logo.file)
     super.chomp(File.extname(super)) + '.jpg'
  end     
end

version :thumb do #all browsers except safari
  process :resize_to_fit => [200,200]
  process :convert => 'jpg' #must convert to jpg before running paper shape
  process :paper_shape
  process :convert => 'jpg' #after running paper_shape it will default to original file type
  def full_filename (for_file = model.logo.file)
    super.chomp(File.extname(super)) + '.jpg'
  end
end

def paper_shape
   manipulate! do |img|
     if img.rows*4 != img.columns*3
       width=img.columns
       height=img.columns/3*4
       img.crop!(0,0,width,height,true)
     else
       img
     end
   end
 end

In the controller/view I used the useragent gem and did this:

documents_controller.rb

def index
  @user_agent=UserAgent.parse(request.user_agent)
  @search = Document.search(params[:q])
end

index.html.rb

<% if @user_agent.browser.downcase == 'safari' %>
<%= link_to(image_tag(doc.pdfdoc_url(:thumb_safari).to_s, :class=>"dropshadow", :size => "150x225"), doc.pdfdoc_url)%>
<% else %>
<%= link_to(image_tag(doc.pdfdoc_url(:thumb).to_s, :class=>"dropshadow", :size => "150x225"), doc.pdfdoc_url)%>
<% end %>

No doubt there is a better way to do this but this is working for now.

Upvotes: 3

Tanzeeb Khalili
Tanzeeb Khalili

Reputation: 7344

I submitted a patch earlier this year to do just this. Use a custom processor:

def cover 
  manipulate! do |frame, index|
    frame if index.zero?
  end
end

process :cover

Upvotes: 15

Related Questions