Add scalashop assignment

This commit is contained in:
Guillaume Martres 2019-02-19 20:44:23 +01:00
commit 690105b675
19 changed files with 928 additions and 0 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
# General
*.DS_Store
*.swp
*~
# Dotty
*.class
*.tasty
*.hasTasty
# sbt
target/
# Dotty IDE
/.dotty-ide-artifact
/.dotty-ide.json

36
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,36 @@
# DO NOT EDIT THIS FILE
stages:
- build
- grade
compile:
stage: build
image: lampepfl/moocs:dotty-2020-02-12
except:
- tags
tags:
- cs206
script:
- sbt packageSubmission
artifacts:
expire_in: 1 day
paths:
- submission.jar
grade:
stage: grade
except:
- tags
tags:
- cs206
image:
name: smarter3/moocs:parprog1-scalashop-2020-02-17
entrypoint: [""]
allow_failure: true
before_script:
- mkdir -p /shared/submission/
- cp submission.jar /shared/submission/submission.jar
script:
- cd /grader
- /grader/grade | /grader/feedback-printer

8
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,8 @@
{
"dotty": {
"trace": {
"remoteTracingUrl": "wss://lamppc36.epfl.ch/dotty-remote-tracer/upload/lsp.log",
"server": { "format": "JSON", "verbosity": "verbose" }
}
}
}

9
assignment.sbt Normal file
View File

@ -0,0 +1,9 @@
// Student tasks (i.e. submit, packageSubmission)
enablePlugins(StudentTasks)
courseraId := ch.epfl.lamp.CourseraId(
key = "OpSNmtC1EeWvXAr2bF16EQ",
itemId = "MhXvy",
premiumItemId = Some("NeGTv"),
partId = "Q2e1P"
)

13
build.sbt Normal file
View File

@ -0,0 +1,13 @@
course := "parprog1"
assignment := "scalashop"
scalaVersion := "0.23.0-bin-20200211-5b006fb-NIGHTLY"
scalacOptions ++= Seq("-language:implicitConversions", "-deprecation")
libraryDependencies ++= Seq(
"com.storm-enroute" %% "scalameter-core" % "0.19",
"org.scala-lang.modules" %% "scala-parallel-collections" % "0.2.0",
"com.novocode" % "junit-interface" % "0.11" % Test
).map(_.withDottyCompat(scalaVersion.value))
testOptions in Test += Tests.Argument(TestFrameworks.JUnit, "-a", "-v", "-s")
testSuite := "scalashop.BlurSuite"

BIN
grading-tests.jar Normal file

Binary file not shown.

View File

@ -0,0 +1,46 @@
package ch.epfl.lamp
import sbt._
import sbt.Keys._
/**
* Coursera uses two versions of each assignment. They both have the same assignment key and part id but have
* different item ids.
*
* @param key Assignment key
* @param partId Assignment partId
* @param itemId Item id of the non premium version
* @param premiumItemId Item id of the premium version (`None` if the assignment is optional)
*/
case class CourseraId(key: String, partId: String, itemId: String, premiumItemId: Option[String])
/**
* Settings shared by all assignments, reused in various tasks.
*/
object MOOCSettings extends AutoPlugin {
object autoImport {
val course = SettingKey[String]("course")
val assignment = SettingKey[String]("assignment")
val options = SettingKey[Map[String, Map[String, String]]]("options")
val courseraId = settingKey[CourseraId]("Coursera-specific information identifying the assignment")
val testSuite = settingKey[String]("Fully qualified name of the test suite of this assignment")
// Convenient alias
type CourseraId = ch.epfl.lamp.CourseraId
val CourseraId = ch.epfl.lamp.CourseraId
}
import autoImport._
override val globalSettings: Seq[Def.Setting[_]] = Seq(
// supershell is verbose, buggy and useless.
useSuperShell := false
)
override val projectSettings: Seq[Def.Setting[_]] = Seq(
parallelExecution in Test := false,
// Report test result after each test instead of waiting for every test to finish
logBuffered in Test := false,
name := s"${course.value}-${assignment.value}"
)
}

