diff --git a/cropper/app/lib/Files.scala b/common-lib/src/main/scala/com/gu/mediaservice/lib/Files.scala similarity index 59% rename from cropper/app/lib/Files.scala rename to common-lib/src/main/scala/com/gu/mediaservice/lib/Files.scala index 6ce2972853..045a1c8987 100644 --- a/cropper/app/lib/Files.scala +++ b/common-lib/src/main/scala/com/gu/mediaservice/lib/Files.scala @@ -1,19 +1,20 @@ -package lib +package com.gu.mediaservice.lib -import java.io.{FileOutputStream, File} -import java.nio.channels.Channels +import java.io.{File, FileOutputStream} import java.net.URL -import scala.concurrent.{Future, ExecutionContext} +import java.nio.channels.Channels import java.util.concurrent.Executors +import scala.concurrent.{ExecutionContext, Future} + object Files { private implicit val ctx = ExecutionContext.fromExecutor(Executors.newCachedThreadPool) - def createTempFile(prefix: String, suffix: String): Future[File] = + def createTempFile(prefix: String, suffix: String, tempDir: File): Future[File] = Future { - File.createTempFile(prefix, suffix, Config.tempDir) + File.createTempFile(prefix, suffix, tempDir) } def transferFromURL(from: URL, to: File): Future[Unit] = @@ -23,9 +24,9 @@ object Files { output.getChannel.transferFrom(channel, 0, java.lang.Long.MAX_VALUE) } - def tempFileFromURL(from: URL, prefix: String, suffix: String): Future[File] = + def tempFileFromURL(from: URL, prefix: String, suffix: String, tempDir: File): Future[File] = for { - tempFile <- createTempFile(prefix, suffix) + tempFile <- createTempFile(prefix, suffix, tempDir: File) _ <- transferFromURL(from, tempFile) } yield tempFile diff --git a/common-lib/src/main/scala/com/gu/mediaservice/lib/imaging/ImageOperations.scala b/common-lib/src/main/scala/com/gu/mediaservice/lib/imaging/ImageOperations.scala new file mode 100644 index 0000000000..4df11b49b3 --- /dev/null +++ b/common-lib/src/main/scala/com/gu/mediaservice/lib/imaging/ImageOperations.scala @@ -0,0 +1,100 @@ +package com.gu.mediaservice.lib.imaging + +import java.io._ + +import org.im4java.core.IMOperation + +import com.gu.mediaservice.lib.Files._ +import com.gu.mediaservice.lib.imaging.im4jwrapper.{ExifTool, ImageMagick} +import com.gu.mediaservice.model.{Asset, Bounds, Dimensions, ImageMetadata} +import play.api.libs.concurrent.Execution.Implicits._ + +import scala.concurrent.Future + + +case class ExportResult(id: String, masterCrop: Asset, othersizings: List[Asset]) + +object ImageOperations { + import ExifTool._ + import ImageMagick._ + + private def profilePath(fileName: String): String = s"${play.api.Play.current.path}/$fileName" + + private def profileLocation(colourModel: String): String = colourModel match { + case "RGB" => profilePath("srgb.icc") + case "CMYK" => profilePath("cmyk.icc") + case "GRAYSCALE" => profilePath("grayscale.icc") + case model => throw new Exception(s"Profile for invalid colour model requested: $model") + } + + private def tagFilter(metadata: ImageMetadata) = { + Map[String, Option[String]]( + "Copyright" -> metadata.copyright, + "CopyrightNotice" -> metadata.copyrightNotice, + "Credit" -> metadata.credit, + "OriginalTransmissionReference" -> metadata.suppliersReference + ).collect { case (key, Some(value)) => (key, value) } + } + + private def applyOutputProfile(base: IMOperation) = profile(base)(profileLocation("RGB")) + + def identifyColourModel(sourceFile: File, mimeType: String): Future[Option[String]] = { + val source = addImage(sourceFile) + // TODO: use mimeType to lookup other properties once we support other formats + val formatter = format(source)("%[JPEG-Colorspace-Name]") + for { + output <- runIdentifyCmd(formatter) + colourModel = output.headOption + } yield colourModel + } + + // Optionally apply transforms to the base operation if the colour space + // in the ICC profile doesn't match the colour model of the image data + private def correctColour(base: IMOperation)(iccColourSpace: Option[String], colourModel: Option[String]) = { + (iccColourSpace, colourModel) match { + // If matching, all is well, just pass through + case (icc, model) if icc == model => base + // If no colour model detected, we can't do anything anyway so just hope all is well + case (_, None) => base + // If mismatching, strip any (incorrect) ICC profile and inject a profile matching the model + // Note: Strip both ICC and ICM (Windows variant?) to be safe + case (_, Some(model)) => profile(stripProfile(base)("icm,icc"))(profileLocation(model)) + } + } + + def cropImage(sourceFile: File, bounds: Bounds, qual: Double = 100d, tempDir: File, + iccColourSpace: Option[String], colourModel: Option[String]): Future[File] = { + for { + outputFile <- createTempFile(s"crop-", ".jpg", tempDir) + cropSource = addImage(sourceFile) + qualified = quality(cropSource)(qual) + corrected = correctColour(qualified)(iccColourSpace, colourModel) + converted = applyOutputProfile(corrected) + stripped = stripMeta(converted) + profiled = applyOutputProfile(stripped) + cropped = crop(profiled)(bounds) + addOutput = addDestImage(cropped)(outputFile) + _ <- runConvertCmd(addOutput) + } + yield outputFile + } + + // Updates metadata on existing file + def appendMetadata(sourceFile: File, metadata: ImageMetadata): Future[File] = { + runExiftoolCmd( + setTags(tagSource(sourceFile))(tagFilter(metadata)) + ).map(_ => sourceFile) + } + + def resizeImage(sourceFile: File, dimensions: Dimensions, qual: Double = 100d, tempDir: File): Future[File] = { + for { + outputFile <- createTempFile(s"resize-", ".jpg", tempDir) + resizeSource = addImage(sourceFile) + qualified = quality(resizeSource)(qual) + resized = scale(qualified)(dimensions) + addOutput = addDestImage(resized)(outputFile) + _ <- runConvertCmd(addOutput) + } + yield outputFile + } +} diff --git a/common-lib/src/main/scala/com/gu/mediaservice/lib/imaging/im4jwrapper/Config.scala b/common-lib/src/main/scala/com/gu/mediaservice/lib/imaging/im4jwrapper/Config.scala new file mode 100644 index 0000000000..f6f440e55e --- /dev/null +++ b/common-lib/src/main/scala/com/gu/mediaservice/lib/imaging/im4jwrapper/Config.scala @@ -0,0 +1,5 @@ +package com.gu.mediaservice.lib.imaging.im4jwrapper + +object Config { + val imagingThreadPoolSize = 4 +} diff --git a/cropper/app/lib/imaging/im4jwrapper/ExifTool.scala b/common-lib/src/main/scala/com/gu/mediaservice/lib/imaging/im4jwrapper/ExifTool.scala similarity index 94% rename from cropper/app/lib/imaging/im4jwrapper/ExifTool.scala rename to common-lib/src/main/scala/com/gu/mediaservice/lib/imaging/im4jwrapper/ExifTool.scala index 4c14173a03..a1870cdc35 100644 --- a/cropper/app/lib/imaging/im4jwrapper/ExifTool.scala +++ b/common-lib/src/main/scala/com/gu/mediaservice/lib/imaging/im4jwrapper/ExifTool.scala @@ -1,7 +1,6 @@ -package lib.imaging.im4jwrapper +package com.gu.mediaservice.lib.imaging.im4jwrapper import java.util.concurrent.Executors -import lib.Config import java.io.File import scala.concurrent.{Future, ExecutionContext} diff --git a/cropper/app/lib/imaging/im4jwrapper/ImageMagick.scala b/common-lib/src/main/scala/com/gu/mediaservice/lib/imaging/im4jwrapper/ImageMagick.scala similarity index 60% rename from cropper/app/lib/imaging/im4jwrapper/ImageMagick.scala rename to common-lib/src/main/scala/com/gu/mediaservice/lib/imaging/im4jwrapper/ImageMagick.scala index 3049cd4e5c..f4169774eb 100644 --- a/cropper/app/lib/imaging/im4jwrapper/ImageMagick.scala +++ b/common-lib/src/main/scala/com/gu/mediaservice/lib/imaging/im4jwrapper/ImageMagick.scala @@ -1,11 +1,14 @@ -package lib.imaging.im4jwrapper +package com.gu.mediaservice.lib.imaging.im4jwrapper import java.util.concurrent.Executors -import lib.Config import java.io.File +import org.im4java.process.ArrayListOutputConsumer + +import scala.collection.JavaConverters._ + import scala.concurrent.{Future, ExecutionContext} -import org.im4java.core.{IMOperation, ConvertCmd} +import org.im4java.core.{IdentifyCmd, IMOperation, ConvertCmd} import scalaz.syntax.id._ import com.gu.mediaservice.model.{Dimensions, Bounds} @@ -18,9 +21,20 @@ object ImageMagick { def addImage(source: File) = (new IMOperation()) <| { op => { op.addImage(source.getAbsolutePath) }} def quality(op: IMOperation)(qual: Double) = op <| (_.quality(qual)) def stripMeta(op: IMOperation) = op <| (_.strip()) + def stripProfile(op: IMOperation)(profile: String) = op <| (_.p_profile(profile)) def addDestImage(op: IMOperation)(dest: File) = op <| (_.addImage(dest.getAbsolutePath)) def crop(op: IMOperation)(b: Bounds): IMOperation = op <| (_.crop(b.width, b.height, b.x, b.y)) def profile(op: IMOperation)(profileFileLocation: String): IMOperation = op <| (_.profile(profileFileLocation)) + def thumbnail(op: IMOperation)(width: Int): IMOperation = op <| (_.thumbnail(width)) def scale(op: IMOperation)(dimensions: Dimensions): IMOperation = op <| (_.scale(dimensions.width, dimensions.height)) + def format(op: IMOperation)(definition: String): IMOperation = op <| (_.format(definition)) + def runConvertCmd(op: IMOperation): Future[Unit] = Future((new ConvertCmd).run(op)) + def runIdentifyCmd(op: IMOperation): Future[List[String]] = Future { + val cmd = new IdentifyCmd() + val output = new ArrayListOutputConsumer() + cmd.setOutputConsumer(output) + cmd.run(op) + output.getOutput.asScala.toList + } } diff --git a/common-lib/src/main/scala/com/gu/mediaservice/lib/metadata/FileMetadataHelper.scala b/common-lib/src/main/scala/com/gu/mediaservice/lib/metadata/FileMetadataHelper.scala new file mode 100644 index 0000000000..8bc1f87216 --- /dev/null +++ b/common-lib/src/main/scala/com/gu/mediaservice/lib/metadata/FileMetadataHelper.scala @@ -0,0 +1,14 @@ +package com.gu.mediaservice.lib.metadata + +import com.gu.mediaservice.model.FileMetadata + +object FileMetadataHelper { + + def normalisedIccColourSpace(fileMetadata: FileMetadata): Option[String] = { + fileMetadata.icc.get("Color space") map { + case "GRAY" => "GRAYSCALE" + case other => other + } + } + +} diff --git a/common-lib/src/main/scala/com/gu/mediaservice/model/SourceImage.scala b/common-lib/src/main/scala/com/gu/mediaservice/model/SourceImage.scala index 44e064dbf3..f35a96e103 100644 --- a/common-lib/src/main/scala/com/gu/mediaservice/model/SourceImage.scala +++ b/common-lib/src/main/scala/com/gu/mediaservice/model/SourceImage.scala @@ -4,13 +4,14 @@ import play.api.libs.json._ import play.api.libs.functional.syntax._ -case class SourceImage(id: String, source: Asset, valid: Boolean, metadata: ImageMetadata) +case class SourceImage(id: String, source: Asset, valid: Boolean, metadata: ImageMetadata, fileMetadata: FileMetadata) object SourceImage { implicit val sourceImageReads: Reads[SourceImage] = ((__ \ "data" \ "id").read[String] ~ (__ \ "data" \ "source").read[Asset] ~ (__ \ "data" \ "valid").read[Boolean] ~ - (__ \ "data" \ "metadata").read[ImageMetadata] + (__ \ "data" \ "metadata").read[ImageMetadata] ~ + (__ \ "data" \ "fileMetadata" \ "data").read[FileMetadata] )(SourceImage.apply _) } diff --git a/common-lib/src/test/resources/cmyk.jpg b/common-lib/src/test/resources/cmyk.jpg new file mode 100644 index 0000000000..6f3d857afc Binary files /dev/null and b/common-lib/src/test/resources/cmyk.jpg differ diff --git a/common-lib/src/test/resources/grayscale-with-profile.jpg b/common-lib/src/test/resources/grayscale-with-profile.jpg new file mode 100644 index 0000000000..5e3773d67f Binary files /dev/null and b/common-lib/src/test/resources/grayscale-with-profile.jpg differ diff --git a/common-lib/src/test/resources/grayscale-wo-profile.jpg b/common-lib/src/test/resources/grayscale-wo-profile.jpg new file mode 100644 index 0000000000..5781405554 Binary files /dev/null and b/common-lib/src/test/resources/grayscale-wo-profile.jpg differ diff --git a/common-lib/src/test/resources/rgb-with-cmyk-profile.jpg b/common-lib/src/test/resources/rgb-with-cmyk-profile.jpg new file mode 100644 index 0000000000..45ddcc2fdb Binary files /dev/null and b/common-lib/src/test/resources/rgb-with-cmyk-profile.jpg differ diff --git a/common-lib/src/test/resources/rgb-with-rgb-profile.jpg b/common-lib/src/test/resources/rgb-with-rgb-profile.jpg new file mode 100644 index 0000000000..d893d55ae1 Binary files /dev/null and b/common-lib/src/test/resources/rgb-with-rgb-profile.jpg differ diff --git a/common-lib/src/test/resources/rgb-wo-profile.jpg b/common-lib/src/test/resources/rgb-wo-profile.jpg new file mode 100644 index 0000000000..11c2aab3f0 Binary files /dev/null and b/common-lib/src/test/resources/rgb-wo-profile.jpg differ diff --git a/common-lib/src/test/scala/com/gu/mediaservice/lib/imaging/ImageOperationsTest.scala b/common-lib/src/test/scala/com/gu/mediaservice/lib/imaging/ImageOperationsTest.scala new file mode 100644 index 0000000000..ca68fb9161 --- /dev/null +++ b/common-lib/src/test/scala/com/gu/mediaservice/lib/imaging/ImageOperationsTest.scala @@ -0,0 +1,63 @@ +package com.gu.mediaservice.lib.imaging + +import java.io.File + +import org.scalatest.time.{Millis, Span} +import org.scalatest.{Ignore, Matchers, FunSpec} +import org.scalatest.concurrent.ScalaFutures + +// This test is disabled for now as it doesn't run on our CI environment, because GraphicsMagick is not present... +@Ignore +class ImageOperationsTest extends FunSpec with Matchers with ScalaFutures { + + implicit override val patienceConfig = PatienceConfig(timeout = Span(1000, Millis), interval = Span(25, Millis)) + + describe("identifyColourModel") { + it("should return RGB for a JPG image with RGB image data and no embedded profile") { + val image = fileAt("rgb-wo-profile.jpg") + val colourModelFuture = ImageOperations.identifyColourModel(image, "image/jpeg") + whenReady(colourModelFuture) { colourModel => + colourModel should be (Some("RGB")) + } + } + + it("should return RGB for a JPG image with RGB image data and an RGB embedded profile") { + val image = fileAt("rgb-with-rgb-profile.jpg") + val colourModelFuture = ImageOperations.identifyColourModel(image, "image/jpeg") + whenReady(colourModelFuture) { colourModel => + colourModel should be (Some("RGB")) + } + } + + it("should return RGB for a JPG image with RGB image data and an incorrect CMYK embedded profile") { + val image = fileAt("rgb-with-cmyk-profile.jpg") + val colourModelFuture = ImageOperations.identifyColourModel(image, "image/jpeg") + whenReady(colourModelFuture) { colourModel => + colourModel should be (Some("RGB")) + } + } + + it("should return CMYK for a JPG image with CMYK image data") { + val image = fileAt("cmyk.jpg") + val colourModelFuture = ImageOperations.identifyColourModel(image, "image/jpeg") + whenReady(colourModelFuture) { colourModel => + colourModel should be (Some("CMYK")) + } + } + + it("should return GRAYSCALE for a JPG image with GRAYSCALE image data and no embedded profile") { + val image = fileAt("grayscale-wo-profile.jpg") + val colourModelFuture = ImageOperations.identifyColourModel(image, "image/jpeg") + whenReady(colourModelFuture) { colourModel => + colourModel should be (Some("GRAYSCALE")) + } + } + } + + // TODO: test cropImage and its conversions + + def fileAt(resourcePath: String): File = { + new File(getClass.getResource(s"/$resourcePath").toURI) + } + +} diff --git a/cropper/app/controllers/Application.scala b/cropper/app/controllers/Application.scala index 54e61330b9..12b39ceb3c 100644 --- a/cropper/app/controllers/Application.scala +++ b/cropper/app/controllers/Application.scala @@ -16,12 +16,11 @@ import com.gu.mediaservice.lib.auth import com.gu.mediaservice.lib.auth._ import com.gu.mediaservice.lib.argo.ArgoHelpers import com.gu.mediaservice.lib.argo.model.{Action, Link} +import com.gu.mediaservice.lib.imaging.ExportResult import com.gu.mediaservice.model.{Crop, SourceImage, CropSource, Bounds} import org.joda.time.DateTime -import lib.imaging.ExportResult - import lib._ @@ -124,10 +123,14 @@ object Application extends Controller with ArgoHelpers { } } - def fetchSourceFromApi(uri: String): Future[SourceImage] = - for (resp <- WS.url(uri).withHeaders("X-Gu-Media-Key" -> mediaApiKey).get) + def fetchSourceFromApi(uri: String): Future[SourceImage] = { + val imageRequest = WS.url(uri). + withHeaders("X-Gu-Media-Key" -> mediaApiKey). + withQueryString("include" -> "fileMetadata") + for (resp <- imageRequest.get) yield { if (resp.status != 200) Logger.warn(s"HTTP status ${resp.status} ${resp.statusText} from $uri") resp.json.as[SourceImage] } + } } diff --git a/cropper/app/lib/Config.scala b/cropper/app/lib/Config.scala index 7d09d85a77..1b83b12302 100644 --- a/cropper/app/lib/Config.scala +++ b/cropper/app/lib/Config.scala @@ -36,8 +36,6 @@ object Config extends CommonPlayAppProperties with CommonPlayAppConfig { val tempDir: File = new File(properties.getOrElse("crop.output.tmp.dir", "/tmp")) - val imagingThreadPoolSize = 4 - val landscapeCropSizingWidths = List(2000, 1000, 500, 140) val portraitCropSizingHeights = List(2000, 1000, 500) } diff --git a/cropper/app/lib/Crops.scala b/cropper/app/lib/Crops.scala index 0b4d74c930..4da888c41f 100644 --- a/cropper/app/lib/Crops.scala +++ b/cropper/app/lib/Crops.scala @@ -1,10 +1,14 @@ package lib +import java.io.File + +import com.gu.mediaservice.lib.metadata.FileMetadataHelper + import scala.concurrent.Future -import lib.imaging.{ExportOperations, ExportResult} + import com.gu.mediaservice.model.{Asset, Dimensions, SourceImage, Crop, Bounds, CropSource} -import java.net.{URI, URL} -import java.io.File +import com.gu.mediaservice.lib.Files +import com.gu.mediaservice.lib.imaging.{ImageOperations, ExportResult} case object InvalidImage extends Exception("Invalid image cannot be cropped") case object MissingSecureSourceUrl extends Exception("Missing secureUrl from source API") @@ -20,13 +24,14 @@ object Crops { s"${source.id}/${Crop.getCropId(bounds)}/${if(isMaster) "master/" else ""}$outputWidth.jpg" } - def createMasterCrop(apiImage: SourceImage, sourceFile: File, crop: Crop, mediaType: String): Future[MasterCrop] = { + def createMasterCrop(apiImage: SourceImage, sourceFile: File, crop: Crop, mediaType: String, colourModel: Option[String]): Future[MasterCrop] = { val source = crop.specification val metadata = apiImage.metadata + val iccColourSpace = FileMetadataHelper.normalisedIccColourSpace(apiImage.fileMetadata) for { - strip <- ExportOperations.cropImage(sourceFile, source.bounds, 100d) - file <- ExportOperations.appendMetadata(strip, metadata) + strip <- ImageOperations.cropImage(sourceFile, source.bounds, 100d, Config.tempDir, iccColourSpace, colourModel) + file <- ImageOperations.appendMetadata(strip, metadata) dimensions = Dimensions(source.bounds.width, source.bounds.height) filename = outputFilename(apiImage, source.bounds, dimensions.width, true) @@ -40,7 +45,7 @@ object Crops { Future.sequence[Asset, List](dimensionList.map { dimensions => val filename = outputFilename(apiImage, crop.specification.bounds, dimensions.width) for { - file <- ExportOperations.resizeImage(sourceFile, dimensions, 75d) + file <- ImageOperations.resizeImage(sourceFile, dimensions, 75d, Config.tempDir) sizing <- CropStore.storeCropSizing(file, filename, mediaType, crop, dimensions) _ <- delete(file) } @@ -75,8 +80,9 @@ object Crops { if(isInvalidCrop(apiImage.source, source)) throw InvalidCropRequest for { - sourceFile <- tempFileFromURL(secureUrl, "cropSource", "") - masterCrop <- createMasterCrop(apiImage, sourceFile, crop, mediaType) + sourceFile <- tempFileFromURL(secureUrl, "cropSource", "", Config.tempDir) + colourModel <- ImageOperations.identifyColourModel(sourceFile, mediaType) + masterCrop <- createMasterCrop(apiImage, sourceFile, crop, mediaType, colourModel) outputDims = dimensionsFromConfig(source.bounds, masterCrop.aspectRatio) :+ masterCrop.dimensions diff --git a/cropper/app/lib/imaging/ExportOperations.scala b/cropper/app/lib/imaging/ExportOperations.scala deleted file mode 100644 index 47eee9c97d..0000000000 --- a/cropper/app/lib/imaging/ExportOperations.scala +++ /dev/null @@ -1,64 +0,0 @@ -package lib.imaging - -import java.io._ - -import scala.concurrent.Future - -import play.api.libs.concurrent.Execution.Implicits._ - -import com.gu.mediaservice.model.{Dimensions, ImageMetadata, Asset, Bounds, CropSource} - -import lib.Files._ - - -case class ExportResult(id: String, masterCrop: Asset, othersizings: List[Asset]) - -object ExportOperations { - import lib.imaging.im4jwrapper.ImageMagick._ - import lib.imaging.im4jwrapper.ExifTool._ - - lazy val imageProfileLocation = s"${play.api.Play.current.path}/srgb.icc" - - def tagFilter(metadata: ImageMetadata) = { - Map[String, Option[String]]( - "Copyright" -> metadata.copyright, - "CopyrightNotice" -> metadata.copyrightNotice, - "Credit" -> metadata.credit, - "OriginalTransmissionReference" -> metadata.suppliersReference - ).collect { case (key, Some(value)) => (key, value) } - } - - def cropImage(sourceFile: File, bounds: Bounds, qual: Double = 100d): Future[File] = { - for { - outputFile <- createTempFile(s"crop-", ".jpg") - cropSource = addImage(sourceFile) - qualified = quality(cropSource)(qual) - converted = profile(qualified)(imageProfileLocation) - stripped = stripMeta(converted) - profiled = profile(stripped)(imageProfileLocation) - cropped = crop(profiled)(bounds) - addOutput = addDestImage(cropped)(outputFile) - _ <- runConvertCmd(addOutput) - } - yield outputFile - } - - // Updates metadata on existing file - def appendMetadata(sourceFile: File, metadata: ImageMetadata): Future[File] = { - runExiftoolCmd( - setTags(tagSource(sourceFile))(tagFilter(metadata)) - ).map(_ => sourceFile) - } - - def resizeImage(sourceFile: File, dimensions: Dimensions, qual: Double = 100d): Future[File] = { - for { - outputFile <- createTempFile(s"resize-", ".jpg") - resizeSource = addImage(sourceFile) - qualified = quality(resizeSource)(qual) - resized = scale(qualified)(dimensions) - addOutput = addDestImage(resized)(outputFile) - _ <- runConvertCmd(addOutput) - } - yield outputFile - } -} diff --git a/cropper/cmyk.icc b/cropper/cmyk.icc new file mode 100644 index 0000000000..078a6443a7 Binary files /dev/null and b/cropper/cmyk.icc differ diff --git a/cropper/grayscale.icc b/cropper/grayscale.icc new file mode 100644 index 0000000000..9bbfdec0b2 Binary files /dev/null and b/cropper/grayscale.icc differ diff --git a/project/Build.scala b/project/Build.scala index f9aed48654..4afc8f0b5e 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -26,7 +26,7 @@ object Build extends Build { val lib = project("common-lib") .libraryDependencies(loggingDeps ++ awsDeps ++ elasticsearchDeps ++ playDeps ++ playWsDeps ++ scalazDeps ++ commonsIODeps ++ akkaAgentDeps ++ - pandaDeps) + pandaDeps ++ imagingDeps) .testDependencies(scalaCheckDeps ++ scalaTestDeps) val thrall = playProject("thrall") diff --git a/project/PlayArtifact.scala b/project/PlayArtifact.scala index 8babb8dfe8..36dd65d629 100644 --- a/project/PlayArtifact.scala +++ b/project/PlayArtifact.scala @@ -31,7 +31,9 @@ object PlayArtifact extends Plugin { ) val cropper = Seq( - base / "srgb.icc" -> s"packages/$packageName/srgb.icc" + base / "cmyk.icc" -> s"packages/$packageName/cmyk.icc", + base / "grayscale.icc" -> s"packages/$packageName/grayscale.icc", + base / "srgb.icc" -> s"packages/$packageName/srgb.icc" ) name match {