CMSC 476: Assignment 6
Overview
Write a program that sets up a pipeline to apply a sequence of image filters in parallel. The pipeline will consist of two stages, one for each filter. The first stage will convert an image to greyscale, one row at a time. The second will reflect the image horizontally (about a vertical axis through the center), one row at a time.
Use the ImageMagick library to load an image and manipulate its pixels, specifically the C++ API.
Create two std::jthread-s, one to convert to greyscale,
and one to reflect. Have them communicate via a ThreadSafeQueue of row pointers. To do
this, use a std::condition_variable. As soon as your
greyscale thread finishes a row, push a pointer to the first pixel in
the row into the queue. Your reflect thread will pull from this queue,
looking for a row of pixels to process.
To convert a pixel to greyscale with ImageMagick, read the red
Quantum into a uint32_t local named red. Do
something similar for the green and blue
channels. Then compute brightness as (306 *
red + 601 * green + 117 * blue)
>> 10. This will avoid FP arithmetic and help ensure your result
matches mine.
To convert the image name use std::filesystem. I will
let you determine which methods to use. You should append “Mod” to the
part of the filename before the extension, and change the extension to
“.pgm” (Portable Grey Map).
Lastly, write a Makefile that will build your executable
or clean up. Ensure ALL dependencies are properly listed. When I type
make your project MUST produce a release build, with no
warnings (or you get a ZERO). You will have to research how to use
pkg-config to build against ImageMagick.
Input Specification
Input an image file name to process.
Use PRECISELY the format below with the EXACT same SPACING and SPELLING.
Image ==> <UserInput>
Output Specification
After you finish processing the image, save it to a file with the following name: original name - extension + “Mod.pgm”.
Use PRECISELY the format shown below with the EXACT same spacing and spelling.
Sample Output
(no spaces before the following line)
Image ==> Daffy.png
Output: DaffyMod.pgm
(no spaces after the preceding line)
Required Types, Concepts, and Functions
// Apply a greyscale filter.
// After processing all rows enqueue `nullptr`.
void
filterGreyscale (Magick::Quantum* pixels,
size_t width,
size_t height,
size_t channels,
ThreadSafeQueue<Magick::Quantum*>& queue)
// Write this lambda inside `filterGreyScale` to
// convert a pixel to greyscale.
// Use the following formula:
// brightness = (306 * red + 601 * green + 117 * blue) >> 10
// std::clamp this value to the range [0, 65'535],
// and then set each channel to this value.
auto convertPixel = [] (Quantum* p)
// Apply a horizontal reflection.
// For each pixel `p` in a row, swap it with pixel (width - 1 - `p`).
// Continue processing rows until a `nullptr` is encountered.
void
filterHorizontalReflect (size_t width,
size_t channels,
ThreadSafeQueue<Magick::Quantum*>& queue)What to Submit
Submit TransformImage.cc,
ThreadSafeQueue.hpp and Makefile. Do NOT
rename any of these files.
Hints
Once your Makefile works, type bear -- make to build a
compile_commands.json compilation database which will allow
clangd to correctly highlight your code.
See ConditionVariable.cc for how to use condition variables.
See ImageMagickDemo.cc for how to access pixels in an image.
For building, the package you want to query is
Magick++.
Comments
Do the results match what you’d expect?
Do you think pipelining with two stages will increase throughput by roughly a factor of two?
Gary M. Zoppetti, Ph.D.