/* ximage-loader.c --- converts image files or data to XImages or Pixmap.
* xscreensaver, Copyright (c) 1998-2020 Jamie Zawinski <jwz@jwz.org>
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
#include "ximage-loader.h"
#if defined(HAVE_GDK_PIXBUF) || defined(HAVE_COCOA) || defined(HAVE_ANDROID)
# include "grabscreen.h" /* for osx_load_image_file() */
# if (__GNUC__ >= 4) /* Ignore useless warnings generated by GTK headers */
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wlong-long"
# pragma GCC diagnostic ignored "-Wvariadic-macros"
# pragma GCC diagnostic ignored "-Wpedantic"
# include <gdk-pixbuf/gdk-pixbuf.h>
# include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
# include <gdk-pixbuf/gdk-pixbuf-xlib.h>
# pragma GCC diagnostic pop
#endif /* HAVE_GDK_PIXBUF */
/* So that debug output shows up in logcat... */
extern void Log(const char *format
, ...);
# define fprintf(S, ...) Log(__VA_ARGS__)
union { int i
; char c
[sizeof(int)]; } u
;
/* Loads the image to an XImage, RGBA -- GDK Pixbuf version.
make_ximage (Display
*dpy
, Visual
*visual
, const char *filename
,
const unsigned char *image_data
, unsigned long data_size
)
# if !GLIB_CHECK_VERSION(2, 36 ,0)
/* Turns out gdk-pixbuf works even if you don't have display
connection, which is good news for analogtv-cli. */
gdk_pixbuf_xlib_init (dpy
, DefaultScreen (dpy
));
xlib_rgb_init (dpy
, DefaultScreenOfDisplay (dpy
));
pb
= gdk_pixbuf_new_from_file (filename
, &gerr
);
fprintf (stderr
, "%s: %s\n", progname
, gerr
->message
);
pb
= gdk_pixbuf_new_from_file (filename
);
fprintf (stderr
, "%s: GDK unable to load %s: %s\n",
progname
, filename
, (gerr
? gerr
->message
: "?"));
g_memory_input_stream_new_from_data (image_data
, data_size
, 0);
pb
= gdk_pixbuf_new_from_stream (s
, 0, &gerr
);
g_input_stream_close (s
, NULL
, NULL
);
/* #### valgrind on xflame says there's a small leak in s? */
/* fprintf (stderr, "%s: GDK unable to parse image data: %s\n",
progname, (gerr ? gerr->message : "?")); */
fprintf (stderr
, "%s: image loading not supported with GTK 1.x\n",
int w
= gdk_pixbuf_get_width (pb
);
int h
= gdk_pixbuf_get_height (pb
);
guchar
*row
= gdk_pixbuf_get_pixels (pb
);
int stride
= gdk_pixbuf_get_rowstride (pb
);
int chan
= gdk_pixbuf_get_n_channels (pb
);
image
= XCreateImage (dpy
, visual
, 32, ZPixmap
, 0, 0, w
, h
, 32, 0);
image
->data
= (char *) malloc(h
* image
->bytes_per_line
);
/* Set the bit order in the XImage structure to whatever the
local host's native bit order is.
image
->bitmap_bit_order
=
(bigendian() ? MSBFirst
: LSBFirst
);
fprintf (stderr
, "%s: out of memory (%d x %d)\n", progname
, w
, h
);
XPutPixel (image
, x
, y
, rgba
);
/* #### valgrind on xflame says there's a small leak in pb? */
#elif defined(HAVE_JWXYZ) /* MacOS, iOS or Android */
/* Loads the image to an XImage, RGBA -- MacOS, iOS or Android version.
make_ximage (Display
*dpy
, Visual
*visual
, const char *filename
,
const unsigned char *image_data
, unsigned long data_size
)
# ifdef HAVE_COCOA /* MacOS */
Screen
*screen
= DefaultScreenOfDisplay (dpy
);
Window window
= RootWindowOfScreen (screen
);
XGetWindowAttributes (dpy
, window
, &xgwa
);
XCreatePixmap (dpy
, window
, xgwa
.width
, xgwa
.height
, xgwa
.depth
);
if (! osx_load_image_file (screen
, window
, pixmap
, filename
, &geom
))
fprintf (stderr
, "%s: %s failed\n", progname
, filename
);
ximage
= XGetImage (dpy
, pixmap
, geom
.x
, geom
.y
,
/* Have to convert ABGR to RGBA */
for (y
= 0; y
< ximage
->height
; y
++)
for (x
= 0; x
< ximage
->width
; x
++)
unsigned long p
= XGetPixel (ximage
, x
, y
);
unsigned long a
= (p
>> 24) & 0xFF;
unsigned long b
= (p
>> 16) & 0xFF;
unsigned long g
= (p
>> 8) & 0xFF;
unsigned long r
= (p
>> 0) & 0xFF;
p
= (r
<< 24) | (g
<< 16) | (b
<< 8) | (a
<< 0);
XPutPixel (ximage
, x
, y
, p
);
XFreePixmap (dpy
, pixmap
);
# else /* !HAVE_COCOA -- iOS or Android. */
fprintf (stderr
, "%s: image file loading not supported\n", progname
);
# endif /* !HAVE_COCOA */
ximage
= jwxyz_png_to_ximage (dpy
, visual
, image_data
, data_size
);
#elif defined(HAVE_LIBPNG)
const unsigned char *buf
;
png_reader_fn (png_structp png_ptr
, png_bytep buf
, png_size_t siz
)
png_read_closure
*r
= png_get_io_ptr (png_ptr
);
if (siz
> r
->siz
- r
->ptr
)
png_error (png_ptr
, "PNG internal read error");
memcpy (buf
, r
->buf
+ r
->ptr
, siz
);
/* Loads the image to an XImage, RGBA -- libpng version.
make_ximage (Display
*dpy
, Visual
*visual
,
const char *filename
, const unsigned char *image_data
,
png_uint_32 width
, height
, channels
;
int bit_depth
, color_type
, interlace_type
;
png_ptr
= png_create_read_struct (PNG_LIBPNG_VER_STRING
, 0, 0, 0);
info_ptr
= png_create_info_struct (png_ptr
);
png_destroy_read_struct (&png_ptr
, 0, 0);
end_info
= png_create_info_struct (png_ptr
);
png_destroy_read_struct (&png_ptr
, &info_ptr
, 0);
if (setjmp (png_jmpbuf(png_ptr
)))
png_destroy_read_struct (&png_ptr
, &info_ptr
, &end_info
);
fp
= fopen (filename
, "r");
fprintf (stderr
, "%s: unable to read %s\n", progname
, filename
);
png_init_io (png_ptr
, fp
);
png_read_closure closure
;
closure
.buf
= image_data
;
png_set_read_fn (png_ptr
, (void *) &closure
, png_reader_fn
);
png_read_info (png_ptr
, info_ptr
);
png_get_IHDR (png_ptr
, info_ptr
,
&width
, &height
, &bit_depth
, &color_type
,
png_set_strip_16 (png_ptr
); /* Truncate 16 bits per component to 8 */
png_set_packing (png_ptr
); /* Unpack to 1 pixel per byte */
if (color_type
== PNG_COLOR_TYPE_PALETTE
) /* Colormap to RGB */
png_set_palette_rgb (png_ptr
);
if (color_type
== PNG_COLOR_TYPE_GRAY
&& bit_depth
< 8) /* Mono to 8bit */
png_set_gray_1_2_4_to_8 (png_ptr
);
if (png_get_valid (png_ptr
, info_ptr
, PNG_INFO_tRNS
)) /* Fix weird alpha */
png_set_tRNS_to_alpha (png_ptr
);
/* At least 8 bits deep */
if (color_type
== PNG_COLOR_TYPE_PALETTE
&& bit_depth
<= 8)
png_set_expand (png_ptr
);
if (bit_depth
== 8 && /* Convert RGB to RGBA */
(color_type
== PNG_COLOR_TYPE_RGB
||
color_type
== PNG_COLOR_TYPE_PALETTE
))
png_set_filler (png_ptr
, 0xFF, PNG_FILLER_AFTER
);
if (color_type
== PNG_COLOR_TYPE_GRAY
&& bit_depth
< 8)
png_set_expand (png_ptr
);
/* Convert graysale to color */
if (color_type
== PNG_COLOR_TYPE_GRAY
||
color_type
== PNG_COLOR_TYPE_GRAY_ALPHA
)
png_set_gray_to_rgb (png_ptr
);
if (png_get_bKGD (png_ptr
, info_ptr
, &bg
))
png_set_background (png_ptr
, bg
, PNG_BACKGROUND_GAMMA_FILE
, 1, 1.0);
png_read_update_info (png_ptr
, info_ptr
);
channels
= png_get_channels (png_ptr
, info_ptr
);
png_bytep
*rows
= png_malloc (png_ptr
, height
* sizeof(*rows
));
for (y
= 0; y
< height
; y
++)
rows
[y
] = png_malloc (png_ptr
, png_get_rowbytes (png_ptr
, info_ptr
));
png_read_image (png_ptr
, rows
);
png_read_end (png_ptr
, info_ptr
);
image
= XCreateImage (dpy
, visual
, 32, ZPixmap
, 0, 0,
image
->data
= (char *) malloc (height
* image
->bytes_per_line
);
/* Set the bit order in the XImage structure to whatever the
local host's native bit order is.
image
->bitmap_bit_order
=
(bigendian() ? MSBFirst
: LSBFirst
);
fprintf (stderr
, "%s: out of memory (%lu x %lu)\n",
progname
, (unsigned long)width
, (unsigned long)height
);
for (y
= 0; y
< height
; y
++)
for (x
= 0; x
< width
; x
++)
XPutPixel (image
, x
, y
, rgba
);
png_free (png_ptr
, rows
[y
]);
png_free (png_ptr
, rows
);
png_destroy_read_struct (&png_ptr
, &info_ptr
, &end_info
);
#else /* No image loaders! */
make_ximage (Display
*dpy
, Visual
*visual
,
const char *filename
, const unsigned char *image_data
,
fprintf (stderr
, "%s: no image loading support!\n", progname
);
/* Given a bitmask, returns the position and width of the field.
decode_mask (unsigned long mask
, unsigned long *pos_ret
,
if (! (mask
& (1L << i
)))
/* Loads the image to a Pixmap and optional 1-bit mask.
make_pixmap (Display
*dpy
, Window window
,
const unsigned char *image_data
, unsigned long data_size
,
int *width_ret
, int *height_ret
, Pixmap
*mask_ret
)
XImage
*in
, *out
, *mask
= 0;
unsigned long crpos
=0, cgpos
=0, cbpos
=0, capos
=0; /* bitfield positions */
unsigned long srpos
=0, sgpos
=0, sbpos
=0;
unsigned long srmsk
=0, sgmsk
=0, sbmsk
=0;
unsigned long srsiz
=0, sgsiz
=0, sbsiz
=0;
// BlackPixel has alpha: 0xFF000000.
unsigned long black
= BlackPixelOfScreen (DefaultScreenOfDisplay (dpy
));
XGetWindowAttributes (dpy
, window
, &xgwa
);
in
= make_ximage (dpy
, xgwa
.visual
, filename
, image_data
, data_size
);
/* Create a new image in the depth and bit-order of the server. */
out
= XCreateImage (dpy
, xgwa
.visual
, xgwa
.depth
, ZPixmap
, 0, 0,
in
->width
, in
->height
, 8, 0);
out
->bitmap_bit_order
= in
->bitmap_bit_order
;
out
->byte_order
= in
->byte_order
;
out
->bitmap_bit_order
= BitmapBitOrder (dpy
);
out
->byte_order
= ImageByteOrder (dpy
);
out
->data
= (char *) malloc (out
->height
* out
->bytes_per_line
);
mask
= XCreateImage (dpy
, xgwa
.visual
, 1, XYPixmap
, 0, 0,
in
->width
, in
->height
, 8, 0);
mask
->byte_order
= in
->byte_order
;
mask
->data
= (char *) malloc (mask
->height
* mask
->bytes_per_line
);
/* Find the server's color masks.
if (!(srmsk
&& sgmsk
&& sbmsk
)) abort(); /* No server color masks? */
decode_mask (srmsk
, &srpos
, &srsiz
);
decode_mask (sgmsk
, &sgpos
, &sgsiz
);
decode_mask (sbmsk
, &sbpos
, &sbsiz
);
/* 'in' is RGBA in client endianness. Convert to what the server wants. */
crpos
= 24, cgpos
= 16, cbpos
= 8, capos
= 0;
crpos
= 0, cgpos
= 8, cbpos
= 16, capos
= 24;
for (y
= 0; y
< in
->height
; y
++)
for (x
= 0; x
< in
->width
; x
++)
unsigned long p
= XGetPixel (in
, x
, y
);
unsigned char a
= (p
>> capos
) & 0xFF;
unsigned char b
= (p
>> cbpos
) & 0xFF;
unsigned char g
= (p
>> cgpos
) & 0xFF;
unsigned char r
= (p
>> crpos
) & 0xFF;
XPutPixel (out
, x
, y
, ((r
<< srpos
) |
XPutPixel (mask
, x
, y
, (a
? 1 : 0));
pixmap
= XCreatePixmap (dpy
, window
, out
->width
, out
->height
, xgwa
.depth
);
gc
= XCreateGC (dpy
, pixmap
, 0, &gcv
);
XPutImage (dpy
, pixmap
, gc
, out
, 0, 0, 0, 0, out
->width
, out
->height
);
Pixmap p2
= XCreatePixmap (dpy
, window
, mask
->width
, mask
->height
, 1);
gc
= XCreateGC (dpy
, p2
, GCForeground
|GCBackground
, &gcv
);
XPutImage (dpy
, p2
, gc
, mask
, 0, 0, 0, 0, mask
->width
, mask
->height
);
if (width_ret
) *width_ret
= out
->width
;
if (height_ret
) *height_ret
= out
->height
;
/* Textures are upside down, so invert XImages before returning them.
flip_ximage (XImage
*ximage
)
data2
= malloc (ximage
->bytes_per_line
* ximage
->height
);
out
= data2
+ ximage
->bytes_per_line
* (ximage
->height
- 1);
for (y
= 0; y
< ximage
->height
; y
++)
memcpy (out
, in
, ximage
->bytes_per_line
);
in
+= ximage
->bytes_per_line
;
out
-= ximage
->bytes_per_line
;
image_data_to_pixmap (Display
*dpy
, Window window
,
const unsigned char *image_data
, unsigned long data_size
,
int *width_ret
, int *height_ret
,
return make_pixmap (dpy
, window
, 0, image_data
, data_size
,
width_ret
, height_ret
, mask_ret
);
file_to_pixmap (Display
*dpy
, Window window
, const char *filename
,
int *width_ret
, int *height_ret
,
return make_pixmap (dpy
, window
, filename
, 0, 0,
width_ret
, height_ret
, mask_ret
);
/* This XImage has RGBA data, which is what OpenGL code typically expects.
Also it is upside down: the origin is at the bottom left of the image.
X11 typically expects 0RGB as it has no notion of alpha, only 1-bit masks.
With X11 code, you should probably use the _pixmap routines instead.
image_data_to_ximage (Display
*dpy
, Visual
*visual
,
const unsigned char *image_data
,
XImage
*ximage
= make_ximage (dpy
, visual
, 0, image_data
, data_size
);
file_to_ximage (Display
*dpy
, Visual
*visual
, const char *filename
)
XImage
*ximage
= make_ximage (dpy
, visual
, filename
, 0, 0);