1 #include "skippy.h"
2 
3 typedef struct {
4 	XRectangle rect;
5 	int num_x, num_y;
6 	double ratio_x, ratio_y;
7 } img_composite_params_t;
8 
9 #define XRECTANGLE_INIT { .x = 0, .y = 0, .width = 0, .height = 0 }
10 
11 #define IMG_COMPOSITE_PARAMS_INIT { \
12 	.rect = XRECTANGLE_INIT, \
13 	.num_x = 0, \
14 	.num_y = 0, \
15 	.ratio_x = 0.0f, \
16 	.ratio_y = 0.0f, \
17 }
18 
19 /**
20  * @brief Crop a rectangle by another rectangle.
21  */
22 static inline void
rect_crop(XRectangle * pdst,const XRectangle * psrc,const XRectangle * pbound)23 rect_crop(XRectangle *pdst, const XRectangle *psrc, const XRectangle *pbound) {
24 	XRectangle *pdst_original = NULL;
25 	XRectangle rtmp;
26 	if (psrc == pdst) {
27 		pdst_original = pdst;
28 		pdst = &rtmp;
29 	}
30 	assert(psrc != pdst);
31 	pdst->x = MAX(psrc->x, pbound->x);
32 	pdst->y = MAX(psrc->y, pbound->y);
33 	pdst->width = MAX(0,
34 			MIN(psrc->x + psrc->width, pbound->x + pbound->width) - pdst->x);
35 	pdst->height = MAX(0,
36 			MIN(psrc->y + psrc->height, pbound->y + pbound->height) - pdst->y);
37 	if (pdst_original)
38 		*pdst_original = *pdst;
39 }
40 
41 /**
42  * @brief Crop a rectangle by another rectangle, and adjust texture
43  *        start coordinate.
44  *
45  * ptex_x and ptex_y must point to initialized values.
46  */
47 static inline void
rect_crop2(XRectangle * pdst,const XRectangle * psrc,const XRectangle * pbound,int * ptex_x,int * ptex_y,double ratio_x,double ratio_y)48 rect_crop2(XRectangle *pdst, const XRectangle *psrc, const XRectangle *pbound,
49 		int *ptex_x, int *ptex_y, double ratio_x, double ratio_y) {
50 	int original_x = psrc->x, original_y = psrc->y;
51 	rect_crop(pdst, psrc, pbound);
52 	*ptex_x += (pdst->x - original_x) / ratio_x;
53 	*ptex_y += (pdst->y - original_y) / ratio_y;
54 }
55 
56 /**
57  * @brief Get length of 1 pixel of data for the specified depth.
58  */
59 static inline int
depth_to_len(int depth)60 depth_to_len(int depth) {
61 	int l = 8;
62 	while (depth > l)
63 		l *= 2;
64 	return l / 8;
65 }
66 
67 /**
68  * @brief Get X Render format for a specified depth.
69  */
70 static inline const XRenderPictFormat *
depth_to_rfmt(session_t * ps,int depth)71 depth_to_rfmt(session_t *ps, int depth) {
72 	switch (depth) {
73 		case 32: return XRenderFindStandardFormat(ps->dpy, PictStandardARGB32);
74 		case 24: return XRenderFindStandardFormat(ps->dpy, PictStandardRGB24);
75 		case 8: return XRenderFindStandardFormat(ps->dpy, PictStandardA8);
76 		case 4: return XRenderFindStandardFormat(ps->dpy, PictStandardA4);
77 		case 1: return XRenderFindStandardFormat(ps->dpy, PictStandardA1);
78 	};
79 	assert(0);
80 	return NULL;
81 }
82 
83 static inline void
free_pictw_keeppixmap(session_t * ps,pictw_t ** ppictw)84 free_pictw_keeppixmap(session_t *ps, pictw_t **ppictw) {
85 	if (*ppictw) {
86 		free_picture(ps, &(*ppictw)->pict);
87 		free(*ppictw);
88 	}
89 	*ppictw = NULL;
90 }
91 
92 static inline void
free_pictw(session_t * ps,pictw_t ** ppictw)93 free_pictw(session_t *ps, pictw_t **ppictw) {
94 	if (*ppictw) {
95 		free_pixmap(ps, &(*ppictw)->pxmap);
96 		free_pictw_keeppixmap(ps, ppictw);
97 	}
98 }
99 
100 /**
101  * @brief Destroy a <code>pictspec_t</code>.
102  */
103 static inline void
free_pictspec(session_t * ps,pictspec_t * p)104 free_pictspec(session_t *ps, pictspec_t *p) {
105 	free_pictw(ps, &p->img);
106 	free(p->path);
107 }
108 
109 /**
110  * @brief Build a pictw_t of specified size and depth.
111  */
112 static inline pictw_t *
create_pictw_frompixmap(session_t * ps,int width,int height,int depth,Pixmap pxmap)113 create_pictw_frompixmap(session_t *ps, int width, int height, int depth,
114 		Pixmap pxmap) {
115 	pictw_t *pictw = NULL;
116 
117 	if (!pxmap) {
118 		printfef("(%d, %d, %d, %#010lx): Missing pixmap.", width, height, depth, pxmap);
119 		return NULL;
120 	}
121 
122 	// Acquire Pixmap info
123 	if (!(width && height && depth)) {
124 		Window rroot = None;
125 		int rx = 0, ry = 0;
126 		unsigned rwidth = 0, rheight = 0, rborder_width = 0, rdepth = 0;
127 		if (!XGetGeometry(ps->dpy, pxmap,
128 				&rroot, &rx, &ry, &rwidth, &rheight, &rborder_width, &rdepth)) {
129 			printfef("(%d, %d, %d, %#010lx): Failed to determine pixmap size.", width, height, depth, pxmap);
130 			return NULL;
131 		}
132 		width = rwidth;
133 		height = rheight;
134 		depth = rdepth;
135 	}
136 
137 	// Sanity check
138 	if (!(width && height && depth)) {
139 		printfef("(%d, %d, %d, %#010lx): Failed to get pixmap info.", width, height, depth, pxmap);
140 		return NULL;
141 	}
142 
143 	// Find X Render format
144 	const XRenderPictFormat *rfmt = depth_to_rfmt(ps, depth);
145 	if (!rfmt) {
146 		printfef("(%d, %d, %d, %#010lx): Failed to find X Render format for depth %d.",
147 				width, height, depth, pxmap, depth);
148 		return NULL;
149 	}
150 
151 	// Create Picture
152 	pictw = scalloc(1, pictw_t);
153 	pictw->pxmap = pxmap;
154 	pictw->width = width;
155 	pictw->height = height;
156 	pictw->depth = depth;
157 
158 	if (!(pictw->pict = XRenderCreatePicture(ps->dpy, pictw->pxmap,
159 					rfmt, 0, NULL))) {
160 		printfef("(%d, %d, %d, %#010lx): Failed to create Picture.",
161 				width, height, depth, pxmap);
162 		free_pictw(ps, &pictw);
163 	}
164 
165 	return pictw;
166 }
167 
168 /**
169  * @brief Build a pictw_t of specified size and depth.
170  */
171 static inline pictw_t *
create_pictw(session_t * ps,int width,int height,int depth)172 create_pictw(session_t *ps, int width, int height, int depth) {
173 	Pixmap pxmap = XCreatePixmap(ps->dpy, ps->root, width, height, depth);
174 	if (!pxmap) {
175 		printfef("(%d, %d, %d): Failed to create Pixmap.", width, height, depth);
176 		return NULL;
177 	}
178 
179 	return create_pictw_frompixmap(ps, width, height, depth, pxmap);
180 }
181 
182 static inline pictw_t *
clone_pictw(session_t * ps,pictw_t * pictw)183 clone_pictw(session_t *ps, pictw_t *pictw) {
184 	if (!pictw) return NULL;
185 	pictw_t *new_pictw = create_pictw(ps, pictw->width, pictw->height, pictw->depth);
186 	if (!new_pictw) return NULL;
187 	XRenderComposite(ps->dpy, PictOpSrc, pictw->pict, None, new_pictw->pict,
188 			0, 0, 0, 0, 0, 0, new_pictw->width, new_pictw->height);
189 
190 	return new_pictw;
191 }
192 
193 pictw_t *
194 simg_load(session_t *ps, const char *path, enum pict_posp_mode mode,
195 		int twidth, int theight, enum align alg, enum align valg,
196 		const XRenderColor *pc);
197 
198 static inline pictw_t *
simg_load_s(session_t * ps,const pictspec_t * spec)199 simg_load_s(session_t *ps, const pictspec_t *spec) {
200 	return simg_load(ps, spec->path, spec->mode, spec->twidth, spec->theight,
201 			spec->alg, spec->valg, &spec->c);
202 }
203 
204 static inline bool
simg_cachespec(session_t * ps,pictspec_t * spec)205 simg_cachespec(session_t *ps, pictspec_t *spec) {
206 	free_pictw(ps, &spec->img);
207 	if (spec->path
208 			&& !(spec->img = simg_load(ps, spec->path, PICTPOSP_ORIG, 0, 0,
209 					ALIGN_MID, ALIGN_MID, NULL)))
210 		return false;
211 	return true;
212 }
213 
214 pictw_t *
215 simg_postprocess(session_t *ps, pictw_t *src, enum pict_posp_mode mode,
216 		int twidth, int theight, enum align alg, enum align valg,
217 		const XRenderColor *pc);
218 
219 void
220 simg_get_composite_params(pictw_t *src, int twidth, int theight,
221 		enum pict_posp_mode mode, enum align alg, enum align valg,
222 		img_composite_params_t *pparams);
223 
224 void
225 simg_composite(session_t *ps, pictw_t *src, Picture dest,
226 		int twidth, int theight, const img_composite_params_t *pparams,
227 		const XRenderColor *pc, Picture mask, const XRectangle *pbound);
228 
229 /**
230  * @brief XRenderFillRectangle() with area cropped by a rectangle.
231  */
232 static inline void
XRenderFillRectangle_cropped(Display * dpy,int op,Picture dst,const XRenderColor * color,int x,int y,unsigned int width,unsigned int height,const XRectangle * pbound)233 XRenderFillRectangle_cropped(Display *dpy, int op, Picture dst,
234 		const XRenderColor *color,
235 		int x, int y, unsigned int width, unsigned int height,
236 		const XRectangle *pbound) {
237 	XRectangle r = { .x = x, .y = y, .width = width, .height = height };
238 	if (pbound)
239 		rect_crop(&r, &r, pbound);
240 	XRenderFillRectangle(dpy, op, dst, color,
241 			r.x, r.y, r.width, r.height);
242 }
243 
244 /**
245  * @brief XRenderComposite() with area cropped by a rectangle.
246  */
247 static inline void
XRenderComposite_cropped(Display * dpy,int op,Picture src,Picture mask,Picture dst,int src_x,int src_y,int mask_x,int mask_y,int dst_x,int dst_y,unsigned int width,unsigned int height,const XRectangle * pbound,double ratio_x,double ratio_y)248 XRenderComposite_cropped(Display *dpy, int op, Picture src, Picture mask,
249 		Picture dst, int src_x, int src_y, int mask_x, int mask_y,
250 		int dst_x, int dst_y, unsigned int width, unsigned int height,
251 		const XRectangle *pbound, double ratio_x, double ratio_y) {
252 	XRectangle r = { .x = dst_x, .y = dst_y, .width = width, .height = height };
253 	if (pbound)
254 		rect_crop2(&r, &r, pbound, &src_x, &src_y, ratio_x, ratio_y);
255 	XRenderComposite(dpy, op, src, mask, dst, src_x, src_y,
256 			mask_x, mask_y, r.x, r.y, r.width, r.height);
257 }
258 
259 static inline pictw_t *
simg_pixmap_to_pictw(session_t * ps,int width,int height,int depth,Pixmap pxmap,Pixmap mask)260 simg_pixmap_to_pictw(session_t *ps, int width, int height, int depth,
261 		Pixmap pxmap, Pixmap mask) {
262 	GC gc = None;
263 	pictw_t *porig = create_pictw_frompixmap(ps, width, height, depth, pxmap);
264 	pictw_t *pmask = NULL;
265 	pictw_t *pictw = NULL;
266 
267 	if (!porig) {
268 		printfef("(%d, %d, %d, %#010lx, %#010lx): Failed to create picture for pixmap.",
269 				width, height, depth, pxmap, mask);
270 		goto simg_pixmap_to_pict_end;
271 	}
272 
273 	if (mask) {
274 		if (!(pmask = create_pictw_frompixmap(ps, width, height, depth, mask))) {
275 			printfef("(%d, %d, %d, %#010lx, %#010lx): Failed to create picture for mask.",
276 					width, height, depth, pxmap, mask);
277 			goto simg_pixmap_to_pict_end;
278 		}
279 		// Probably we should check for width/height consistency between pixmap
280 		// and mask...
281 	}
282 
283 	if (!(pictw = create_pictw(ps, porig->width, porig->height, (mask ? 32: porig->depth)))) {
284 		printfef("(%d, %d, %d, %#010lx, %#010lx): Failed to create target picture.",
285 				width, height, depth, pxmap, mask);
286 		goto simg_pixmap_to_pict_end;
287 	}
288 
289 	// Copy content
290 	static const XRenderColor XRC_TRANS = {
291 		.red = 0, .green = 0, .blue = 0, .alpha = 0
292 	};
293 	XRenderFillRectangle(ps->dpy, PictOpSrc, pictw->pict, &XRC_TRANS,
294 			0, 0, pictw->width, pictw->height);
295 	XRenderComposite(ps->dpy, PictOpSrc, porig->pict, (pmask ? pmask->pict: None),
296 			pictw->pict, 0, 0, 0, 0, 0, 0, pictw->width, pictw->height);
297 
298 	// Does core Xlib handle transparency correctly?
299 	/*
300 	gc = XCreateGC(ps->dpy, pictw->pxmap, 0, 0);
301 	if (!gc) {
302 		printfef("(%#010lx, %#010lx, %d, %d, %d): Failed to create GC.",
303 				pxmap, mask, width, height, depth);
304 		free_pictw(ps, &pictw);
305 		goto simg_data_to_pict_end;
306 	}
307 	if (XCopyArea(ps->dpy, pxmap, pictw->pxmap, gc, 0, 0, width, height, 0, 0)) {
308 	}
309 	*/
310 
311 simg_pixmap_to_pict_end:
312 	free_pictw_keeppixmap(ps, &porig);
313 	free_pictw_keeppixmap(ps, &pmask);
314 	if (gc)
315 		XFreeGC(ps->dpy, gc);
316 
317 	return pictw;
318 }
319 
320 static inline pictw_t *
simg_data_to_pictw(session_t * ps,int width,int height,int depth,const unsigned char * data,int bytes_per_line)321 simg_data_to_pictw(session_t *ps, int width, int height, int depth,
322 		const unsigned char *data, int bytes_per_line) {
323 	assert(data);
324 	data = mmemcpy(data, height
325 			* (bytes_per_line ? bytes_per_line: depth_to_len(depth) * width));
326 	pictw_t *pictw = NULL;
327 	GC gc = None;
328 
329 	// Use ARGB visual if needed
330 	Visual *visual = DefaultVisual(ps->dpy, ps->screen);
331 	if (32 == depth && ps->argb_visual)
332 		visual = ps->argb_visual;
333 	XImage *img = XCreateImage(ps->dpy, visual,
334 			depth, ZPixmap, 0, (char *) data, width, height,
335 			8, bytes_per_line);
336 	if (!img) {
337 		printfef("(%d, %d, %d): Failed to create XImage.",
338 				width, height, depth);
339 		goto simg_data_to_pict_end;
340 	}
341 	if (!(pictw = create_pictw(ps, width, height, depth))) {
342 		printfef("(%d, %d, %d): Failed to create Picture.",
343 				width, height, depth);
344 		goto simg_data_to_pict_end;
345 	}
346 	gc = XCreateGC(ps->dpy, pictw->pxmap, 0, 0);
347 	if (!gc) {
348 		printfef("(%d, %d, %d): Failed to create GC.",
349 				width, height, depth);
350 		free_pictw(ps, &pictw);
351 		goto simg_data_to_pict_end;
352 	}
353 	XPutImage(ps->dpy, pictw->pxmap, gc, img, 0, 0, 0, 0, width, height);
354 
355 simg_data_to_pict_end:
356 	if (img)
357 		XDestroyImage(img);
358 	if (gc)
359 		XFreeGC(ps->dpy, gc);
360 
361 	return pictw;
362 }
363 
364 static inline void
simg_data24_premultiply(unsigned char * data,int len)365 simg_data24_premultiply(unsigned char *data, int len) {
366 	for (; len > 0; len--, data += 4) {
367 		const unsigned char alpha = data[3];
368 		if (0xff == alpha || !alpha) continue;
369 		// This is slightly inaccurate because 1.0 == 255 instead of 256 (2 ** 8).
370 		// Of course there are "smarter" ways to perform 3 multiplications all at
371 		// once, but I don't feel like writing endian-specific code.
372 		data[0] = ((uint_fast16_t) data[0] * alpha) >> 8;
373 		data[1] = ((uint_fast16_t) data[1] * alpha) >> 8;
374 		data[2] = ((uint_fast16_t) data[2] * alpha) >> 8;
375 	}
376 }
377 
378 static inline void
simg_data24_fillalpha(unsigned char * data,int len)379 simg_data24_fillalpha(unsigned char *data, int len) {
380 	for (--len; len >= 0; --len) {
381 		if (len) {
382 			data[len * 4 + 0] = data[len * 3 + 0];
383 			data[len * 4 + 1] = data[len * 3 + 1];
384 			data[len * 4 + 2] = data[len * 3 + 2];
385 		}
386 		data[len * 4 + 3] = 0xff;
387 	}
388 }
389 
390 static inline void
simg_data24_tobgr(unsigned char * data,int len)391 simg_data24_tobgr(unsigned char *data, int len) {
392 	for (--len; len >= 0; --len) {
393 		unsigned char r = data[len * 4];
394 		data[len * 4 + 0] = data[len * 4 + 2];
395 		data[len * 4 + 2] = r;
396 	}
397 }
398 
399 static inline unsigned char *
simg_data32_from_long(const long * src,int len)400 simg_data32_from_long(const long *src, int len) {
401 	if (4 == sizeof(long))
402 		return (unsigned char *) src;
403 
404 	uint32_t *data = smalloc(len, uint32_t);
405 	for (int i = 0; i < len; ++i)
406 		data[i] = src[i];
407 	return (unsigned char *) data;
408 }
409 
410 static inline void
simg_data32_premultiply(unsigned char * data,int len)411 simg_data32_premultiply(unsigned char *data, int len) {
412 	for (--len; len >= 0; --len) {
413 		float a = (float) data[len * 4 + 3] / 0xff;
414 		data[len * 4 + 0] *= a;
415 		data[len * 4 + 1] *= a;
416 		data[len * 4 + 2] *= a;
417 	}
418 }
419