318
project/StudentTasks.scala Normal file
View File

@ -0,0 +1,318 @@
package ch.epfl.lamp
import sbt._
import Keys._
// import scalaj.http._
import java.io.{File, FileInputStream, IOException}
import org.apache.commons.codec.binary.Base64
// import play.api.libs.json.{Json, JsObject, JsPath}
import scala.util.{Failure, Success, Try}
/**
* Provides tasks for submitting the assignment
*/
object StudentTasks extends AutoPlugin {
override def requires = super.requires && MOOCSettings
object autoImport {
val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project")
val packageBinWithoutResources = TaskKey[File]("packageBinWithoutResources", "Like packageBin, but without the resources")
val packageSubmissionZip = TaskKey[File]("packageSubmissionZip")
val packageSubmission = inputKey[Unit]("package solution as an archive file")
val runGradingTests = taskKey[Unit]("run black-box tests used for final grading")
}
import autoImport._
import MOOCSettings.autoImport._
override lazy val projectSettings = Seq(
packageSubmissionSetting,
// submitSetting,
runGradingTestsSettings,
fork := true,
connectInput in run := true,
outputStrategy := Some(StdoutOutput),
) ++ packageSubmissionZipSettings
lazy val runGradingTestsSettings = runGradingTests := {
val testSuiteJar = "grading-tests.jar"
if (!new File(testSuiteJar).exists) {
throw new MessageOnlyException(s"Could not find tests JarFile: $testSuiteJar")
}
val classPath = s"${(Test / dependencyClasspath).value.map(_.data).mkString(File.pathSeparator)}${File.pathSeparator}$testSuiteJar"
val junitProcess =
Fork.java.fork(
ForkOptions(),
"-cp" :: classPath ::
"org.junit.runner.JUnitCore" ::
(Test / testSuite).value ::
Nil
)
// Wait for tests to complete.
junitProcess.exitValue()
}
/** **********************************************************
* SUBMITTING A SOLUTION TO COURSERA
*/
val packageSubmissionZipSettings = Seq(
packageSubmissionZip := {
val submission = crossTarget.value / "submission.zip"
val sources = (packageSourcesOnly in Compile).value
val binaries = (packageBinWithoutResources in Compile).value
IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission)
submission
},
artifactClassifier in packageSourcesOnly := Some("sources"),
artifact in (Compile, packageBinWithoutResources) ~= (art => art.withName(art.name + "-without-resources"))
) ++
inConfig(Compile)(
Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings) ++
Defaults.packageTaskSettings(packageBinWithoutResources, Def.task {
val relativePaths =
(unmanagedResources in Compile).value.flatMap(Path.relativeTo((unmanagedResourceDirectories in Compile).value)(_))
(mappings in (Compile, packageBin)).value.filterNot { case (_, path) => relativePaths.contains(path) }
})
)
val maxSubmitFileSize = {
val mb = 1024 * 1024
10 * mb
}
/** Check that the jar exists, isn't empty, isn't crazy big, and can be read
* If so, encode jar as base64 so we can send it to Coursera
*/
def prepareJar(jar: File, s: TaskStreams): String = {
val errPrefix = "Error submitting assignment jar: "
val fileLength = jar.length()
if (!jar.exists()) {
s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath)
failSubmit()
} else if (fileLength == 0L) {
s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath)
failSubmit()
} else if (fileLength > maxSubmitFileSize) {
s.log.error(errPrefix + "jar archive is too big. Allowed size: " +
maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" +
jar.getAbsolutePath)
failSubmit()
} else {
val bytes = new Array[Byte](fileLength.toInt)
val sizeRead = try {
val is = new FileInputStream(jar)
val read = is.read(bytes)
is.close()
read
} catch {
case ex: IOException =>
s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString)
failSubmit()
}
if (sizeRead != bytes.length) {
s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead)
failSubmit()
} else encodeBase64(bytes)
}
}
/** Task to package solution to a given file path */
lazy val packageSubmissionSetting = packageSubmission := {
val args: Seq[String] = Def.spaceDelimited("[path]").parsed
val s: TaskStreams = streams.value // for logging
val jar = (packageSubmissionZip in Compile).value
val base64Jar = prepareJar(jar, s)
val path = args.headOption.getOrElse((baseDirectory.value / "submission.jar").absolutePath)
scala.tools.nsc.io.File(path).writeAll(base64Jar)
}
/*
/** Task to submit a solution to coursera */
val submit = inputKey[Unit]("submit solution to Coursera")
lazy val submitSetting = submit := {
// Fail if scalafix linting does not pass.
scalafixLinting.value
val args: Seq[String] = Def.spaceDelimited("<arg>").parsed
val s: TaskStreams = streams.value // for logging
val jar = (packageSubmissionZip in Compile).value
val assignmentDetails =
courseraId.?.value.getOrElse(throw new MessageOnlyException("This assignment can not be submitted to Coursera because the `courseraId` setting is undefined"))
val assignmentKey = assignmentDetails.key
val courseName =
course.value match {
case "capstone" => "scala-capstone"
case "bigdata" => "scala-spark-big-data"
case other => other
}
val partId = assignmentDetails.partId
val itemId = assignmentDetails.itemId
val premiumItemId = assignmentDetails.premiumItemId
val (email, secret) = args match {
case email :: secret :: Nil =>
(email, secret)
case _ =>
val inputErr =
s"""|Invalid input to `submit`. The required syntax for `submit` is:
|submit <email-address> <submit-token>
|
|The submit token is NOT YOUR LOGIN PASSWORD.
|It can be obtained from the assignment page:
|https://www.coursera.org/learn/$courseName/programming/$itemId
|${
premiumItemId.fold("") { id =>
s"""or (for premium learners):
|https://www.coursera.org/learn/$courseName/programming/$id
""".stripMargin
}
}
""".stripMargin
s.log.error(inputErr)
failSubmit()
}
val base64Jar = prepareJar(jar, s)
val json =
s"""|{
| "assignmentKey":"$assignmentKey",
| "submitterEmail":"$email",
| "secret":"$secret",
| "parts":{
| "$partId":{
| "output":"$base64Jar"
| }
| }
|}""".stripMargin
def postSubmission[T](data: String): Try[HttpResponse[String]] = {
val http = Http("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1")
val hs = List(
("Cache-Control", "no-cache"),
("Content-Type", "application/json")
)
s.log.info("Connecting to Coursera...")
val response = Try(http.postData(data)
.headers(hs)
.option(HttpOptions.connTimeout(10000)) // scalaj default timeout is only 100ms, changing that to 10s
.asString) // kick off HTTP POST
response
}
val connectMsg =
s"""|Attempting to submit "${assignment.value}" assignment in "$courseName" course
|Using:
|- email: $email
|- submit token: $secret""".stripMargin
s.log.info(connectMsg)
def reportCourseraResponse(response: HttpResponse[String]): Unit = {
val code = response.code
val respBody = response.body
/* Sample JSON response from Coursera
{
"message": "Invalid email or token.",
"details": {
"learnerMessage": "Invalid email or token."
}
}
*/
// Success, Coursera responds with 2xx HTTP status code
if (response.is2xx) {
val successfulSubmitMsg =
s"""|Successfully connected to Coursera. (Status $code)
|
|Assignment submitted successfully!
|
|You can see how you scored by going to:
|https://www.coursera.org/learn/$courseName/programming/$itemId/
|${
premiumItemId.fold("") { id =>
s"""or (for premium learners):
|https://www.coursera.org/learn/$courseName/programming/$id
""".stripMargin
}
}
|and clicking on "My Submission".""".stripMargin
s.log.info(successfulSubmitMsg)
}
// Failure, Coursera responds with 4xx HTTP status code (client-side failure)
else if (response.is4xx) {
val result = Try(Json.parse(respBody)).toOption
val learnerMsg = result match {
case Some(resp: JsObject) =>
(JsPath \ "details" \ "learnerMessage").read[String].reads(resp).get
case Some(x) => // shouldn't happen
"Could not parse Coursera's response:\n" + x
case None =>
"Could not parse Coursera's response:\n" + respBody
}
val failedSubmitMsg =
s"""|Submission failed.
|There was something wrong while attempting to submit.
|Coursera says:
|$learnerMsg (Status $code)""".stripMargin
s.log.error(failedSubmitMsg)
}
// Failure, Coursera responds with 5xx HTTP status code (server-side failure)
else if (response.is5xx) {
val failedSubmitMsg =
s"""|Submission failed.
|Coursera seems to be unavailable at the moment (Status $code)
|Check https://status.coursera.org/ and try again in a few minutes.
""".stripMargin
s.log.error(failedSubmitMsg)
}
// Failure, Coursera repsonds with an unexpected status code
else {
val failedSubmitMsg =
s"""|Submission failed.
|Coursera replied with an unexpected code (Status $code)
""".stripMargin
s.log.error(failedSubmitMsg)
}
}
// kick it all off, actually make request
postSubmission(json) match {
case Success(resp) => reportCourseraResponse(resp)
case Failure(e) =>
val failedConnectMsg =
s"""|Connection to Coursera failed.
|There was something wrong while attempting to connect to Coursera.
|Check your internet connection.
|${e.toString}""".stripMargin
s.log.error(failedConnectMsg)
}
}
*/
def failSubmit(): Nothing = {
sys.error("Submission failed")
}
/**
* *****************
* DEALING WITH JARS
*/
def encodeBase64(bytes: Array[Byte]): String =
new String(Base64.encodeBase64(bytes))
}

