In the video the png images are being transformed into jpg images by first being decoded into a pixel based image structure using the PNG decoder and then transformed into jpg images using external library.
The decoder conforms to the requirements of decoders mentioned in standard PNG specification supporting all the required chunks of the PNG format (i.e. IHDR Image header
, PLTE Palette
, IDAT Image data
, and IEND Image trailer
).
- Grayscale images
- Regular RGB images
- Indexed images (i.e. with a fixed palette of colors)
- Images containing alpha-channel (transparency)
Note: bit depth <= 8
supported. The entry point is Image ReadPng(std::string_view filename)
function in png_decoder.h
.
- Read signature bytes and validate it.
- Read chunks and validate its
CRC
untilIEND
chunk encountered. - Save the information provided by
IHDR
andPLTE
chunks for future decoding use. - Concatenate the content of all
IDAT
chunks into a single byte vector (lets call itV
). - Once
IEND
chunk reached, decoding ofV
starts: deflate to decompressV
into another byte vectorD
. - Process
D
by scanlines, applying specified filters. - In case of interlaced image use Adam7 algorithm to decode the image.
- zlib1g-dev (zlib) - deflate algorithm for decompressing the image data.
- Boost - CRC calculation.
- Decoder is implemented as a class, receiving
std::istream&
(assuming binary mode) in it's constructor. - Deflate logic is completely separated from the decoder. Since zlib1g-dev (zlib) is a C libraries, there is a RAII wrapper written
around the library functionality (see
Inflate
). - Error handling:
- CRC validation for ancillary chunks.
- Checking of EOF when reading from the input stream.
- Exceptions throwing for invalid png images.
- In case of bit depth being less than 8 bits bits reading functionality is used which takes a sequence of bytes and reads it bitwise.
There are 3 main classes that are responsible for decoding the image after deflate algorithm is used: ScanlineReader
, Defilter
, and PixelStrategy
.
ScanlineReader
applies defilters to the scanlines to remove additional encoding layer from the actual image data, after that depending on the pixel storing format (which is stored inside IHDR
chunk) a concrete implementation of abstract PixelStrategy
used to convert scanline bytes into image pixels.
Defilters are concrete implementations of abstact class Defilter
that comform to Applicable Design Pattern: the caller (i.e. ScanlineReader
) provides every scanline to the method Defilter::applicable
which tells whether the currect defilter can be used in order to apply defiltering (it can be determined by a header stored in scanlines), in case of success the caller invokes Defilter::apply
:
// ScanlineReader workflow:
for (const auto& defilter : m_defilters) {
if (defilter->applicable(scanline)) {
defilter->apply(scanline, m_previousScanline, bpp);
}
}
PNG format supports 3 main image formats: grayscale, RGB, and color pallete images, where the former two may also contain alpha-channel, which leads to having 5 completely different image formats.
In order to uniformly treat all the image formats the Strategy Design Pattern is applied: factory method PixelStrategy::create
determines which image format is used in the current image and returns the concrete implementor of the image format, i.e. one of PixelGrayscaleStrategy
, PixelRGBStrategy
, PixelPaletteIndexStrategy
, PixelGrayscaleAlphaStrategy
, and PixelRGBAlphaStrategy
:
std::unique_ptr<PixelStrategy> PixelStrategy::create(uint8_t colorType, uint8_t bitDepth, PLTE plte) {
if (colorType == PIXEL_GRAYSCALE_COLOR_TYPE) {
return std::make_unique<PixelGrayscaleStrategy>(bitDepth, std::move(plte));
}
else if (colorType == PIXEL_RGB_COLOR_TYPE) {
return std::make_unique<PixelRGBStrategy>(bitDepth, std::move(plte));
}
else if (colorType == PIXEL_PALETTE_INDEX_COLOR_TYPE) {
return std::make_unique<PixelPaletteIndexStrategy>(bitDepth, std::move(plte));
}
else if (colorType == PIXEL_GRAYSCALE_ALPHA_COLOR_TYPE) {
return std::make_unique<PixelGrayscaleAlphaStrategy>(bitDepth, std::move(plte));
}
else if (colorType == PIXEL_RGB_ALPHA_COLOR_TYPE) {
return std::make_unique<PixelRGBAlphaStrategy>(bitDepth, std::move(plte));
}
throw exceptions::InvalidColorTypeChunkException(
PNG_DECODER_ERROR_MESSAGE("Unsupported color type in IHDR: " + std::to_string(colorType)));
}