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