1
project/build.properties Normal file
View File

@ -0,0 +1 @@
sbt.version=1.3.8

View File

@ -0,0 +1,5 @@
// Used for Coursera submission (StudentPlugin)
// libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2"
// libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4"
// Used for Base64 (StudentPlugin)
libraryDependencies += "commons-codec" % "commons-codec" % "1.10"

2
project/plugins.sbt Normal file
View File

@ -0,0 +1,2 @@
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.28")
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.4.0")

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

View File

@ -0,0 +1,60 @@
package scalashop
import org.scalameter._
object HorizontalBoxBlurRunner {
val standardConfig = config(
Key.exec.minWarmupRuns -> 5,
Key.exec.maxWarmupRuns -> 10,
Key.exec.benchRuns -> 10,
Key.verbose -> true
) withWarmer(new Warmer.Default)
def main(args: Array[String]): Unit = {
val radius = 3
val width = 1920
val height = 1080
val src = new Img(width, height)
val dst = new Img(width, height)
val seqtime = standardConfig measure {
HorizontalBoxBlur.blur(src, dst, 0, height, radius)
}
println(s"sequential blur time: $seqtime")
val numTasks = 32
val partime = standardConfig measure {
HorizontalBoxBlur.parBlur(src, dst, numTasks, radius)
}
println(s"fork/join blur time: $partime")
println(s"speedup: ${seqtime.value / partime.value}")
}
}
/** A simple, trivially parallelizable computation. */
object HorizontalBoxBlur extends HorizontalBoxBlurInterface {
/** Blurs the rows of the source image `src` into the destination image `dst`,
* starting with `from` and ending with `end` (non-inclusive).
*
* Within each row, `blur` traverses the pixels by going from left to right.
*/
def blur(src: Img, dst: Img, from: Int, end: Int, radius: Int): Unit = {
// TODO implement this method using the `boxBlurKernel` method
???
}
/** Blurs the rows of the source image in parallel using `numTasks` tasks.
*
* Parallelization is done by stripping the source image `src` into
* `numTasks` separate strips, where each strip is composed of some number of
* rows.
*/
def parBlur(src: Img, dst: Img, numTasks: Int, radius: Int): Unit = {
// TODO implement using the `task` construct and the `blur` method
???
}
}

