1 |
/* |
2 |
* static char *rcsid_png_c = |
3 |
* "$Id$"; |
4 |
*/ |
5 |
/* |
6 |
Crossfire client, a client program for the crossfire program. |
7 |
|
8 |
Copyright (C) 2002 Mark Wedel & Crossfire Development Team |
9 |
|
10 |
This program is free software; you can redistribute it and/or modify |
11 |
it under the terms of the GNU General Public License as published by |
12 |
the Free Software Foundation; either version 2 of the License, or |
13 |
(at your option) any later version. |
14 |
|
15 |
This program is distributed in the hope that it will be useful, |
16 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 |
GNU General Public License for more details. |
19 |
|
20 |
You should have received a copy of the GNU General Public License |
21 |
along with this program; if not, write to the Free Software |
22 |
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
23 |
|
24 |
The authors can be reached via e-mail at crossfire-devel@real-time.com |
25 |
*/ |
26 |
|
27 |
/* This is a light weight png -> xpixmap function. Most of the code is from |
28 |
* the example png documentation. |
29 |
* I wrote this because I could not find a simple function that did this - |
30 |
* most all libraries out there tended to do a lot more than I needed for |
31 |
* crossfire - in addition, imLib actually has bugs which prevents it from |
32 |
* rendering some images properly. |
33 |
* |
34 |
* This function is far from complete, but does the job and removes the need |
35 |
* for yet another library. |
36 |
*/ |
37 |
|
38 |
#include <stdlib.h> |
39 |
#include <sys/stat.h> |
40 |
#include <unistd.h> |
41 |
#include <png.h> |
42 |
#include <X11/Xlib.h> |
43 |
#include <X11/Xutil.h> |
44 |
|
45 |
|
46 |
/* Defines for PNG return values */ |
47 |
/* These should be in a header file, but currently our calling functions |
48 |
* routines just check for nonzero return status and don't really care |
49 |
* why the load failed. |
50 |
*/ |
51 |
#define PNGX_NOFILE 1 |
52 |
#define PNGX_OUTOFMEM 2 |
53 |
#define PNGX_DATA 3 |
54 |
|
55 |
static char *data_cp; |
56 |
static int data_len, data_start; |
57 |
static XImage *ximage; |
58 |
static int rmask=0, bmask=0,gmask=0,need_color_alloc=0, rshift=16, bshift=0, gshift=8, |
59 |
rev_rshift=0, rev_gshift=0, rev_bshift=0; |
60 |
static int colors_alloced=0, private_cmap=0, colormap_size; |
61 |
struct Pngx_Color_Values { |
62 |
unsigned char red, green, blue; |
63 |
long pixel_value; |
64 |
} *color_values; |
65 |
|
66 |
#define COLOR_FACTOR 3 |
67 |
#define BRIGHTNESS_FACTOR 1 |
68 |
|
69 |
/* This function is used to find the pixel and return it |
70 |
* to the caller. We store what pixels we have already allocated |
71 |
* and try to find a match against that. The reason for this is that |
72 |
* XAllocColor is a very slow routine. Before my optimizations, |
73 |
* png loading took about 140 seconds, of which 60 seconds of that |
74 |
* was in XAllocColor calls. |
75 |
*/ |
76 |
static long pngx_find_color(Display *display, Colormap *cmap, int red, int green, int blue) |
77 |
{ |
78 |
|
79 |
int i, closeness=0xffffff, close_entry=-1, tmpclose; |
80 |
XColor scolor; |
81 |
|
82 |
for (i=0; i<colors_alloced; i++) { |
83 |
if ((color_values[i].red == red) && (color_values[i].green == green) && |
84 |
(color_values[i].blue == blue)) return color_values[i].pixel_value; |
85 |
|
86 |
tmpclose = COLOR_FACTOR * (abs(red - color_values[i].red) + |
87 |
abs(green - color_values[i].green) + |
88 |
abs(blue - color_values[i].blue)) + |
89 |
BRIGHTNESS_FACTOR * abs((red + green + blue) - |
90 |
(color_values[i].red + color_values[i].green + color_values[i].blue)); |
91 |
|
92 |
/* I already know that 8 bit is not enough to hold all the PNG colors, |
93 |
* so lets do some early optimization |
94 |
*/ |
95 |
if (tmpclose < 3) return color_values[i].pixel_value; |
96 |
if (tmpclose < closeness) { |
97 |
closeness = tmpclose; |
98 |
close_entry = i; |
99 |
} |
100 |
} |
101 |
|
102 |
/* If the colormap is full, no reason to do anything more */ |
103 |
if (colors_alloced == colormap_size) |
104 |
return color_values[close_entry].pixel_value; |
105 |
|
106 |
|
107 |
/* If we get here, we haven't cached the color */ |
108 |
|
109 |
scolor.red = (red << 8) + red; |
110 |
scolor.green = (green << 8) + green; |
111 |
scolor.blue = (blue << 8) + blue; |
112 |
|
113 |
|
114 |
again: |
115 |
if (!XAllocColor(display, *cmap, &scolor)) { |
116 |
if (!private_cmap) { |
117 |
fprintf(stderr,"Going to private colormap after %d allocs\n", colors_alloced); |
118 |
*cmap = XCopyColormapAndFree(display, *cmap); |
119 |
private_cmap=1; |
120 |
goto again; |
121 |
} |
122 |
else { |
123 |
#if 0 |
124 |
fprintf(stderr,"Unable to allocate color %d %d %d, %d colors alloced, will use closeness value %d\n", |
125 |
red, green, blue, colors_alloced, closeness); |
126 |
#endif |
127 |
colors_alloced = colormap_size; /* Colormap is exhausted */ |
128 |
return color_values[close_entry].pixel_value; |
129 |
} |
130 |
} |
131 |
color_values[colors_alloced].red = red; |
132 |
color_values[colors_alloced].green = green; |
133 |
color_values[colors_alloced].blue = blue; |
134 |
color_values[colors_alloced].pixel_value= scolor.pixel; |
135 |
colors_alloced++; |
136 |
return scolor.pixel; |
137 |
} |
138 |
|
139 |
|
140 |
|
141 |
static void user_read_data(png_structp png_ptr, png_bytep data, png_size_t length) { |
142 |
memcpy(data, data_cp + data_start, length); |
143 |
data_start += length; |
144 |
} |
145 |
|
146 |
int init_pngx_loader(Display *display) |
147 |
{ |
148 |
int pad,depth; |
149 |
XVisualInfo xvinfo, *xvret; |
150 |
Visual *visual; |
151 |
|
152 |
depth = DefaultDepth(display, DefaultScreen(display)); |
153 |
visual = DefaultVisual(display, DefaultScreen(display)); |
154 |
xvinfo.visualid = XVisualIDFromVisual(visual); |
155 |
xvret = XGetVisualInfo(display, VisualIDMask, &xvinfo, &pad); |
156 |
if (pad != 1) { |
157 |
fprintf(stderr,"XGetVisual found %d matching visuals?\n", pad); |
158 |
return 1; |
159 |
} |
160 |
rmask = xvret -> red_mask; |
161 |
gmask = xvret -> green_mask; |
162 |
bmask = xvret -> blue_mask; |
163 |
/* We need to figure out how many bits to shift. Thats what this |
164 |
* following block of code does. We can't presume to use just |
165 |
* 16, 8, 0 bits for RGB respectively, as if you are on 16 bit, |
166 |
* that is not correct. There may be a much easier way to do this - |
167 |
* it is just bit manipulation. Note that we want to preserver |
168 |
* the most significant bits, so these shift values can very |
169 |
* well be negative, in which case we need to know that - |
170 |
* the shift operators don't work with negative values. |
171 |
* An example is 5 bits for blue - in that case, we really |
172 |
* want to shfit right (>>) by 3 bits. |
173 |
*/ |
174 |
rshift=0; |
175 |
if (rmask) { |
176 |
while (!((1 << rshift) & rmask)) rshift++; |
177 |
while (((1 << rshift) & rmask)) rshift++; |
178 |
rshift -= 8; |
179 |
if (rshift < 0 ) { |
180 |
rev_rshift=1; |
181 |
rshift = -rshift; |
182 |
} |
183 |
} |
184 |
gshift=0; |
185 |
if (gmask) { |
186 |
while (!((1 << gshift) & gmask)) gshift++; |
187 |
while (((1 << gshift) & gmask)) gshift++; |
188 |
gshift -= 8; |
189 |
if (gshift < 0 ) { |
190 |
rev_gshift=1; |
191 |
gshift = -gshift; |
192 |
} |
193 |
} |
194 |
bshift=0; |
195 |
if (bmask) { |
196 |
while (!((1 << bshift) & bmask)) bshift++; |
197 |
while (((1 << bshift) & bmask)) bshift++; |
198 |
bshift -= 8; |
199 |
if (bshift < 0 ) { |
200 |
rev_bshift=1; |
201 |
bshift = -bshift; |
202 |
} |
203 |
} |
204 |
|
205 |
|
206 |
if (xvret->class==PseudoColor) { |
207 |
need_color_alloc=1; |
208 |
if (xvret->colormap_size>256) { |
209 |
fprintf(stderr,"One a pseudocolor visual, but colormap has %d entries?\n", xvret->colormap_size); |
210 |
} |
211 |
color_values=malloc(sizeof(struct Pngx_Color_Values) * xvret->colormap_size); |
212 |
colormap_size = xvret->colormap_size-1; /* comparing # of alloced colors against this */ |
213 |
} |
214 |
XFree(xvret); |
215 |
|
216 |
if (depth>16) pad = 32; |
217 |
else if (depth > 8) pad = 16; |
218 |
else pad = 8; |
219 |
|
220 |
ximage = XCreateImage(display, visual, |
221 |
depth, |
222 |
ZPixmap, 0, 0, |
223 |
256, 256, pad, 0); |
224 |
if (!ximage) { |
225 |
fprintf(stderr,"Failed to create Ximage\n"); |
226 |
return 1; |
227 |
} |
228 |
ximage->data = malloc(ximage->bytes_per_line * 256); |
229 |
if (!ximage->data) { |
230 |
fprintf(stderr,"Failed to create Ximage data\n"); |
231 |
return 1; |
232 |
} |
233 |
return 0; |
234 |
} |
235 |
|
236 |
|
237 |
int png_to_xpixmap(Display *display, Drawable draw, char *data, int len, |
238 |
Pixmap *pix, Pixmap *mask, Colormap *cmap, |
239 |
unsigned long *width, unsigned long *height) |
240 |
{ |
241 |
static char *pixels=NULL; |
242 |
static int pixels_byte=0, rows_byte=0; |
243 |
static png_bytepp rows=NULL; |
244 |
|
245 |
png_structp png_ptr=NULL; |
246 |
png_infop info_ptr=NULL, end_info=NULL; |
247 |
int bit_depth, color_type, interlace_type, compression_type, filter_type, |
248 |
red,green,blue, lastred=-1, lastgreen=-1, lastblue=-1,alpha,bpp, x,y, |
249 |
has_alpha=0,cmask, lastcmask=-1, lastcolor=-1; |
250 |
GC gc, gc_alpha; |
251 |
|
252 |
|
253 |
data_len=len; |
254 |
data_cp = data; |
255 |
data_start=0; |
256 |
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, |
257 |
NULL, NULL, NULL); |
258 |
if (!png_ptr) { |
259 |
return PNGX_OUTOFMEM; |
260 |
} |
261 |
info_ptr = png_create_info_struct (png_ptr); |
262 |
|
263 |
if (!info_ptr) { |
264 |
png_destroy_read_struct (&png_ptr, NULL, NULL); |
265 |
return PNGX_OUTOFMEM; |
266 |
} |
267 |
end_info = png_create_info_struct (png_ptr); |
268 |
if (!end_info) { |
269 |
png_destroy_read_struct (&png_ptr, &info_ptr, NULL); |
270 |
return PNGX_OUTOFMEM; |
271 |
} |
272 |
if (setjmp (png_ptr->jmpbuf)) { |
273 |
png_destroy_read_struct (&png_ptr, &info_ptr, &end_info); |
274 |
return PNGX_DATA; |
275 |
} |
276 |
|
277 |
png_set_read_fn(png_ptr, NULL, user_read_data); |
278 |
png_read_info (png_ptr, info_ptr); |
279 |
|
280 |
png_get_IHDR(png_ptr, info_ptr, width, height, &bit_depth, |
281 |
&color_type, &interlace_type, &compression_type, &filter_type); |
282 |
|
283 |
if (color_type == PNG_COLOR_TYPE_PALETTE && |
284 |
bit_depth <= 8) { |
285 |
|
286 |
/* Convert indexed images to RGB */ |
287 |
png_set_expand (png_ptr); |
288 |
|
289 |
} else if (color_type == PNG_COLOR_TYPE_GRAY && |
290 |
bit_depth < 8) { |
291 |
|
292 |
/* Convert grayscale to RGB */ |
293 |
png_set_expand (png_ptr); |
294 |
|
295 |
} else if (png_get_valid (png_ptr, |
296 |
info_ptr, PNG_INFO_tRNS)) { |
297 |
|
298 |
/* If we have transparency header, convert it to alpha |
299 |
channel */ |
300 |
png_set_expand(png_ptr); |
301 |
|
302 |
} else if (bit_depth < 8) { |
303 |
|
304 |
/* If we have < 8 scale it up to 8 */ |
305 |
png_set_expand(png_ptr); |
306 |
|
307 |
|
308 |
/* Conceivably, png_set_packing() is a better idea; |
309 |
* God only knows how libpng works |
310 |
*/ |
311 |
} |
312 |
/* If we are 16-bit, convert to 8-bit */ |
313 |
if (bit_depth == 16) { |
314 |
png_set_strip_16(png_ptr); |
315 |
} |
316 |
|
317 |
/* If gray scale, convert to RGB */ |
318 |
if (color_type == PNG_COLOR_TYPE_GRAY || |
319 |
color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { |
320 |
png_set_gray_to_rgb(png_ptr); |
321 |
} |
322 |
|
323 |
/* If interlaced, handle that */ |
324 |
if (interlace_type != PNG_INTERLACE_NONE) { |
325 |
png_set_interlace_handling(png_ptr); |
326 |
} |
327 |
|
328 |
/* Update the info the reflect our transformations */ |
329 |
png_read_update_info(png_ptr, info_ptr); |
330 |
/* re-read due to transformations just made */ |
331 |
png_get_IHDR(png_ptr, info_ptr, width, height, &bit_depth, |
332 |
&color_type, &interlace_type, &compression_type, &filter_type); |
333 |
if (color_type & PNG_COLOR_MASK_ALPHA) |
334 |
bpp = 4; |
335 |
else |
336 |
bpp = 3; |
337 |
|
338 |
/* Allocate the memory we need once, and increase it if necessary. |
339 |
* This is more efficient the allocating this block of memory every time. |
340 |
*/ |
341 |
if (pixels_byte==0) { |
342 |
pixels = (char*)malloc(*width * *height * bpp); |
343 |
pixels_byte =*width * *height * bpp; |
344 |
} else if ((*width * *height * bpp) > pixels_byte) { |
345 |
pixels=realloc(pixels, *width * *height * bpp); |
346 |
pixels_byte =*width * *height * bpp; |
347 |
} |
348 |
|
349 |
if (!pixels) { |
350 |
pixels_byte=0; |
351 |
png_destroy_read_struct (&png_ptr, &info_ptr, &end_info); |
352 |
return PNGX_OUTOFMEM; |
353 |
} |
354 |
if (rows_byte == 0) { |
355 |
rows =(png_bytepp) malloc(sizeof(char*) * *height); |
356 |
rows_byte=*height; |
357 |
} else if (*height > rows_byte) { |
358 |
rows =(png_bytepp) realloc(rows, sizeof(char*) * *height); |
359 |
rows_byte=*height; |
360 |
} |
361 |
if (!rows) { |
362 |
pixels_byte=0; |
363 |
png_destroy_read_struct (&png_ptr, &info_ptr, &end_info); |
364 |
return PNGX_OUTOFMEM; |
365 |
} |
366 |
|
367 |
for (y=0; y<*height; y++) |
368 |
rows[y] = pixels + y * *width * bpp; |
369 |
|
370 |
png_read_image(png_ptr, rows); |
371 |
#if 0 |
372 |
fprintf(stderr,"image is %d X %d, bpp=%d, color_type=%d\n", |
373 |
*width, *height, bpp, color_type); |
374 |
#endif |
375 |
|
376 |
*pix = XCreatePixmap(display, draw, *width, *height, |
377 |
DefaultDepth(display, DefaultScreen(display))); |
378 |
|
379 |
gc=XCreateGC(display, *pix, 0, NULL); |
380 |
XSetFunction(display, gc, GXcopy); |
381 |
XSetPlaneMask(display, gc, AllPlanes); |
382 |
|
383 |
if (color_type & PNG_COLOR_MASK_ALPHA) { |
384 |
/* The foreground/background colors are not really |
385 |
* colors, but rather values to set in the mask. |
386 |
* The values used below work properly on at least |
387 |
* 8 bit and 16 bit display - using things like |
388 |
* blackpixel & whitepixel does NO work on |
389 |
* both types of display. |
390 |
*/ |
391 |
*mask=XCreatePixmap(display ,draw, *width, *height,1); |
392 |
gc_alpha=XCreateGC(display, *mask, 0, NULL); |
393 |
XSetFunction(display, gc_alpha, GXcopy); |
394 |
XSetPlaneMask(display, gc_alpha, AllPlanes); |
395 |
XSetForeground(display, gc_alpha, 1); |
396 |
XFillRectangle(display, *mask, gc_alpha, 0, 0, *width, *height); |
397 |
XSetForeground(display, gc_alpha, 0); |
398 |
has_alpha=1; |
399 |
} |
400 |
else { |
401 |
*mask = None; |
402 |
gc_alpha = None; /* Prevent compile warnings */ |
403 |
} |
404 |
|
405 |
for (y=0; y<*height; y++) { |
406 |
for (x=0; x<*width; x++) { |
407 |
red=rows[y][x*bpp]; |
408 |
green=rows[y][x*bpp+1]; |
409 |
blue=rows[y][x*bpp+2]; |
410 |
if (has_alpha) { |
411 |
alpha = rows[y][x*bpp+3]; |
412 |
/* Transparent bit */ |
413 |
if (alpha==0) { |
414 |
XDrawPoint(display, *mask, gc_alpha, x, y); |
415 |
} |
416 |
} |
417 |
if (need_color_alloc) { |
418 |
/* We only use cmask to avoid calling pngx_find_color repeatedly. |
419 |
* when the color has not changed from the last pixel. |
420 |
*/ |
421 |
if ((lastred != red) && (lastgreen != green) && (lastblue != blue)) { |
422 |
lastcolor = pngx_find_color(display, cmap, red, green, blue); |
423 |
lastcmask = cmask; |
424 |
} |
425 |
XPutPixel(ximage, x, y, lastcolor); |
426 |
} else { |
427 |
if ((lastred != red) && (lastgreen != green) && (lastblue != blue)) { |
428 |
if (rev_rshift) red >>= rshift; |
429 |
else red <<= rshift; |
430 |
if (rev_gshift) green >>= gshift; |
431 |
else green <<= gshift; |
432 |
if (rev_bshift) blue >>= bshift; |
433 |
else blue <<= bshift; |
434 |
|
435 |
cmask = (red & rmask) | (green & gmask) | (blue & bmask); |
436 |
} |
437 |
XPutPixel(ximage, x, y, cmask); |
438 |
} |
439 |
} |
440 |
} |
441 |
|
442 |
XPutImage(display, *pix, gc, ximage, 0, 0, 0, 0, *width, *height); |
443 |
if (has_alpha) |
444 |
XFreeGC(display, gc_alpha); |
445 |
XFreeGC(display, gc); |
446 |
png_destroy_read_struct (&png_ptr, &info_ptr, &end_info); |
447 |
return 0; |
448 |
} |
449 |
#if 0 |
450 |
int main(int argc, char *argv[]) |
451 |
{ |
452 |
Display *disp; |
453 |
char data[256],buf[1024*1024]; |
454 |
int fd,i,z, alpha; |
455 |
unsigned long width, height; |
456 |
Window window; |
457 |
XSetWindowAttributes wattrs; |
458 |
Pixmap pix, mask; |
459 |
GC gc; |
460 |
|
461 |
if (argc!=2) { |
462 |
fprintf(stderr,"Usage: %s <filename>\n", argv[0]); |
463 |
exit(1); |
464 |
} |
465 |
|
466 |
if (!(disp=XOpenDisplay(NULL))) { |
467 |
fprintf(stderr,"Unable to open display\n"); |
468 |
exit(1); |
469 |
} |
470 |
|
471 |
wattrs.backing_store = WhenMapped; |
472 |
wattrs.background_pixel = WhitePixel(disp, DefaultScreen(disp)); |
473 |
|
474 |
window=XCreateWindow(disp, DefaultRootWindow(disp), 0, 0, |
475 |
32, 32, 0, CopyFromParent, InputOutput, CopyFromParent, |
476 |
CWBackingStore|CWBackPixel, &wattrs); |
477 |
|
478 |
i = open(argv[1], O_RDONLY); |
479 |
z=read(i, buf, 1024*1024); |
480 |
close(i); |
481 |
fprintf(stderr,"Read %d bytes from %s\n", z, argv[1]); |
482 |
png_to_xpixmap(disp, window, buf, z, &pix, &mask, DefaultColormap(disp, |
483 |
DefaultScreen(disp)), &width, &height); |
484 |
XResizeWindow(disp, window, width, height); |
485 |
if (!window) { |
486 |
fprintf(stderr,"Unable to create window\n"); |
487 |
exit(1); |
488 |
} |
489 |
XMapWindow(disp, window); |
490 |
XFlush(disp); |
491 |
gc=XCreateGC(disp, pix, 0, NULL); |
492 |
if (mask) |
493 |
XSetClipMask(disp, gc, mask); |
494 |
|
495 |
/* A simple way to display the image without needing to worry |
496 |
* about exposures and redraws. |
497 |
*/ |
498 |
for (i=0; i<30; i++) { |
499 |
XCopyArea(disp, pix, window, gc, 0, 0, width, height, 0 , 0); |
500 |
XFlush(disp); |
501 |
sleep(1); |
502 |
} |
503 |
|
504 |
exit(0); |
505 |
} |
506 |
|
507 |
|
508 |
#endif |