Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • lamp/cs206
  • bwermeil/cs206-2020
  • zabifade/cs206-2020
  • cauderan/cs206-2020
  • malonga/cs206-2020
  • dumoncel/cs206
  • bounekhe/cs206
  • bergerault/cs206
  • flealsan/cs206
  • hsu/cs206
  • mouchel/cs206
  • vebraun/cs206
  • vcanard/cs206
  • ybelghmi/cs206
  • belghmi/cs206
  • bousbina/cs206
  • waked/cs206
  • gtagemou/cs206
  • arahmoun/cs206
  • elhachem/cs206
  • benrahha/cs206
  • benslima/cs206
22 results
Select Git revision
  • bergerault-master-patch-06528
  • master
2 results
Show changes
Showing
with 0 additions and 723 deletions
# Example lab
The goal of this lab is to familiarize yourself with the infrastructure and tools used in this class. Even though the grade in this lab won't influence your grade for the course, it is important that you work through this lab carefully.
## Part 1: Obtaining the Project Files
First, make sure you've followed the [Tools Setup](tools-setup.md) page.
**At this point, we strongly encourage you to take the time to read at least the first three chapters of the [Git Book](https://git-scm.com/book/en/v2). If you just copy-paste the commands we give you without understanding them, it's likely that you'll make a mistake somewhere and waste time. Git can be a huge productivity enhancer when used correctly, so it's definitely worth the investment!**
We'll starting by cloning the repository containing all our lab (make
sure to replace `GASPAR` with your EPFL username (the one with letters, not the
one with number) in the following command).
```shell
git clone -b example git@gitlab.epfl.ch:lamp/student-repositories-s21/cs206-GASPAR.git cs206-example
```
**If this command fails, make sure you've [logged into
gitlab](https://gitlab.epfl.ch/users/sign_in) and [registered in a
group](https://gitlab.epfl.ch/lamp/cs206/-/blob/master/exercises/Group%20workspaces.md),
then wait a few minutes.
If it still doesn't work it's likely that you didn't correctly upload your ssh
key to gitlab, look at the last part of the [Tools Setup](tools-setup.md) page again.**
```shell
cd cs206-example
```
Now that we've obtained the project, let's take a look at its structure:
```shell
.
├── build.sbt
├── project
│ ├── ...
└── src
├── main
│ └── scala
│ └── example
│ └── Lists.scala
└── test
└── scala
└── example
└── ListsSuite.scala
```
- All the files ending with `.sbt` or in the `project/` directory are build tool configuration files: you don't need to modify them or look at them for any of the labs
- The project sources are in `src/main/scala/`
- The sources of the unit tests are in `src/test/scala/`. You will need to make all the tests pass to complete the labs, and you should write additional tests to check for cases that our tests do not cover.
## Part 2: Using sbt
Start sbt by running:
```shell
sbt
```
Once it's finished starting (this may take a while), you'll be able to enter sbt
commands. You can compile your project using `compile` and run the tests with
`test` (this automatically compiles your code if needed to). Note that if
compilation fails, no tests will be run. The first time you'll run `test` in an
lab you should see many errors: that's normal, your job is to make the
tests pass! But first, let's look at a failed test in detail:
![](images/sbt-test-error.png)
This tells us several things:
- There's a test named `sum of a few numbers (10pts)` in the class `ListsSuite` in the package `example`
- The test failed (that's why it's in red and starts with `==> X`) with an exception: `NotImplementedError`.
- This exception was thrown from the method `???` in `scala.Predef` in the file
`Predef.scala`, this file is not part of our project (that's why it's in
grey), to find the actual error in our code we have to look at where this
method was called from.
- This method was called from method `max` in `example.Lists` in the file
`Lists.scala` at line 40, this is where the bug is!
- It's also important to see where in our test this was called from, here
it's line 102 of `ListsSuite.scala`.
Time to go fix that bug! The next section will show you how to do that using the IDE.
## Part 3: Using the IDE
### Setup
Let's upgrade the IDE support first, close VSCode if it's open and run:
```shell
code --force --install-extension scalameta.metals
```
### Startup
To start Code, run the following in the project directory (the same directory where you
previously ran `sbt`), it's important to run Code in the correct directory or
it won't be able to import your project:
```shell
code .
```
(In this command the `.` is important, it's how we tell Code to run in the
current directory)
(if you see an error `Expected ';'` it means you're inside sbt, open a new
terminal in the same directory)
The first time the IDE starts, it will take some time to download more
components, eventually it will ask you to import the build, please click "Import
build":
![](images/metals-import.png)
You'll need to wait a bit for the import to finish, if an error appears try
closing and restarting Code in the same way we started it above.
### Usage
It's now time to dig in! Earlier we talked about a failing test, the stack trace
told us that it was failing on line 102 of the file `ListsSuite.scala`, so let's open that file:
![](images/open-test.png)
Here's the source code of the test:
![](images/test-source.png)
The first line gives a name to the test, the second line runs
`sum(List(1, 2, 0))` and tests that it equals 3, but in our case we never got to
this point because an exception was thrown, recall that the second line
of the stack trace was:
```scala
at example.Lists$.sum(Lists.scala:25)
```
This tells us that the crash happened when calling `sum`, we can hover with our mouse over the call to `sum` in the test method to get more information on it:
![](images/hover.png)
**If hovering doesn't show this see the [Troubleshooting](#troubleshooting) section.**
The hover is split into two parts: the first part is:
```scala
def sum(xs: List[Int]): Int
```
This means that `sum` is a method that takes a `List` of `Int` as argument and
returns an `Int`. The second part is the documentation of `sum`. We can jump to
the definition of sum by `Ctrl+click` (`Cmd+click` on Mac) or by `right click ->
Go to Definition`. Once there we see:
![](images/sum-def.png)
Now we know why the test failed: `sum` calls `???`, which is a method defined in
the Scala standard library that simply crashes your program: whenever you see it
in a lab it means that this is something you need to replace by your own
implementation.
Once you've implemented this method, you can run `test` from sbt again to see if
the test passed, if you want to run a single test instead of all tests you can
use `testOnly` instead and specify part of the name of the test:
```scala
testOnly -- "--tests=.*max of a few.*"
```
You now know enough to be able to work with the IDE, here are some additional tips:
- When you press `Enter` to make a new line, the IDE will automatically indent the
line if needed (for example, if the last word on the previous line was
`then`), however it will never unindent code for you (for example, when
writing `else`). You can indent code manually by pressing `Tab` and
unindent it by pressing `Backspace` or `Shift + Tab`.
- When working on an lab, you are free to create as many methods, classes and objects as you want. **But you shouldn't change the name of existing methods, classes and objects, because that may break the automated grading system, this is important!**.
- You can see a list of all warnings and errors reported by the compiler by clicking on ![](images/warnings-errors.png) at the bottom left of Code.
- The IDE can show you on hover the documentation of classes, defs and vals defined in the current project but support for external project is currently missing. To compensate for this, you can consult the documentation online:
- The documentation for the Scala standard library is at [https://www.scala-lang.org/files/archive/api/2.13.3/](https://www.scala-lang.org/files/archive/api/2.13.3/)
- The documentation for the Java standard library is at [https://docs.oracle.com/en/java/javase/15/docs/api/index.html](https://docs.oracle.com/en/java/javase/15/docs/api/index.html)
- You can customize Code as much as you want, including installing additional extensions, but please avoid installing other Scala-related extensions: they may conflict with the one we use for this course.
- While working on your lab, you will regularly want to go back to the sbt console to run the tests. You could simply run the command `test` every time, but you can take advantage of the watch mode instead: if a command is prefixed by `~`, sbt will watch the source directory for changes and re-run the command every time a file is saved. So a possible workflow is:
1. Start the IDE
2. Start sbt in the terminal (protip: you can start a terminal inside Code from the
menu: `Terminal -> New Terminal`)
3. Inside sbt, run `~test`
4. Work in the IDE, and check the output of the sbt console from time to time
## Part 4: Running your code
Writing code and running tests is nice, but sometimes more direct feedback is useful, like when you want to experiment with Scala, or try out some methods that you implemented. You can do this using the Scala REPL (Read/Eval/Print Loop) or with a worksheet.
### The REPL
After having started sbt, you can start the REPL by typing `console`, you will see the following prompt:
```scala
scala>
```
At this point you can write any Scala expression you want, for example:
```scala
scala> val l = List(3,7,2)
val l: List[Int] = List(3, 7, 2)
```
(If you write an expression without wrapping it in a `val` or a `def`, the REPL will give it a name for you, starting with `res`)
```scala
scala> l.isEmpty
val res0: Boolean = false
scala> println(res0)
false
scala> l.tail.head
res1: Int = 7
scala> List().isEmpty
res2: Boolean = true
```
The classes of the lab are available inside the REPL, so you can for instance import all the methods from `object Lists` and start using `max`:
```scala
scala> import example.Lists._
import example.Lists._
scala> max(List(1,3,2))
res1: Int = 3
```
You can enter a multiline expression in the REPL by using `Alt+Enter` instead of `Enter`:
```scala
scala> if 1 == 1 then
| "a"
| else
| "b"
val res0: String = a
```
(on macOS, first go to `Terminal -> Preference -> Profiles -> Keyboard` and then
select `Use Option as Meta key`, then `Option+Enter` will work for multiline
expressions.)
In order to exit the Scala REPL and go back to sbt, type `Ctrl+D`.
### The worksheet mode
A *worksheet* is a file where every line of code written in the IDE is executed and its output displayed as a comment.
#### Creating a worksheet
Any file that ends in `.worksheet.sc` is considered to be a worksheet by the IDE. Once Code is launched in a project, all you have to do is create a new file and save it (`Ctrl+N, Ctrl+S`) using any name as long as it ends in `.worksheet.sc`.
#### Using the worksheet mode
Inside this file, you can type any line of code you would type in the REPL. The
worksheet will be automatically run when the code is saved and auto-save is
enabled by default. Each line of code will be executed one by one and its output
will be shown in green on the right.
## Part 5: Submitting your Solution
[Click here to learn how to submit your lab.](grading-and-submission.md)
# Troubleshooting
## sbt fails to start
If you see any kind of error when sbt starts that prevents you from using it,
try cleaning the project cache by running:
```shell
git clean -Xdf
```
Then restarting `sbt`, if this still doesn't work, try deleting the global sbt
cache:
```shell
rm -r ~/.sbt
```
## IDE features like type-on-hover or go-to-definition do not work
It's likely that the build wasn't imported, we can import it manually:
Click on the "m" logo in the left bar (where the red arrow is in the screenshot below):
![](images/menu-metals.png)
In the sidebar that appears, click on "Import build", then wait a bit:
![](images/import-build.png)
If things still don't work, try restarting Code (launch it in the same way you
started it before, using `code .` from the project directory). If you're still
having issues, try clicking on "Clean compile workspace" from the same
sidebar.
## Warning about Bloop installed version
If you get a warning like this:
![](images/bloop-update.png)
Please click the first button "Turn off old server".
# Grading and submission
## Local tests and grading
Each lab is shipped with two sets of tests:
* The tests defined under `src/test/scala`, these are usually incomplete: we
strongly encourage you to write additional tests to exercise every aspect of
your code. You can run these tests from `sbt` using the following command:
```shell
test
```
* The **grading tests**, that we use to grade your labs, these are
provided as a "black box": you can run them but are not allowed to see their
content, to encourage you to write your own tests. You can run them from
`sbt` using the following command:
```shell
grading:test
```
Your grade is determined by the number of these tests that pass, see the
next section to see how to actually get a grade.
(remember that you can always put `~` before an sbt command to automatically
re-run it when you save a file)
## Committing and pushing your code
**You need to push your code to gitlab before the deadline for the lab to
receive a grade. If you forget, your grade will be zero.**
If you've read the first few chapters of [Git
book](https://git-scm.com/book/en/v2) as recommended, you must already be
familiar with committing and pushing, but let's go over the basics once again:
At any point while working on an lab, you can see what changes you
haven't committed by writing:
```shell
git status
```
(If you get the error `Not a valid command: git`, it means you're inside sbt,
you can't run git from sbt itself, you need to open another terminal in the same
directory)
This will display the list of files you have modified since the last commit, to
see the exact changes you made, run:
```shell
git diff
```
To save these changes in a commit, run: (replace `"My message"` by a message
describing the changes since the last commit, see [How to Write a Git Commit
Message](https://chris.beams.io/posts/git-commit/) for some tips on writing
good commit messages)
```shell
git commit -am "My message"
```
(It's good practice to commit your code regularly to document your changes)
You can then synchronize your local git repository with the gitlab server by
first running:
```shell
git pull --no-rebase
```
This will merge in your local repository any change we made to the lab
(we only change labs to fix critical bugs found after it's been released,
so most of the time this will not do anything).
Finally, you can push your changes to gitlab:
```shell
git push
```
Note that there are also graphical user interfaces to interact with git, for
example [VSCode has built-in git
support](https://code.visualstudio.com/docs/editor/versioncontrol#_git-support).
Once you've pushed your code, you can see the result online by going to
`gitlab.epfl.ch/lamp/student-repositories-s21/cs206-GASPAR/pipelines` where
`GASPAR` is your username (you can also access this page from the main page of
your repository by clicking on the rocket icon on the left side of the gitlab
interface, then clicking on "Pipelines").
![](images/pipeline-tab.png)
The grading pipeline contains two steps:
* *compile*: your code does not compile for grading if the job fail.
* *grade*: the job results in a warning if you do not get the maximum grade
(10.00/10.00)
If a job is marked "pending", it means there are other students running jobs at
the same time and you'll have to wait a bit for your job to run.
You can click on the `grade` job to see its output:
![](images/pipeline-details.png)
![](images/pipeline-logs.png)
Your grade is given on the line that starts with `Your overall score for this
lab is`
**If you push to gitlab multiple times, your final grade will be based on the
last commit you pushed before the deadline.**
labs/images/bloop-update.png

18.1 KiB

labs/images/clone-url.png

41.8 KiB

labs/images/gitlab-public-ssh-key.png

111 KiB

labs/images/gitlab-settings.png

24.8 KiB

labs/images/hover.png

163 KiB

labs/images/import-build.png

35.3 KiB

labs/images/menu-metals.png

54.6 KiB

labs/images/metals-import.png

30.4 KiB

labs/images/open-test.png

17.6 KiB

labs/images/pipeline-details.png

47.1 KiB

labs/images/pipeline-logs.png

157 KiB

labs/images/pipeline-tab.png

48.9 KiB

labs/images/sbt-test-error.png

44.4 KiB

labs/images/sum-def.png

6.75 KiB

labs/images/syntax-error-bug.png

47.3 KiB

labs/images/test-source.png

19 KiB

labs/images/warnings-errors.png

1.8 KiB

# Parallel Box Blur Filter
## Setup
Let's upgrade the IDE support first, close VSCode if it's open and run:
```shell
code --force --install-extension scalameta.metals
```
Use the following commands to make a fresh clone of your repository:
```
git clone -b scalashop git@gitlab.epfl.ch:lamp/student-repositories-s21/cs206-GASPAR.git cs206-scalashop
```
## Useful links
* [A guide to the Scala parallel collections](https://docs.scala-lang.org/overviews/parallel-collections/overview.html)
* [The API documentation of the Scala parallel collections](https://www.javadoc.io/doc/org.scala-lang.modules/scala-parallel-collections_2.13/latest/scala/collection/index.html)
* [The API documentation of the Scala standard library](https://www.scala-lang.org/files/archive/api/2.13.4)
* [The API documentation of the Java standard library](https://docs.oracle.com/en/java/javase/15/docs/api/index.html)
**If you have issues with the IDE, try [reimporting the
build](https://gitlab.epfl.ch/lamp/cs206/-/blob/master/labs/example-lab.md#ide-features-like-type-on-hover-or-go-to-definition-do-not-work),
if you still have problems, use `compile` in sbt instead.**
## Setup scalashop
In this assignment, we will implement a box blur filter, used in applications
like Adobe® PhotoShop® to blur images.
For the purposes of this assignment, a filter is an algorithm that takes an
input image and transforms it in some way into an output image.
The box blur filter outputs an image in which every pixel has an average value
of the surrounding pixels from the original image.
The box blur filter is an example of an *embarrassingly parallel* problem -- no
or very little effort is required to separate it into parallel tasks.
Every pixel of the output image can be computed independently of the other
pixels, and in parallel.
We will proceed in four steps. First, we will implement the kernel of the box
blur filter, a method used to compute a single pixel of the output image.
Then we will implement two versions of the parallel box blur, and measure the
performance difference.
Finally, we will try out our parallel box blur implementations on real images,
using ScalaShop -- the image manipulation tool that would impress even
the Adobe® folks.
By the time you are finished with this assignment, you will:
- understand how the box blur filter works
- be able to recognize embarrassingly parallel problems
- know how to measure the performance of a parallel algorithm
- see how to agglomerate parallel computations into batches
- better understand how memory access patterns affect performance of parallel
algorithms
## Preliminaries
Before we begin, we need to cover some basic data types and helper methods.
These utilities have already been implemented in the package object (in the file `package.scala`) for this
exercise.
First, we will use the `RGBA` type to refer to the value of an image pixel.
We will limit ourselves to 32-bit pixel depth images, so we define `RGBA` to
be equal to the 32-bit integer type:
```scala
type RGBA = Int
```
Why do we call this type `RGBA`?
This is because each pixel value is composed from 4 components - red, green,
blue and alpha, where alpha denotes the amount of transparency in the
respective pixel.
These components are referred to as *channels*.
The value of each channel is at least 0 and at most 255.
We can extract the red, green, blue and alpha channel using the following
utility methods, which use bit masking and bit shifting:
```scala
def red(c: RGBA): Int = (0xff000000 & c) >>> 24
def green(c: RGBA): Int = (0x00ff0000 & c) >>> 16
def blue(c: RGBA): Int = (0x0000ff00 & c) >>> 8
def alpha(c: RGBA): Int = (0x000000ff & c) >>> 0
```
Similarly, given the values of the four channels, we can obtain the pixel value
like this:
```scala
def rgba(r: Int, g: Int, b: Int, a: Int): RGBA =
(r << 24) | (g << 16) | (b << 8) | (a << 0)
```
Now that we know how to manipulate individual pixels, we can define our image
type `Img`:
```scala
class Img(val width: Int, val height: Int) {
private val data = new Array[RGBA](width * height)
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
}
```
The image is a two-dimensional entity -- to refer to a pixel in an image, we
need to specify an `x` and `y` component.
On the other hand, the underlying memory model is one-dimensional -- a single
offset value specifies the position in the array.
When we store the image into memory, we need to map between the
two-dimensional image model and the one-dimensional memory model.
We will do this by storing consecutive rows of the image one after another, as
illustrated in the following figure:
![mapping](mapping.png)
Thus, the offset of a pixel at coordinates `x` and `y`, is equal to
`y * width + x`, where `width` is the number of pixels in a single row.
Although there are other mappings used in practice, we will restrict to this
simple mapping throughout the exercise.
To ensure that the value of the `x` and `y` coordinates are confined to the
dimensions of the image, namely width and height, occasionally we have to call
the `clamp` method:
```scala
def clamp(v: Int, min: Int, max: Int): Int
```
Finally, we will use the `task` construct to start parallel computations.
Every `task` construct invocation returns an object on which we can call the
`join` method.
Calling the `join` method blocks the execution of the program until the parallel
computation ends.
Below, we calculate the expression `1 + 1` in parallel to the main program:
```scala
val computation = task {
val result = 1 + 1
println("Done!")
result
}
println("About to wait for some heavy calculation...")
computation.join()
```
We now have everything we need to start implementing the parallel box filter.
## The Box Blur Filter Kernel
In the first part of the assignment, we will implement the box blur filter
kernel method.
A kernel is a method that computes the resulting value of a single pixel.
The kernel method is typically computationally cheap and is not worth
parallelizing its implementation.
However, as we will later see, we can apply the same kernel method to different
pixels in parallel.
![kernel](kernel.png)
The `boxBlurKernel` method takes the source image `src`, coordinates `x` and `y`
of the pixel, and the `radius` of the blur.
It returns the resulting average value of the surrounding pixels.
We compute the average value by separating the pixel into four channels,
computing the average of each of the channels,
and using the four average values to produce the final pixel value.
In the previous figure, the `radius` parameter is equal to `1` and the average
is computed from `9` pixels.
You can find its signature in the package object of this assignment:
def boxBlurKernel(src: Img, x: Int, y: Int, radius: Int): RGBA
Implement the `boxBlurKernel` method.
Use two nested `while`-loops.
Make sure that the pixels at the image edges are affected only by the
pixels inside the image (hint: use the `clamp` method from the package object).
## Vertical Stripping Box Blur
We can now implement the parallel box blur filter.
Note that the `boxBlurKernel` method is relatively inexpensive.
Executing `boxBlurKernel` might be much faster than starting a parallel
computation, so having a separate parallel computation for the value of each
pixel would be far too expensive.
For this reason, we want to batch together many `boxBlurKernel` calls, and have
a smaller number of parallel tasks.
This is sometimes referred to as *agglomeration*, and is present in many
parallel algorithm implementations.
There are many different ways we can do agglomeration for the parallel box blur.
One is to divide the image into a fixed number of equally wide vertical strips.
For each strip, we start a parallel task, and wait for their completion.
Within each strip, we traverse the pixels going from the top to the bottom of
the image, as illustrated in the following figure:
![stripping](vertical.png)
We start by implementing the sequential `blur` method in the
`VerticalBoxBlur.scala` source file, which takes the source image `src`, the
destination image `dst`, the starting (included) and ending (excluded) `x` coordinates
(i.e, column indices) of the strip, called `from` and `end`, and the blur `radius`.
The `blur` method blurs the pixels from the `src` image and writes them to the
`dst` image:
```scala
def blur(src: Img, dst: Img, from: Int, end: Int, radius: Int): Unit
```
The implementation of `blur` should rely on the previously defined `boxBlurKernel`.
Then, implement the `parBlur` method, which divides the image into `numTasks` vertical strips and runs each task in parallel:
```scala
def parBlur(src: Img, dst: Img, numTasks: Int, radius: Int): Unit
```
Use Scala ranges to create a list of splitting points
(hint: use the `by` method on ranges).
Then use collection combinators on the list of splitting points to create a list
of start and end tuples, one for each strip
(hint: use the `zip` and `tail` methods).
Finally, use the `task` construct to start a parallel task for each strip,
and then call `join` on each task to wait for its completion.
### Benchmarking VerticalBoxBlur
Before running the benchmarking program, go to the top of `VerticalBoxBlur.scala`
and replace the line:
```scala
Key.verbose -> true
```
by:
```scala
Key.verbose -> false
```
Now you can run the `VerticalBoxBlur` program with the following sbt command:
```
> runMain scalashop.VerticalBoxBlurRunner
```
Change the number of tasks and the radius parameter.
How does the performance change?
## Horizontal Stripping Box Blur
In this part of the exercise we will pick an alternative agglomeration for the
box blur algorithm.
Instead of dividing the image into vertical strips, we will divide it into
horizontal strips in a similar way:
![stripping](horizontal.png)
We implement the two methods, `blur` and `parBlur` in a similar way as before:
```scala
def blur(src: Img, dst: Img, from: Int, end: Int, radius: Int): Unit
def parBlur(src: Img, dst: Img, numTasks: Int, radius: Int): Unit
```
Note that the arguments `from` (included) and `end` (excluded) this time denote the values of the
`y` coordinate (i.e, row indices), and that we traverse the pixels left-to-right within each strip.
### Benchmarking HorizontalBoxBlur
Before running the benchmarking program, go to the top of `HorizontalBoxBlur.scala`
and replace the line:
```scala
Key.verbose -> true
```
by:
```scala
Key.verbose -> false
```
Now you can now run the `HorizontalBoxBlur` program from sbt with:
```
> runMain scalashop.HorizontalBoxBlurRunner
```
If you implemented the two blur versions correctly, you should observe that the
horizontal stripping is slightly faster.
This is because the pixel traversal order visits the pixels which are closer
together in memory (remember the mapping between the pixel coordinates and the
memory addresses). As a result, each core can reuse some of the pixels that it
fetched from the memory during the previous invocation of `boxBlurKernel`.
The processor cores spend less time fetching pixels from memory and lower the
pressure on the memory bus.
## ScalaShop
Now we have everything we need to start ScalaShop, from sbt run:
```
> runMain scalashop.ScalaShop
```
Change the blur implementation, parallelism level and blur radius, and study the
effect your changes have on performance.
* Which of the two blur implementations is faster?
* For which values of the `radius` parameter is the difference most significant?
* Why?
* What if we split the image into rectangles? Will this be faster?
*"Adobe" and "Photoshop" are either registered trademarks or trademarks of Adobe
Systems Incorporated in the United States and/or other countries.*