View File

@ -0,0 +1,18 @@
package scalashop
// Interfaces used by the grading infrastructure. Do not change signatures
// or your submission will fail with a NoSuchMethodError.
trait HorizontalBoxBlurInterface {
def blur(src: Img, dst: Img, from: Int, end: Int, radius: Int): Unit
def parBlur(src: Img, dst: Img, numTasks: Int, radius: Int): Unit
}
trait VerticalBoxBlurInterface {
def blur(src: Img, dst: Img, from: Int, end: Int, radius: Int): Unit
def parBlur(src: Img, dst: Img, numTasks: Int, radius: Int): Unit
}
trait BoxBlurKernelInterface {
def boxBlurKernel(src: Img, x: Int, y: Int, radius: Int): RGBA
}

View File

@ -0,0 +1,85 @@
package scalashop
import java.awt._
import java.awt.event._
import java.awt.image._
import java.io._
import javax.imageio._
import javax.swing._
import javax.swing.event._
class PhotoCanvas extends JComponent {
var imagePath: Option[String] = None
var image = loadScalaImage()
override def getPreferredSize = {
new Dimension(image.width, image.height)
}
private def loadScalaImage(): Img = {
val stream = this.getClass.getResourceAsStream("/scalashop/scala.jpg")
try {
loadImage(stream)
} finally {
stream.close()
}
}
private def loadFileImage(path: String): Img = {
val stream = new FileInputStream(path)
try {
loadImage(stream)
} finally {
stream.close()
}
}
private def loadImage(inputStream: InputStream): Img = {
val bufferedImage = ImageIO.read(inputStream)
val width = bufferedImage.getWidth
val height = bufferedImage.getHeight
val img = new Img(width, height)
for (x <- 0 until width; y <- 0 until height) img(x, y) = bufferedImage.getRGB(x, y)
img
}
def reload(): Unit = {
image = imagePath match {
case Some(path) => loadFileImage(path)
case None => loadScalaImage()
}
repaint()
}
def loadFile(path: String): Unit = {
imagePath = Some(path)
reload()
}
def applyFilter(filterName: String, numTasks: Int, radius: Int): Unit = {
val dst = new Img(image.width, image.height)
filterName match {
case "horizontal-box-blur" =>
HorizontalBoxBlur.parBlur(image, dst, numTasks, radius)
case "vertical-box-blur" =>
VerticalBoxBlur.parBlur(image, dst, numTasks, radius)
case "" =>
}
image = dst
repaint()
}
override def paintComponent(gcan: Graphics) = {
super.paintComponent(gcan)
val width = image.width
val height = image.height
val bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
for (x <- 0 until width; y <- 0 until height) bufferedImage.setRGB(x, y, image(x, y))
gcan.drawImage(bufferedImage, 0, 0, null)
}
}

