MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
mxvk_jpeg.cpp
Go to the documentation of this file.
1/**
2 * @file mxvk_jpeg.cpp
3 * @brief Implementation of mxvk JPEG loading and saving utilities.
4 */
5
6#include "mxvk/mxvk_jpeg.hpp"
7
8#if defined(MXVK_WITH_JPEG) || defined(WITH_JPEG)
9
10#include <jpeglib.h>
11
12#include <SDL3/SDL.h>
13
14#include <algorithm>
15#include <csetjmp>
16#include <cstdio>
17#include <iostream>
18#include <memory>
19#include <vector>
20
21namespace mxvk {
22
23 namespace {
24
25 struct JpegErrorManager {
26 jpeg_error_mgr pub{};
27 std::jmp_buf jump_buffer{};
28 };
29
30 using JpegErrorPtr = JpegErrorManager *;
31
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);
36 }
37
38 [[nodiscard]] bool WriteJpegFromRgb24Surface(const SDL_Surface *surface,
39 const char *filename,
40 const int quality) {
41 if (surface == nullptr || filename == nullptr) {
42 std::cerr << "mxvk: Invalid JPEG save parameters.\n";
43 return false;
44 }
45
46 const int clamped_quality = std::clamp(quality, 0, 100);
47
48 FILE *file = std::fopen(filename, "wb");
49 if (file == nullptr) {
50 std::cerr << "mxvk: Failed to open JPEG output: " << filename << "\n";
51 return false;
52 }
53
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);
59
60 cinfo.image_width = surface->w;
61 cinfo.image_height = surface->h;
62 cinfo.input_components = 3;
63 cinfo.in_color_space = JCS_RGB;
64
65 jpeg_set_defaults(&cinfo);
66 jpeg_set_quality(&cinfo, clamped_quality, TRUE);
67 jpeg_start_compress(&cinfo, TRUE);
68
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);
73 }
74
75 jpeg_finish_compress(&cinfo);
76 jpeg_destroy_compress(&cinfo);
77 std::fclose(file);
78 return true;
79 }
80
81 } // namespace
82
83 SDL_Surface *VK_JPEG::Load(const char *filename) {
84 if (filename == nullptr) {
85 return nullptr;
86 }
87
88 FILE *file = std::fopen(filename, "rb");
89 if (file == nullptr) {
90 return nullptr;
91 }
92
93 jpeg_decompress_struct cinfo{};
94 JpegErrorManager jerr{};
95 cinfo.err = jpeg_std_error(&jerr.pub);
96 jerr.pub.error_exit = JpegErrorExit;
97
98 if (setjmp(jerr.jump_buffer) != 0) {
99 jpeg_destroy_decompress(&cinfo);
100 std::fclose(file);
101 return nullptr;
102 }
103
104 jpeg_create_decompress(&cinfo);
105 jpeg_stdio_src(&cinfo, file);
106 jpeg_read_header(&cinfo, TRUE);
107 jpeg_start_decompress(&cinfo);
108
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);
114 std::fclose(file);
115 return nullptr;
116 }
117
118 // Decode into tightly packed RGB24 first and then convert to RGBA32 to
119 // align with MXVK's texture upload path expectations.
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);
124 std::fclose(file);
125 return nullptr;
126 }
127
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);
132 }
133
134 jpeg_finish_decompress(&cinfo);
135 jpeg_destroy_decompress(&cinfo);
136 std::fclose(file);
137
138 SDL_Surface *rgba_surface = SDL_ConvertSurface(rgb_surface, SDL_PIXELFORMAT_RGBA32);
139 SDL_DestroySurface(rgb_surface);
140 return rgba_surface;
141 }
142
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";
146 return false;
147 }
148
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";
152 return false;
153 }
154
155 SDL_Surface *captured = SDL_RenderReadPixels(renderer, nullptr);
156
157 if (!SDL_SetRenderTarget(renderer, previous_target)) {
158 std::cerr << "mxvk: Failed to restore render target after JPEG save: " << SDL_GetError() << "\n";
159 }
160
161 if (captured == nullptr) {
162 std::cerr << "mxvk: Failed to read texture pixels for JPEG save: " << SDL_GetError() << "\n";
163 return false;
164 }
165
166 std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> captured_surface(captured, &SDL_DestroySurface);
167 return SaveSurface(captured_surface.get(), filename, quality);
168 }
169
170 bool VK_JPEG::SaveSurface(const SDL_Surface *surface, const char *filename, const int quality) {
171 if (surface == nullptr || filename == nullptr) {
172 return false;
173 }
174
175 std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> rgb_surface(nullptr, &SDL_DestroySurface);
176 const SDL_Surface *source_surface = surface;
177
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";
182 return false;
183 }
184 rgb_surface.reset(converted);
185 source_surface = rgb_surface.get();
186 }
187
188 return WriteJpegFromRgb24Surface(source_surface, filename, quality);
189 }
190
191 SDL_Surface *LoadJPEG(const char *filename) {
192 return VK_JPEG::Load(filename);
193 }
194
195 bool SaveJPEG(SDL_Texture *texture, SDL_Renderer *renderer, const char *filename, const int quality) {
196 return VK_JPEG::SaveTexture(texture, renderer, filename, quality);
197 }
198
199} // namespace mxvk
200
201#endif
JPEG image loading and saving utilities.
Utilities for loading and saving PNG images.
Definition mxvk.hpp:30