Lift image upload, resize, store in database, display
Asked Answered
K

3

9

Is there a succinct example of how to upload an image, resize it, store it in a database and then serve the image up using Lift?

I'm sure I could piece it together from the file upload, Java 2D API, Lift Mapper and Response APIs. But is there any example code I can follow to do it the 'correct' or recommended way?

Ketose answered 10/9, 2009 at 11:46 Comment(2)
No offence, but this sounds like a decent project to write yourself! It has everything: intrigue, adventure, and SQL.Siphonophore
Yeah I was going to! I just thought I'd seek advice before starting.Ketose
M
6

I did this for a Mapper field linked to s3 by creating a new MappedField. I also have a some code to resize, but haven't tested or deployed (so use with caution).

class MappedS3Image[T<:Mapper[T]](owner: T, val path:String, maxWidth: String, maxHeight:String) extends MappedString[T](owner, 36) {

  def url:String = MappedS3Image.fullImgPath(path, is)

  def setFromUpload(fileHolder: Box[FileParamHolder]) = {
      S3Sender.uploadImageToS3(path, fileHolder).map(this.set(_))
  }

  override def asHtml:Node = <img src={url} style={"max-width:" + maxWidth + ";max-height:"+maxHeight} />
  override def _toForm: Box[Elem] = Full(SHtml.fileUpload(fu=>setFromUpload(Full(fu))))

}


import java.awt.Image 
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import java.awt.Graphics2D
import java.awt.AlphaComposite

object ImageResizer {

    def resize(is:java.io.InputStream, maxWidth:Int, maxHeight:Int):BufferedImage = {
        val originalImage:BufferedImage = ImageIO.read(is)

        val height = originalImage.getHeight
        val width = originalImage.getWidth

        if (width <= maxWidth && height <= maxHeight)
            originalImage
        else {
            var scaledWidth:Int = width
            var scaledHeight:Int = height
            val ratio:Double = width/height
            if (scaledWidth > maxWidth){
                scaledWidth = maxWidth
                scaledHeight = (scaledWidth.doubleValue/ratio).intValue
            }
            if (scaledHeight > maxHeight){
                scaledHeight = maxHeight
                scaledWidth = (scaledHeight.doubleValue*ratio).intValue
            }
            val scaledBI = new BufferedImage(scaledWidth, scaledHeight,  BufferedImage.TYPE_INT_RGB)
            val g = scaledBI.createGraphics
            g.setComposite(AlphaComposite.Src)
            g.drawImage(originalImage, 0, 0, scaledWidth, scaledHeight, null);
            g.dispose
            scaledBI
        }
    }
}
Mikey answered 19/9, 2009 at 3:59 Comment(1)
Flawed math. This messes up the width/height ratio. I answered with a fixed version (three years later). Still, it answered the questions about the correct libraries to use for me and was very useful.Inactive
A
3

The other answer nicely describes how to resize the image and store a reference to the file on the file system.

If you want to use the lift mapper to store the actual file contents, you have to create your custom model object, and define a binary field on it. Try something like this:

package code {
package model {


import _root_.net.liftweb.mapper._
import _root_.net.liftweb.util._
import _root_.net.liftweb.common._


// singleton object which manipulates storing of Document instances
object Document extends Document with KeyedMetaMapper[Long, Document] {
}



class Document extends KeyedMapper[Long, Document] {
  def getSingleton = Document
  def primaryKeyField = id

  object id extends MappedLongIndex(this)

  object name extends MappedString(this, 20) {
    override def displayName = "Name"
    override def writePermission_? = true
  }

  object content extends MappedBinary(this) {
    override def displayName = "Content"
    override def writePermission_? = true
  }
}



}
}

Then, in bootstrap class, add this Document at the end:

Schemifier.schemify(true, Schemifier.infoF _, User, Document)

Voila. Using Document save (new Document) stores it into database. A new Document's fields can be set using the set method. Try playing with delete_!, find, findAll methods of the Document singleton to delete or find it in the database. It should be straightforward from this point on.

Finally, to display the image, you can override Lift's dispatching rules (in bootstrap class, Boot.scala). Try playing around with this example which overrides the rules for pdf requests:

def getFile(filename: String): Option[Document] = {
  val alldocs = Document.findAll()
  alldocs.find(_.name.get == filename)
}

LiftRules.statelessDispatchTable.append {
  case Req("file" :: name :: Nil, "pdf", GetRequest) =>
    () =>
    println("Got request for: " + name + ".pdf")
    for {
      stream <- tryo(
        getFile(name + ".pdf") map { 
          doc => new java.io.ByteArrayInputStream(doc.content.get)
        } getOrElse null
      )
      if null ne stream
    } yield StreamingResponse(stream,
                              () => stream.close,
                              stream.available,
                              List("Content-Type" -> "application/pdf"),
                              Nil,
                              200)
}
Affective answered 10/10, 2010 at 18:1 Comment(0)
I
2

Based on the accepted answer by Jon Hoffman, I fixed the bugs. His version messes up the aspect ratio (it always becomes 1:1), because the math was off in a few spots. This version resizes big pictures until they fit, and respects the aspect ratio.

def resize(is:java.io.InputStream, maxWidth:Int, maxHeight:Int):BufferedImage = {
    require (maxWidth > 0)
    require (maxHeight > 0)
    val originalImage:BufferedImage = ImageIO.read(is)

    var height = originalImage.getHeight
    var width = originalImage.getWidth

    // Shortcut to save a pointless reprocessing in case the image is small enough already
    if (width <= maxWidth && height <= maxHeight)
        originalImage
    else {          
        // If the picture was too big, it will either fit by width or height.
        // This essentially resizes the dimensions twice, until it fits
        if (width > maxWidth){
          height = (height.doubleValue() * (maxWidth.doubleValue() / width.doubleValue())).intValue
          width = maxWidth
        }
        if (height > maxHeight){
          width = (width.doubleValue() * (maxHeight.doubleValue() / height.doubleValue())).intValue
          height = maxHeight
        }
        val scaledBI = new BufferedImage(width, height,  BufferedImage.TYPE_INT_RGB)
        val g = scaledBI.createGraphics
        g.setComposite(AlphaComposite.Src)
        g.drawImage(originalImage, 0, 0, width, height, null);
        g.dispose
        scaledBI
    }
}
Inactive answered 30/10, 2012 at 21:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.