View File

@ -0,0 +1,140 @@
package scalashop
import org.scalameter._
import java.awt._
import java.awt.event._
import javax.swing._
import javax.swing.event._
import scala.collection.mutable.ArrayBuffer
import scala.reflect.ClassTag
object ScalaShop {
class ScalaShopFrame extends JFrame("ScalaShop\u2122") {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
setSize(1024, 600)
setLayout(new BorderLayout)
val rightpanel = new JPanel
rightpanel.setBorder(BorderFactory.createEtchedBorder(border.EtchedBorder.LOWERED))
rightpanel.setLayout(new BorderLayout)
add(rightpanel, BorderLayout.EAST)
val controls = new JPanel
controls.setLayout(new GridLayout(0, 2))
rightpanel.add(controls, BorderLayout.NORTH)
val filterLabel = new JLabel("Filter")
controls.add(filterLabel)
val filterCombo = new JComboBox(Array(
"horizontal-box-blur",
"vertical-box-blur"
))
controls.add(filterCombo)
val radiusLabel = new JLabel("Radius")
controls.add(radiusLabel)
val radiusSpinner = new JSpinner(new SpinnerNumberModel(3, 1, 16, 1))
controls.add(radiusSpinner)
val tasksLabel = new JLabel("Tasks")
controls.add(tasksLabel)
val tasksSpinner = new JSpinner(new SpinnerNumberModel(32, 1, 128, 1))
controls.add(tasksSpinner)
val stepbutton = new JButton("Apply filter")
stepbutton.addActionListener(new ActionListener {
def actionPerformed(e: ActionEvent): Unit = {
val time = measure {
canvas.applyFilter(getFilterName, getNumTasks, getRadius)
}
updateInformationBox(time.value)
}
})
controls.add(stepbutton)
val clearButton = new JButton("Reload")
clearButton.addActionListener(new ActionListener {
def actionPerformed(e: ActionEvent): Unit = {
canvas.reload()
}
})
controls.add(clearButton)
val info = new JTextArea(" ")
info.setBorder(BorderFactory.createLoweredBevelBorder)
rightpanel.add(info, BorderLayout.SOUTH)
val mainMenuBar = new JMenuBar()
val fileMenu = new JMenu("File")
val openMenuItem = new JMenuItem("Open...")
openMenuItem.addActionListener(new ActionListener {
def actionPerformed(e: ActionEvent): Unit = {
val fc = new JFileChooser()
if (fc.showOpenDialog(ScalaShopFrame.this) == JFileChooser.APPROVE_OPTION) {
canvas.loadFile(fc.getSelectedFile.getPath)
}
}
})
fileMenu.add(openMenuItem)
val exitMenuItem = new JMenuItem("Exit")
exitMenuItem.addActionListener(new ActionListener {
def actionPerformed(e: ActionEvent): Unit = {
sys.exit(0)
}
})
fileMenu.add(exitMenuItem)
mainMenuBar.add(fileMenu)
val helpMenu = new JMenu("Help")
val aboutMenuItem = new JMenuItem("About")
aboutMenuItem.addActionListener(new ActionListener {
def actionPerformed(e: ActionEvent): Unit = {
JOptionPane.showMessageDialog(null, "ScalaShop, the ultimate image manipulation tool\nBrought to you by EPFL, 2015")
}
})
helpMenu.add(aboutMenuItem)
mainMenuBar.add(helpMenu)
setJMenuBar(mainMenuBar)
val canvas = new PhotoCanvas
val scrollPane = new JScrollPane(canvas)
add(scrollPane, BorderLayout.CENTER)
setVisible(true)
def updateInformationBox(time: Double): Unit = {
info.setText(s"Time: $time")
}
def getNumTasks: Int = tasksSpinner.getValue.asInstanceOf[Int]
def getRadius: Int = radiusSpinner.getValue.asInstanceOf[Int]
def getFilterName: String = {
filterCombo.getSelectedItem.asInstanceOf[String]
}
}
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
} catch {
case _: Exception => println("Cannot set look and feel, using the default one.")
}
val frame = new ScalaShopFrame
def main(args: Array[String]): Unit = {
frame.repaint()
}
}

