jpeg_reader: support for spiff pictures

+ hint for GPUJPEG to set limited 601
This commit is contained in:
Martin Pulec
2024-10-09 10:48:38 +02:00
parent 73fbec1c8c
commit f2d2fcdb8d
2 changed files with 207 additions and 6 deletions

View File

@@ -270,6 +270,38 @@ static const char* jpeg_marker_name(enum jpeg_marker_code code)
(uint16_t)(((*(image)) << 8) + (*((image) + 1))); \
image += 2;
#define read_4byte(image) \
(uint32_t)(((uint32_t) (image)[0]) << 24U | ((uint32_t) (image)[1] << 16U) | ((uint32_t) (image)[2] << 8U) | ((uint32_t) (image)[3])); \
(image) += 4;
#define SPIFF_VERSION 0x100 // Version 1.00
#define SPIFF_COMPRESSION_JPEG 5
#define SPIFF_ENTRY_TAG_EOD 0x1
#define SPIFF_ENTRY_TAG_EOD_LENGHT 8 // length is 2 bytes longer for EOD to contain also following SOI
#define SPIFF_MARKER_LEN 32 ///< including length field
static const char*
color_space_get_name(enum jpeg_color_spec color_space)
{
switch ( color_space ) {
case JPEG_COLOR_SPEC_NONE :
return "None";
case JPEG_COLOR_SPEC_RGB:
return "RGB";
case JPEG_COLOR_SPEC_YCBCR_601:
return "YCbCr BT.601 (limtted range)";
case JPEG_COLOR_SPEC_YCBCR_JPEG:
return "YCbCr BT.601 256 Levels (YCbCr JPEG)";
case JPEG_COLOR_SPEC_YCBCR_709:
return "YCbCr BT.709 (limited range)";
case JPEG_COLOR_SPEC_CMYK:
return "CMYK";
case JPEG_COLOR_SPEC_YCCK:
return "YCCK";
}
return "Unknown";
}
static int read_marker(uint8_t** image)
{
uint8_t byte = read_byte(*image);
@@ -280,6 +312,25 @@ static int read_marker(uint8_t** image)
return marker;
}
///< new bound-checking version of read_marker() as used by GPUJPEG
static int
read_marker_new(uint8_t** image, const uint8_t* image_end)
{
if(image_end - *image < 2) {
fprintf(stderr, "[GPUJPEG] [Error] Failed to read marker from JPEG data (end of data)\n");
return -1;
}
uint8_t byte = read_byte(*image);
if( byte != 0xFF ) {
fprintf(stderr, "[Error] Failed to read marker from JPEG data (0xFF was expected but 0x%X was presented)\n", byte);
return -1;
}
int marker = read_byte(*image);
return marker;
}
static void skip_marker_content(uint8_t** image)
{
int length = (int)read_2byte(*image);
@@ -505,6 +556,133 @@ static int read_sos(struct jpeg_info *param, uint8_t** image)
return 0;
}
static int
read_spiff_header(uint8_t** image, enum jpeg_color_spec *color_space, bool *in_spiff)
{
int version = read_2byte(*image); // version
int profile_id = read_byte(*image); // profile ID
int comp_count = read_byte(*image); // component count
int width = read_4byte(*image); // width
int height = read_4byte(*image); // height
int spiff_color_space = read_byte(*image);
int bps = read_byte(*image); // bits per sample
int compression = read_byte(*image);
int pixel_units = read_byte(*image); // resolution units
int pixel_xdpu = read_4byte(*image); // vertical res
int pixel_ydpu = read_4byte(*image); // horizontal res
(void) profile_id, (void) comp_count, (void) width, (void) height, (void) pixel_units, (void) pixel_xdpu, (void) pixel_ydpu;
if (version != SPIFF_VERSION) {
verbose_msg("Unknown SPIFF version %d.%d.\n", version >> 8, version & 0xFF);
}
if (bps != 8) {
error_msg("Wrong bits per sample %d, only 8 is supported.\n", bps);
}
if (compression != SPIFF_COMPRESSION_JPEG) {
error_msg("Unexpected compression index %d, expected %d (JPEG)\n", compression, SPIFF_COMPRESSION_JPEG);
return -1;
}
switch (spiff_color_space) {
case 1: // NOLINT
*color_space = JPEG_COLOR_SPEC_YCBCR_709;
break;
case 2: // NOLINT
break;
case 3: // NOLINT
case 8: /* grayscale */ // NOLINT
*color_space = JPEG_COLOR_SPEC_YCBCR_JPEG;
break;
case 4: // NOLINT
*color_space = JPEG_COLOR_SPEC_YCBCR_601;
break;
case 10: // NOLINT
*color_space = JPEG_COLOR_SPEC_RGB;
break;
default:
error_msg(
"Unsupported or unrecongnized SPIFF color space %d!\n",
spiff_color_space);
return -1;
}
debug_msg("APP8 SPIFF parsed succesfully, internal color space: %s\n",
color_space_get_name(*color_space));
*in_spiff = 1;
return 0;
}
static int
read_spiff_directory(uint8_t** image, const uint8_t* image_end, int length, _Bool *in_spiff)
{
if (length < 4) {
fprintf(stderr, "[GPUJPEG] [Error] APP8 SPIFF directory too short (%d bytes)\n", length + 2);
image += length;
return -1;
}
uint32_t tag = read_4byte(*image);
debug_msg("Read SPIFF tag 0x%x with length %d.\n", tag, length + 2);
if (tag == SPIFF_ENTRY_TAG_EOD && length == SPIFF_ENTRY_TAG_EOD_LENGHT - 2) {
int marker_soi = read_marker_new(image, image_end);
if ( marker_soi != JPEG_MARKER_SOI ) {
verbose_msg("SPIFF entry 0x1 should be followed directly with SOI.\n");
return -1;
}
debug_msg("SPIFF EOD presented.\n");
*in_spiff = 0;
} else if (tag >> 24U != 0) {
verbose_msg( "Erroneous SPIFF tag 0x%x (first byte should be 0).", tag);
} else {
debug_msg("SPIFF tag 0x%x with length %d presented.\n", tag, length + 2);
}
return 0;
}
static int
read_app8(uint8_t** image, const uint8_t* image_end, enum jpeg_color_spec *color_space, bool *in_spiff)
{
if(image_end - *image < 2) {
fprintf(stderr, "[GPUJPEG] [Error] Could not read APP8 marker length (end of data)\n");
return -1;
}
int length = read_2byte(*image);
length -= 2;
if(image_end - *image < length) {
fprintf(stderr, "[GPUJPEG] [Error] APP8 marker goes beyond end of data\n");
return -1;
}
if (*in_spiff) {
return read_spiff_directory(image, image_end, length, in_spiff);
}
if (length + 2 != SPIFF_MARKER_LEN) {
verbose_msg("APP8 segment length is %d, expected 32 for SPIFF.\n", length + 2);
*image += length;
return 0;
}
const char spiff_marker_name[6] = { 'S', 'P', 'I', 'F', 'F', '\0' };
char marker_name[sizeof spiff_marker_name];
for (unsigned i = 0; i < sizeof marker_name; i++) {
marker_name[i] = read_byte(*image);
if (!isprint(marker_name[i])) {
marker_name[i] = '\0';
}
}
length -= sizeof marker_name;
if (strcmp(marker_name, spiff_marker_name) != 0) {
verbose_msg("APP8 marker identifier should be 'SPIFF\\0' but '%-6.6s' was presented!\n", marker_name);
*image += length - 2;
return 0;
}
return read_spiff_header(image, color_space, in_spiff);
}
static int read_adobe_app14(struct jpeg_info *param, uint8_t** image)
{
int length = read_2byte(*image);
@@ -530,7 +708,7 @@ static int read_adobe_app14(struct jpeg_info *param, uint8_t** image)
if (color_transform == 0) {
param->color_spec = JPEG_COLOR_SPEC_CMYK; // or RGB - will be determined later
} else if (color_transform == 1) {
param->color_spec = JPEG_COLOR_SPEC_YCBCR;
param->color_spec = JPEG_COLOR_SPEC_YCBCR_JPEG;
} else if (color_transform == 2) {
param->color_spec = JPEG_COLOR_SPEC_YCCK;
} else {
@@ -559,6 +737,8 @@ int jpeg_read_info(uint8_t *image, int len, struct jpeg_info *info)
return -1;
}
const uint8_t *image_end = image + len;
info->huff_lum_dc[0] = 255;
info->huff_lum_ac[0] = 255;
info->huff_chm_dc[0] = 255;
@@ -567,8 +747,9 @@ int jpeg_read_info(uint8_t *image, int len, struct jpeg_info *info)
info->interleaved = true;
info->restart_interval = 0; // if DRI is not present
info->com[0] = '\0'; // if COM is not present
info->color_spec = JPEG_COLOR_SPEC_YCBCR; // default
info->color_spec = JPEG_COLOR_SPEC_YCBCR_JPEG; // default
bool marker_present[255] = { 0 };
bool in_spiff = false;
// currently reading up to SOS marker gives us all needed data
while (!marker_present[JPEG_MARKER_EOI] && !marker_present[JPEG_MARKER_SOS]
@@ -628,6 +809,12 @@ int jpeg_read_info(uint8_t *image, int len, struct jpeg_info *info)
}
break;
case JPEG_MARKER_APP8:
if ( read_app8(&image, image_end, &info->color_spec, &in_spiff ) != 0 ) {
return -1;
}
break;
case JPEG_MARKER_APP14:
if ((rc = read_adobe_app14(info, &image)) != 0) {
log_msg(LOG_LEVEL_ERROR, "Error reading APP14!\n");
@@ -740,9 +927,21 @@ check_rtp_compatibility(const struct jpeg_info *info)
log_msg(LOG_LEVEL_ERROR, "Unsupported JPEG component count: %d!\n", info->comp_count);
return false;
}
if (info->color_spec != JPEG_COLOR_SPEC_YCBCR) {
log_msg(LOG_LEVEL_ERROR, "Unsupported JPEG color space (only YCbCr supported!\n");
return false;
if (info->color_spec != JPEG_COLOR_SPEC_YCBCR_JPEG) {
if (info->color_spec != JPEG_COLOR_SPEC_YCBCR_601 &&
info->color_spec != JPEG_COLOR_SPEC_YCBCR_709) {
MSG(ERROR,
"Unsupported JPEG color space %s "
"(only YCbCr supported)!\n",
color_space_get_name(info->color_spec));
return false;
}
MSG_ONCE(WARNING,
"JPEG CS should be full-range BT.601, have: %s\n",
color_space_get_name(info->color_spec));
if (strstr(info->com, "GPUJPEG") != NULL) {
MSG_ONCE(INFO, "Try setting \"-c gpujpeg:Y601full\"\n");
}
}
if (!info->interleaved) {
if (strstr(info->com, "GPUJPEG")) {

View File

@@ -49,7 +49,9 @@
enum jpeg_color_spec {
JPEG_COLOR_SPEC_NONE = 0,
JPEG_COLOR_SPEC_YCBCR,
JPEG_COLOR_SPEC_YCBCR_JPEG,
JPEG_COLOR_SPEC_YCBCR_601,
JPEG_COLOR_SPEC_YCBCR_709,
JPEG_COLOR_SPEC_RGB,
JPEG_COLOR_SPEC_CMYK,
JPEG_COLOR_SPEC_YCCK