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