View File

@ -0,0 +1,60 @@
package scalashop
import org.scalameter._
object VerticalBoxBlurRunner {
val standardConfig = config(
Key.exec.minWarmupRuns -> 5,
Key.exec.maxWarmupRuns -> 10,
Key.exec.benchRuns -> 10,
Key.verbose -> true
) withWarmer(new Warmer.Default)
def main(args: Array[String]): Unit = {
val radius = 3
val width = 1920
val height = 1080
val src = new Img(width, height)
val dst = new Img(width, height)
val seqtime = standardConfig measure {
VerticalBoxBlur.blur(src, dst, 0, width, radius)
}
println(s"sequential blur time: $seqtime")
val numTasks = 32
val partime = standardConfig measure {
VerticalBoxBlur.parBlur(src, dst, numTasks, radius)
}
println(s"fork/join blur time: $partime")
println(s"speedup: ${seqtime.value / partime.value}")
}
}
/** A simple, trivially parallelizable computation. */
object VerticalBoxBlur extends VerticalBoxBlurInterface {
/** Blurs the columns of the source image `src` into the destination image
* `dst`, starting with `from` and ending with `end` (non-inclusive).
*
* Within each column, `blur` traverses the pixels by going from top to
* bottom.
*/
def blur(src: Img, dst: Img, from: Int, end: Int, radius: Int): Unit = {
// TODO implement this method using the `boxBlurKernel` method
???
}
/** Blurs the columns of the source image in parallel using `numTasks` tasks.
*
* Parallelization is done by stripping the source image `src` into
* `numTasks` separate strips, where each strip is composed of some number of
* columns.
*/
def parBlur(src: Img, dst: Img, numTasks: Int, radius: Int): Unit = {
// TODO implement using the `task` construct and the `blur` method
???
}
}

