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.

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