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