blob: aa5970802b58bca82b40b9983fd73b335d6d2bc7 [file] [log] [blame]
/* 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_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"
" --no-set-monitors don't 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_NO_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 },
{ "no-set-monitors", no_argument, NULL, FLAG_NO_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;
bool set_monitors = true;
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_NO_SET_MONITORS) {
set_monitors = false;
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);
ply_monitor_setup(&fb_area, set_monitors);
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;
}