/* See LICENSE file for copyright and license details. */ #define LIBBLIND_NO_NAMESPACE #include "libblind.h" #include #include #include #include #include #include extern int writeall(int fd, const void *buf, size_t n); #if defined(POSIX_FADV_SEQUENTIAL) # define fadvise_sequential(...) posix_fadvise(__VA_ARGS__, POSIX_FADV_SEQUENTIAL) #else # define fadvise_sequential(...) #endif #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define ELEMSOF(A) (sizeof(A) / sizeof(*(A))) #define X(NUM, NAM, STR) ,STR static const char *error_names[] = { "Success", NULL LIBBLIND_LIST_ERRORS(X) }; #undef X const char * libblind_strerror(int errnum) { const char *ret; errnum = -errnum; if (errnum < 0 || errnum >= ELEMSOF(error_names)) return "Unknown libblind error"; ret = error_names[errnum]; return ret ? ret : strerror(errno); } void libblind_perror(const char *prefix, int errnum) { fprintf(stderr, "%s%s%s\n", prefix ? prefix : "", prefix ? ": " : "", libblind_strerror(errnum)); } const char * libblind_get_pixfmt(const char *specified, const char *current) { enum colour_space space = CIEXYZ; enum alpha alpha = UNPREMULTIPLIED; enum encoding encoding = UINT16; int level = -1; size_t n = strlen(specified); if ((n >= 2 && !strcmp(specified - 2, " f")) || !strcmp(specified, "raw0") || !strcmp(specified, "raw1") || !strcmp(specified, "raw2") || !strcmp(specified, "raw2a")) return specified; if (!strcmp(current, "xyza")) space = CIEXYZ, encoding = DOUBLE; else if (!strcmp(current, "xyza f")) space = CIEXYZ, encoding = FLOAT; else if (!strcmp(current, "raw0")) level = 0; else if (!strcmp(current, "raw1")) level = 1; else if (!strcmp(current, "raw2")) level = 2, alpha = NO_ALPHA; else if (!strcmp(current, "raw2a")) level = 2; else if (!strcmp(current, "raw3")) level = 3, encoding = DOUBLE, alpha = NO_ALPHA; else if (!strcmp(current, "raw3a")) level = 3, encoding = DOUBLE; else if (!strcmp(current, "raw3 f")) level = 3, encoding = FLOAT, alpha = NO_ALPHA; else if (!strcmp(current, "raw3a f")) level = 3, encoding = FLOAT; else if (!strcmp(current, "raw4")) level = 4, encoding = DOUBLE, alpha = NO_ALPHA; else if (!strcmp(current, "raw4a")) level = 4, encoding = DOUBLE; else if (!strcmp(current, "raw4 f")) level = 4, encoding = FLOAT, alpha = NO_ALPHA; else if (!strcmp(current, "raw4a f")) level = 4, encoding = FLOAT; else if (!strcmp(current, "raw5")) level = 5, encoding = DOUBLE, alpha = NO_ALPHA; else if (!strcmp(current, "raw5a")) level = 5, encoding = DOUBLE; else if (!strcmp(current, "raw5 f")) level = 5, encoding = FLOAT, alpha = NO_ALPHA; else if (!strcmp(current, "raw5a f")) level = 5, encoding = FLOAT; else return specified; if (!strcmp(specified, "f")) encoding = FLOAT; else if (!strcmp(specified, "!f")) encoding = DOUBLE; else if (!strcmp(specified, "xyza")) level = -1, alpha = UNPREMULTIPLIED, space = CIEXYZ; else if (!strcmp(specified, "raw3")) level = 3, alpha = NO_ALPHA; else if (!strcmp(specified, "raw3a")) level = 3, alpha = UNPREMULTIPLIED; else if (!strcmp(specified, "raw4")) level = 4, alpha = NO_ALPHA; else if (!strcmp(specified, "raw4a")) level = 4, alpha = UNPREMULTIPLIED; else if (!strcmp(specified, "raw5")) level = 5, alpha = NO_ALPHA; else if (!strcmp(specified, "raw5a")) level = 5, alpha = UNPREMULTIPLIED; else if (!strcmp(specified, "xyza !f")) return "xyza"; else if (!strcmp(specified, "raw3 !f")) return "raw3"; else if (!strcmp(specified, "raw3a !f")) return "raw3a"; else if (!strcmp(specified, "raw4 !f")) return "raw4"; else if (!strcmp(specified, "raw4a !f")) return "raw4a"; else if (!strcmp(specified, "raw5 !f")) return "raw5"; else if (!strcmp(specified, "raw5a !f")) return "raw5a"; else return specified; if (level == 0 && encoding == UINT16) return "raw0"; else if (level == 1 && encoding == UINT16) return "raw1"; else if (level == 2 && encoding == UINT16) return alpha ? "raw2a" : "raw2"; else if (level == 3 && encoding == DOUBLE) return alpha ? "raw3a" : "raw3"; else if (level == 3 && encoding == FLOAT) return alpha ? "raw3a f" : "raw3 f"; else if (level == 4 && encoding == DOUBLE) return alpha ? "raw4a" : "raw4"; else if (level == 4 && encoding == FLOAT) return alpha ? "raw4a f" : "raw4 f"; else if (level == 5 && encoding == DOUBLE) return alpha ? "raw5a" : "raw5"; else if (level == 5 && encoding == FLOAT) return alpha ? "raw5a f" : "raw5 f"; else if (level < 0 && space == CIEXYZ && alpha == UNPREMULTIPLIED) return encoding == FLOAT ? "xyza f" : encoding == DOUBLE ? "xyza" : specified; else return specified; } int libblind_set_pixfmt(struct libblind_stream *stream, const char *pixfmt) { #define TEST_ENCODING_AGNOSTIC(FMT) (!strcmp(stream->pixfmt, FMT) || !strcmp(stream->pixfmt, FMT" f")) if (pixfmt) { pixfmt = get_pixfmt(pixfmt, stream->pixfmt[0] ? stream->pixfmt : "xyza"); if (strlen(pixfmt) >= sizeof(stream->pixfmt)) return LIBBLIND_EPIXFMTNOSUPPORT; strcpy(stream->pixfmt, pixfmt); } stream->n_chan = 4; stream->alpha = UNPREMULTIPLIED; stream->encoding = DOUBLE; stream->endian = HOST; stream->alpha_chan = 3; stream->luma_chan = -1; if (!strcmp(stream->pixfmt, "xyza")) { stream->space = CIEXYZ; } else if (!strcmp(stream->pixfmt, "xyza f")) { stream->space = CIEXYZ; stream->encoding = FLOAT; } else if (!strcmp(stream->pixfmt, "raw0")) { stream->space = YUV_NONLINEAR; stream->encoding = UINT16; stream->endian = LITTLE; stream->alpha_chan = 0; stream->luma_chan = 1; } else if (!strcmp(stream->pixfmt, "raw1")) { stream->space = YUV_NONLINEAR; stream->encoding = UINT16; stream->endian = LITTLE; } else if (!strcmp(stream->pixfmt, "raw2a") || !strcmp(stream->pixfmt, "raw2")) { stream->space = YUV_NONLINEAR; stream->alpha = stream->pixfmt[4] == 'a' ? UNPREMULTIPLIED : NO_ALPHA; stream->encoding = UINT16; } else if (TEST_ENCODING_AGNOSTIC("raw3") || TEST_ENCODING_AGNOSTIC("raw3a")) { stream->space = YUV_NONLINEAR; stream->alpha = stream->pixfmt[4] == 'a' ? UNPREMULTIPLIED : NO_ALPHA; stream->encoding = strlen(stream->pixfmt) > 5 ? FLOAT : DOUBLE; } else if (TEST_ENCODING_AGNOSTIC("raw4") || TEST_ENCODING_AGNOSTIC("raw4a")) { stream->space = SRGB_NONLINEAR; stream->alpha = stream->pixfmt[4] == 'a' ? UNPREMULTIPLIED : NO_ALPHA; stream->encoding = strlen(stream->pixfmt) > 5 ? FLOAT : DOUBLE; } else if (TEST_ENCODING_AGNOSTIC("raw5") || TEST_ENCODING_AGNOSTIC("raw5a")) { stream->space = SRGB; stream->alpha = stream->pixfmt[4] == 'a' ? UNPREMULTIPLIED : NO_ALPHA; stream->encoding = strlen(stream->pixfmt) > 5 ? FLOAT : DOUBLE; } else { return LIBBLIND_EPIXFMTNOSUPPORT; } if (stream->alpha == NO_ALPHA) { stream->n_chan -= 1; stream->alpha_chan = -1; } if (stream->luma_chan == -1) { if (stream->space == CIEXYZ) stream->luma_chan = 1; else if (stream->space == YUV_NONLINEAR) stream->luma_chan = 0; } switch (stream->encoding) { case FLOAT: stream->chan_size = sizeof(float); break; case DOUBLE: stream->chan_size = sizeof(double); break; case LONG_DOUBLE: stream->chan_size = sizeof(long double); break; case UINT8: stream->chan_size = sizeof(uint8_t); break; case UINT16: stream->chan_size = sizeof(uint16_t); break; case UINT32: stream->chan_size = sizeof(uint32_t); break; case UINT64: stream->chan_size = sizeof(uint64_t); break; default: return LIBBLIND_ECORRUPTSTATE; } stream->pixel_size = stream->n_chan * stream->chan_size; stream->row_size = stream->pixel_size * stream->width; stream->col_size = stream->pixel_size * stream->height; stream->frame_size = stream->pixel_size * stream->height * stream->width; return 0; #undef TEST_ENCODING_AGNOSTIC } ssize_t libblind_read_stream(struct libblind_stream *stream, size_t n) { ssize_t r; n = MIN(n, sizeof(stream->buf) - stream->ptr); r = read(stream->fd, &stream->buf[stream->ptr], n); if (r > 0) stream->ptr += (size_t)r; return r; /* LIBBLIND_ESYSTEM == -1 and read(3p) explicitly specifies -1 on error */ } static inline int get_dimension(size_t *out, const char *s, int dimerr) { char *end; int saved_errno = errno; errno = 0; *out = strtoul(s, &end, 10); if (errno == ERANGE && *s != '-') return dimerr; errno = saved_errno; return LIBBLIND_EBADFORMAT; } int libblind_init_stream(struct libblind_stream *stream) { char *p = NULL, *w, *h, *f; size_t n; ssize_t r; int err; if (stream->fd >= 0) { fadvise_sequential(stream->fd, 0, 0); for (stream->ptr = 0; !p; p = memchr(stream->buf, '\n', stream->ptr)) if ((r = libblind_read_stream(stream, SIZE_MAX)) <= 0) return r ? r : LIBBLIND_EBADFORMAT; } else { p = memchr(stream->buf, '\n', stream->ptr); } *p = '\0'; if (!(w = strchr(stream->buf, ' ')) || !(h = strchr(&w[1], ' ')) || !(f = strchr(&h[1], ' '))) return LIBBLIND_EBADFORMAT; *w++ = *h++ = *f++ = '\0'; if (strlen(f) >= sizeof(stream->pixfmt)) return LIBBLIND_EBADFORMAT; strcpy(stream->pixfmt, f); if ((err = get_dimension(&stream->frames, stream->buf, LIBBLIND_EVIDTOOLONG)) || (err = get_dimension(&stream->width, w, LIBBLIND_EVIDTOOWIDE)) || (err = get_dimension(&stream->height, h, LIBBLIND_EVIDTOOTALL))) return err; if (!stream->width) return LIBBLIND_EZEROWIDTH; if (!stream->height) return LIBBLIND_EZEROHEIGHT; n = (size_t)(p - stream->buf) + 1; memmove(stream->buf, &stream->buf[n], stream->ptr -= n); while (stream->ptr < 5) if ((r = libblind_read_stream(stream, SIZE_MAX)) <= 0) return r ? r : LIBBLIND_EBADFORMAT; if (stream->buf[0] != '\0' || stream->buf[1] != 'u' || stream->buf[2] != 'i' || stream->buf[3] != 'v' || stream->buf[4] != 'f') return LIBBLIND_EBADFORMAT; memmove(stream->buf, &stream->buf[5], stream->ptr -= 5); stream->headlen = n + 5; if ((err = libblind_set_pixfmt(stream, NULL))) return err; stream->xptr = 0; return 0; } int libblind_check_dimensions(const struct stream *stream, enum dimension dimensions) { size_t n; if (!stream->pixel_size) return LIBBLIND_ECORRUPTSTATE; n = SIZE_MAX / stream->pixel_size; if ((dimensions & WIDTH) && stream->width > n) return LIBBLIND_EFRMTOOWIDE; if ((dimensions & HEIGHT) && stream->height > n) return LIBBLIND_EFRMTOOTALL; if (!stream->width || !stream->height) return 0; if ((dimensions & (WIDTH | HEIGHT)) == (WIDTH | HEIGHT)) if (stream->width > n / stream->height) return LIBBLIND_EFRMTOOLARGE; if (!(dimensions & LENGTH)) return 0; if (dimensions & WIDTH) n /= stream->width; if (dimensions & HEIGHT) n /= stream->height; if (stream->frames > n) return LIBBLIND_EVIDTOOLARGE; return 0; } int libblind_read_segment(struct stream *stream, void *buf, size_t n) { char *buffer = buf; ssize_t r; size_t m; if (stream->ptr) { m = MIN(stream->ptr, n); memcpy(&buffer[stream->xptr], stream->buf, m); memmove(stream->buf, &stream->buf[m], stream->ptr -= m); stream->xptr += m; } for (; stream->xptr < n; stream->xptr += (size_t)r) { r = read(stream->fd, &buffer[stream->xptr], n - stream->xptr); if (r < 0) return LIBBLIND_ESYSTEM; if (!r) { if (!stream->xptr) break; return LIBBLIND_EINCOMPLETE; } } if (!stream->xptr) return 0; stream->xptr -= n; return 1; } ssize_t libblind_send_frames(struct libblind_stream *stream, int outfd, size_t frames, int *outfailed) { size_t h, w, p, n, ret; ssize_t r; for (ret = 0; ret < frames; ret++) { for (p = stream->pixel_size; p; p--) { for (h = stream->height; h; h--) { for (w = stream->width; w; w -= n) { if (!stream->ptr && (r = libblind_read_stream(stream, w)) <= 0) { if (outfailed) *outfailed = 0; if (!r) goto done; return r; } n = MIN(stream->ptr, p); if (outfd >= 0 && writeall(outfd, stream->buf, n)) { if (outfailed) *outfailed = 1; return LIBBLIND_ESYSTEM; } memmove(stream->buf, &stream->buf[n], stream->ptr -= n); } } } } return ret; done: if (p != stream->pixel_size) return (ssize_t)LIBBLIND_EINCOMPLETEFRAME; /* Note that due to order of the nested for-loops, we don't * know which unit is incomplete: pixel, row, or frame. */ return ret; } ssize_t libblind_send_rows(struct libblind_stream *stream, int outfd, size_t rows, int *outfailed) { size_t w, p, n, ret; ssize_t r; for (ret = 0; ret < rows; ret++) { for (p = stream->pixel_size; p; p--) { for (w = stream->width; w; w -= n) { if (!stream->ptr && (r = libblind_read_stream(stream, w)) <= 0) { if (outfailed) *outfailed = 0; if (!r) goto done; return r; } n = MIN(stream->ptr, p); if (outfd >= 0 && writeall(outfd, stream->buf, n)) { if (outfailed) *outfailed = 1; return LIBBLIND_ESYSTEM; } memmove(stream->buf, &stream->buf[n], stream->ptr -= n); } } } return ret; done: if (p != stream->pixel_size) return (ssize_t)LIBBLIND_EINCOMPLETEROW; /* Note that due to order of the nested for-loops, we don't * know whether a pixel is incomplete or just a row. */ return ret; } ssize_t libblind_send_pixels(struct libblind_stream *stream, int outfd, size_t pixels, int *outfailed) { size_t p, n, ret; ssize_t r; for (ret = 0; ret < pixels; ret++) { for (p = stream->pixel_size; p; p -= n) { if (!stream->ptr && (r = libblind_read_stream(stream, p)) <= 0) { if (outfailed) *outfailed = 0; if (!r) goto done; return r; } n = MIN(stream->ptr, p); if (outfd >= 0 && writeall(outfd, stream->buf, n)) { if (outfailed) *outfailed = 1; return LIBBLIND_ESYSTEM; } memmove(stream->buf, &stream->buf[n], stream->ptr -= n); } } return ret; done: if (p != stream->pixel_size) return (ssize_t)LIBBLIND_EINCOMPLETEPIX; return ret; } int libblind_send_stream(struct libblind_stream *stream, int outfd, int *outfailed) { ssize_t r; do { if (writeall(outfd, stream->buf, stream->ptr)) { if (outfailed) *outfailed = 1; return LIBBLIND_ESYSTEM; } stream->ptr = 0; } while ((r = libblind_read_stream(stream, SIZE_MAX)) > 0); if (outfailed) *outfailed = 0; return -(r < 0); }