MXVK Vulkan Framework 0.35.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, const char *filename, const int quality) {
39 if (surface == nullptr || filename == nullptr) {
40 std::cerr << "mxvk: Invalid JPEG save parameters.\n";
41 return false;
42 }
43
44 const int clamped_quality = std::clamp(quality, 0, 100);
45
46 FILE *file = std::fopen(filename, "wb");
47 if (file == nullptr) {
48 std::cerr << "mxvk: Failed to open JPEG output: " << filename << "\n";
49 return false;
50 }
51
52 jpeg_compress_struct cinfo{};
53 jpeg_error_mgr jerr{};
54 cinfo.err = jpeg_std_error(&jerr);
55 jpeg_create_compress(&cinfo);
56 jpeg_stdio_dest(&cinfo, file);
57
58 cinfo.image_width = surface->w;
59 cinfo.image_height = surface->h;
60 cinfo.input_components = 3;
61 cinfo.in_color_space = JCS_RGB;
62
63 jpeg_set_defaults(&cinfo);
64 jpeg_set_quality(&cinfo, clamped_quality, TRUE);
65 jpeg_start_compress(&cinfo, TRUE);
66
67 while (cinfo.next_scanline < cinfo.image_height) {
68 JSAMPROW row = static_cast<JSAMPROW>(static_cast<void *>(static_cast<Uint8 *>(surface->pixels) + (cinfo.next_scanline * surface->pitch)));
69 jpeg_write_scanlines(&cinfo, &row, 1);
70 }
71
72 jpeg_finish_compress(&cinfo);
73 jpeg_destroy_compress(&cinfo);
74 std::fclose(file);
75 return true;
76 }
77
78 } // namespace
79
80 SDL_Surface *VK_JPEG::Load(const char *filename) {
81 if (filename == nullptr) {
82 return nullptr;
83 }
84
85 FILE *file = std::fopen(filename, "rb");
86 if (file == nullptr) {
87 return nullptr;
88 }
89
90 jpeg_decompress_struct cinfo{};
91 JpegErrorManager jerr{};
92 cinfo.err = jpeg_std_error(&jerr.pub);
93 jerr.pub.error_exit = JpegErrorExit;
94
95 if (setjmp(jerr.jump_buffer) != 0) {
96 jpeg_destroy_decompress(&cinfo);
97 std::fclose(file);
98 return nullptr;
99 }
100
101 jpeg_create_decompress(&cinfo);
102 jpeg_stdio_src(&cinfo, file);
103 jpeg_read_header(&cinfo, TRUE);
104 jpeg_start_decompress(&cinfo);
105
106 const int width = static_cast<int>(cinfo.output_width);
107 const int height = static_cast<int>(cinfo.output_height);
108 if (width <= 0 || height <= 0) {
109 jpeg_finish_decompress(&cinfo);
110 jpeg_destroy_decompress(&cinfo);
111 std::fclose(file);
112 return nullptr;
113 }
114
115 // Decode into tightly packed RGB24 first and then convert to RGBA32 to
116 // align with MXVK's texture upload path expectations.
117 SDL_Surface *rgb_surface = SDL_CreateSurface(width, height, SDL_PIXELFORMAT_RGB24);
118 if (rgb_surface == nullptr) {
119 jpeg_finish_decompress(&cinfo);
120 jpeg_destroy_decompress(&cinfo);
121 std::fclose(file);
122 return nullptr;
123 }
124
125 while (cinfo.output_scanline < cinfo.output_height) {
126 JSAMPROW row = static_cast<JSAMPROW>(static_cast<void *>(static_cast<Uint8 *>(rgb_surface->pixels) + (cinfo.output_scanline * rgb_surface->pitch)));
127 jpeg_read_scanlines(&cinfo, &row, 1);
128 }
129
130 jpeg_finish_decompress(&cinfo);
131 jpeg_destroy_decompress(&cinfo);
132 std::fclose(file);
133
134 SDL_Surface *rgba_surface = SDL_ConvertSurface(rgb_surface, SDL_PIXELFORMAT_RGBA32);
135 SDL_DestroySurface(rgb_surface);
136 return rgba_surface;
137 }
138
139 bool VK_JPEG::SaveTexture(SDL_Texture *texture, SDL_Renderer *renderer, const char *filename, const int quality) {
140 if (texture == nullptr || renderer == nullptr || filename == nullptr) {
141 std::cerr << "mxvk: Invalid SaveTexture JPEG parameters.\n";
142 return false;
143 }
144
145 SDL_Texture *previous_target = SDL_GetRenderTarget(renderer);
146 if (!SDL_SetRenderTarget(renderer, texture)) {
147 std::cerr << "mxvk: Failed to set render target for JPEG save: " << SDL_GetError() << "\n";
148 return false;
149 }
150
151 SDL_Surface *captured = SDL_RenderReadPixels(renderer, nullptr);
152
153 if (!SDL_SetRenderTarget(renderer, previous_target)) {
154 std::cerr << "mxvk: Failed to restore render target after JPEG save: " << SDL_GetError() << "\n";
155 }
156
157 if (captured == nullptr) {
158 std::cerr << "mxvk: Failed to read texture pixels for JPEG save: " << SDL_GetError() << "\n";
159 return false;
160 }
161
162 std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> captured_surface(captured, &SDL_DestroySurface);
163 return SaveSurface(captured_surface.get(), filename, quality);
164 }
165
166 bool VK_JPEG::SaveSurface(const SDL_Surface *surface, const char *filename, const int quality) {
167 if (surface == nullptr || filename == nullptr) {
168 return false;
169 }
170
171 std::unique_ptr<SDL_Surface, decltype(&SDL_DestroySurface)> rgb_surface(nullptr, &SDL_DestroySurface);
172 const SDL_Surface *source_surface = surface;
173
174 if (surface->format != SDL_PIXELFORMAT_RGB24) {
175 SDL_Surface *converted = SDL_ConvertSurface(const_cast<SDL_Surface *>(surface), SDL_PIXELFORMAT_RGB24);
176 if (converted == nullptr) {
177 std::cerr << "mxvk: Failed to convert surface to RGB24 for JPEG save: " << SDL_GetError() << "\n";
178 return false;
179 }
180 rgb_surface.reset(converted);
181 source_surface = rgb_surface.get();
182 }
183
184 return WriteJpegFromRgb24Surface(source_surface, filename, quality);
185 }
186
187 SDL_Surface *LoadJPEG(const char *filename) { return VK_JPEG::Load(filename); }
188
189 bool SaveJPEG(SDL_Texture *texture, SDL_Renderer *renderer, const char *filename, const int quality) { return VK_JPEG::SaveTexture(texture, renderer, filename, quality); }
190
191} // namespace mxvk
192
193#endif
JPEG image loading and saving utilities.
Utilities for loading and saving PNG images.
Definition mxvk.hpp:31