8#if defined(MXVK_WITH_JPEG) || defined(WITH_JPEG)
25 struct JpegErrorManager {
27 std::jmp_buf jump_buffer{};
30 using JpegErrorPtr = JpegErrorManager *;
32 void JpegErrorExit(j_common_ptr cinfo) {
33 auto *err =
reinterpret_cast<JpegErrorPtr
>(cinfo->err);
34 (*cinfo->err->output_message)(cinfo);
35 std::longjmp(err->jump_buffer, 1);
38 [[nodiscard]]
bool WriteJpegFromRgb24Surface(
const SDL_Surface *surface,
41 if (surface ==
nullptr || filename ==
nullptr) {
42 std::cerr <<
"mxvk: Invalid JPEG save parameters.\n";
46 const int clamped_quality = std::clamp(quality, 0, 100);
48 FILE *file = std::fopen(filename,
"wb");
49 if (file ==
nullptr) {
50 std::cerr <<
"mxvk: Failed to open JPEG output: " << filename <<
"\n";
54 jpeg_compress_struct cinfo{};
55 jpeg_error_mgr jerr{};
56 cinfo.err = jpeg_std_error(&jerr);
57 jpeg_create_compress(&cinfo);
58 jpeg_stdio_dest(&cinfo, file);
60 cinfo.image_width = surface->w;
61 cinfo.image_height = surface->h;
62 cinfo.input_components = 3;
63 cinfo.in_color_space = JCS_RGB;
65 jpeg_set_defaults(&cinfo);
66 jpeg_set_quality(&cinfo, clamped_quality, TRUE);
67 jpeg_start_compress(&cinfo, TRUE);
69 while (cinfo.next_scanline < cinfo.image_height) {
70 JSAMPROW row =
static_cast<JSAMPROW
>(
71 static_cast<void *
>(
static_cast<Uint8 *
>(surface->pixels) + (cinfo.next_scanline * surface->pitch)));
72 jpeg_write_scanlines(&cinfo, &row, 1);
75 jpeg_finish_compress(&cinfo);
76 jpeg_destroy_compress(&cinfo);
83 SDL_Surface *VK_JPEG::Load(
const char *filename) {
84 if (filename ==
nullptr) {
88 FILE *file = std::fopen(filename,
"rb");
89 if (file ==
nullptr) {
93 jpeg_decompress_struct cinfo{};
94 JpegErrorManager jerr{};
95 cinfo.err = jpeg_std_error(&jerr.pub);
96 jerr.pub.error_exit = JpegErrorExit;
98 if (setjmp(jerr.jump_buffer) != 0) {
99 jpeg_destroy_decompress(&cinfo);
104 jpeg_create_decompress(&cinfo);
105 jpeg_stdio_src(&cinfo, file);
106 jpeg_read_header(&cinfo, TRUE);
107 jpeg_start_decompress(&cinfo);
109 const int width =
static_cast<int>(cinfo.output_width);
110 const int height =
static_cast<int>(cinfo.output_height);
111 if (width <= 0 || height <= 0) {
112 jpeg_finish_decompress(&cinfo);
113 jpeg_destroy_decompress(&cinfo);
120 SDL_Surface *rgb_surface = SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGB24);
121 if (rgb_surface ==
nullptr) {
122 jpeg_finish_decompress(&cinfo);
123 jpeg_destroy_decompress(&cinfo);
128 while (cinfo.output_scanline < cinfo.output_height) {
129 JSAMPROW row =
static_cast<JSAMPROW
>(
130 static_cast<void *
>(
static_cast<Uint8 *
>(rgb_surface->pixels) + (cinfo.output_scanline * rgb_surface->pitch)));
131 jpeg_read_scanlines(&cinfo, &row, 1);
134 jpeg_finish_decompress(&cinfo);
135 jpeg_destroy_decompress(&cinfo);
138 SDL_Surface *rgba_surface = SDL_ConvertSurface(rgb_surface, SDL_PIXELFORMAT_RGBA32);
139 SDL_DestroySurface(rgb_surface);
143 bool VK_JPEG::SaveTexture(SDL_Texture *texture, SDL_Renderer *renderer,
const char *filename,
const int quality) {
144 if (texture ==
nullptr || renderer ==
nullptr || filename ==
nullptr) {
145 std::cerr <<
"mxvk: Invalid SaveTexture JPEG parameters.\n";
149 SDL_Texture *previous_target = SDL_GetRenderTarget(renderer);
150 if (!SDL_SetRenderTarget(renderer, texture)) {
151 std::cerr <<
"mxvk: Failed to set render target for JPEG save: " << SDL_GetError() <<
"\n";
155 SDL_Surface *captured = SDL_RenderReadPixels(renderer,
nullptr);
157 if (!SDL_SetRenderTarget(renderer, previous_target)) {
158 std::cerr <<
"mxvk: Failed to restore render target after JPEG save: " << SDL_GetError() <<
"\n";
161 if (captured ==
nullptr) {
162 std::cerr <<
"mxvk: Failed to read texture pixels for JPEG save: " << SDL_GetError() <<
"\n";
166 std::unique_ptr<SDL_Surface,
decltype(&SDL_DestroySurface)> captured_surface(captured, &SDL_DestroySurface);
167 return SaveSurface(captured_surface.get(), filename, quality);
170 bool VK_JPEG::SaveSurface(
const SDL_Surface *surface,
const char *filename,
const int quality) {
171 if (surface ==
nullptr || filename ==
nullptr) {
175 std::unique_ptr<SDL_Surface,
decltype(&SDL_DestroySurface)> rgb_surface(
nullptr, &SDL_DestroySurface);
176 const SDL_Surface *source_surface = surface;
178 if (surface->format != SDL_PIXELFORMAT_RGB24) {
179 SDL_Surface *converted = SDL_ConvertSurface(
const_cast<SDL_Surface *
>(surface), SDL_PIXELFORMAT_RGB24);
180 if (converted ==
nullptr) {
181 std::cerr <<
"mxvk: Failed to convert surface to RGB24 for JPEG save: " << SDL_GetError() <<
"\n";
184 rgb_surface.reset(converted);
185 source_surface = rgb_surface.get();
188 return WriteJpegFromRgb24Surface(source_surface, filename, quality);
191 SDL_Surface *LoadJPEG(
const char *filename) {
192 return VK_JPEG::Load(filename);
195 bool SaveJPEG(SDL_Texture *texture, SDL_Renderer *renderer,
const char *filename,
const int quality) {
196 return VK_JPEG::SaveTexture(texture, renderer, filename, quality);
JPEG image loading and saving utilities.
Utilities for loading and saving PNG images.