From http://www.jwz.org/xscreensaver/xscreensaver-5.40.tar.gz
[xscreensaver] / jwxyz / jwxyz-image.c
1 /* xscreensaver, Copyright (c) 1991-2018 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /* JWXYZ Is Not Xlib.
13
14    But it's a bunch of function definitions that bear some resemblance to
15    Xlib and that do things to an XImage that bear some resemblance to the
16    things that Xlib might have done.
17
18    This handles things when jwxyz-gl.c can't.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #ifdef JWXYZ_IMAGE /* entire file */
26
27 #include "jwxyzI.h"
28 #include "jwxyz.h"
29 #include "jwxyz-timers.h"
30 #include "pow2.h"
31
32 #include <wchar.h>
33
34
35 union color_bytes { // Hello, again.
36   uint32_t pixel;
37   uint8_t bytes[4];
38 };
39
40 struct jwxyz_Display {
41   const struct jwxyz_vtbl *vtbl; // Must come first.
42
43   Window main_window;
44   Visual visual;
45   struct jwxyz_sources_data *timers_data;
46
47   unsigned long window_background;
48 };
49
50 struct jwxyz_GC {
51   XGCValues gcv;
52   unsigned int depth;
53 };
54
55
56 extern const struct jwxyz_vtbl image_vtbl;
57
58 Display *
59 jwxyz_image_make_display (Window w, const unsigned char *rgba_bytes)
60 {
61   Display *d = (Display *) calloc (1, sizeof(*d));
62   d->vtbl = &image_vtbl;
63
64   Visual *v = &d->visual;
65   v->class      = TrueColor;
66   Assert (rgba_bytes[3] == 3, "alpha not last");
67   unsigned long masks[4];
68   for (unsigned i = 0; i != 4; ++i) {
69     union color_bytes color;
70     color.pixel = 0;
71     color.bytes[rgba_bytes[i]] = 0xff;
72     masks[i] = color.pixel;
73   }
74   v->red_mask   = masks[0];
75   v->green_mask = masks[1];
76   v->blue_mask  = masks[2];
77   v->alpha_mask = masks[3];
78
79   d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d));
80   d->window_background = BlackPixel(d,0);
81   d->main_window = w;
82
83   return d;
84 }
85
86 void
87 jwxyz_image_free_display (Display *dpy)
88 {
89   jwxyz_sources_free (dpy->timers_data);
90
91   free (dpy);
92 }
93
94
95 static jwxyz_sources_data *
96 display_sources_data (Display *dpy)
97 {
98   return dpy->timers_data;
99 }
100
101
102 static Window
103 root (Display *dpy)
104 {
105   return dpy->main_window;
106 }
107
108 static Visual *
109 visual (Display *dpy)
110 {
111   return &dpy->visual;
112 }
113
114
115 static void
116 next_point(short *v, XPoint p, int mode)
117 {
118   switch (mode) {
119     case CoordModeOrigin:
120       v[0] = p.x;
121       v[1] = p.y;
122       break;
123     case CoordModePrevious:
124       v[0] += p.x;
125       v[1] += p.y;
126       break;
127     default:
128       Assert (False, "next_point: bad mode");
129       break;
130   }
131 }
132
133 #define SEEK_DRAWABLE(d, x, y) \
134   SEEK_XY (jwxyz_image_data(d), jwxyz_image_pitch(d), x, y)
135
136 static int
137 DrawPoints (Display *dpy, Drawable d, GC gc,
138             XPoint *points, int count, int mode)
139 {
140   Assert (gc->gcv.function == GXcopy, "XDrawPoints: bad GC function");
141
142   const XRectangle *frame = jwxyz_frame (d);
143   short v[2] = {0, 0};
144   for (unsigned i = 0; i < count; i++) {
145     next_point(v, points[i], mode);
146     if (v[0] >= 0 && v[0] < frame->width &&
147         v[1] >= 0 && v[1] < frame->height)
148       *SEEK_DRAWABLE(d, v[0], v[1]) = gc->gcv.foreground;
149   }
150
151   return 0;
152 }
153
154
155 static void
156 copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
157            int src_x, int src_y, unsigned int width, unsigned int height,
158            int dst_x, int dst_y)
159 {
160   jwxyz_blit (jwxyz_image_data (src), jwxyz_image_pitch (src), src_x, src_y, 
161               jwxyz_image_data (dst), jwxyz_image_pitch (dst), dst_x, dst_y, 
162               width, height);
163 }
164
165
166 static void
167 draw_line (Drawable d, unsigned long pixel,
168            short x0, short y0, short x1, short y1)
169 {
170 // TODO: Assert line_Width == 1, line_stipple == solid, etc.
171
172   const XRectangle *frame = jwxyz_frame (d);
173   if (x0 < 0 || x0 >= frame->width ||
174       x1 < 0 || x1 >= frame->width ||
175       y0 < 0 || y0 >= frame->height ||
176       y1 < 0 || y1 >= frame->height) {
177     Log ("draw_line: out of bounds");
178     return;
179   }
180
181   int dx = abs(x1 - x0), dy = abs(y1 - y0);
182
183   unsigned dmod0, dmod1;
184   int dpx0, dpx1;
185   if (dx > dy) {
186     dmod0 = dy;
187     dmod1 = dx;
188     dpx0 = x1 > x0 ? 1 : -1;
189     dpx1 = y1 > y0 ? frame->width : -frame->width;
190   } else {
191     dmod0 = dx;
192     dmod1 = dy;
193     dpx0 = y1 > y0 ? frame->width : -frame->width;
194     dpx1 = x1 > x0 ? 1 : -1;
195   }
196
197   unsigned n = dmod1;
198   unsigned mod = n;
199   ++n;
200
201   dmod0 <<= 1;
202   dmod1 <<= 1;
203
204   uint32_t *px = SEEK_DRAWABLE(d, x0, y0);
205
206   for(; n; --n) {
207     *px = pixel;
208
209     mod += dmod0;
210     if(mod > dmod1) {
211       mod -= dmod1;
212       px += dpx1;
213     }
214
215     px += dpx0;
216   }
217 }
218
219 static int
220 DrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count,
221            int mode)
222 {
223   short v[2] = {0, 0}, v_prev[2] = {0, 0};
224   unsigned long pixel = gc->gcv.foreground;
225   for (unsigned i = 0; i != count; ++i) {
226     next_point(v, points[i], mode);
227     if (i)
228       draw_line (d, pixel, v_prev[0], v_prev[1], v[0], v[1]);
229     v_prev[0] = v[0];
230     v_prev[1] = v[1];
231   }
232   return 0;
233 }
234
235
236 static int
237 DrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
238 {
239   unsigned long pixel = gc->gcv.foreground;
240   for (unsigned i = 0; i != count; ++i) {
241     XSegment *seg = &segments[i];
242     draw_line (d, pixel, seg->x1, seg->y1, seg->x2, seg->y2);
243   }
244   return 0;
245 }
246
247
248 static int
249 ClearWindow (Display *dpy, Window win)
250 {
251   Assert (win == dpy->main_window, "not a window");
252   const XRectangle *wr = jwxyz_frame (win);
253   return XClearArea (dpy, win, 0, 0, wr->width, wr->height, 0);
254 }
255
256 static unsigned long *
257 window_background (Display *dpy)
258 {
259   return &dpy->window_background;
260 }
261
262 static void
263 fill_rects (Display *dpy, Drawable d, GC gc,
264             const XRectangle *rectangles, unsigned long nrectangles,
265             unsigned long pixel)
266 {
267   Assert (!gc || gc->gcv.function == GXcopy, "XDrawPoints: bad GC function");
268
269   const XRectangle *frame = jwxyz_frame (d);
270   void *image_data = jwxyz_image_data (d);
271   ptrdiff_t image_pitch = jwxyz_image_pitch (d);
272
273   for (unsigned i = 0; i != nrectangles; ++i) {
274     const XRectangle *rect = &rectangles[i];
275     unsigned x0 = rect->x >= 0 ? rect->x : 0, y0 = rect->y >= 0 ? rect->y : 0;
276     int x1 = rect->x + rect->width, y1 = rect->y + rect->height;
277     if (y1 > frame->height)
278       y1 = frame->height;
279     if (x1 > frame->width)
280       x1 = frame->width;
281     unsigned x_size = x1 - x0, y_size = y1 - y0;
282     void *dst = SEEK_XY (image_data, image_pitch, x0, y0);
283     while (y_size) {
284 # if __SIZEOF_WCHAR_T__ == 4
285       wmemset (dst, (wchar_t) pixel, x_size);
286 # else
287       for(size_t i = 0; i != x_size; ++i)
288         ((uint32_t *)dst)[i] = pixel;
289 # endif
290       --y_size;
291       dst = (char *) dst + image_pitch;
292     }
293   }
294 }
295
296
297 static int
298 FillPolygon (Display *dpy, Drawable d, GC gc,
299              XPoint *points, int npoints, int shape, int mode)
300 {
301   Log ("XFillPolygon: not implemented");
302   return 0;
303 }
304
305 static int
306 draw_arc (Display *dpy, Drawable d, GC gc, int x, int y,
307                 unsigned int width, unsigned int height,
308                 int angle1, int angle2, Bool fill_p)
309 {
310   Log ("jwxyz_draw_arc: not implemented");
311   return 0;
312 }
313
314
315 static XGCValues *
316 gc_gcv (GC gc)
317 {
318   return &gc->gcv;
319 }
320
321
322 static unsigned int
323 gc_depth (GC gc)
324 {
325   return gc->depth;
326 }
327
328
329 static GC
330 CreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv)
331 {
332   struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc));
333   gc->depth = jwxyz_drawable_depth (d);
334
335   jwxyz_gcv_defaults (dpy, &gc->gcv, gc->depth);
336   XChangeGC (dpy, gc, mask, xgcv);
337   return gc;
338 }
339
340
341 static int
342 FreeGC (Display *dpy, GC gc)
343 {
344   if (gc->gcv.font)
345     XUnloadFont (dpy, gc->gcv.font);
346
347   free (gc);
348   return 0;
349 }
350
351
352 static int
353 PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
354           int src_x, int src_y, int dest_x, int dest_y,
355           unsigned int w, unsigned int h)
356 {
357   const XRectangle *wr = jwxyz_frame (d);
358
359   Assert (gc, "no GC");
360   Assert ((w < 65535), "improbably large width");
361   Assert ((h < 65535), "improbably large height");
362   Assert ((src_x  < 65535 && src_x  > -65535), "improbably large src_x");
363   Assert ((src_y  < 65535 && src_y  > -65535), "improbably large src_y");
364   Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x");
365   Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y");
366
367   // Clip width and height to the bounds of the Drawable
368   //
369   if (dest_x + w > wr->width) {
370     if (dest_x > wr->width)
371       return 0;
372     w = wr->width - dest_x;
373   }
374   if (dest_y + h > wr->height) {
375     if (dest_y > wr->height)
376       return 0;
377     h = wr->height - dest_y;
378   }
379   if (w <= 0 || h <= 0)
380     return 0;
381
382   // Clip width and height to the bounds of the XImage
383   //
384   if (src_x + w > ximage->width) {
385     if (src_x > ximage->width)
386       return 0;
387     w = ximage->width - src_x;
388   }
389   if (src_y + h > ximage->height) {
390     if (src_y > ximage->height)
391       return 0;
392     h = ximage->height - src_y;
393   }
394   if (w <= 0 || h <= 0)
395     return 0;
396
397   /* Assert (d->win */
398
399   if (jwxyz_dumb_drawing_mode(dpy, d, gc, dest_x, dest_y, w, h))
400     return 0;
401
402   XGCValues *gcv = gc_gcv (gc);
403
404   Assert (gcv->function == GXcopy, "XPutImage: bad GC function");
405   Assert (!ximage->xoffset, "XPutImage: bad xoffset");
406
407   ptrdiff_t
408     src_pitch = ximage->bytes_per_line,
409     dst_pitch = jwxyz_image_pitch (d);
410
411   const void *src_ptr = SEEK_XY (ximage->data, src_pitch, src_x, src_y);
412   void *dst_ptr = SEEK_XY (jwxyz_image_data (d), dst_pitch, dest_x, dest_y);
413
414   if (gcv->alpha_allowed_p) {
415     Assert (ximage->depth == 32, "XPutImage: depth != 32");
416     Assert (ximage->format == ZPixmap, "XPutImage: bad format");
417     Assert (ximage->bits_per_pixel == 32, "XPutImage: bad bits_per_pixel");
418
419     const uint8_t *src_row = src_ptr;
420     uint8_t *dst_row = dst_ptr;
421
422     /* Slight loss of precision here: color values may end up being one less
423        than what they should be.
424      */
425     while (h) {
426       for (unsigned x = 0; x != w; ++x) {
427         // Pixmaps don't contain alpha. (Yay.)
428         const uint8_t *src = src_row + x * 4;
429         uint8_t *dst = dst_row + x * 4;
430
431         // ####: This is pretty SIMD friendly.
432         // Protip: Align dst (load + store), let src be unaligned (load only)
433         uint16_t alpha = src[3], alpha1 = 0xff - src[3];
434         dst[0] = (src[0] * alpha + dst[0] * alpha1) >> 8;
435         dst[1] = (src[1] * alpha + dst[1] * alpha1) >> 8;
436         dst[2] = (src[2] * alpha + dst[2] * alpha1) >> 8;
437       }
438
439       src_row += src_pitch;
440       dst_row += dst_pitch;
441       --h;
442     }
443   } else {
444     Assert (ximage->depth == 1 || ximage->depth == 32,
445             "XPutImage: depth != 1 && depth != 32");
446
447     if (ximage->depth == 32) {
448       Assert (ximage->format == ZPixmap, "XPutImage: bad format");
449       Assert (ximage->bits_per_pixel == 32, "XPutImage: bad bits_per_pixel");
450       jwxyz_blit (ximage->data, ximage->bytes_per_line, src_x, src_y,
451                   jwxyz_image_data (d), jwxyz_image_pitch (d), dest_x, dest_y,
452                   w, h);
453     } else {
454       Log ("XPutImage: depth == 1");
455     }
456   }
457
458   return 0;
459 }
460
461 static XImage *
462 GetSubImage (Display *dpy, Drawable d, int x, int y,
463              unsigned int width, unsigned int height,
464              unsigned long plane_mask, int format,
465              XImage *dest_image, int dest_x, int dest_y)
466 {
467   Assert ((width  < 65535), "improbably large width");
468   Assert ((height < 65535), "improbably large height");
469   Assert ((x < 65535 && x > -65535), "improbably large x");
470   Assert ((y < 65535 && y > -65535), "improbably large y");
471
472   Assert (dest_image->depth == 32 && jwxyz_drawable_depth (d) == 32,
473           "XGetSubImage: bad depth");
474   Assert (format == ZPixmap, "XGetSubImage: bad format");
475
476   jwxyz_blit (jwxyz_image_data (d), jwxyz_image_pitch (d), x, y,
477               dest_image->data, dest_image->bytes_per_line, dest_x, dest_y,
478               width, height);
479
480   return dest_image;
481 }
482
483
484 static int
485 SetClipMask (Display *dpy, GC gc, Pixmap m)
486 {
487   Log ("TODO: No clip masks yet"); // Slip/colorbars.c needs this.
488   return 0;
489 }
490
491 static int
492 SetClipOrigin (Display *dpy, GC gc, int x, int y)
493 {
494   gc->gcv.clip_x_origin = x;
495   gc->gcv.clip_y_origin = y;
496   return 0;
497 }
498
499
500 const struct jwxyz_vtbl image_vtbl = {
501   root,
502   visual,
503   display_sources_data,
504
505   window_background,
506   draw_arc,
507   fill_rects,
508   gc_gcv,
509   gc_depth,
510   jwxyz_draw_string,
511
512   copy_area,
513
514   DrawPoints,
515   DrawSegments,
516   CreateGC,
517   FreeGC,
518   ClearWindow,
519   SetClipMask,
520   SetClipOrigin,
521   FillPolygon,
522   DrawLines,
523   PutImage,
524   GetSubImage
525 };
526
527 #endif /* JWXYZ_IMAGE -- entire file */