Skip to content

Commit

Permalink
Merge pull request #360 from NDLANO/pixel-crop
Browse files Browse the repository at this point in the history
Add support for pixel-crop
  • Loading branch information
gunnarvelle authored Jan 16, 2024
2 parents 70e5807 + fbd9026 commit 727296e
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,19 @@ trait RawController {
queryParam[Option[Int]]("height")
.description("The target height to resize the image (the unit is pixles). Image proportions are kept intact"),
queryParam[Option[Int]]("cropStartX").description(
"The first image coordinate X, in percent (0 to 100), specifying the crop start position. If used the other crop parameters must also be supplied"
"The first image coordinate X, in percent (0 to 100) or pixels depending on cropUnit, specifying the crop start position. If used the other crop parameters must also be supplied"
),
queryParam[Option[Int]]("cropStartY").description(
"The first image coordinate Y, in percent (0 to 100), specifying the crop start position. If used the other crop parameters must also be supplied"
"The first image coordinate Y, in percent (0 to 100) or pixels depending on cropUnit, specifying the crop start position. If used the other crop parameters must also be supplied"
),
queryParam[Option[Int]]("cropEndX").description(
"The end image coordinate X, in percent (0 to 100), specifying the crop end position. If used the other crop parameters must also be supplied"
"The end image coordinate X, in percent (0 to 100) or pixels depending on cropUnit, specifying the crop end position. If used the other crop parameters must also be supplied"
),
queryParam[Option[Int]]("cropEndY").description(
"The end image coordinate Y, in percent (0 to 100), specifying the crop end position. If used the other crop parameters must also be supplied"
"The end image coordinate Y, in percent (0 to 100) or pixels depending on cropUnit, specifying the crop end position. If used the other crop parameters must also be supplied"
),
queryParam[Option[String]]("cropUnit").description(
"The unit of the crop parameters. Can be either 'percent' or 'pixel'. If omitted the unit is assumed to be 'percent'"
),
queryParam[Option[Int]]("focalX").description(
"The end image coordinate X, in percent (0 to 100), specifying the focal point. If used the other focal point parameter, width and/or height, must also be supplied"
Expand Down Expand Up @@ -156,15 +159,30 @@ trait RawController {
}

private def crop(image: ImageStream)(implicit request: HttpServletRequest): Try[ImageStream] = {
val startX = doubleInRange("cropStartX", PercentPoint.MinValue, PercentPoint.MaxValue)
val startY = doubleInRange("cropStartY", PercentPoint.MinValue, PercentPoint.MaxValue)
val endX = doubleInRange("cropEndX", PercentPoint.MinValue, PercentPoint.MaxValue)
val endY = doubleInRange("cropEndY", PercentPoint.MinValue, PercentPoint.MaxValue)

(startX, startY, endX, endY) match {
case (Some(sx), Some(sy), Some(ex), Some(ey)) =>
imageConverter.crop(image, PercentPoint(sx, sy), PercentPoint(ex, ey))
case _ => Success(image)
val unit = paramOrDefault("cropUnit", "percent")
unit match {
case "percent" => {
val startX = doubleInRange("cropStartX", PercentPoint.MinValue, PercentPoint.MaxValue)
val startY = doubleInRange("cropStartY", PercentPoint.MinValue, PercentPoint.MaxValue)
val endX = doubleInRange("cropEndX", PercentPoint.MinValue, PercentPoint.MaxValue)
val endY = doubleInRange("cropEndY", PercentPoint.MinValue, PercentPoint.MaxValue)
(startX, startY, endX, endY) match {
case (Some(sx), Some(sy), Some(ex), Some(ey)) =>
imageConverter.crop(image, PercentPoint(sx, sy), PercentPoint(ex, ey))
case _ => Success(image)
}
}
case "pixel" => {
val startX = castIntOrNone("cropStartX")
val startY = castIntOrNone("cropStartY")
val endX = castIntOrNone("cropEndX")
val endY = castIntOrNone("cropEndY")
(startX, startY, endX, endY) match {
case (Some(sx), Some(sy), Some(ex), Some(ey)) =>
imageConverter.crop(image, PixelPoint(sx, sy), PixelPoint(ex, ey))
case _ => Success(image)
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ trait ImageConverter {
crop(originalImage, sourceImage, topLeft, bottomRight)
}

def crop(originalImage: ImageStream, start: PixelPoint, end: PixelPoint): Try[ImageStream] = {
val sourceImage = originalImage.sourceImage
crop(originalImage, sourceImage, start, end)
}

private def getStartEndCoords(focalPoint: Int, targetDimensionSize: Int, originalDimensionSize: Int): (Int, Int) = {
val ts = min(targetDimensionSize.toDouble, originalDimensionSize.toDouble) / 2.0
val (start, end) = (focalPoint - ts.floor.toInt, focalPoint + ts.round.toInt)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ trait NdlaControllerBase {
}
override def renderPipeline: RenderPipeline = tryRenderer.orElse(super.renderPipeline)

def isInteger(value: String): Boolean = value.forall(_.isDigit)
def isDouble(value: String): Boolean = Try(value.toDouble).isSuccess
def isBoolean(value: String): Boolean = Try(value.toBoolean).isSuccess
private def isInteger(value: String): Boolean = value.forall(_.isDigit)
private def isDouble(value: String): Boolean = Try(value.toDouble).isSuccess
private def isBoolean(value: String): Boolean = Try(value.toBoolean).isSuccess

def long(paramName: String)(implicit request: HttpServletRequest): Long = {
val paramValue = params(paramName)
Expand Down Expand Up @@ -129,7 +129,7 @@ trait NdlaControllerBase {
(extractDoubleOpt(one), extractDoubleOpt(two))
}

def extractDoubleOpt(paramName: String)(implicit request: HttpServletRequest): Option[Double] = {
private def extractDoubleOpt(paramName: String)(implicit request: HttpServletRequest): Option[Double] = {
params.get(paramName) match {
case Some(value) =>
if (!isDouble(value))
Expand Down Expand Up @@ -178,6 +178,10 @@ trait NdlaControllerBase {
paramOrNone(name).flatMap(i => Try(i.toInt).toOption)
}

def castIntOrNone(name: String)(implicit request: HttpServletRequest): Option[Int] = {
doubleOrNone(name).map(_.toInt)
}

def paramOrDefault(paramName: String, default: String)(implicit request: HttpServletRequest): String = {
paramOrNone(paramName).getOrElse(default)
}
Expand Down Expand Up @@ -205,7 +209,7 @@ trait NdlaControllerBase {
}
}

val digitsOnlyError = (paramName: String) =>
private val digitsOnlyError = (paramName: String) =>
Failure(
new ValidationException(
errors = Seq(ValidationMessage(paramName, s"Invalid value for $paramName. Only digits are allowed."))
Expand Down

0 comments on commit 727296e

Please sign in to comment.