PROJECT / 002 · IMAGE PROCESSING
Grayscale Image Processing Library
A closer look at every pixel.
Conceptual illustration · not a screenshot or measured result
Overview
GrayLib is a dependency-free C++17 library for single-channel, 8-bit images. It implements pixel storage, ASCII and binary PGM I/O, image arithmetic, region-of-interest extraction, point transformations, convolution filters, and drawing primitives without relying on OpenCV.
Motivation
Keeping image storage and processing in the project itself makes the underlying work visible: how pixels are owned, how a file becomes an image, and how local numerical operations change that image. Grayscale data keeps the scope focused while still exposing the main ideas behind filtering and transformation.
How it works
An Image can be created in memory or loaded from a PGM file. Processors return transformed images through a shared ImageProcessor interface. Available operations include brightness and contrast adjustment, gamma correction, mean and Gaussian blur, and horizontal or vertical Sobel filtering. Drawing utilities add lines, rectangles, and circles; results can be saved as P2 or P5 PGM files.
Architecture
Public headers under include/imgproc separate Image, Geometry, Processing, and Draw. Implementations in src handle storage and file I/O, transformations, and drawing. The examples program generates synthetic images and processed outputs. A dependency-free test executable covers the library, with CMake and a GitHub Actions workflow providing the build and test structure.
Implementation decisions
Image owns its heap-allocated pixels and implements deep-copy and move semantics. Arithmetic saturates within the 0–255 range, while checked pixel access catches invalid coordinates. Odd-sized convolution kernels support either zero padding or extended borders. The polymorphic processing interface lets operations be selected through one API, while the implementation remains a straightforward single-threaded CPU approach.
Challenges
Image boundaries, pixel-range limits, and object lifetimes all need explicit behavior. The library addresses them with border modes, clamped output values, clipped drawing, and defined copy/move ownership. Tests cover those contracts alongside region extraction and both PGM encodings. Color images, broad codec support, and large-image optimization remain outside the project's intended scope.
What I learned
An image-processing algorithm is only part of a useful library. Storage, copying, bounds, and file parsing determine whether it behaves predictably. Implementing filters and drawing primitives also connects the mathematics to individual pixels, while a common processing interface shows how object-oriented design can organize operations without hiding their implementation.