| /* ply-image.c - png file loader |
| * |
| * Copyright (C) 2006, 2007 Red Hat, Inc. |
| * Copyright (C) 2003 University of Southern California |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2, or (at your option) |
| * any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA |
| * 02111-1307, USA. |
| * |
| * Some implementation taken from the cairo library. |
| * |
| * Shamelessly taken and unmercifully hacked for temporary use |
| * in Chrome OS. |
| * |
| * Written by: Charlie Brej <cbrej@cs.man.ac.uk> |
| * Kristian Høgsberg <krh@redhat.com> |
| * Ray Strode <rstrode@redhat.com> |
| * Carl D. Worth <cworth@cworth.org> |
| */ |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <getopt.h> |
| #include <inttypes.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| // <png.h> depends on <math.h> but doesn't include it. Boo! |
| // |
| // Also, <png.h> includes <setjmp.h> and is designed to break if we |
| // re-include it. Boo! |
| #include <math.h> |
| #include <png.h> |
| |
| #include "ply-frame-buffer.h" |
| #include "ply-gamma.h" |
| #include "ply-monitor.h" |
| #include "ply-utils.h" |
| |
| #define MS_PER_SEC 1000LL |
| #define NS_PER_SEC (1000LL * 1000LL * 1000LL) |
| #define NS_PER_MS (NS_PER_SEC / MS_PER_SEC) |
| |
| /* Default framerate for animations. */ |
| #define DEFAULT_FRAMES_PER_SEC 20 |
| |
| typedef union { |
| uint32_t *as_pixels; |
| png_byte *as_png_bytes; |
| char *address; |
| } ply_image_layout_t; |
| |
| typedef struct { |
| char *filename; |
| FILE *fp; |
| |
| ply_image_layout_t layout; |
| size_t size; |
| |
| png_uint_32 width; |
| png_uint_32 height; |
| } ply_image_t; |
| |
| |
| /* Returns the current CLOCK_MONOTONIC time in milliseconds. */ |
| static int64_t get_monotonic_time_ms() { |
| struct timespec spec; |
| assert(clock_gettime(CLOCK_MONOTONIC, &spec) == 0); |
| return MS_PER_SEC * spec.tv_sec + spec.tv_nsec / NS_PER_MS; |
| } |
| |
| static bool ply_image_open_file(ply_image_t *image) { |
| assert(image != NULL); |
| |
| image->fp = fopen(image->filename, "r"); |
| |
| return image->fp != NULL; |
| } |
| |
| static void ply_image_close_file(ply_image_t *image) { |
| assert(image != NULL); |
| |
| if (image->fp == NULL) |
| return; |
| fclose(image->fp); |
| image->fp = NULL; |
| } |
| |
| static ply_image_t *ply_image_new(const char *filename) { |
| ply_image_t *image; |
| |
| assert(filename != NULL); |
| |
| image = calloc(1, sizeof(*image)); |
| |
| image->filename = strdup(filename); |
| image->fp = NULL; |
| image->layout.address = NULL; |
| image->size = -1; |
| image->width = -1; |
| image->height = -1; |
| |
| return image; |
| } |
| |
| static void ply_image_free(ply_image_t *image) { |
| if (image == NULL) |
| return; |
| |
| assert(image->filename != NULL); |
| |
| if (image->layout.address != NULL) { |
| free(image->layout.address); |
| image->layout.address = NULL; |
| } |
| |
| free(image->filename); |
| free(image); |
| } |
| |
| /* |
| * The following function courtesy of Intel Moblin's |
| * plymouth-lite port. |
| */ |
| static void transform_to_rgb32(png_struct *png, |
| png_row_info *row_info, |
| png_byte *data) { |
| unsigned int i; |
| |
| for (i = 0; i < row_info->rowbytes; i += 4) { |
| uint8_t red, green, blue, alpha; |
| uint32_t pixel_value; |
| |
| red = data[i + 0]; |
| green = data[i + 1]; |
| blue = data[i + 2]; |
| alpha = data[i + 3]; |
| pixel_value = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0); |
| memcpy(data + i, &pixel_value, sizeof(pixel_value)); |
| } |
| } |
| |
| static bool ply_image_load(ply_image_t *image) { |
| png_struct *png; |
| png_info *info; |
| png_uint_32 width, height, bytes_per_row, row; |
| int bits_per_pixel, color_type, interlace_method; |
| png_byte **rows; |
| |
| assert(image != NULL); |
| |
| if (!ply_image_open_file(image)) |
| return false; |
| |
| png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); |
| assert(png != NULL); |
| |
| info = png_create_info_struct(png); |
| assert(info != NULL); |
| |
| png_init_io(png, image->fp); |
| |
| if (setjmp(png_jmpbuf(png)) != 0) { |
| ply_image_close_file(image); |
| return false; |
| } |
| |
| png_read_info(png, info); |
| png_get_IHDR(png, info, |
| &width, &height, &bits_per_pixel, |
| &color_type, &interlace_method, NULL, NULL); |
| bytes_per_row = 4 * width; |
| |
| if (color_type == PNG_COLOR_TYPE_PALETTE) |
| png_set_palette_to_rgb(png); |
| |
| if ((color_type == PNG_COLOR_TYPE_GRAY) && (bits_per_pixel < 8)) |
| 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 (bits_per_pixel == 16) |
| png_set_strip_16(png); |
| |
| if (bits_per_pixel < 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_method != PNG_INTERLACE_NONE) |
| png_set_interlace_handling(png); |
| |
| png_set_filler(png, 0xff, PNG_FILLER_AFTER); |
| |
| png_set_read_user_transform_fn(png, transform_to_rgb32); |
| |
| png_read_update_info(png, info); |
| |
| rows = malloc(height * sizeof(*rows)); |
| image->layout.address = malloc(height * bytes_per_row); |
| |
| for (row = 0; row < height; row++) |
| rows[row] = &image->layout.as_png_bytes[row * bytes_per_row]; |
| |
| png_read_image(png, rows); |
| |
| free(rows); |
| png_read_end(png, info); |
| ply_image_close_file(image); |
| png_destroy_read_struct(&png, &info, NULL); |
| |
| image->width = width; |
| image->height = height; |
| |
| return true; |
| } |
| |
| static const uint32_t *ply_image_get_data(const ply_image_t *image) { |
| assert(image != NULL); |
| |
| return image->layout.as_pixels; |
| } |
| |
| static long ply_image_get_width(const ply_image_t *image) { |
| assert(image != NULL); |
| |
| return image->width; |
| } |
| |
| static long ply_image_get_height(const ply_image_t *image) { |
| assert(image != NULL); |
| |
| return image->height; |
| } |
| |
| static ply_image_t *ply_image_from_file(const char *path) { |
| ply_image_t *image = ply_image_new(path); |
| |
| if (!ply_image_load(image)) { |
| perror(path); |
| exit(errno); |
| } |
| return image; |
| } |
| |
| static int debug = 0; |
| static bool center_image = true; |
| static ply_frame_buffer_area_t image_location, image_offset; |
| static uint32_t clear_color; |
| |
| static void ply_frame_buffer_show_image( |
| ply_frame_buffer_t *buffer, const ply_image_t *image) { |
| const uint32_t *data; |
| ply_frame_buffer_area_t area, buffer_area; |
| bool fill_succeeded; |
| |
| area.width = ply_image_get_width(image); |
| area.height = ply_image_get_height(image); |
| data = ply_image_get_data(image); |
| |
| if (center_image) { |
| ply_frame_buffer_get_size(buffer, &buffer_area); |
| area.x = (long) (buffer_area.visible_width - area.width) / 2; |
| area.y = (long) (buffer_area.visible_height - area.height) / 2; |
| } else { |
| area.x = image_location.x; |
| area.y = image_location.y; |
| } |
| |
| area.x += image_offset.x; |
| area.y += image_offset.y; |
| if (debug) { |
| fprintf(stderr, "displaying %lux%lu image at (%ld, %ld)\n", |
| area.width, area.height, area.x, area.y); |
| } |
| fill_succeeded = ply_frame_buffer_fill(buffer, &area, data); |
| |
| if (!fill_succeeded) |
| fprintf(stderr, "image was not displayed\n"); |
| } |
| |
| |
| static int usage(void) { |
| fprintf(stderr, |
| "usage: ply-image [OPTION]... [FILE]...\n" |
| "Copy one or more images to the framebuffer.\n" |
| "\n" |
| "Options:\n" |
| " --clear=0xRRGGBB hexadecimal framebuffer clear color\n" |
| " --debug print debugging info to stderr\n" |
| " --frame-interval=MSEC time interval between images\n" |
| " --gamma=FILE file containing gamma ramps\n" |
| " --help display this message\n" |
| " --location=X,Y image location relative to top left\n" |
| " corner of framebuffer\n" |
| " --set-monitors turn off all non-main monitors\n" |
| " --offset=[+|-]X,[+|-]Y image location as offset from center\n" |
| " of framebuffer\n" |
| " --print-resolution print space-separated framebuffer\n" |
| " width and height to stdout\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| |
| static bool parse_color(const char *arg) { |
| char *endptr; |
| |
| clear_color = strtol(arg, &endptr, 16); |
| if (endptr == arg || *endptr != '\0') { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool parse_xy(const char *arg, ply_frame_buffer_area_t *area) { |
| char *endptr; |
| long x, y; |
| |
| x = strtol(arg, &endptr, 10); |
| if (endptr == arg || *endptr != ',') { |
| return false; |
| } |
| |
| arg = endptr + 1; |
| y = strtol(arg, &endptr, 10); |
| if (endptr == arg || *endptr != '\0') { |
| return false; |
| } |
| |
| area->x = x; |
| area->y = y; |
| |
| return true; |
| } |
| |
| static bool parse_location(const char *arg) { |
| bool result = parse_xy(arg, &image_location); |
| if (result) |
| center_image = false; |
| return result; |
| } |
| |
| static bool parse_offset(const char *arg) { |
| return parse_xy(arg, &image_offset); |
| } |
| |
| |
| /* Frame rate as milliseconds per frame */ |
| static int64_t frame_interval_ms = MS_PER_SEC / DEFAULT_FRAMES_PER_SEC; |
| |
| static bool set_frame_interval(const char *arg) { |
| char *endptr; |
| |
| long interval = strtol(arg, &endptr, 10); |
| if (endptr == arg || *endptr != '\0') { |
| return false; |
| } |
| |
| frame_interval_ms = interval; |
| return true; |
| } |
| |
| |
| /* |
| * Animate the series of images named on the command line. An attempt is made |
| * to let frame_interval_ms elapse between frames being displayed onscreen, |
| * although longer delays can occur. |
| */ |
| static void process_images( |
| ply_frame_buffer_t *buffer, int argc, char *argv[]) { |
| int64_t last_show_ms = -1; |
| |
| for (; argc > 0; --argc, ++argv) { |
| int64_t now_ms = -1; |
| ply_image_t* image = NULL; |
| |
| if (debug) { |
| fprintf(stderr, "loading %s at %" PRId64 "\n", argv[0], |
| get_monotonic_time_ms()); |
| } |
| image = ply_image_from_file(argv[0]); |
| |
| now_ms = get_monotonic_time_ms(); |
| if (last_show_ms > 0) { |
| int64_t sleep_ms = frame_interval_ms - (now_ms - last_show_ms); |
| if (sleep_ms > 0) { |
| struct timespec sleep_spec; |
| if (debug) { |
| fprintf(stderr, "sleeping %" PRId64 " ms at %" PRId64 "\n", |
| sleep_ms, now_ms); |
| } |
| sleep_spec.tv_sec = sleep_ms / MS_PER_SEC; |
| sleep_spec.tv_nsec = (sleep_ms % MS_PER_SEC) * NS_PER_MS; |
| nanosleep(&sleep_spec, NULL); |
| } |
| } |
| |
| now_ms = get_monotonic_time_ms(); |
| if (debug) { |
| fprintf(stderr, "showing %s at %" PRId64 "\n", argv[0], now_ms); |
| } |
| ply_frame_buffer_show_image(buffer, image); |
| ply_image_free(image); |
| last_show_ms = now_ms; |
| } |
| } |
| |
| |
| #define FLAG_CLEAR 'c' |
| #define FLAG_DEBUG 'D' |
| #define FLAG_FRAME 'f' |
| #define FLAG_GAMMA 'g' |
| #define FLAG_HELP 'h' |
| #define FLAG_LOCATION 'l' |
| #define FLAG_SET_MONITORS 'm' |
| #define FLAG_OFFSET 'o' |
| #define FLAG_PRINT_RESOLUTION 'p' |
| |
| static struct option command_options[] = { |
| { "clear", required_argument, NULL, FLAG_CLEAR }, |
| { "debug", no_argument, NULL, FLAG_DEBUG }, |
| { "frame-interval", required_argument, NULL, FLAG_FRAME }, |
| { "gamma", required_argument, NULL, FLAG_GAMMA }, |
| { "help", no_argument, NULL, FLAG_HELP }, |
| { "location", required_argument, NULL, FLAG_LOCATION }, |
| { "set-monitors", no_argument, NULL, FLAG_SET_MONITORS }, |
| { "offset", required_argument, NULL, FLAG_OFFSET }, |
| { "print-resolution", no_argument, NULL, FLAG_PRINT_RESOLUTION }, |
| { NULL, 0, NULL, 0 }, |
| }; |
| |
| |
| int main(int argc, char **argv) { |
| int clear = 0; |
| bool location_assigned = false; |
| bool offset_assigned = false; |
| bool print_resolution = false; |
| ply_frame_buffer_t *buffer; |
| |
| /* |
| * TODO(jrbarnette): The command line parsing here and throughout |
| * the code is a bit rudimentary, especially w.r.t. error |
| * checking. This command is only invoked in a handful of places |
| * in Chromium OS; outside of those cases, you should be grateful |
| * any time the error message isn't |
| * Segmentation fault (core dumped) |
| * :-) |
| */ |
| for (;;) { |
| int c = getopt_long(argc, argv, "", command_options, NULL); |
| if (c == -1) { |
| break; |
| } |
| |
| if (c == FLAG_HELP) { |
| usage(); |
| } |
| |
| if (c == FLAG_DEBUG) { |
| debug = 1; |
| continue; |
| } |
| |
| if (c == FLAG_CLEAR) { |
| clear = 1; |
| if (!parse_color(optarg)) { |
| usage(); |
| } |
| continue; |
| } |
| |
| if (c == FLAG_FRAME) { |
| if (!set_frame_interval(optarg)) { |
| usage(); |
| } |
| continue; |
| } |
| |
| if (c == FLAG_GAMMA) { |
| // Not all platforms necessarily have a gamma ramp file, so we |
| // ignore errors from this call. |
| (void) ply_gamma_set(optarg); |
| continue; |
| } |
| |
| if (c == FLAG_LOCATION) { |
| if (!parse_location(optarg)) { |
| usage(); |
| } |
| location_assigned = true; |
| continue; |
| } |
| |
| if (c == FLAG_SET_MONITORS) { |
| ply_monitor_set_monitors = true; |
| continue; |
| } |
| |
| if (c == FLAG_OFFSET) { |
| if (!parse_offset(optarg)) { |
| usage(); |
| } |
| offset_assigned = true; |
| continue; |
| } |
| |
| if (c == FLAG_PRINT_RESOLUTION) { |
| print_resolution = true; |
| continue; |
| } |
| } |
| |
| if (location_assigned && offset_assigned) { |
| fprintf(stderr, |
| "--location and --offset should not be assigned at same time.\n"); |
| usage(); |
| } |
| |
| buffer = ply_frame_buffer_new(NULL); |
| if (!ply_frame_buffer_open(buffer)) { |
| perror("could not open framebuffer"); |
| return errno; |
| } |
| |
| ply_frame_buffer_area_t fb_area; |
| ply_frame_buffer_get_size(buffer, &fb_area); |
| |
| if (print_resolution) { |
| printf("%zu %zu\n", fb_area.visible_width, fb_area.visible_height); |
| } |
| |
| if (debug) { |
| fprintf(stderr, "screen dimensions: %lux%lu\n", |
| fb_area.width, fb_area.height); |
| } |
| |
| if (clear) { |
| ply_frame_buffer_clear(buffer, clear_color); |
| } |
| |
| process_images(buffer, argc - optind, argv + optind); |
| return EXIT_SUCCESS; |
| } |