You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
7.7 KiB
240 lines
7.7 KiB
package com.sothr.imagetools.image
|
|
|
|
import java.awt.image.{BufferedImage, ColorConvertOp, DataBufferByte}
|
|
import java.io.{File, IOException}
|
|
import javax.imageio.ImageIO
|
|
|
|
import com.sothr.imagetools.AppConfig
|
|
import com.sothr.imagetools.dao.ImageDAO
|
|
import com.sothr.imagetools.hash.HashService
|
|
import com.sothr.imagetools.util.{PropertiesService, PropertyEnum}
|
|
import grizzled.slf4j.Logging
|
|
import net.coobird.thumbnailator.Thumbnails
|
|
import net.sf.ehcache.Element
|
|
|
|
object ImageService extends Logging {
|
|
|
|
val imageCache = AppConfig.cacheManager.getCache("images")
|
|
private val imageDAO = new ImageDAO()
|
|
|
|
private def lookupImage(file:File):Image = {
|
|
var image:Image = null
|
|
var found = false
|
|
//get from memory cache if possible
|
|
try {
|
|
if (imageCache.isKeyInCache(file.getAbsolutePath)) {
|
|
image = imageCache.get(file.getAbsolutePath).getObjectValue.asInstanceOf[Image]
|
|
found = true
|
|
}
|
|
} catch {
|
|
case npe:NullPointerException => debug(s"\'${file.getAbsolutePath}\' was supposed to be in the cache, but was not")
|
|
}
|
|
//get from datastore if possible
|
|
if (!found) {
|
|
try {
|
|
val tempImage = imageDAO.find(file.getAbsolutePath)
|
|
if (tempImage != null) image = tempImage
|
|
} catch {
|
|
case ex:Exception => error(s"Error looking up \'${file.getAbsolutePath}\' was supposed to be in the database, but was not", ex)
|
|
}
|
|
}
|
|
image
|
|
}
|
|
|
|
private def saveImage(image:Image):Image = {
|
|
//save to cache
|
|
imageCache.put(new Element(image.imagePath, image))
|
|
//save to datastore
|
|
try {
|
|
imageDAO.save(image)
|
|
} catch {
|
|
case ex:Exception => error(s"Error saving \'${image.imagePath}\' to database", ex)
|
|
}
|
|
image
|
|
}
|
|
|
|
def getImage(file:File):Image = {
|
|
try {
|
|
val image = lookupImage(file)
|
|
if (image != null) {
|
|
debug(s"${file.getAbsolutePath} was already processed")
|
|
return image
|
|
} else {
|
|
debug(s"Processing image: ${file.getAbsolutePath}")
|
|
val bufferedImage = ImageIO.read(file)
|
|
val hashes = HashService.getImageHashes(bufferedImage, file.getAbsolutePath)
|
|
var thumbnailPath = lookupThumbnailPath(hashes.md5)
|
|
if (thumbnailPath == null) thumbnailPath = getThumbnail(bufferedImage, hashes.md5)
|
|
val imageSize = { (bufferedImage.getWidth, bufferedImage.getHeight) }
|
|
val image = new Image(file.getAbsolutePath, thumbnailPath, imageSize, hashes)
|
|
debug(s"Created image: $image")
|
|
return saveImage(image)
|
|
}
|
|
} catch {
|
|
case ioe:IOException => error(s"Error processing ${file.getAbsolutePath}", ioe)
|
|
case ex:Exception => error(s"Error processing ${file.getAbsolutePath}", ex)
|
|
}
|
|
null
|
|
}
|
|
|
|
def calculateThumbPath(md5:String):String = {
|
|
//break the path down into 4 char parts
|
|
val subPath = md5.substring(0, 3)
|
|
var path:String = s"${PropertiesService.get(PropertyEnum.ThumbnailDirectory.toString)}${PropertiesService.get(PropertyEnum.ThumbnailSize.toString)}/$subPath/"
|
|
try {
|
|
val dir = new File(path)
|
|
if (!dir.exists()) dir.mkdirs()
|
|
} catch {
|
|
case ioe:IOException => error(s"Unable to create dirs for path: \'$path\'", ioe)
|
|
}
|
|
path += md5 + ".jpg"
|
|
path
|
|
}
|
|
|
|
def lookupThumbnailPath(md5:String):String = {
|
|
var thumbPath:String = null
|
|
if (md5 != null) {
|
|
//check for the actual file
|
|
val checkPath = calculateThumbPath(md5)
|
|
if (new File(checkPath).exists) thumbPath = checkPath
|
|
} else {
|
|
error("Null md5 passed in")
|
|
}
|
|
thumbPath
|
|
}
|
|
|
|
def getThumbnail(image:BufferedImage, md5:String):String = {
|
|
//create thumbnail
|
|
val thumb = resize(image, PropertiesService.get(PropertyEnum.ThumbnailSize.toString).toInt, forced=false)
|
|
//calculate path
|
|
val path = calculateThumbPath(md5)
|
|
// save thumbnail to path
|
|
try {
|
|
ImageIO.write(thumb, "jpg", new File(path))
|
|
debug(s"Wrote thumbnail to $path")
|
|
} catch {
|
|
case ioe:IOException => error(s"Unable to save thumbnail to $path", ioe)
|
|
}
|
|
// return path
|
|
path
|
|
}
|
|
|
|
/**
|
|
* Get the raw data for an image
|
|
*/
|
|
def getImageData(image:BufferedImage):Array[Array[Int]] = {
|
|
convertTo2DWithoutUsingGetRGB(image)
|
|
}
|
|
|
|
/**
|
|
* Quickly convert an image to grayscale
|
|
*
|
|
* @param image image to convert to greyscale
|
|
* @return
|
|
*/
|
|
def convertToGray(image:BufferedImage):BufferedImage = {
|
|
//debug("Converting an image to grayscale")
|
|
val grayImage = new BufferedImage(image.getWidth, image.getHeight, BufferedImage.TYPE_BYTE_GRAY)
|
|
|
|
//create a color conversion operation
|
|
val op = new ColorConvertOp(
|
|
image.getColorModel.getColorSpace,
|
|
grayImage.getColorModel.getColorSpace, null)
|
|
|
|
//convert the image to grey
|
|
val result = op.filter(image, grayImage)
|
|
|
|
//val g = image.getGraphics
|
|
//g.drawImage(image,0,0,null)
|
|
//g.dispose()
|
|
result
|
|
}
|
|
|
|
def resize(image:BufferedImage, size:Int, forced:Boolean=false):BufferedImage = {
|
|
//debug(s"Resizing an image to size: ${size}x${size} forced: $forced")
|
|
if (forced) {
|
|
Thumbnails.of(image).forceSize(size,size).asBufferedImage
|
|
} else {
|
|
Thumbnails.of(image).size(size,size).asBufferedImage
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert a buffered image into a 2d pixel data array
|
|
*
|
|
* @param image image to convert without using RGB
|
|
* @return
|
|
*/
|
|
private def convertTo2DWithoutUsingGetRGB(image:BufferedImage):Array[Array[Int]] = {
|
|
|
|
val pixels = image.getRaster.getDataBuffer.asInstanceOf[DataBufferByte].getData
|
|
val numPixels = pixels.length
|
|
val width = image.getWidth
|
|
val height = image.getHeight
|
|
val isSingleChannel = if(numPixels == (width * height)) true else false
|
|
val hasAlphaChannel = image.getAlphaRaster != null
|
|
//debug(s"Converting image to 2d. width:$width height:$height")
|
|
|
|
val result = Array.ofDim[Int](height,width)
|
|
if (isSingleChannel) {
|
|
//debug(s"Processing Single Channel Image")
|
|
val pixelLength = 1
|
|
var row = 0
|
|
var col = 0
|
|
//debug(s"Processing pixels 0 until $numPixels by $pixelLength")
|
|
for (pixel <- 0 until numPixels by pixelLength) {
|
|
//debug(s"Processing pixel: $pixel/${numPixels - 1}")
|
|
val argb:Int = pixels(pixel).toInt //singleChannel
|
|
//debug(s"Pixel data: $argb")
|
|
result(row)(col) = argb
|
|
col += 1
|
|
if (col == width) {
|
|
col = 0
|
|
row += 1
|
|
}
|
|
}
|
|
}
|
|
else if (hasAlphaChannel) {
|
|
//debug(s"Processing Four Channel Image")
|
|
val pixelLength = 4
|
|
var row = 0
|
|
var col = 0
|
|
//debug(s"Processing pixels 0 until $numPixels by $pixelLength")
|
|
for (pixel <- 0 until numPixels by pixelLength) {
|
|
//debug(s"Processing pixel: $pixel/${numPixels - 1}")
|
|
var argb:Int = 0
|
|
argb += pixels(pixel).toInt << 24 //alpha
|
|
argb += pixels(pixel + 1).toInt //blue
|
|
argb += pixels(pixel + 2).toInt << 8 //green
|
|
argb += pixels(pixel + 3).toInt << 16 //red
|
|
result(row)(col) = argb
|
|
col += 1
|
|
if (col == width) {
|
|
col = 0
|
|
row += 1
|
|
}
|
|
}
|
|
} else {
|
|
//debug(s"Processing Three Channel Image")
|
|
val pixelLength = 3
|
|
var row = 0
|
|
var col = 0
|
|
//debug(s"Processing pixels 0 until $numPixels by $pixelLength")
|
|
for (pixel <- 0 until numPixels by pixelLength) {
|
|
//debug(s"Processing pixel: $pixel/${numPixels - 1}")
|
|
var argb:Int = 0
|
|
argb += -16777216; // 255 alpha
|
|
argb += pixels(pixel).toInt //blue
|
|
argb += pixels(pixel + 1).toInt << 8 //green
|
|
argb += pixels(pixel + 2).toInt << 16 //red
|
|
result(row)(col) = argb
|
|
col += 1
|
|
if (col == width) {
|
|
col = 0
|
|
row += 1
|
|
}
|
|
}
|
|
}
|
|
result
|
|
}
|
|
}
|