@@ -22,10 +22,13 @@ class Crops(config: CropperConfig, store: CropStore, imageOperations: ImageOpera
2222
2323 private val cropQuality = 75d
2424 private val masterCropQuality = 95d
25+ // For PNGs, Magick considers "quality" parameter as effort spent on compression - 1 meaning none, 100 meaning max.
26+ // We don't overly care about output crop file sizes here, but prefer a fast output, so turn it right down.
27+ private val pngCropQuality = 1d
2528
2629 def outputFilename (source : SourceImage , bounds : Bounds , outputWidth : Int , fileType : MimeType , isMaster : Boolean = false ): String = {
2730 val masterString : String = if (isMaster) " master/" else " "
28- s " ${source.id}/ ${Crop .getCropId(bounds)}/ ${ masterString} $outputWidth${fileType.fileExtension}"
31+ s " ${source.id}/ ${Crop .getCropId(bounds)}/ $masterString$outputWidth${fileType.fileExtension}"
2932 }
3033
3134 def createMasterCrop (
@@ -36,41 +39,52 @@ class Crops(config: CropperConfig, store: CropStore, imageOperations: ImageOpera
3639 colourModel : Option [String ],
3740 )(implicit logMarker : LogMarker ): Future [MasterCrop ] = {
3841
39- val source = crop.specification
40- val metadata = apiImage.metadata
41- val iccColourSpace = FileMetadataHelper .normalisedIccColourSpace(apiImage.fileMetadata)
42-
43- logger.info(logMarker, s " creating master crop for ${apiImage.id}" )
44-
45- for {
46- strip <- imageOperations.cropImage(
47- sourceFile, apiImage.source.mimeType, source.bounds, masterCropQuality, config.tempDir,
48- iccColourSpace, colourModel, mediaType, isTransformedFromSource = false
49- )
50- file : File <- imageOperations.appendMetadata(strip, metadata)
51- dimensions = Dimensions (source.bounds.width, source.bounds.height)
52- filename = outputFilename(apiImage, source.bounds, dimensions.width, mediaType, isMaster = true )
53- sizing = store.storeCropSizing(file, filename, mediaType, crop, dimensions)
54- dirtyAspect = source.bounds.width.toFloat / source.bounds.height
55- aspect = crop.specification.aspectRatio.flatMap(AspectRatio .clean).getOrElse(dirtyAspect)
42+ Stopwatch .async(s " creating master crop for ${apiImage.id}" ) {
43+ val source = crop.specification
44+ val metadata = apiImage.metadata
45+ val iccColourSpace = FileMetadataHelper .normalisedIccColourSpace(apiImage.fileMetadata)
46+ // pngs are always lossless, so quality only means effort spent compressing them. We don't
47+ // care too much about filesize of master crops, so skip expensive compression to get faster cropping
48+ val quality = if (mediaType == Png ) pngCropQuality else masterCropQuality
49+
50+ for {
51+ strip <- imageOperations.cropImage(
52+ sourceFile, apiImage.source.mimeType, source.bounds, quality, config.tempDir,
53+ iccColourSpace, colourModel, mediaType, isTransformedFromSource = false
54+ )
55+ file : File <- imageOperations.appendMetadata(strip, metadata)
56+ dimensions = Dimensions (source.bounds.width, source.bounds.height)
57+ filename = outputFilename(apiImage, source.bounds, dimensions.width, mediaType, isMaster = true )
58+ sizing = store.storeCropSizing(file, filename, mediaType, crop, dimensions)
59+ dirtyAspect = source.bounds.width.toFloat / source.bounds.height
60+ aspect = crop.specification.aspectRatio.flatMap(AspectRatio .clean).getOrElse(dirtyAspect)
61+ }
62+ yield MasterCrop (sizing, file, dimensions, aspect)
5663 }
57- yield MasterCrop (sizing, file, dimensions, aspect)
5864 }
5965
6066 def createCrops (sourceFile : File , dimensionList : List [Dimensions ], apiImage : SourceImage , crop : Crop , cropType : MimeType )(implicit logMarker : LogMarker ): Future [List [Asset ]] = {
61- logger.info(logMarker, s " creating crops for ${apiImage.id}" )
62-
63- Future .sequence(dimensionList.map { dimensions =>
64- for {
65- file <- imageOperations.resizeImage(sourceFile, apiImage.source.mimeType, dimensions, cropQuality, config.tempDir, cropType)
66- optimisedFile = imageOperations.optimiseImage(file, cropType)
67- filename = outputFilename(apiImage, crop.specification.bounds, dimensions.width, cropType)
68- sizing <- store.storeCropSizing(optimisedFile, filename, cropType, crop, dimensions)
69- _ <- delete(file)
70- _ <- delete(optimisedFile)
71- }
72- yield sizing
73- })
67+ val quality = if (cropType == Png ) pngCropQuality else cropQuality
68+
69+ Stopwatch .async(s " creating crops for ${apiImage.id}" ) {
70+ Future .sequence(dimensionList.map { dimensions =>
71+ val cropLogMarker = logMarker ++ Map (" crop-dimensions" -> s " ${dimensions.width}x ${dimensions.height}" )
72+ for {
73+ file <- imageOperations.resizeImage(sourceFile,
74+ apiImage.source.mimeType,
75+ dimensions,
76+ quality,
77+ config.tempDir,
78+ cropType)(cropLogMarker)
79+ optimisedFile = imageOperations.optimiseImage(file, cropType)(cropLogMarker)
80+ filename = outputFilename(apiImage, crop.specification.bounds, dimensions.width, cropType)
81+ sizing <- store.storeCropSizing(optimisedFile, filename, cropType, crop, dimensions)(cropLogMarker)
82+ _ <- delete(file)
83+ _ <- delete(optimisedFile)
84+ }
85+ yield sizing
86+ })
87+ }
7488 }
7589
7690 def deleteCrops (id : String )(implicit logMarker : LogMarker ): Future [Unit ] = store.deleteCrops(id)
@@ -97,7 +111,7 @@ class Crops(config: CropperConfig, store: CropStore, imageOperations: ImageOpera
97111 val hasAlpha = apiImage.fileMetadata.colourModelInformation.get(" hasAlpha" ).flatMap(a => Try (a.toBoolean).toOption).getOrElse(true )
98112 val cropType = Crops .cropType(mimeType, colourType, hasAlpha)
99113
100- Stopwatch (s " making crop assets for ${apiImage.id} ${Crop .getCropId(source.bounds)}" ) {
114+ Stopwatch .async (s " making crop assets for ${apiImage.id} ${Crop .getCropId(source.bounds)}" ) {
101115 for {
102116 sourceFile <- tempFileFromURL(secureUrl, " cropSource" , " " , config.tempDir)
103117 colourModel <- ImageOperations .identifyColourModel(sourceFile, mimeType)
@@ -126,8 +140,7 @@ object Crops {
126140 val outputAsPng = hasAlpha || isGraphic
127141
128142 mediaType match {
129- case Png if outputAsPng => Png
130- case Tiff if outputAsPng => Png
143+ case Png | Tiff if outputAsPng => Png
131144 case _ => Jpeg
132145 }
133146 }
0 commit comments