libgui 2.01
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
GuiImageData.cpp
1/****************************************************************************
2 * libgui
3 * Daryl Borth 2009-2026
4 * GuiImageData.cpp
5 ***************************************************************************/
6
7#include <png.h>
8#include <setjmp.h>
9
10#include "Gui.h"
11
12namespace {
13 uint8_t * scratchBuffer = nullptr;
14 unsigned int scratchBufferSize = 0;
15
16 Mutex & getScratchMutex()
17 {
18 static Mutex scratchMutex;
19 return scratchMutex;
20 }
21
22 void ErrorCb(png_structp png_ptr, png_const_charp) { longjmp(png_jmpbuf(png_ptr), 1); }
23 void WarningCb(png_structp, png_const_charp) {}
24}
25
26void GuiImageData::setDecodeScratch(void * buffer, unsigned int size)
27{
28 scratchBuffer = static_cast<uint8_t *>(buffer);
29 scratchBufferSize = buffer ? size : 0;
30}
31
33{
34 return getScratchMutex();
35}
36
38{
39 texture = nullptr;
40 width = 0;
41 height = 0;
42 ownsTexture = false;
43 capWidth = 0;
44 capHeight = 0;
45}
46
47GuiImageData::GuiImageData(const uint8_t * i, int maxw, int maxh)
48{
49 texture = nullptr;
50 width = 0;
51 height = 0;
52 ownsTexture = false;
53 capWidth = 0;
54 capHeight = 0;
55
56 if(i)
57 decodeImage(i, &width, &height, maxw, maxh);
58}
59
60GuiImageData::GuiImageData(const uint8_t * i, uint8_t * dst, int maxw, int maxh)
61{
62 texture = dst;
63 width = 0;
64 height = 0;
65 ownsTexture = false;
66 capWidth = 0;
67 capHeight = 0;
68
69 if(i) {
70 decodeImage(i, &width, &height, maxw, maxh);
71 }
72}
73
74GuiImageData::GuiImageData(void * tex, int w, int h, bool takeOwnership)
75{
76 texture = tex;
77 width = w;
78 height = h;
79 ownsTexture = takeOwnership && tex;
80 capWidth = ownsTexture ? w : 0;
81 capHeight = ownsTexture ? h : 0;
82}
83
85{
86 if(ownsTexture && texture)
87 {
88 platform->getVideo()->getImageRenderer()->destroyTexture(texture);
89 }
90 texture = nullptr;
91}
92
95{
96 const uint8_t * data;
97 size_t offset;
98};
99
100static void ReadPngDataCb(png_structp png_ptr, png_bytep data, png_size_t length)
101{
102 PngMemoryData * memData = static_cast<PngMemoryData *>(png_get_io_ptr(png_ptr));
103 if(!memData)
104 return;
105
106 memcpy(data, memData->data + memData->offset, length);
107 memData->offset += length;
108}
109
110bool GuiImageData::reload(const uint8_t * pngData, int maxw, int maxh)
111{
112 if(!pngData)
113 return false;
114
115 int w = 0, h = 0;
116 return decodeImage(pngData, &w, &h, maxw, maxh);
117}
118
119bool GuiImageData::decodeImage(const uint8_t * pngData, int * outWidth, int * outHeight, int maxw, int maxh)
120{
121 if(!pngData)
122 return false;
123
124 MutexLock scratchGuard(getScratchMutex());
125 uint8_t * const localScratchBuffer = scratchBuffer;
126 const unsigned int localScratchBufferSize = scratchBufferSize;
127
128 if(!localScratchBuffer || localScratchBufferSize == 0)
129 return false;
130
131 if(png_sig_cmp(static_cast<png_const_bytep>(pngData), 0, 8))
132 return false;
133
134 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, ErrorCb, WarningCb);
135 if(!png_ptr)
136 return false;
137
138 png_infop info_ptr = png_create_info_struct(png_ptr);
139 if(!info_ptr)
140 {
141 png_destroy_read_struct(&png_ptr, nullptr, nullptr);
142 return false;
143 }
144
145 if(setjmp(png_jmpbuf(png_ptr)))
146 {
147 png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
148 return false;
149 }
150
151 PngMemoryData memData = { pngData, 0 };
152 png_set_read_fn(png_ptr, &memData, ReadPngDataCb);
153
154 png_read_info(png_ptr, info_ptr);
155
156 png_uint_32 srcW, srcH;
157 int bit_depth, color_type, interlace_type;
158 png_get_IHDR(png_ptr, info_ptr, &srcW, &srcH, &bit_depth, &color_type, &interlace_type, nullptr, nullptr);
159
160 png_uint_32 w = srcW, h = srcH;
161 bool needsResize = (maxw > 0 && static_cast<int>(srcW) > maxw) || (maxh > 0 && static_cast<int>(srcH) > maxh);
162 if(needsResize)
163 {
164 double wScale = maxw > 0 ? static_cast<double>(maxw) / srcW : 1e30;
165 double hScale = maxh > 0 ? static_cast<double>(maxh) / srcH : 1e30;
166 double scale = wScale < hScale ? wScale : hScale;
167 w = static_cast<png_uint_32>(srcW * scale);
168 h = static_cast<png_uint_32>(srcH * scale);
169 if(w < 1) w = 1;
170 if(h < 1) h = 1;
171 }
172
173 if(bit_depth == 16)
174 png_set_strip_16(png_ptr);
175 if(color_type == PNG_COLOR_TYPE_PALETTE)
176 png_set_palette_to_rgb(png_ptr);
177 if(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
178 png_set_expand_gray_1_2_4_to_8(png_ptr);
179 if(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
180 png_set_tRNS_to_alpha(png_ptr);
181 if(color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)
182 png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
183 if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
184 png_set_gray_to_rgb(png_ptr);
185
186 png_read_update_info(png_ptr, info_ptr);
187 unsigned int rowBytes = png_get_rowbytes(png_ptr, info_ptr);
188
189 unsigned long long rowPtrBytes = static_cast<unsigned long long>(srcH) * sizeof(png_bytep);
190 unsigned long long srcRgbaBytes = static_cast<unsigned long long>(rowBytes) * srcH;
191 unsigned long long resizedRgbaBytes = needsResize ? static_cast<unsigned long long>(w) * h * 4 : 0;
192 unsigned long long totalScratchBytes = rowPtrBytes + srcRgbaBytes + resizedRgbaBytes;
193 if(totalScratchBytes > localScratchBufferSize)
194 {
195 png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
196 return false;
197 }
198
199 bool haveUsableTexture = texture && (!ownsTexture || (static_cast<int>(w) <= capWidth && static_cast<int>(h) <= capHeight));
200
201 void * newTexture = texture;
202 if(!haveUsableTexture)
203 {
204 newTexture = platform->getVideo()->getImageRenderer()->createTexture(w, h);
205 if(!newTexture)
206 {
207 png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
208 return false;
209 }
210 }
211
212 png_bytep * row_pointers = reinterpret_cast<png_bytep *>(localScratchBuffer);
213 uint8_t * srcRgba = localScratchBuffer + rowPtrBytes;
214
215 for(png_uint_32 i = 0; i < srcH; i++)
216 row_pointers[i] = srcRgba + (static_cast<size_t>(i) * rowBytes);
217
218 png_read_image(png_ptr, row_pointers);
219
220 const uint8_t * finalRgba = srcRgba;
221 if(needsResize)
222 {
223 uint8_t * resizedRgba = srcRgba + srcRgbaBytes;
224 uint32_t xRatio = ((srcW << 16) / w) + 1;
225 uint32_t yRatio = ((srcH << 16) / h) + 1;
226
227 for(png_uint_32 y = 0; y < h; y++)
228 {
229 png_uint_32 sy = (y * yRatio) >> 16;
230 if(sy >= srcH) sy = srcH - 1;
231 const uint8_t * srcRow = srcRgba + static_cast<size_t>(sy) * rowBytes;
232 uint8_t * dstRow = resizedRgba + static_cast<size_t>(y) * w * 4;
233
234 for(png_uint_32 x = 0; x < w; x++)
235 {
236 png_uint_32 sx = (x * xRatio) >> 16;
237 if(sx >= srcW) sx = srcW - 1;
238 memcpy(dstRow + x * 4, srcRow + sx * 4, 4);
239 }
240 }
241
242 finalRgba = resizedRgba;
243 }
244
245 platform->getVideo()->getImageRenderer()->loadTextureData(newTexture, finalRgba, w, h);
246
247 if(!haveUsableTexture)
248 {
249 if(ownsTexture && texture)
251 texture = newTexture;
252 ownsTexture = true;
253 capWidth = w;
254 capHeight = h;
255 }
256
257 int designW = static_cast<int>(w);
258 int designH = static_cast<int>(h);
259 if(maxw == 0 && maxh == 0)
260 {
261 const PlatformConfig& config = platform->getConfig();
262 designW = static_cast<int>(w / config.assetScaleX + 0.5f);
263 designH = static_cast<int>(h / config.assetScaleY + 0.5f);
264 if(designW < 1) designW = 1;
265 if(designH < 1) designH = 1;
266 }
267
268 width = designW;
269 height = designH;
270 if(outWidth) *outWidth = designW;
271 if(outHeight) *outHeight = designH;
272
273 png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
274 return true;
275}
Umbrella header for the platform-agnostic libgui core.
int width
Width of image.
~GuiImageData()
Destructor.
GuiImageData()
Constructor.
void * texture
Attached platform-native texture.
bool reload(const uint8_t *pngData, int maxw=0, int maxh=0)
Decodes new PNG data into this object's own texture, reusing the existing allocation whenever it's al...
static void setDecodeScratch(void *buffer, unsigned int size)
Sets the scratch buffer that every decode (the PNG-decoding constructors, and reload()) will stage it...
int height
Height of image.
static Mutex & scratchLock()
virtual void * createTexture(int width, int height)=0
Allocates an empty hardware-aligned texture buffer.
virtual void loadTextureData(void *texture, const uint8_t *rgba, int width, int height)=0
Loads raw RGBA8 pixels into the pre-allocated texture.
virtual void destroyTexture(void *texture)=0
Destroys a texture created by createTexture.
RAII lock guard - locks on construction, unlocks on destruction.
Definition Mutex.h:37
A simple mutual-exclusion lock.
Definition Mutex.h:14
virtual VideoDriver * getVideo()=0
const PlatformConfig & getConfig() const
The PlatformConfig this platform was init()'d with.
Definition Platform.h:74
virtual ImageRenderer * getImageRenderer()=0
float assetScaleX
Horizontal ratio of source art pixels to design-canvas pixels.
Definition Platform.h:56
float assetScaleY
Vertical counterpart of assetScaleX.
Definition Platform.h:58
Read cursor over an in-memory PNG, used as the libpng read callback's I/O pointer.
const uint8_t * data
Start of the PNG file in memory.
size_t offset
Bytes consumed so far.