Rendering Engine 0.2.17
Modular Graphics Rendering Engine | v0.2.17
Loading...
Searching...
No Matches
image_codec_jpeg.hpp
Go to the documentation of this file.
1// This file is part of the Rendering Engine project.
2// Author: Alexander Obzherin <alexanderobzherin@gmail.com>
3// Copyright (c) 2026 Alexander Obzherin
4// Distributed under the terms of the zlib License. See LICENSE.md for details.
5
6/**
7 * @file image_codec_jpeg.hpp
8 * @brief JPEG read/write backend using libjpeg.
9 *
10 * Provides low-level I/O routines bridging `ImageData` and the libjpeg API.
11 * Supports 8-bit RGB images for loading and saving.
12 *
13 * @details
14 * The implementation follows the official libjpeg API workflow:
15 * - https://libjpeg.sourceforge.io/
16 * - Steps: setup error handler -> create compressor/decompressor -> set parameters -> read/write scanlines -> clean up.
17 *
18 * Custom error handling is implemented via `codec_error_mgr` to safely recover
19 * from libjpeg's internal `longjmp` behavior.
20 *
21 * @note This file is an internal backend of the Rendering Engine and is not part of the public API.
22 *
23 * @see rendering_engine::ImageData
24 */
25#pragma once
26
27#include <iostream>
28#include <limits>
29#include <stdio.h>
30#include "jpeglib.h"
31#include <setjmp.h>
32#include "image_data.hpp"
33
34 /**
35 * @brief Saves image data to a JPEG file.
36 * @param imageData Source image in RGB format.
37 * @param filename Path to the output file.
38 */
39static void SaveTextureFileJpeg(rendering_engine::ImageData const& imageData, char const* filename)
40{
41 auto const imageDataVector = imageData.GetImageDataRGB();
42 JSAMPLE* imageBuffer = new JSAMPLE[imageDataVector.size()];
43 std::copy(imageDataVector.begin(), imageDataVector.end(), imageBuffer);
44 int quality = 100;
45
46 struct jpeg_compress_struct cinfo;
47 struct jpeg_error_mgr jerr;
48
49 FILE* outfile; /* target file */
50 JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
51
52 cinfo.err = jpeg_std_error(&jerr);
53 jpeg_create_compress(&cinfo);
54
55 if( (outfile = fopen(filename, "wb")) == NULL ) {
56 fprintf(stderr, "can't open %s\n", filename);
57 exit(1);
58 }
59 jpeg_stdio_dest(&cinfo, outfile);
60
61 cinfo.image_width = imageData.GetWidth(); /* image width and height, in pixels */
62 cinfo.image_height = imageData.GetHeight();
63 cinfo.input_components = 3; /* # of color components per pixel */
64 cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
65
66 jpeg_set_defaults(&cinfo);
67
68 jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
69
70 jpeg_start_compress(&cinfo, TRUE);
71
72 const size_t rowStride = static_cast<size_t>(imageData.GetWidth()) * 3U; /* JSAMPLEs per row in image_buffer */
73
74 while( cinfo.next_scanline < cinfo.image_height )
75 {
76 /* jpeg_write_scanlines expects an array of pointers to scanlines.
77 * Here the array is only one element long, but you could pass
78 * more than one scanline at a time if that's more convenient.
79 */
80 row_pointer[0] = &imageBuffer[static_cast<size_t>(cinfo.next_scanline) * rowStride];
81 (void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
82 }
83
84 jpeg_finish_compress(&cinfo);
85 /* After finish_compress, we can close the output file. */
86 fclose(outfile);
87
88 jpeg_destroy_compress(&cinfo);
89 delete[] imageBuffer;
90}
91
93 struct jpeg_error_mgr pub; /* "public" fields */
94
95 jmp_buf setjmp_buffer; /* for return to caller */
96};
97
99
100/*
101 * Here's the routine that will replace the standard error_exit method:
102 */
103
104METHODDEF(void)
105codec_error_exit(j_common_ptr cinfo)
106{
107 /* cinfo->err really points to a codec_error_mgr struct, so coerce pointer */
108 codec_error_mgr_ptr codecErr = (codec_error_mgr_ptr)cinfo->err;
109
110 /* Always display the message. */
111 /* We could postpone this until after returning, if we chose. */
112 (*cinfo->err->output_message) (cinfo);
113
114 /* Return control to the setjmp point */
115 longjmp(codecErr->setjmp_buffer, 1);
116}
117
118static bool
119DoReadJpegFile(struct jpeg_decompress_struct* cinfo,
120 char const* filename, unsigned int& width, unsigned int& height, std::vector<std::uint8_t>& rgbImageDataVector);
121
122static bool DoReadJpegFromMemory(
123 struct jpeg_decompress_struct* cinfo,
124 const unsigned char* memory,
125 size_t memorySize,
126 unsigned int& width,
127 unsigned int& height,
128 std::vector<std::uint8_t>& rgbImageDataVector);
129
130/*
131 * Sample routine for JPEG decompression. We assume that the source file name
132 * is passed in. We want to return 1 on success, 0 on error.
133 */
134
135 /**
136 * @brief Reads a JPEG file into an RGB image buffer.
137 * @param filename Path to input JPEG file.
138 * @param width [out] Image width in pixels.
139 * @param height [out] Image height in pixels.
140 * @param rgbImageDataVector [out] Output vector of RGB pixel data (3 bytes per pixel).
141 * @return true on success, false on failure.
142 */
143static bool ReadJpegFile(char const* filename, unsigned int& width, unsigned int& height, std::vector<std::uint8_t>& rgbImageDataVector)
144{
145 /* This struct contains the JPEG decompression parameters and pointers to
146 * working space (which is allocated as needed by the JPEG library).
147 */
148 struct jpeg_decompress_struct cinfo;
149
150 return DoReadJpegFile(&cinfo, filename, width, height, rgbImageDataVector);
151}
152
153/*
154 * We call the libjpeg API from within a separate function, because modifying
155 * the local non-volatile jpeg_decompress_struct instance below the setjmp()
156 * return point and then accessing the instance after setjmp() returns would
157 * result in undefined behavior that may potentially overwrite all or part of
158 * the structure.
159 */
160
161bool
162DoReadJpegFile(struct jpeg_decompress_struct* cinfo, char const* filename, unsigned int& width, unsigned int& height, std::vector<std::uint8_t>& rgbImageDataVector)
163{
164 /* We use our private extension JPEG error handler.
165 * Note that this struct must live as long as the main JPEG parameter
166 * struct, to avoid dangling-pointer problems.
167 */
168 struct codec_error_mgr jerr;
169
170 FILE* infile; /* source file */
171 JSAMPARRAY buffer; /* Output row buffer */
172
173 if( (infile = fopen(filename, "rb")) == NULL )
174 {
175 std::cout << "File can not be open." << std::endl;
176 return false;
177 }
178
179 /* Step 1: allocate and initialize JPEG decompression object */
180
181 /* We set up the normal JPEG error routines, then override error_exit. */
182 cinfo->err = jpeg_std_error(&jerr.pub);
183 jerr.pub.error_exit = codec_error_exit;
184 /* Establish the setjmp return context for my_error_exit to use. */
185 if( setjmp(jerr.setjmp_buffer) ) {
186 /* If we get here, the JPEG code has signaled an error.
187 * We need to clean up the JPEG object, close the input file, and return.
188 */
189 jpeg_destroy_decompress(cinfo);
190 fclose(infile);
191 return false;
192 }
193 /* Now we can initialize the JPEG decompression object. */
194 jpeg_create_decompress(cinfo);
195
196 /* Step 2: specify data source (eg, a file) */
197
198 jpeg_stdio_src(cinfo, infile);
199
200 /* Step 3: read file parameters with jpeg_read_header() */
201
202 (void)jpeg_read_header(cinfo, TRUE);
203 /* We can ignore the return value from jpeg_read_header since
204 * (a) suspension is not possible with the stdio data source, and
205 * (b) we passed TRUE to reject a tables-only JPEG file as an error.
206 * See libjpeg.txt for more info.
207 */
208
209 /* Step 4: set parameters for decompression */
210
211 /* In this example, we don't need to change any of the defaults set by
212 * jpeg_read_header(), so we do nothing here.
213 */
214
215 /* Step 5: Start decompressor and fulfill the output data */
216 width = cinfo->image_width;
217 height = cinfo->image_height;
218
219 rgbImageDataVector.clear();
220
221 (void)jpeg_start_decompress(cinfo);
222 /* We can ignore the return value since suspension is not possible
223 * with the stdio data source.
224 */
225
226 /* We may need to do some setup of our own at this point before reading
227 * the data. After jpeg_start_decompress() we have the correct scaled
228 * output image dimensions available, as well as the output colormap
229 * if we asked for color quantization.
230 * In this example, we need to make an output work buffer of the right size.
231 */
232 /* JSAMPLEs per row in output buffer */
233 const JDIMENSION rowStride = cinfo->output_width * static_cast<JDIMENSION>(cinfo->output_components);
234 /* Make a one-row-high sample array that will go away when done with image */
235 buffer = (*cinfo->mem->alloc_sarray)
236 ((j_common_ptr)cinfo, JPOOL_IMAGE, rowStride, 1);
237
238 /* Step 6: while (scan lines remain to be read) */
239 /* jpeg_read_scanlines(...); */
240
241 /* Here we use the library's state variable cinfo->output_scanline as the
242 * loop counter, so that we don't have to keep track ourselves.
243 */
244 ;
245 while( cinfo->output_scanline < cinfo->output_height ) {
246 /* jpeg_read_scanlines expects an array of pointers to scanlines.
247 * Here the array is only one element long, but you could ask for
248 * more than one scanline at a time if that's more convenient.
249 */
250 (void)jpeg_read_scanlines(cinfo, buffer, 1);
251 /* Assume put_scanline_someplace wants a pointer and sample count. */
252 for(JDIMENSION i = 0; i < rowStride; ++i)
253 {
254 rgbImageDataVector.push_back(*(buffer[0] + i));
255 }
256 }
257
258 /* Step 7: Finish decompression */
259
260 (void)jpeg_finish_decompress(cinfo);
261 /* We can ignore the return value since suspension is not possible
262 * with the stdio data source.
263 */
264
265 /* Step 8: Release JPEG decompression object */
266
267 /* This is an important step since it will release a good deal of memory. */
268 jpeg_destroy_decompress(cinfo);
269
270 /* After finish_decompress, we can close the input file.
271 * Here we postpone it until after no more JPEG errors are possible,
272 * so as to simplify the setjmp error logic above. (Actually, I don't
273 * think that jpeg_destroy can do an error exit, but why assume anything...)
274 */
275 fclose(infile);
276
277 /* At this point you may want to check to see whether any corrupt-data
278 * warnings occurred (test whether jerr.pub.num_warnings is nonzero).
279 */
280
281 /* And we're done! */
282 return true;
283}
284
285/**
286 * @brief Decode a JPEG image directly from a memory buffer.
287 *
288 * This function behaves similarly to ReadJpegFile(), but instead of
289 * reading from disk, it reads raw JPEG bytes already loaded in memory.
290 *
291 * Useful when assets are stored in:
292 * - packed archives (Pack.bin)
293 * - networked streams
294 * - compressed inline resources
295 *
296 * The function expects the buffer to contain valid JPEG-formatted data.
297 *
298 * @param memory
299 * Pointer to the start of the JPEG byte stream.
300 *
301 * @param memorySize
302 * Total size of the memory buffer in bytes.
303 *
304 * @param width [out]
305 * Decoded image width in pixels.
306 *
307 * @param height [out]
308 * Decoded image height in pixels.
309 *
310 * @param rgbImageDataVector [out]
311 * Output RGB pixel buffer (3 bytes per pixel).
312 *
313 * @return
314 * - `true` → Successful decode.
315 * - `false` → Memory buffer was not a valid JPEG stream or decode failed.
316 *
317 * @warning
318 * The function does not attempt format detection beyond JPEG signature.
319 * Callers must ensure the memory buffer contains JPEG data.
320 */
322 const unsigned char* memory,
323 size_t memorySize,
324 unsigned int& width,
325 unsigned int& height,
326 std::vector<std::uint8_t>& rgbImageDataVector)
327{
328 struct jpeg_decompress_struct cinfo;
329
331 &cinfo,
332 memory,
333 memorySize,
334 width,
335 height,
336 rgbImageDataVector);
337}
338
340 struct jpeg_decompress_struct* cinfo,
341 const unsigned char* memory,
342 size_t memorySize,
343 unsigned int& width,
344 unsigned int& height,
345 std::vector<std::uint8_t>& rgbImageDataVector)
346{
347 struct codec_error_mgr jerr;
348
349 cinfo->err = jpeg_std_error(&jerr.pub);
350 jerr.pub.error_exit = codec_error_exit;
351
352 if (setjmp(jerr.setjmp_buffer))
353 {
354 jpeg_destroy_decompress(cinfo);
355 return false;
356 }
357
358 jpeg_create_decompress(cinfo);
359
360 if (memorySize >
361 static_cast<size_t>(
362 std::numeric_limits<unsigned long>::max()))
363 {
364 jpeg_destroy_decompress(cinfo);
365 return false;
366 }
367
368 jpeg_mem_src(
369 cinfo,
370 memory,
371 static_cast<unsigned long>(memorySize));
372
373 jpeg_read_header(cinfo, TRUE);
374 jpeg_start_decompress(cinfo);
375
376 width = cinfo->output_width;
377 height = cinfo->output_height;
378
379 const size_t components =
380 static_cast<size_t>(cinfo->output_components);
381
382 const size_t rowStride =
383 static_cast<size_t>(width) * components;
384
385 const size_t imageSize =
386 static_cast<size_t>(width) *
387 static_cast<size_t>(height) *
388 components;
389
390 rgbImageDataVector.resize(imageSize);
391
392 while (cinfo->output_scanline < cinfo->output_height)
393 {
394 unsigned char* row =
395 rgbImageDataVector.data() +
396 static_cast<size_t>(cinfo->output_scanline) * rowStride;
397
398 jpeg_read_scanlines(cinfo, &row, 1);
399 }
400
401 jpeg_finish_decompress(cinfo);
402 jpeg_destroy_decompress(cinfo);
403
404 return true;
405}
Represents raw 2D image data stored in memory.
unsigned int GetWidth() const
Returns the image width.
std::vector< uint8_t > GetImageDataRGB() const
Gets raw image data in RGB format.
unsigned int GetHeight() const
Returns the image height.
static bool DoReadJpegFile(struct jpeg_decompress_struct *cinfo, char const *filename, unsigned int &width, unsigned int &height, std::vector< std::uint8_t > &rgbImageDataVector)
struct codec_error_mgr * codec_error_mgr_ptr
codec_error_exit(j_common_ptr cinfo)
static bool ReadJpegFromMemory(const unsigned char *memory, size_t memorySize, unsigned int &width, unsigned int &height, std::vector< std::uint8_t > &rgbImageDataVector)
Decode a JPEG image directly from a memory buffer.
static bool ReadJpegFile(char const *filename, unsigned int &width, unsigned int &height, std::vector< std::uint8_t > &rgbImageDataVector)
Reads a JPEG file into an RGB image buffer.
static void SaveTextureFileJpeg(rendering_engine::ImageData const &imageData, char const *filename)
Saves image data to a JPEG file.
static bool DoReadJpegFromMemory(struct jpeg_decompress_struct *cinfo, const unsigned char *memory, size_t memorySize, unsigned int &width, unsigned int &height, std::vector< std::uint8_t > &rgbImageDataVector)
struct jpeg_error_mgr pub