From 7abfb48551224ac2b4a8d1820c023915e31ffb5f Mon Sep 17 00:00:00 2001 From: Iliyan Malchev Date: Thu, 23 Apr 2009 13:14:13 -0700 Subject: [PATCH] yuv420sp2rgb: a tool to convert from YUV 4:2:0 semiplanar to various RGB formats Signed-off-by: Iliyan Malchev --- tools/yuv420sp2rgb/Android.mk | 16 ++ tools/yuv420sp2rgb/cmdline.c | 147 +++++++++++++ tools/yuv420sp2rgb/cmdline.h | 15 ++ tools/yuv420sp2rgb/debug.c | 38 ++++ tools/yuv420sp2rgb/debug.h | 90 ++++++++ tools/yuv420sp2rgb/yuv420sp2rgb.c | 339 ++++++++++++++++++++++++++++++ 6 files changed, 645 insertions(+) create mode 100644 tools/yuv420sp2rgb/Android.mk create mode 100644 tools/yuv420sp2rgb/cmdline.c create mode 100644 tools/yuv420sp2rgb/cmdline.h create mode 100644 tools/yuv420sp2rgb/debug.c create mode 100644 tools/yuv420sp2rgb/debug.h create mode 100644 tools/yuv420sp2rgb/yuv420sp2rgb.c diff --git a/tools/yuv420sp2rgb/Android.mk b/tools/yuv420sp2rgb/Android.mk new file mode 100644 index 000000000..b7e780248 --- /dev/null +++ b/tools/yuv420sp2rgb/Android.mk @@ -0,0 +1,16 @@ +# Copyright 2005 Google Inc. All Rights Reserved. +# +# Android.mk for yuv420sp2rgb +# + +LOCAL_PATH:= $(call my-dir) + +ifeq ($(TARGET_ARCH),arm) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := yuv420sp2rgb.c cmdline.c debug.c + +LOCAL_MODULE := yuv420sp2rgb + +include $(BUILD_HOST_EXECUTABLE) +endif diff --git a/tools/yuv420sp2rgb/cmdline.c b/tools/yuv420sp2rgb/cmdline.c new file mode 100644 index 000000000..eb870b56c --- /dev/null +++ b/tools/yuv420sp2rgb/cmdline.c @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include +#include + +extern char *optarg; +extern int optind, opterr, optopt; + +static struct option long_options[] = { + {"output", required_argument, 0, 'o'}, + {"height", required_argument, 0, 'h'}, + {"width", required_argument, 0, 'w'}, + {"gray", no_argument, 0, 'g'}, + {"type", required_argument, 0, 't'}, + {"rotate", required_argument, 0, 'r'}, + {"verbose", no_argument, 0, 'V'}, + {"help", no_argument, 0, 1}, + {0, 0, 0, 0}, +}; + +/* This array must parallel long_options[] */ +static const char *descriptions[] = { + "output file", + "image height in pixels", + "image width in pixels", + "process the luma plane only", + "encode as one of { 'ppm', 'rgb', or 'argb' }", + "rotate (90, -90, 180 degrees)", + "print verbose output", + "print this help screen", +}; + +void print_help(const char *name) { + fprintf(stdout, + "Converts yuv 4:2:0 to rgb24 and generates a PPM file.\n" + "invokation:\n" + "\t%s infile --height --width --output -t [ --gray ] [ --rotate ] [ --verbose ]\n" + "\t%s infile --help\n", + name, name); + fprintf(stdout, "options:\n"); + struct option *opt = long_options; + const char **desc = descriptions; + while (opt->name) { + fprintf(stdout, "\t-%c/--%s%s: %s\n", + isprint(opt->val) ? opt->val : ' ', + opt->name, + (opt->has_arg ? " (argument)" : ""), + *desc); + opt++; + desc++; + } +} + +int get_options(int argc, char **argv, + char **outfile, + int *height, + int *width, + int *gray, + char **type, + int *rotate, + int *verbose) { + int c; + + ASSERT(outfile); *outfile = NULL; + ASSERT(height); *height = -1; + ASSERT(width); *width = -1; + ASSERT(gray); *gray = 0; + ASSERT(rotate); *rotate = 0; + ASSERT(verbose); *verbose = 0; + ASSERT(type); *type = NULL; + + while (1) { + /* getopt_long stores the option index here. */ + int option_index = 0; + + c = getopt_long (argc, argv, + "Vgo:h:w:r:t:", + long_options, + &option_index); + /* Detect the end of the options. */ + if (c == -1) break; + + if (isgraph(c)) { + INFO ("option -%c with value `%s'\n", c, (optarg ?: "(null)")); + } + +#define SET_STRING_OPTION(name) do { \ + ASSERT(optarg); \ + (*name) = strdup(optarg); \ +} while(0) + +#define SET_INT_OPTION(val) do { \ + ASSERT(optarg); \ + if (strlen(optarg) >= 2 && optarg[0] == '0' && optarg[1] == 'x') { \ + FAILIF(1 != sscanf(optarg+2, "%x", val), \ + "Expecting a hexadecimal argument!\n"); \ + } else { \ + FAILIF(1 != sscanf(optarg, "%d", val), \ + "Expecting a decimal argument!\n"); \ + } \ +} while(0) + + switch (c) { + case 0: + /* If this option set a flag, do nothing else now. */ + if (long_options[option_index].flag != 0) + break; + INFO ("option %s", long_options[option_index].name); + if (optarg) + INFO (" with arg %s", optarg); + INFO ("\n"); + break; + case 1: print_help(argv[0]); exit(1); break; + case 'o': + SET_STRING_OPTION(outfile); + break; + case 't': + SET_STRING_OPTION(type); + break; + case 'h': + SET_INT_OPTION(height); + break; + case 'w': + SET_INT_OPTION(width); + break; + case 'r': + SET_INT_OPTION(rotate); + break; + case 'g': *gray = 1; break; + case 'V': *verbose = 1; break; + case '?': + /* getopt_long already printed an error message. */ + break; + +#undef SET_STRING_OPTION +#undef SET_INT_OPTION + + default: + FAILIF(1, "Unknown option"); + } + } + + return optind; +} diff --git a/tools/yuv420sp2rgb/cmdline.h b/tools/yuv420sp2rgb/cmdline.h new file mode 100644 index 000000000..49760adbf --- /dev/null +++ b/tools/yuv420sp2rgb/cmdline.h @@ -0,0 +1,15 @@ +#ifndef CMDLINE_H +#define CMDLINE_H + +void print_help(const char *executable_name); + +extern int get_options(int argc, char **argv, + char **outfile, + int *height, + int *width, + int *gray, + char **type, + int *rotate, + int *verbose); + +#endif/*CMDLINE_H*/ diff --git a/tools/yuv420sp2rgb/debug.c b/tools/yuv420sp2rgb/debug.c new file mode 100644 index 000000000..263e09f32 --- /dev/null +++ b/tools/yuv420sp2rgb/debug.c @@ -0,0 +1,38 @@ +#include +#include +#include + +#define NUM_COLS (32) + +int dump_hex_buffer(FILE *s, void *b, size_t len, size_t elsize) { + int num_nonprintable = 0; + int i, last; + char *pchr = (char *)b; + fputc('\n', s); + fprintf(s, "%p: ", b); + for (i = last = 0; i < len; i++) { + if (!elsize) { + if (i && !(i % 4)) fprintf(s, " "); + if (i && !(i % 8)) fprintf(s, " "); + } else { + if (i && !(i % elsize)) fprintf(s, " "); + } + + if (i && !(i % NUM_COLS)) { + while (last < i) { + if (isprint(pchr[last])) + fputc(pchr[last], s); + else { + fputc('.', s); + num_nonprintable++; + } + last++; + } + fprintf(s, " (%d)\n%p: ", i, b); + } + fprintf(s, "%02x", (unsigned char)pchr[i]); + } + if (i && (i % NUM_COLS)) fputs("\n", s); + return num_nonprintable; +} + diff --git a/tools/yuv420sp2rgb/debug.h b/tools/yuv420sp2rgb/debug.h new file mode 100644 index 000000000..9bbf47fa3 --- /dev/null +++ b/tools/yuv420sp2rgb/debug.h @@ -0,0 +1,90 @@ +#ifndef DEBUG_H +#define DEBUG_H + +#include +#include + +#define unlikely(expr) __builtin_expect (expr, 0) +#define likely(expr) __builtin_expect (expr, 1) + +#ifdef DEBUG + + #define FAILIF(cond, msg...) do { \ + if (unlikely(cond)) { \ + fprintf(stderr, "%s(%d): ", __FILE__, __LINE__); \ + fprintf(stderr, ##msg); \ + exit(1); \ + } \ +} while(0) + +/* Debug enabled */ + #define ASSERT(x) do { \ + if (unlikely(!(x))) { \ + fprintf(stderr, \ + "ASSERTION FAILURE %s:%d: [%s]\n", \ + __FILE__, __LINE__, #x); \ + exit(1); \ + } \ +} while(0) + +#else + + #define FAILIF(cond, msg...) do { \ + if (unlikely(cond)) { \ + fprintf(stderr, ##msg); \ + exit(1); \ + } \ +} while(0) + +/* No debug */ + #define ASSERT(x) do { } while(0) + +#endif/* DEBUG */ + +#define FAILIF_LIBELF(cond, function) \ + FAILIF(cond, "%s(): %s\n", #function, elf_errmsg(elf_errno())); + +static inline void *MALLOC(unsigned int size) { + void *m = malloc(size); + FAILIF(NULL == m, "malloc(%d) failed!\n", size); + return m; +} + +static inline void *CALLOC(unsigned int num_entries, unsigned int entry_size) { + void *m = calloc(num_entries, entry_size); + FAILIF(NULL == m, "calloc(%d, %d) failed!\n", num_entries, entry_size); + return m; +} + +static inline void *REALLOC(void *ptr, unsigned int size) { + void *m = realloc(ptr, size); + FAILIF(NULL == m, "realloc(%p, %d) failed!\n", ptr, size); + return m; +} + +static inline void FREE(void *ptr) { + free(ptr); +} + +static inline void FREEIF(void *ptr) { + if (ptr) FREE(ptr); +} + +#define PRINT(x...) do { \ + extern int quiet_flag; \ + if(likely(!quiet_flag)) \ + fprintf(stdout, ##x); \ +} while(0) + +#define ERROR PRINT + +#define INFO(x...) do { \ + extern int verbose_flag; \ + if(unlikely(verbose_flag)) \ + fprintf(stdout, ##x); \ +} while(0) + +/* Prints a hex and ASCII dump of the selected buffer to the selected stream. */ +int dump_hex_buffer(FILE *s, void *b, size_t l, size_t elsize); + +#endif/*DEBUG_H*/ diff --git a/tools/yuv420sp2rgb/yuv420sp2rgb.c b/tools/yuv420sp2rgb/yuv420sp2rgb.c new file mode 100644 index 000000000..6fe82e5d0 --- /dev/null +++ b/tools/yuv420sp2rgb/yuv420sp2rgb.c @@ -0,0 +1,339 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef max +#define max(a,b) ({typeof(a) _a = (a); typeof(b) _b = (b); _a > _b ? _a : _b; }) +#define min(a,b) ({typeof(a) _a = (a); typeof(b) _b = (b); _a < _b ? _a : _b; }) +#endif + +#define CONVERT_TYPE_PPM 0 +#define CONVERT_TYPE_RGB 1 +#define CONVERT_TYPE_ARGB 2 + +/* + YUV 4:2:0 image with a plane of 8 bit Y samples followed by an interleaved + U/V plane containing 8 bit 2x2 subsampled chroma samples. + except the interleave order of U and V is reversed. + + H V + Y Sample Period 1 1 + U (Cb) Sample Period 2 2 + V (Cr) Sample Period 2 2 + */ + +typedef struct rgb_context { + unsigned char *buffer; + int width; + int height; + int rotate; + int i; + int j; + int size; /* for debugging */ +} rgb_context; + +typedef void (*rgb_cb)( + unsigned char r, + unsigned char g, + unsigned char b, + rgb_context *ctx); + +const int bytes_per_pixel = 2; + +static void color_convert_common( + unsigned char *pY, unsigned char *pUV, + int width, int height, + unsigned char *buffer, + int size, /* buffer size in bytes */ + int gray, + int rotate, + rgb_cb cb) +{ + int i, j; + int nR, nG, nB; + int nY, nU, nV; + rgb_context ctx; + + ctx.buffer = buffer; + ctx.size = size; /* debug */ + ctx.width = width; + ctx.height = height; + ctx.rotate = rotate; + + if (gray) { + for (i = 0; i < height; i++) { + for (j = 0; j < width; j++) { + nB = *(pY + i * width + j); + ctx.i = i; + ctx.j = j; + cb(nB, nB, nB, &ctx); + } + } + } else { + // YUV 4:2:0 + for (i = 0; i < height; i++) { + for (j = 0; j < width; j++) { + nY = *(pY + i * width + j); + nV = *(pUV + (i/2) * width + bytes_per_pixel * (j/2)); + nU = *(pUV + (i/2) * width + bytes_per_pixel * (j/2) + 1); + + // Yuv Convert + nY -= 16; + nU -= 128; + nV -= 128; + + if (nY < 0) + nY = 0; + + // nR = (int)(1.164 * nY + 2.018 * nU); + // nG = (int)(1.164 * nY - 0.813 * nV - 0.391 * nU); + // nB = (int)(1.164 * nY + 1.596 * nV); + + nB = (int)(1192 * nY + 2066 * nU); + nG = (int)(1192 * nY - 833 * nV - 400 * nU); + nR = (int)(1192 * nY + 1634 * nV); + + nR = min(262143, max(0, nR)); + nG = min(262143, max(0, nG)); + nB = min(262143, max(0, nB)); + + nR >>= 10; nR &= 0xff; + nG >>= 10; nG &= 0xff; + nB >>= 10; nB &= 0xff; + + ctx.i = i; + ctx.j = j; + cb(nR, nG, nB, &ctx); + } + } + } +} + +static void rgb16_cb( + unsigned char r, + unsigned char g, + unsigned char b, + rgb_context *ctx) +{ + unsigned short *rgb16 = (unsigned short *)ctx->buffer; + *(rgb16 + ctx->i * ctx->width + ctx->j) = b | (g << 5) | (r << 11); +} + +static void common_rgb_cb( + unsigned char r, + unsigned char g, + unsigned char b, + rgb_context *ctx, + int alpha) +{ + unsigned char *out = ctx->buffer; + int offset = 0; + int bpp; + int i = 0; + switch(ctx->rotate) { + case 0: /* no rotation */ + offset = ctx->i * ctx->width + ctx->j; + break; + case 1: /* 90 degrees */ + offset = ctx->height * (ctx->j + 1) - ctx->i; + break; + case 2: /* 180 degrees */ + offset = (ctx->height - 1 - ctx->i) * ctx->width + ctx->j; + break; + case 3: /* 270 degrees */ + offset = (ctx->width - 1 - ctx->j) * ctx->height + ctx->i; + break; + default: + FAILIF(1, "Unexpected roation value %d!\n", ctx->rotate); + } + + bpp = 3 + !!alpha; + offset *= bpp; + FAILIF(offset < 0, "point (%d, %d) generates a negative offset.\n", ctx->i, ctx->j); + FAILIF(offset + bpp > ctx->size, "point (%d, %d) at offset %d exceeds the size %d of the buffer.\n", + ctx->i, ctx->j, + offset, + ctx->size); + + out += offset; + + if (alpha) out[i++] = 0xff; + out[i++] = r; + out[i++] = g; + out[i] = b; +} + +static void rgb24_cb( + unsigned char r, + unsigned char g, + unsigned char b, + rgb_context *ctx) +{ + return common_rgb_cb(r,g,b,ctx,0); +} + +static void argb_cb( + unsigned char r, + unsigned char g, + unsigned char b, + rgb_context *ctx) +{ + return common_rgb_cb(r,g,b,ctx,1); +} + +static void convert(const char *infile, + const char *outfile, + int height, + int width, + int gray, + int type, + int rotate) +{ + void *in, *out; + int ifd, ofd, rc; + int psz = getpagesize(); + static char header[1024]; + int header_size; + size_t outsize; + + int bpp = 3; + switch (type) { + case CONVERT_TYPE_PPM: + PRINT("encoding PPM\n"); + if (rotate & 1) + header_size = snprintf(header, sizeof(header), "P6\n%d %d\n255\n", height, width); + else + header_size = snprintf(header, sizeof(header), "P6\n%d %d\n255\n", width, height); + break; + case CONVERT_TYPE_RGB: + PRINT("encoding raw RGB24\n"); + header_size = 0; + break; + case CONVERT_TYPE_ARGB: + PRINT("encoding raw ARGB\n"); + header_size = 0; + bpp = 4; + break; + } + + outsize = header_size + width * height * bpp; + outsize = (outsize + psz - 1) & ~(psz - 1); + + INFO("Opening input file %s\n", infile); + ifd = open(infile, O_RDONLY); + FAILIF(ifd < 0, "open(%s) failed: %s (%d)\n", + infile, strerror(errno), errno); + + INFO("Opening output file %s\n", outfile); + ofd = open(outfile, O_RDWR | O_CREAT, 0664); + FAILIF(ofd < 0, "open(%s) failed: %s (%d)\n", + outfile, strerror(errno), errno); + + INFO("Memory-mapping input file %s\n", infile); + in = mmap(0, width * height * 3 / 2, PROT_READ, MAP_PRIVATE, ifd, 0); + FAILIF(in == MAP_FAILED, "could not mmap input file: %s (%d)\n", + strerror(errno), errno); + + INFO("Truncating output file %s to %d bytes\n", outfile, outsize); + FAILIF(ftruncate(ofd, outsize) < 0, + "Could not truncate output file to required size: %s (%d)\n", + strerror(errno), errno); + + INFO("Memory mapping output file %s\n", outfile); + out = mmap(0, outsize, PROT_WRITE, MAP_SHARED, ofd, 0); + FAILIF(out == MAP_FAILED, "could not mmap output file: %s (%d)\n", + strerror(errno), errno); + + INFO("PPM header (%d) bytes:\n%s\n", header_size, header); + FAILIF(write(ofd, header, header_size) != header_size, + "Error wrinting PPM header: %s (%d)\n", + strerror(errno), errno); + + INFO("Converting %dx%d YUV 4:2:0 to RGB24...\n", width, height); + color_convert_common(in, in + width * height, + width, height, + out + header_size, outsize - header_size, + gray, rotate, + type == CONVERT_TYPE_ARGB ? argb_cb : rgb24_cb); +} + +int verbose_flag; +int quiet_flag; + +int main(int argc, char **argv) { + + char *infile, *outfile, *type; + int height, width, gray, rotate; + int cmdline_error = 0; + + /* Parse command-line arguments. */ + + int first = get_options(argc, argv, + &outfile, + &height, + &width, + &gray, + &type, + &rotate, + &verbose_flag); + + if (first == argc) { + ERROR("You must specify an input file!\n"); + cmdline_error++; + } + if (!outfile) { + ERROR("You must specify an output file!\n"); + cmdline_error++; + } + if (height < 0 || width < 0) { + ERROR("You must specify both image height and width!\n"); + cmdline_error++; + } + + FAILIF(rotate % 90, "Rotation angle must be a multiple of 90 degrees!\n"); + + rotate /= 90; + rotate %= 4; + if (rotate < 0) rotate += 4; + + if (cmdline_error) { + print_help(argv[0]); + exit(1); + } + + infile = argv[first]; + + INFO("input file: [%s]\n", infile); + INFO("output file: [%s]\n", outfile); + INFO("height: %d\n", height); + INFO("width: %d\n", width); + INFO("gray only: %d\n", gray); + INFO("encode as: %s\n", type); + INFO("rotation: %d\n", rotate); + + /* Convert the image */ + + int conv_type; + if (!type || !strcmp(type, "ppm")) + conv_type = CONVERT_TYPE_PPM; + else if (!strcmp(type, "rgb")) + conv_type = CONVERT_TYPE_RGB; + else if (!strcmp(type, "argb")) + conv_type = CONVERT_TYPE_ARGB; + else FAILIF(1, "Unknown encoding type %s.\n", type); + + convert(infile, outfile, + height, width, gray, + conv_type, + rotate); + + free(outfile); + return 0; +}