diff --git a/clients/cairo-util.c b/clients/cairo-util.c index 61de82fc..02cd5bd6 100644 --- a/clients/cairo-util.c +++ b/clients/cairo-util.c @@ -30,6 +30,7 @@ #include "cairo-util.h" #include +#include #define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) @@ -311,12 +312,11 @@ error_exit(j_common_ptr cinfo) longjmp(cinfo->client_data, 1); } -cairo_surface_t * -load_jpeg(const char *filename) +static cairo_surface_t * +load_jpeg(FILE *fp) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; - FILE *fp; int stride, i, first; JSAMPLE *data, *rows[4]; jmp_buf env; @@ -329,11 +329,6 @@ load_jpeg(const char *filename) jpeg_create_decompress(&cinfo); - fp = fopen(filename, "rb"); - if (fp == NULL) { - fprintf(stderr, "can't open %s\n", filename); - return NULL; - } jpeg_stdio_src(&cinfo, fp); jpeg_read_header(&cinfo, TRUE); @@ -361,8 +356,6 @@ load_jpeg(const char *filename) jpeg_finish_decompress(&cinfo); - fclose(fp); - jpeg_destroy_decompress(&cinfo); return cairo_image_surface_create_for_data (data, @@ -371,3 +364,183 @@ load_jpeg(const char *filename) cinfo.output_height, stride); } + +static inline int +multiply_alpha(int alpha, int color) +{ + int temp = (alpha * color) + 0x80; + + return ((temp + (temp >> 8)) >> 8); +} + +static void +premultiply_data(png_structp png, + png_row_infop row_info, + png_bytep data) +{ + unsigned int i; + png_bytep p; + + for (i = 0, p = data; i < row_info->rowbytes; i += 4, p += 4) { + png_byte alpha = p[3]; + uint32_t w; + + if (alpha == 0) { + w = 0; + } else { + png_byte red = p[0]; + png_byte green = p[1]; + png_byte blue = p[2]; + + if (alpha != 0xff) { + red = multiply_alpha(alpha, red); + green = multiply_alpha(alpha, green); + blue = multiply_alpha(alpha, blue); + } + w = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0); + } + + * (uint32_t *) p = w; + } +} + +static void +read_func(png_structp png, png_bytep data, png_size_t size) +{ + FILE *fp = png_get_io_ptr(png); + + if (fread(data, 1, size, fp) < 0) + png_error(png, NULL); +} + +static void +png_error_callback(png_structp png, png_const_charp error_msg) +{ + longjmp (png_jmpbuf (png), 1); +} + +static cairo_surface_t * +load_png(FILE *fp) +{ + png_struct *png; + png_info *info; + png_byte *data = NULL; + png_byte **row_pointers = NULL; + png_uint_32 width, height; + int depth, color_type, interlace, stride; + unsigned int i; + + png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, + png_error_callback, NULL); + if (!png) + return NULL; + + info = png_create_info_struct(png); + if (!info) { + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } + + if (setjmp(png_jmpbuf(png))) { + if (data) + free(data); + if (row_pointers) + free(row_pointers); + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } + + png_set_read_fn(png, fp, read_func); + png_read_info(png, info); + png_get_IHDR(png, info, + &width, &height, &depth, + &color_type, &interlace, NULL, NULL); + + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png); + + if (color_type == PNG_COLOR_TYPE_GRAY) + png_set_expand_gray_1_2_4_to_8(png); + + if (png_get_valid(png, info, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png); + + if (depth == 16) + png_set_strip_16(png); + + if (depth < 8) + png_set_packing(png); + + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + if (interlace != PNG_INTERLACE_NONE) + png_set_interlace_handling(png); + + png_set_filler(png, 0xff, PNG_FILLER_AFTER); + png_set_read_user_transform_fn(png, premultiply_data); + png_read_update_info(png, info); + png_get_IHDR(png, info, + &width, &height, &depth, + &color_type, &interlace, NULL, NULL); + + + stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, width); + data = malloc(stride * height); + if (!data) { + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } + + row_pointers = malloc(height * sizeof row_pointers[0]); + if (row_pointers == NULL) { + free(data); + png_destroy_read_struct(&png, &info, NULL); + return NULL; + } + + for (i = 0; i < height; i++) + row_pointers[i] = &data[i * stride]; + + png_read_image(png, row_pointers); + png_read_end(png, info); + + free(row_pointers); + png_destroy_read_struct(&png, &info, NULL); + + return cairo_image_surface_create_for_data (data, + CAIRO_FORMAT_RGB24, + width, height, stride); +} + +cairo_surface_t * +load_image(const char *filename) +{ + cairo_surface_t *surface; + static const unsigned char png_header[] = { 0x89, 'P', 'N', 'G' }; + static const unsigned char jpeg_header[] = { 0xff, 0xd8, 0xff, 0xe0 }; + unsigned char header[4]; + FILE *fp; + + fp = fopen(filename, "rb"); + if (fp == NULL) + return NULL; + + fread(header, sizeof header, 1, fp); + rewind(fp); + if (memcmp(header, png_header, sizeof header) == 0) + surface = load_png(fp); + else if (memcmp(header, jpeg_header, sizeof header) == 0) + surface = load_jpeg(fp); + else { + fprintf(stderr, "unrecognized file header for %s: " + "0x%02x 0x%02x 0x%02x 0x%02x\n", + filename, header[0], header[1], header[2], header[3]); + surface = NULL; + } + + fclose(fp); + + return surface; +} diff --git a/clients/cairo-util.h b/clients/cairo-util.h index 07613ac2..3a9f3bc5 100644 --- a/clients/cairo-util.h +++ b/clients/cairo-util.h @@ -41,6 +41,6 @@ void rounded_rect(cairo_t *cr, int x0, int y0, int x1, int y1, int radius); cairo_surface_t * -load_jpeg(const char *filename); +load_image(const char *filename); #endif diff --git a/clients/desktop-shell.c b/clients/desktop-shell.c index fde00179..097bcd34 100644 --- a/clients/desktop-shell.c +++ b/clients/desktop-shell.c @@ -376,7 +376,7 @@ background_draw(struct widget *widget, void *data) widget_get_allocation(widget, &allocation); image = NULL; if (key_background_image) - image = load_jpeg(key_background_image); + image = load_image(key_background_image); if (image) { pattern = cairo_pattern_create_for_surface(image); sx = (double) cairo_image_surface_get_width(image) / diff --git a/clients/tablet-shell.c b/clients/tablet-shell.c index 6f1f231a..1a647e8f 100644 --- a/clients/tablet-shell.c +++ b/clients/tablet-shell.c @@ -88,7 +88,7 @@ paint_background(cairo_t *cr, const char *path, struct rectangle *allocation) cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); if (path) - image = load_jpeg(path); + image = load_image(path); if (image) { pattern = cairo_pattern_create_for_surface(image); sx = (double) cairo_image_surface_get_width(image) /