1 /* halftone, Copyright (c) 2002 by Peter Jaric <peter@jaric.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  * Description:
12  * Draws the gravitational force in each point on the screen seen
13  * through a halftone dot pattern. The force is calculated from a set
14  * of moving mass points. View it from a distance for best effect.
15  */
16 
17 #include <math.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include "screenhack.h"
21 
22 #define DEFAULT_DELAY          10000
23 #define DEFAULT_SPACING        14
24 #define DEFAULT_SIZE_FACTOR    1.5
25 #define DEFAULT_COUNT          10
26 #define DEFAULT_MIN_MASS       0.001
27 #define DEFAULT_MAX_MASS       0.02
28 #define DEFAULT_MIN_SPEED      0.001
29 #define DEFAULT_MAX_SPEED      0.02
30 
31 
32 typedef struct
33 {
34   /* halftone dots */
35   double * dots;
36   int dots_width;
37   int dots_height;
38   int spacing;
39   int max_dot_size;
40 
41   /* Moving gravity points */
42   int gravity_point_count;
43 
44   double* gravity_point_x;
45   double* gravity_point_y;
46   double* gravity_point_mass;
47   double* gravity_point_x_inc;
48   double* gravity_point_y_inc;
49 
50   /* X stuff */
51   Display *dpy;
52   Window window;
53   GC gc;
54 
55   int ncolors;
56   XColor *colors;
57   int color0, color1;
58   int color_tick, cycle_speed;
59 
60   /* Off screen buffer */
61   Pixmap buffer;
62   GC buffer_gc;
63   int buffer_width;
64   int buffer_height;
65 
66   int delay;
67 
68 } halftone_screen;
69 
70 
update_buffer(halftone_screen * halftone,XWindowAttributes * attrs)71 static void update_buffer(halftone_screen *halftone, XWindowAttributes * attrs)
72 {
73   if (halftone->buffer_width != attrs->width ||
74       halftone->buffer_height != attrs->height)
75   {
76     XGCValues gc_values;
77 
78     if (halftone->buffer_width != -1 &&
79 	halftone->buffer_height != -1)
80     {
81       if (halftone->buffer != halftone->window)
82         XFreePixmap(halftone->dpy, halftone->buffer);
83       XFreeGC(halftone->dpy, halftone->buffer_gc);
84     }
85 
86     halftone->buffer_width = attrs->width;
87     halftone->buffer_height = attrs->height;
88 #ifdef HAVE_JWXYZ	/* Don't second-guess Quartz's double-buffering */
89     halftone->buffer = halftone->window;
90 #else
91     halftone->buffer = XCreatePixmap(halftone->dpy, halftone->window, halftone->buffer_width, halftone->buffer_height, attrs->depth);
92 #endif
93 
94     halftone->buffer_gc = XCreateGC(halftone->dpy, halftone->buffer, 0, &gc_values);
95   }
96 }
97 
update_dot_attributes(halftone_screen * halftone,XWindowAttributes * attrs)98 static void update_dot_attributes(halftone_screen *halftone, XWindowAttributes * attrs)
99 {
100   double dots_width = attrs->width / halftone->spacing + 1;
101   double dots_height = attrs->height / halftone->spacing + 1;
102 
103   if (halftone->dots == NULL ||
104       (dots_width != halftone->dots_width ||
105        dots_height != halftone->dots_height))
106   {
107     if (halftone->dots != NULL)
108       free(halftone->dots);
109 
110     halftone->dots_width = dots_width;
111     halftone->dots_height = dots_height;
112     halftone->dots = (double *) malloc(halftone->dots_width * halftone->dots_height * sizeof(double));
113   }
114 }
115 
116 static void *
halftone_init(Display * dpy,Window window)117 halftone_init (Display *dpy, Window window)
118 {
119   int x, y, i;
120   int count;
121   int spacing;
122   double factor;
123   double min_mass;
124   double max_mass;
125   double min_speed;
126   double max_speed;
127   XGCValues gc_values;
128   XWindowAttributes attrs;
129   halftone_screen *halftone;
130 
131   halftone = (halftone_screen *) calloc (1, sizeof(halftone_screen));
132 
133   halftone->dpy = dpy;
134   halftone->window = window;
135 
136   halftone->delay = get_integer_resource (dpy, "delay", "Integer");
137   halftone->delay = (halftone->delay < 0 ? DEFAULT_DELAY : halftone->delay);
138 
139   halftone->gc = XCreateGC (halftone->dpy, halftone->window, 0, &gc_values);
140 
141   halftone->buffer_width = -1;
142   halftone->buffer_height = -1;
143   halftone->dots = NULL;
144 
145   /* Read command line arguments and set all settings. */
146   count = get_integer_resource (dpy, "count", "Count");
147   halftone->gravity_point_count = count < 1 ? DEFAULT_COUNT : count;
148 
149   spacing = get_integer_resource (dpy, "spacing", "Integer");
150   halftone->spacing = spacing < 1 ? DEFAULT_SPACING : spacing;
151 
152   factor = get_float_resource (dpy, "sizeFactor", "Double");
153   halftone->max_dot_size =
154     (factor < 0 ? DEFAULT_SIZE_FACTOR : factor) * halftone->spacing;
155 
156   min_mass = get_float_resource (dpy, "minMass", "Double");
157   min_mass = min_mass < 0 ? DEFAULT_MIN_MASS : min_mass;
158 
159   max_mass = get_float_resource (dpy, "maxMass", "Double");
160   max_mass = max_mass < 0 ? DEFAULT_MAX_MASS : max_mass;
161   max_mass = max_mass < min_mass ? min_mass : max_mass;
162 
163   min_speed = get_float_resource (dpy, "minSpeed", "Double");
164   min_speed = min_speed < 0 ? DEFAULT_MIN_SPEED : min_speed;
165 
166   max_speed = get_float_resource (dpy, "maxSpeed", "Double");
167   max_speed = max_speed < 0 ? DEFAULT_MAX_SPEED : max_speed;
168   max_speed = max_speed < min_speed ? min_speed : max_speed;
169 
170 
171   /* Set up the moving gravity points. */
172   halftone->gravity_point_x = (double *) malloc(halftone->gravity_point_count * sizeof(double));
173   halftone->gravity_point_y = (double *) malloc(halftone->gravity_point_count * sizeof(double));
174   halftone->gravity_point_mass = (double *) malloc(halftone->gravity_point_count * sizeof(double));
175   halftone->gravity_point_x_inc = (double *) malloc(halftone->gravity_point_count * sizeof(double));
176   halftone->gravity_point_y_inc = (double *) malloc(halftone->gravity_point_count * sizeof(double));
177 
178   for (i = 0; i < halftone->gravity_point_count; i++)
179   {
180     halftone->gravity_point_x[i] = frand(1);
181     halftone->gravity_point_y[i] = frand(1);
182     halftone->gravity_point_mass[i] = min_mass + (max_mass - min_mass) * frand(1);
183     halftone->gravity_point_x_inc[i] = min_speed + (max_speed - min_speed) * frand(1);
184     halftone->gravity_point_y_inc[i] = min_speed + (max_speed - min_speed) * frand(1);
185   }
186 
187 
188   /* Set up the dots. */
189   XGetWindowAttributes(halftone->dpy, halftone->window, &attrs);
190 
191   halftone->ncolors = get_integer_resource (dpy, "colors", "Colors");
192   if (halftone->ncolors < 4) halftone->ncolors = 4;
193   halftone->colors = (XColor *) calloc(halftone->ncolors, sizeof(XColor));
194   make_smooth_colormap (attrs.screen, attrs.visual, attrs.colormap,
195                         halftone->colors, &halftone->ncolors,
196                         True, 0, False);
197   halftone->color0 = 0;
198   halftone->color1 = halftone->ncolors / 2;
199   halftone->cycle_speed = get_integer_resource (dpy, "cycleSpeed", "CycleSpeed");
200   halftone->color_tick = 0;
201 
202   update_buffer(halftone, &attrs);
203   update_dot_attributes(halftone, &attrs);
204 
205   for (x = 0; x < halftone->dots_width; x++)
206     for (y = 0; y < halftone->dots_height; y++)
207     {
208 	halftone->dots[x + y * halftone->dots_width] = 0;
209     }
210 
211   return halftone;
212 }
213 
214 
215 
fill_circle(Display * dpy,Window window,GC gc,int x,int y,int size)216 static void fill_circle(Display *dpy, Window window, GC gc, int x, int y, int size)
217 {
218   int start_x = x - (size / 2);
219   int start_y = y - (size / 2);
220   unsigned int width = size;
221   unsigned int height = size;
222   int angle1 = 0;
223   int angle2 = 360 * 64; /* A full circle */
224 
225   XFillArc (dpy, window, gc,
226 	    start_x, start_y, width, height,
227 	    angle1, angle2);
228 }
229 
repaint_halftone(halftone_screen * halftone)230 static void repaint_halftone(halftone_screen *halftone)
231 {
232   int x, y;
233   /*
234   int x_offset = halftone->spacing / 2;
235   int y_offset = halftone->spacing / 2;
236   */
237   int x_offset = 0;
238   int y_offset = 0;
239 
240 
241   /* Fill buffer with background color */
242   XSetForeground (halftone->dpy, halftone->buffer_gc,
243                   halftone->colors[halftone->color0].pixel);
244   XFillRectangle(halftone->dpy, halftone->buffer, halftone->buffer_gc, 0, 0, halftone->buffer_width, halftone->buffer_height);
245 
246   /* Draw dots on buffer */
247   XSetForeground (halftone->dpy, halftone->buffer_gc,
248                   halftone->colors[halftone->color1].pixel);
249 
250   if (halftone->color_tick++ >= halftone->cycle_speed)
251     {
252       halftone->color_tick = 0;
253       halftone->color0 = (halftone->color0 + 1) % halftone->ncolors;
254       halftone->color1 = (halftone->color1 + 1) % halftone->ncolors;
255     }
256 
257   for (x = 0; x < halftone->dots_width; x++)
258     for (y = 0; y < halftone->dots_height; y++)
259       fill_circle(halftone->dpy, halftone->buffer, halftone->buffer_gc,
260 		  x_offset + x * halftone->spacing, y_offset + y * halftone->spacing,
261 		  halftone->max_dot_size * halftone->dots[x + y * halftone->dots_width]);
262 
263   /* Copy buffer to window */
264   if (halftone->buffer != halftone->window)
265     XCopyArea(halftone->dpy, halftone->buffer, halftone->window, halftone->gc, 0, 0, halftone->buffer_width, halftone->buffer_height, 0, 0);
266 }
267 
calculate_gravity(halftone_screen * halftone,int x,int y)268 static double calculate_gravity(halftone_screen *halftone, int x, int y)
269 {
270   int i;
271   double gx = 0;
272   double gy = 0;
273 
274   for (i = 0; i < halftone->gravity_point_count; i++)
275   {
276     double dx = ((double) x) - halftone->gravity_point_x[i] * halftone->dots_width;
277     double dy = ((double) y) - halftone->gravity_point_y[i] * halftone->dots_height;
278     double distance = sqrt(dx * dx + dy * dy);
279 
280     if (distance != 0)
281     {
282       double gravity = halftone->gravity_point_mass[i] / (distance * distance  / (halftone->dots_width * halftone->dots_height));
283 
284       gx += (dx / distance) * gravity;
285       gy += (dy / distance) * gravity;
286     }
287   }
288 
289   return sqrt(gx * gx + gy * gy);
290 }
291 
update_halftone(halftone_screen * halftone)292 static void update_halftone(halftone_screen *halftone)
293 {
294   int x, y, i;
295   XWindowAttributes attrs;
296 
297   XGetWindowAttributes(halftone->dpy, halftone->window, &attrs);
298 
299   /* Make sure we have a valid buffer */
300   update_buffer(halftone, &attrs);
301 
302   /* Make sure all dot attributes (spacing, width, height, etc) are correct */
303   update_dot_attributes(halftone, &attrs);
304 
305   /* Move gravity points */
306   for (i = 0; i < halftone->gravity_point_count; i++)
307   {
308     halftone->gravity_point_x_inc[i] =
309       (halftone->gravity_point_x[i] >= 1 || halftone->gravity_point_x[i] <= 0 ?
310        -halftone->gravity_point_x_inc[i] :
311        halftone->gravity_point_x_inc[i]);
312     halftone->gravity_point_y_inc[i] =
313       (halftone->gravity_point_y[i] >= 1 || halftone->gravity_point_y[i] <= 0 ?
314        -halftone->gravity_point_y_inc[i] :
315        halftone->gravity_point_y_inc[i]);
316 
317     halftone->gravity_point_x[i] += halftone->gravity_point_x_inc[i];
318     halftone->gravity_point_y[i] += halftone->gravity_point_y_inc[i];
319   }
320 
321   /* Update gravity in each dot .*/
322   for (x = 0; x < halftone->dots_width; x++)
323     for (y = 0; y < halftone->dots_height; y++)
324     {
325       double gravity = calculate_gravity(halftone, x, y);
326 
327       halftone->dots[x + y * halftone->dots_width] = (gravity > 1 ? 1 : (gravity < 0 ? 0 : gravity));
328     }
329 }
330 
331 
332 static unsigned long
halftone_draw(Display * dpy,Window window,void * closure)333 halftone_draw (Display *dpy, Window window, void *closure)
334 {
335   halftone_screen *halftone = (halftone_screen *) closure;
336 
337   repaint_halftone(halftone);
338   update_halftone(halftone);
339 
340   return halftone->delay;
341 }
342 
343 
344 static void
halftone_reshape(Display * dpy,Window window,void * closure,unsigned int w,unsigned int h)345 halftone_reshape (Display *dpy, Window window, void *closure,
346                  unsigned int w, unsigned int h)
347 {
348 }
349 
350 static Bool
halftone_event(Display * dpy,Window window,void * closure,XEvent * event)351 halftone_event (Display *dpy, Window window, void *closure, XEvent *event)
352 {
353   return False;
354 }
355 
356 static void
halftone_free(Display * dpy,Window window,void * closure)357 halftone_free (Display *dpy, Window window, void *closure)
358 {
359   halftone_screen *halftone = (halftone_screen *) closure;
360   free (halftone->gravity_point_x);
361   free (halftone->gravity_point_y);
362   free (halftone->gravity_point_mass);
363   free (halftone->gravity_point_x_inc);
364   free (halftone->gravity_point_y_inc);
365   free (halftone->dots);
366   free (halftone->colors);
367   if (halftone->buffer && halftone->buffer != halftone->window)
368     XFreePixmap (dpy, halftone->buffer);
369   if (halftone->gc) XFreeGC (dpy, halftone->gc);
370   if (halftone->buffer_gc) XFreeGC (dpy, halftone->buffer_gc);
371   free (halftone);
372 }
373 
374 
375 static const char *halftone_defaults [] = {
376   ".background:		Black",
377   "*delay:		10000",
378   "*count:		10",
379   "*minMass:		0.001",
380   "*maxMass:		0.02",
381   "*minSpeed:		0.001",
382   "*maxSpeed:		0.02",
383   "*spacing:		14",
384   "*sizeFactor:		1.5",
385   "*colors:		200",
386   "*cycleSpeed:		10",
387 #ifdef HAVE_MOBILE
388   "*ignoreRotation:     True",
389 #endif
390   ".lowrez:		true",  /* Too slow on Retina screens otherwise */
391   0
392 };
393 
394 static XrmOptionDescRec halftone_options [] = {
395   { "-delay",		".delay",	XrmoptionSepArg, 0 },
396   { "-count",		".count",	XrmoptionSepArg, 0 },
397   { "-minmass",		".minMass",	XrmoptionSepArg, 0 },
398   { "-maxmass",		".maxMass",	XrmoptionSepArg, 0 },
399   { "-minspeed",	".minSpeed",	XrmoptionSepArg, 0 },
400   { "-maxspeed",	".maxSpeed",	XrmoptionSepArg, 0 },
401   { "-spacing",		".spacing",	XrmoptionSepArg, 0 },
402   { "-sizefactor",	".sizeFactor",	XrmoptionSepArg, 0 },
403   { "-colors",		".colors",	XrmoptionSepArg, 0 },
404   { "-cycle-speed",	".cycleSpeed",	XrmoptionSepArg, 0 },
405   { 0, 0, 0, 0 }
406 };
407 
408 
409 XSCREENSAVER_MODULE ("Halftone", halftone)
410