View File

@ -0,0 +1,100 @@
import java.util.concurrent._
import scala.util.DynamicVariable
import org.scalameter._
package object scalashop extends BoxBlurKernelInterface {
/** The value of every pixel is represented as a 32 bit integer. */
type RGBA = Int
/** Returns the red component. */
def red(c: RGBA): Int = (0xff000000 & c) >>> 24
/** Returns the green component. */
def green(c: RGBA): Int = (0x00ff0000 & c) >>> 16
/** Returns the blue component. */
def blue(c: RGBA): Int = (0x0000ff00 & c) >>> 8
/** Returns the alpha component. */
def alpha(c: RGBA): Int = (0x000000ff & c) >>> 0
/** Used to create an RGBA value from separate components. */
def rgba(r: Int, g: Int, b: Int, a: Int): RGBA = {
(r << 24) | (g << 16) | (b << 8) | (a << 0)
}
/** Restricts the integer into the specified range. */
def clamp(v: Int, min: Int, max: Int): Int = {
if (v < min) min
else if (v > max) max
else v
}
/** Image is a two-dimensional matrix of pixel values. */
class Img(val width: Int, val height: Int, private val data: Array[RGBA]) {
def this(w: Int, h: Int) = this(w, h, new Array(w * h))
def apply(x: Int, y: Int): RGBA = data(y * width + x)
def update(x: Int, y: Int, c: RGBA): Unit = data(y * width + x) = c
}
/** Computes the blurred RGBA value of a single pixel of the input image. */
def boxBlurKernel(src: Img, x: Int, y: Int, radius: Int): RGBA = {
// TODO implement using while loops
???
}
val forkJoinPool = new ForkJoinPool
abstract class TaskScheduler {
def schedule[T](body: => T): ForkJoinTask[T]
def parallel[A, B](taskA: => A, taskB: => B): (A, B) = {
val right = task {
taskB
}
val left = taskA
(left, right.join())
}
}
class DefaultTaskScheduler extends TaskScheduler {
def schedule[T](body: => T): ForkJoinTask[T] = {
val t = new RecursiveTask[T] {
def compute = body
}
Thread.currentThread match {
case wt: ForkJoinWorkerThread =>
t.fork()
case _ =>
forkJoinPool.execute(t)
}
t
}
}
val scheduler =
new DynamicVariable[TaskScheduler](new DefaultTaskScheduler)
def task[T](body: => T): ForkJoinTask[T] = {
scheduler.value.schedule(body)
}
def parallel[A, B](taskA: => A, taskB: => B): (A, B) = {
scheduler.value.parallel(taskA, taskB)
}
def parallel[A, B, C, D](taskA: => A, taskB: => B, taskC: => C, taskD: => D): (A, B, C, D) = {
val ta = task { taskA }
val tb = task { taskB }
val tc = task { taskC }
val td = taskD
(ta.join(), tb.join(), tc.join(), td)
}
// Workaround Dotty's handling of the existential type KeyValue
implicit def keyValueCoerce[T](kv: (Key[T], T)): KeyValue = {
kv.asInstanceOf[KeyValue]
}
}

View File

@ -0,0 +1,11 @@
package scalashop
import java.util.concurrent._
import scala.collection._
import org.junit._
import org.junit.Assert.assertEquals
class BlurSuite {
@Rule def individualTestTimeout = new org.junit.rules.Timeout(10 * 1000)
}