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

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