1 /*
2 Copyright (c) 2013-2016, 2018-2019 Cong Xu
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8 Redistributions of source code must retain the above copyright notice, this
9 list of conditions and the following disclaimer.
10 Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 POSSIBILITY OF SUCH DAMAGE.
25 */
26 #include "pic.h"
27
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "c_hashmap/hashmap.h"
32 #include "defs.h"
33 #include "grafx.h"
34 #include "log.h"
35 #include "texture.h"
36 #include "utils.h"
37
38 map_t textureDebugger = NULL;
39
40
PixelToColor(const SDL_PixelFormat * f,const Uint8 aShift,const Uint32 pixel)41 color_t PixelToColor(
42 const SDL_PixelFormat *f, const Uint8 aShift, const Uint32 pixel)
43 {
44 color_t c;
45 SDL_GetRGBA(pixel, f, &c.r, &c.g, &c.b, &c.a);
46 // Manually apply the alpha as SDL seems to always set it to 0
47 c.a = (Uint8)((pixel & ~(f->Rmask | f->Gmask | f->Bmask)) >> aShift);
48 return c;
49 }
ColorToPixel(const SDL_PixelFormat * f,const Uint8 aShift,const color_t color)50 Uint32 ColorToPixel(
51 const SDL_PixelFormat *f, const Uint8 aShift, const color_t color)
52 {
53 const Uint32 pixel = SDL_MapRGBA(f, color.r, color.g, color.b, color.a);
54 // Manually apply the alpha as SDL seems to always set it to 0
55 return (pixel & (f->Rmask | f->Gmask | f->Bmask)) |
56 ((Uint32)color.a << aShift);
57 }
58
59
PicLoad(Pic * p,const struct vec2i size,const struct vec2i offset,const SDL_Surface * image)60 void PicLoad(
61 Pic *p, const struct vec2i size, const struct vec2i offset, const SDL_Surface *image)
62 {
63 memset(p, 0, sizeof *p);
64 p->size = size;
65 p->offset = svec2i_zero();
66 CMALLOC(p->Data, size.x * size.y * sizeof *((Pic *)0)->Data);
67 if (p->Data == NULL)
68 {
69 return;
70 }
71 // Manually copy the pixels and replace the alpha component,
72 // since our gfx device format has no alpha
73 int srcI = offset.y*image->w + offset.x;
74 for (int i = 0; i < size.x * size.y; i++, srcI++)
75 {
76 const Uint32 pixel = ((Uint32 *)image->pixels)[srcI];
77 color_t c;
78 SDL_GetRGBA(pixel, image->format, &c.r, &c.g, &c.b, &c.a);
79 // If completely transparent, replace rgb with black (0) too
80 // This is because transparency blitting checks entire pixel
81 if (c.a == 0)
82 {
83 p->Data[i] = 0;
84 }
85 else
86 {
87 p->Data[i] = COLOR2PIXEL(c);
88 }
89 if ((i + 1) % size.x == 0)
90 {
91 srcI += image->w - size.x;
92 }
93 }
94
95 if (!PicTryMakeTex(p))
96 {
97 goto bail;
98 }
99 return;
100
101 bail:
102 PicFree(p);
103 }
PicTryMakeTex(Pic * p)104 bool PicTryMakeTex(Pic *p)
105 {
106 CASSERT(!PicIsNone(p), "cannot make tex of none pic");
107 if (textureDebugger == NULL)
108 {
109 textureDebugger = hashmap_new();
110 }
111 if (p->Tex != NULL)
112 {
113 LOG(LM_GFX, LL_TRACE, "destroying texture %p data(%p)", p->Tex, p->Data);
114 SDL_DestroyTexture(p->Tex);
115 if (LL_TRACE >= LogModuleGetLevel(LM_GFX))
116 {
117 char key[32];
118 sprintf(key, "%p", p->Tex);
119 if (hashmap_get(textureDebugger, key, NULL) == MAP_OK)
120 {
121 if (hashmap_remove(textureDebugger, key) != MAP_OK)
122 {
123 LOG(LM_GFX, LL_TRACE, "Error: cannot remove tex from debugger");
124 }
125 else
126 {
127 LOG(LM_GFX, LL_TRACE, "Texture count: %d",
128 hashmap_length(textureDebugger));
129 }
130 }
131 else
132 {
133 LOG(LM_GFX, LL_TRACE, "Error: destroying unknown texture");
134 }
135 }
136 }
137 p->Tex = TextureCreate(
138 gGraphicsDevice.gameWindow.renderer, SDL_TEXTUREACCESS_STATIC,
139 p->size, SDL_BLENDMODE_NONE, 255);
140 if (p->Tex == NULL)
141 {
142 LOG(LM_GFX, LL_ERROR, "cannot create texture: %s", SDL_GetError());
143 return false;
144 }
145 if (SDL_UpdateTexture(
146 p->Tex, NULL, p->Data, p->size.x * sizeof(Uint32)) != 0)
147 {
148 LOG(LM_GFX, LL_ERROR, "cannot update texture: %s", SDL_GetError());
149 return false;
150 }
151 if (SDL_SetTextureBlendMode(p->Tex, SDL_BLENDMODE_BLEND) != 0)
152 {
153 LOG(LM_GFX, LL_ERROR, "cannot set texture blend mode: %s",
154 SDL_GetError());
155 return false;
156 }
157 LOG(LM_GFX, LL_TRACE, "made texture %p data(%p) count(%d)",
158 p->Tex, p->Data, hashmap_length(textureDebugger));
159 if (LL_TRACE >= LogModuleGetLevel(LM_GFX))
160 {
161 char key[32];
162 sprintf(key, "%p", p->Tex);
163 if (hashmap_get(textureDebugger, key, NULL) != MAP_MISSING)
164 {
165 LOG(LM_GFX, LL_TRACE, "Error: repeated texture loc");
166 }
167 if (hashmap_put(textureDebugger, key, (any_t)0) != MAP_OK)
168 {
169 LOG(LM_GFX, LL_TRACE, "Error: cannot add texture to debugger");
170 }
171 }
172 return true;
173 }
174
175 // Note: does not copy the texture
PicCopy(const Pic * src)176 Pic PicCopy(const Pic *src)
177 {
178 Pic p = *src;
179 const size_t size = p.size.x * p.size.y * sizeof *p.Data;
180 CMALLOC(p.Data, size);
181 memcpy(p.Data, src->Data, size);
182 p.Tex = NULL;
183 return p;
184 }
185
PicFree(Pic * pic)186 void PicFree(Pic *pic)
187 {
188 pic->size = svec2i_zero();
189 if (pic->Tex != NULL)
190 {
191 LOG(LM_GFX, LL_TRACE, "freeing texture %p data(%p)", pic->Tex, pic->Data);
192 SDL_DestroyTexture(pic->Tex);
193 if (LL_TRACE >= LogModuleGetLevel(LM_GFX))
194 {
195 char key[32];
196 sprintf(key, "%p", pic->Tex);
197 if (hashmap_get(textureDebugger, key, NULL) == MAP_OK)
198 {
199 if (hashmap_remove(textureDebugger, key) != MAP_OK)
200 {
201 LOG(LM_GFX, LL_TRACE, "Error: cannot remove tex from debugger");
202 }
203 else
204 {
205 LOG(LM_GFX, LL_TRACE, "Texture count: %d",
206 hashmap_length(textureDebugger));
207 }
208 }
209 else
210 {
211 LOG(LM_GFX, LL_TRACE, "Error: destroying unknown texture");
212 }
213 }
214 }
215 CFREE(pic->Data);
216 pic->Data = NULL;
217 }
218
PicIsNone(const Pic * pic)219 bool PicIsNone(const Pic *pic)
220 {
221 return pic->size.x == 0 || pic->size.y == 0 || pic->Data == NULL;
222 }
223
PicTrim(Pic * pic,const bool xTrim,const bool yTrim)224 void PicTrim(Pic *pic, const bool xTrim, const bool yTrim)
225 {
226 // Scan all pixels looking for the min/max of x and y
227 struct vec2i min = pic->size;
228 struct vec2i max = svec2i_zero();
229 for (struct vec2i pos = svec2i_zero(); pos.y < pic->size.y; pos.y++)
230 {
231 for (pos.x = 0; pos.x < pic->size.x; pos.x++)
232 {
233 const Uint32 pixel = *(pic->Data + pos.x + pos.y * pic->size.x);
234 if (pixel > 0)
235 {
236 min.x = MIN(min.x, pos.x);
237 min.y = MIN(min.y, pos.y);
238 max.x = MAX(max.x, pos.x);
239 max.y = MAX(max.y, pos.y);
240 }
241 }
242 }
243 // If no opaque pixels found, don't trim
244 struct vec2i newSize = pic->size;
245 struct vec2i offset = svec2i_zero();
246 if (min.x < max.x && min.y < max.y)
247 {
248 if (xTrim)
249 {
250 newSize.x = max.x - min.x + 1;
251 offset.x = min.x;
252 }
253 if (yTrim)
254 {
255 newSize.y = max.y - min.y + 1;
256 offset.y = min.y;
257 }
258 }
259 PicShrink(pic, newSize, offset);
260 }
PicShrink(Pic * pic,const struct vec2i size,const struct vec2i offset)261 void PicShrink(Pic *pic, const struct vec2i size, const struct vec2i offset)
262 {
263 // Trim by copying pixels
264 Uint32 *newData;
265 CMALLOC(newData, size.x * size.y * sizeof *newData);
266 if (newData == NULL)
267 {
268 return;
269 }
270 for (struct vec2i pos = svec2i_zero(); pos.y < size.y; pos.y++)
271 {
272 for (pos.x = 0; pos.x < size.x; pos.x++)
273 {
274 Uint32 *target = newData + pos.x + pos.y * size.x;
275 const int srcIdx =
276 pos.x + offset.x + (pos.y + offset.y) * pic->size.x;
277 *target = *(pic->Data + srcIdx);
278 }
279 }
280 // Replace the old data
281 CFREE(pic->Data);
282 pic->Data = newData;
283 pic->size = size;
284 pic->offset = svec2i_zero();
285 PicTryMakeTex(pic);
286 }
287
PicPxIsEdge(const Pic * pic,const struct vec2i pos,const bool isPixel)288 bool PicPxIsEdge(const Pic *pic, const struct vec2i pos, const bool isPixel)
289 {
290 const bool isTopOrBottomEdge = pos.y == -1 || pos.y == pic->size.y;
291 const bool isLeftOrRightEdge = pos.x == -1 || pos.x == pic->size.x;
292 const bool isLeft =
293 pos.x > 0 && !isTopOrBottomEdge &&
294 PIXEL2COLOR(*(pic->Data + pos.x - 1 + pos.y * pic->size.x)).a;
295 const bool isRight =
296 pos.x < pic->size.x - 1 && !isTopOrBottomEdge &&
297 PIXEL2COLOR(*(pic->Data + pos.x + 1 + pos.y * pic->size.x)).a;
298 const bool isAbove =
299 pos.y > 0 && !isLeftOrRightEdge &&
300 PIXEL2COLOR(*(pic->Data + pos.x + (pos.y - 1) * pic->size.x)).a;
301 const bool isBelow =
302 pos.y < pic->size.y - 1 && !isLeftOrRightEdge &&
303 PIXEL2COLOR(*(pic->Data + pos.x + (pos.y + 1) * pic->size.x)).a;
304 if (isPixel)
305 {
306 return !(isLeft && isRight && isAbove && isBelow);
307 }
308 else
309 {
310 return isLeft || isRight || isAbove || isBelow;
311 }
312 }
PicGetRandomColor(const Pic * p)313 color_t PicGetRandomColor(const Pic *p)
314 {
315 // Get a random non-transparent pixel from the pic
316 for (;;)
317 {
318 const uint32_t px = p->Data[rand() % (p->size.x * p->size.y)];
319 const color_t c = PIXEL2COLOR(px);
320 if (c.a > 0)
321 {
322 return c;
323 }
324 }
325 }
326
PicRender(const Pic * p,SDL_Renderer * r,const struct vec2i pos,const color_t mask,const double radians,const struct vec2 scale,const SDL_RendererFlip flip,const Rect2i srcRect)327 void PicRender(
328 const Pic *p, SDL_Renderer *r, const struct vec2i pos, const color_t mask,
329 const double radians, const struct vec2 scale, const SDL_RendererFlip flip,
330 const Rect2i srcRect)
331 {
332 Rect2i src = Rect2iNew(
333 svec2i_max(srcRect.Pos, svec2i_zero()), svec2i_zero()
334 );
335 src.Size = svec2i_is_zero(srcRect.Size) ? p->size :
336 svec2i_min(svec2i_subtract(srcRect.Size, src.Pos), p->size);
337 Rect2i dest = Rect2iNew(pos, src.Size);
338 // Apply scale to render dest
339 if (!svec2_is_equal(scale, svec2_one()))
340 {
341 dest.Pos.x -= (mint_t)MROUND((scale.x - 1) * src.Size.x / 2);
342 dest.Pos.y -= (mint_t)MROUND((scale.y - 1) * src.Size.y / 2);
343 dest.Size.x = (mint_t)MROUND(src.Size.x * scale.x);
344 dest.Size.y = (mint_t)MROUND(src.Size.y * scale.y);
345 }
346 const double angle = ToDegrees(radians);
347 TextureRender(p->Tex, r, src, dest, mask, angle, flip);
348 }
349