blob: f98901c662a1ba3f3a207fc26635cfe9922fed97 [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 <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-utils.h"
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;
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 uint32_t *ply_image_get_data(ply_image_t *image) {
assert(image != NULL);
return image->layout.as_pixels;
}
static long ply_image_get_width(ply_image_t *image) {
assert(image != NULL);
return image->width;
}
static long ply_image_get_height(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 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_file(
ply_frame_buffer_t *buffer, const char *path) {
ply_image_t *image;
uint32_t *data;
ply_frame_buffer_area_t area, buffer_area;
bool fill_succeeded;
image = ply_image_from_file(path);
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.width - area.width) / 2;
area.y = (long) (buffer_area.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;
fill_succeeded = ply_frame_buffer_fill(buffer, &area, data);
if (!fill_succeeded)
fprintf(stderr, "image was not displayed\n");
ply_image_free(image);
}
static int usage(void) {
fprintf(stderr,
"usage: ply-image --clear <hexcolor>\n"
" ply-image [--gamma <gammafile>] "
"[--location <x>,<y> | --offset <[+|-]x>,<[+|-]y>] "
"<frame> [<frame> ...]\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);
}
#define MS_PER_SEC 1000
#define NS_PER_SEC (1000 * 1000 * 1000)
#define NS_PER_MS (NS_PER_SEC / MS_PER_SEC)
#define FRAMES_PER_SEC 20
/* Frame rate as milliseconds per frame */
static long frame_interval_ms = MS_PER_SEC / 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. The
* animation is timed so that the average time per frame is
* frame_interval_ms milliseconds.
*/
static void process_images(
ply_frame_buffer_t *buffer, int argc, char *argv[]) {
struct timespec curr_time, last_time;
int64_t sleep_ns = 0;
clock_gettime(CLOCK_MONOTONIC, &last_time);
while (argc > 0) {
if (sleep_ns > 0) {
struct timespec sleep_time;
sleep_time.tv_sec = sleep_ns / NS_PER_SEC;
sleep_time.tv_nsec = sleep_ns % NS_PER_SEC;
nanosleep(&sleep_time, NULL);
} else {
sleep_ns = 0;
}
ply_frame_buffer_show_file(buffer, argv[0]);
clock_gettime(CLOCK_MONOTONIC, &curr_time);
sleep_ns += frame_interval_ms * NS_PER_MS +
NS_PER_SEC * (last_time.tv_sec - curr_time.tv_sec) +
last_time.tv_nsec - curr_time.tv_nsec;
last_time = curr_time;
argc--;
argv++;
}
}
#define FLAG_CLEAR 'c'
#define FLAG_FRAME 'f'
#define FLAG_GAMMA 'g'
#define FLAG_HELP 'h'
#define FLAG_LOCATION 'l'
#define FLAG_OFFSET 'o'
static struct option command_options[] = {
{ "clear", required_argument, NULL, FLAG_CLEAR },
{ "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 },
{ "offset", required_argument, NULL, FLAG_OFFSET },
{ NULL, 0, NULL, 0 },
};
int main(int argc, char **argv) {
int clear = 0;
bool location_assigned = false, offset_assigned = 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_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_OFFSET) {
if (!parse_offset(optarg)) {
usage();
}
offset_assigned = 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;
}
if (clear) {
ply_frame_buffer_clear(buffer, clear_color);
}
process_images(buffer, argc - optind, argv + optind);
return EXIT_SUCCESS;
}