PixelMath.PixelMath
private PixelMath() {
Image matrices, grayscale conversion, convolution, Sobel edge detection, pixel math, normalization, clamped sampling, and PNG output.
scripts/run.sh lesson_4 demo
scripts/run.sh lesson_4 exercise
scripts/apply_answers.sh lesson_4
scripts/run.sh lesson_4 verify
scripts/reset_exercises.sh lesson_4
67 pages generated for Java functions in this lesson.
This lesson builds a small Java library around image preprocessing, convolution, and Sobel edge detection.
| Minutes | Activity |
|---|---|
| 0-10 | Frame images as matrices and compare Java arrays with array libraries in other ecosystems. |
| 10-30 | Run scripts/run.sh lesson_4 demo and inspect generated PNG files under build/out/lesson_4. |
| 30-50 | Read ImageMatrix, Convolution, and EdgeDetector. |
| 50-70 | Discuss bounds handling, clamping, grayscale conversion, and kernel design. |
| 70-100 | Implement pixel math exercise functions. |
| 100-115 | Apply answers and compare numerical choices. |
| 115-120 | Sketch a library API change, such as adding blur before edge detection. |
Use workbook.md for the full two-hour learner path. It includes image tracing, numeric implementation rounds, and boundary edge cases.
Edit src/exercise/java/tutorial/lesson4/exercise/PixelMath.java.
Functions to implement:
clampinvertGraythresholdlumapackGraynormalizeToByteblendsobelMagnitudesampleClampedmean3x3The toy library itself is runnable independent of the exercise answers.
After applying answers, run scripts/run.sh lesson_4 verify to check byte validation, luma math, packed pixels, normalization, blending, Sobel clamping, rectangular image validation, and boundary sampling.
This workbook is designed to take about two hours. The goal is to understand Java library structure and image preprocessing through small numeric functions.
0..255.Run:
scripts/reset_exercises.sh lesson_4
scripts/run.sh lesson_4 demo
scripts/run.sh lesson_4 exercise
Open:
build/out/lesson_4/synthetic-input.png
build/out/lesson_4/sobel-edges.png
Write down what changed between the input and edge output.
Open src/demo/java/tutorial/lesson4/demo/EdgeDetectDemo.java.
Trace:
Then open:
ImageMatrix.javaKernel.javaConvolution.javaEdgeDetector.javaFor ImageMatrix, pay attention to the difference between (x, y) coordinates and values[y][x] storage.
Open src/exercise/java/tutorial/lesson4/exercise/PixelMath.java.
Group functions by skill:
clamp, invertGray, thresholdluma, packGraynormalizeToByte, blendsobelMagnitudesampleClamped, mean3x3Before editing, write expected outputs for at least four functions.
Round 1:
clamp.min <= max.Round 2:
invertGray and threshold.Round 3:
luma.Round 4:
packGray.Round 5:
normalizeToByte.0; values above range map to 255.Round 6:
blend.0, 0.25, 0.5, 1, and invalid values.Round 7:
sobelMagnitude.255.Round 8:
sampleClamped.Round 9:
mean3x3.Run:
scripts/apply_answers.sh lesson_4
scripts/run.sh lesson_4 verify
scripts/run.sh lesson_4 demo
scripts/reset_exercises.sh lesson_4
The verifier checks numerical edge cases that are easy to miss by visual inspection alone.
Try these cases:
0, 1, and invalid valuesmean3x3 on a one-pixel imageReset when done:
scripts/reset_exercises.sh lesson_4
Teach Java library organization through a small image preprocessing library. Learners should understand package layout, matrix-style data structures, grayscale conversion, convolution kernels, Sobel edge detection, file I/O, and numerical helper functions.
By the end of 120 minutes, learners should be able to:
BufferedImage data becomes a grayscale matrix.Run:
scripts/reset_exercises.sh lesson_4
scripts/run.sh lesson_4 demo
scripts/run.sh lesson_4 exercise
Open the generated files:
build/out/lesson_4/synthetic-input.pngbuild/out/lesson_4/sobel-edges.pngKnow the key files:
src/demo/java/tutorial/lesson4/demo/EdgeDetectDemo.javasrc/demo/java/tutorial/lesson4/image/ImageMatrix.javasrc/demo/java/tutorial/lesson4/image/Convolution.javasrc/demo/java/tutorial/lesson4/image/EdgeDetector.javasrc/exercise/java/tutorial/lesson4/exercise/PixelMath.java| Time | Segment | Instructor Moves |
|---|---|---|
| 0-10 | Orientation | Ask learners how images are represented in tools they know. Establish pixels as numeric samples. |
| 10-25 | Run the demo | Run scripts/run.sh lesson_4 demo and open the generated PNG files. |
| 25-40 | Entry point reading | Open EdgeDetectDemo.java. Trace input selection, output paths, and library calls. |
| 40-58 | Image matrix | Open ImageMatrix.java. Explain width, height, row-major storage, grayscale conversion, and clamped sampling. |
| 58-75 | Convolution | Open Kernel.java and Convolution.java. Trace one 3x3 sample by hand. |
| 75-88 | Sobel | Open EdgeDetector.java. Explain blur, horizontal gradient, vertical gradient, and magnitude. |
| 88-105 | Exercise work | Learners implement the staged pixel-math and boundary-handling lab from workbook.md. |
| 105-115 | Solution compare | Apply answers, run the exercise, and discuss rounding choices. |
| 115-120 | Extension | Ask learners where they would add thresholding, blur strength, or command-line options. |
Start with:
"This is a toy library, not a full image framework. That is intentional. The smaller surface lets us see Java package design and numeric processing without extra dependencies."
For ImageMatrix:
double[][] values.(x, y), while the array is indexed [y][x].getClamped handles edges by repeating border pixels.toBufferedImage clamps numeric values into displayable byte range.Checkpoint question:
"What bug would appear if we indexed values[x][y] instead?"
For convolution:
Explain that every output pixel is a weighted sum of neighboring input pixels. The kernel is centered over the current pixel. Boundary handling matters because the kernel extends past the image edge.
For Sobel:
sobelX responds to vertical edges.sobelY responds to horizontal edges.Math.hypot(gx, gy) combines gradient components safely.Learners edit PixelMath.java.
Recommended order:
clamp and validation.invertGray and threshold.luma by extracting red, green, and blue.packGray, normalizeToByte, and blend.sobelMagnitude using Math.hypot.sampleClamped and mean3x3.Hints:
(rgb >> 16) & 0xff."0..255."After applying answers:
scripts/apply_answers.sh lesson_4
scripts/run.sh lesson_4 verify
scripts/run.sh lesson_4 demo
Expected important lines:
lesson_4 answers verified
The demo should also write:
build/out/lesson_4/synthetic-input.png
build/out/lesson_4/sobel-edges.png
gx * gx + gy * gy without considering overflow for larger values.ImageMatrix store double, int, or byte values?Ask learners to add a threshold step that turns edge magnitudes above 100 white and everything else black. Then ask whether thresholding should be part of EdgeDetector.sobel, a separate preprocessing function, or a caller option.
private PixelMath() {
public static int clamp(int value, int min, int max) {
public static int invertGray(int gray) {
public static int threshold(int gray, int cutoff) {
public static int luma(int rgb) {
public static int packGray(int gray) {
public static int normalizeToByte(double value, double min, double max) {
public static int blend(int leftGray, int rightGray, double alpha) {
public static int sobelMagnitude(int gx, int gy) {
public static int sampleClamped(int[][] image, int x, int y) {
public static int mean3x3(int[][] image, int centerX, int centerY) {
private static void requireByte(int value, String name) {
private static void validateImage(int[][] image) {
private EdgeDetectDemo() {
public static void main(String[] args) throws IOException {
private Convolution() {
public static ImageMatrix apply(ImageMatrix input, Kernel kernel) {
private EdgeDetector() {
public static ImageMatrix sobel(ImageMatrix input) {
private ImageIo() {
public static ImageMatrix readGrayscale(Path path) throws IOException {
public static void writeGrayscale(ImageMatrix matrix, Path path) throws IOException {
public ImageMatrix(int width, int height) {
public int width() {
public int height() {
public double get(int x, int y) {
public double getClamped(int x, int y) {
public void set(int x, int y, double value) {
public static ImageMatrix syntheticTestPattern(int width, int height) {
public static ImageMatrix fromBufferedImage(BufferedImage image) {
public BufferedImage toBufferedImage() {
private static int clampToByte(double value) {
public Kernel {
public int width() {
public int height() {
public double at(int x, int y) {
public static Kernel sobelX() {
public static Kernel sobelY() {
public static Kernel boxBlur3() {
private AnswerVerifier() {
public static void main(String[] args) {
private static void expectEquals(int expected, int actual, String label) {
private static void expectThrows(Runnable action, String label) {
private ExerciseRunner() {
public static void main(String[] args) {
private PixelMath() {
public static int clamp(int value, int min, int max) {
public static int invertGray(int gray) {
public static int threshold(int gray, int cutoff) {
public static int luma(int rgb) {
public static int packGray(int gray) {
public static int normalizeToByte(double value, double min, double max) {
public static int blend(int leftGray, int rightGray, double alpha) {
public static int sobelMagnitude(int gx, int gy) {
public static int sampleClamped(int[][] image, int x, int y) {
public static int mean3x3(int[][] image, int centerX, int centerY) {
private PixelMath() {
public static int clamp(int value, int min, int max) {
public static int invertGray(int gray) {
public static int threshold(int gray, int cutoff) {
public static int luma(int rgb) {
public static int packGray(int gray) {
public static int normalizeToByte(double value, double min, double max) {
public static int blend(int leftGray, int rightGray, double alpha) {
public static int sobelMagnitude(int gx, int gy) {
public static int sampleClamped(int[][] image, int x, int y) {
public static int mean3x3(int[][] image, int centerX, int centerY) {