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.

242 lines
7.6 KiB

  1. package com.sothr.imagetools
  2. import grizzled.slf4j.Logging
  3. import java.awt.image.{DataBufferByte, BufferedImage, ColorConvertOp}
  4. import net.coobird.thumbnailator.Thumbnails
  5. import java.io.File
  6. import com.sothr.imagetools.image.Image
  7. import com.sothr.imagetools.hash.HashService
  8. import javax.imageio.ImageIO
  9. import java.io.IOException
  10. import net.sf.ehcache.Element
  11. import com.sothr.imagetools.util.{PropertiesEnum, PropertiesService}
  12. import com.sothr.imagetools.dao.ImageDAO
  13. object ImageService extends Logging {
  14. val imageCache = AppConfig.cacheManager.getCache("images")
  15. private val imageDAO = new ImageDAO()
  16. private def lookupImage(file:File):Image = {
  17. var image:Image = null
  18. var found = false
  19. //get from memory cache if possible
  20. try {
  21. if (imageCache.isKeyInCache(file.getAbsolutePath)) {
  22. found = true
  23. image = imageCache.get(file.getAbsolutePath).getObjectValue.asInstanceOf[Image]
  24. }
  25. } catch {
  26. case npe:NullPointerException => error(s"Error grabbing \'${file.getAbsolutePath}\' from cache", npe)
  27. }
  28. //get from datastore if possible
  29. if (!found) {
  30. try {
  31. val tempImage = imageDAO.find(file.getAbsolutePath)
  32. if (tempImage != null) image = tempImage
  33. } catch {
  34. case ex:Exception => error(s"Error looking up \'${file.getAbsolutePath}\' from database", ex)
  35. }
  36. }
  37. image
  38. }
  39. private def saveImage(image:Image):Image = {
  40. //save to cache
  41. imageCache.put(new Element(image.imagePath, image))
  42. //save to datastore
  43. try {
  44. imageDAO.save(image)
  45. } catch {
  46. case ex:Exception => error(s"Error saving \'${image.imagePath}\' to database", ex)
  47. }
  48. image
  49. }
  50. def getImage(file:File):Image = {
  51. try {
  52. val image = lookupImage(file)
  53. if (image != null) {
  54. debug(s"${file.getAbsolutePath} was already processed")
  55. return image
  56. } else {
  57. debug(s"Processing image: ${file.getAbsolutePath}")
  58. val bufferedImage = ImageIO.read(file)
  59. val hashes = HashService.getImageHashes(bufferedImage, file.getAbsolutePath)
  60. var thumbnailPath = lookupThumbnailPath(hashes.md5)
  61. if (thumbnailPath == null) thumbnailPath = getThumbnail(bufferedImage, hashes.md5)
  62. val imageSize = { (bufferedImage.getWidth, bufferedImage.getHeight) }
  63. val image = new Image(file.getAbsolutePath, thumbnailPath, imageSize, hashes)
  64. debug(s"Created image: $image")
  65. return saveImage(image)
  66. }
  67. } catch {
  68. case ioe:IOException => error(s"Error processing ${file.getAbsolutePath}", ioe)
  69. case ex:Exception => error(s"Error processing ${file.getAbsolutePath}", ex)
  70. }
  71. null
  72. }
  73. def calculateThumbPath(md5:String):String = {
  74. //break the path down into 4 char parts
  75. val split:List[String] = md5.grouped(4).toList
  76. var dirPath = ""
  77. for (seg <- split) dirPath += seg + "/"
  78. var path:String = s"${PropertiesService.get(PropertiesEnum.ThumbnailDirectory.toString)}${PropertiesService.get(PropertiesEnum.ThumbnailSize.toString)}/$dirPath"
  79. try {
  80. val dir = new File(path)
  81. if (!dir.exists()) dir.mkdirs()
  82. } catch {
  83. case ioe:IOException => error(s"Unable to create dirs for path: \'$path\'", ioe)
  84. }
  85. path += md5 + ".jpg"
  86. path
  87. }
  88. def lookupThumbnailPath(md5:String):String = {
  89. var thumbPath:String = null
  90. if (md5 != null) {
  91. //check for the actual file
  92. val checkPath = calculateThumbPath(md5)
  93. if (new File(checkPath).exists) thumbPath = checkPath
  94. } else {
  95. error("Null md5 passed in")
  96. }
  97. thumbPath
  98. }
  99. def getThumbnail(image:BufferedImage, md5:String):String = {
  100. //create thumbnail
  101. val thumb = resize(image, PropertiesService.get(PropertiesEnum.ThumbnailSize.toString).toInt, forced=false)
  102. //calculate path
  103. val path = calculateThumbPath(md5)
  104. // save thumbnail to path
  105. try {
  106. ImageIO.write(thumb, "jpg", new File(path))
  107. debug(s"Wrote thumbnail to $path")
  108. } catch {
  109. case ioe:IOException => error(s"Unable to save thumbnail to $path", ioe)
  110. }
  111. // return path
  112. path
  113. }
  114. /**
  115. * Get the raw data for an image
  116. */
  117. def getImageData(image:BufferedImage):Array[Array[Int]] = {
  118. convertTo2DWithoutUsingGetRGB(image)
  119. }
  120. /**
  121. * Quickly convert an image to grayscale
  122. *
  123. * @param image
  124. * @return
  125. */
  126. def convertToGray(image:BufferedImage):BufferedImage = {
  127. //debug("Converting an image to grayscale")
  128. val grayImage = new BufferedImage(image.getWidth, image.getHeight, BufferedImage.TYPE_BYTE_GRAY)
  129. //create a color conversion operation
  130. val op = new ColorConvertOp(
  131. image.getColorModel.getColorSpace,
  132. grayImage.getColorModel.getColorSpace, null)
  133. //convert the image to grey
  134. val result = op.filter(image, grayImage)
  135. //val g = image.getGraphics
  136. //g.drawImage(image,0,0,null)
  137. //g.dispose()
  138. result
  139. }
  140. def resize(image:BufferedImage, size:Int, forced:Boolean=false):BufferedImage = {
  141. //debug(s"Resizing an image to size: ${size}x${size} forced: $forced")
  142. if (forced) {
  143. Thumbnails.of(image).forceSize(size,size).asBufferedImage
  144. } else {
  145. Thumbnails.of(image).size(size,size).asBufferedImage
  146. }
  147. }
  148. /**
  149. * Convert a buffered image into a 2d pixel data array
  150. *
  151. * @param image
  152. * @return
  153. */
  154. private def convertTo2DWithoutUsingGetRGB(image:BufferedImage):Array[Array[Int]] = {
  155. val pixels = image.getRaster.getDataBuffer.asInstanceOf[DataBufferByte].getData
  156. val numPixels = pixels.length
  157. val width = image.getWidth
  158. val height = image.getHeight
  159. val isSingleChannel = if(numPixels == (width * height)) true else false
  160. val hasAlphaChannel = image.getAlphaRaster != null
  161. //debug(s"Converting image to 2d. width:$width height:$height")
  162. val result = Array.ofDim[Int](height,width)
  163. if (isSingleChannel) {
  164. //debug(s"Processing Single Channel Image")
  165. val pixelLength = 1
  166. var row = 0
  167. var col = 0
  168. //debug(s"Processing pixels 0 until $numPixels by $pixelLength")
  169. for (pixel <- 0 until numPixels by pixelLength) {
  170. //debug(s"Processing pixel: $pixel/${numPixels - 1}")
  171. val argb:Int = pixels(pixel).toInt //singleChannel
  172. //debug(s"Pixel data: $argb")
  173. result(row)(col) = argb
  174. col += 1
  175. if (col == width) {
  176. col = 0
  177. row += 1
  178. }
  179. }
  180. }
  181. else if (hasAlphaChannel) {
  182. //debug(s"Processing Four Channel Image")
  183. val pixelLength = 4
  184. var row = 0
  185. var col = 0
  186. //debug(s"Processing pixels 0 until $numPixels by $pixelLength")
  187. for (pixel <- 0 until numPixels by pixelLength) {
  188. //debug(s"Processing pixel: $pixel/${numPixels - 1}")
  189. var argb:Int = 0
  190. argb += pixels(pixel).toInt << 24 //alpha
  191. argb += pixels(pixel + 1).toInt //blue
  192. argb += pixels(pixel + 2).toInt << 8 //green
  193. argb += pixels(pixel + 3).toInt << 16 //red
  194. result(row)(col) = argb
  195. col += 1
  196. if (col == width) {
  197. col = 0
  198. row += 1
  199. }
  200. }
  201. } else {
  202. //debug(s"Processing Three Channel Image")
  203. val pixelLength = 3
  204. var row = 0
  205. var col = 0
  206. //debug(s"Processing pixels 0 until $numPixels by $pixelLength")
  207. for (pixel <- 0 until numPixels by pixelLength) {
  208. //debug(s"Processing pixel: $pixel/${numPixels - 1}")
  209. var argb:Int = 0
  210. argb += -16777216; // 255 alpha
  211. argb += pixels(pixel).toInt //blue
  212. argb += pixels(pixel + 1).toInt << 8 //green
  213. argb += pixels(pixel + 2).toInt << 16 //red
  214. result(row)(col) = argb
  215. col += 1
  216. if (col == width) {
  217. col = 0
  218. row += 1
  219. }
  220. }
  221. }
  222. result
  223. }
  224. }