Skip to content

Commit

Permalink
CICP Refactor (breaking change!)
Browse files Browse the repository at this point in the history
* Remove most references to "NCLX", as it is mostly an implementation detail, and the values are really from MPEG-CICP
* Eliminate avifProfileFormat: having an ICC profile is not mutually exclusive with signaling CICP
* CICP is now always available in an avifImage, set to unspecified by default
* Added --cicp as an alias for --nclx (semi-deprecated)
* Setting CICP via avifenc no longer overrides ICC profiles, they co-exist
* Simplified avifenc argument parsing / warnings logic
* avifenc/avifdec/avifdump now all display CICP when dumping AVIF information
* nclx colr box contents are guaranteed to override AV1 bitstream CICP (as MIAF standard specifies)
* Added comments explaining various decisions and citing standards
* Removed ICC inspection code regarding chroma-derived mtxCoeffs; this was overdesigned. Now just honor the assoc. colorPrimaries enum
* Reworked all examples in the README to reflect the new state of things, and clean out some cruft
  • Loading branch information
Joe Drago committed May 8, 2020
1 parent 70f4122 commit a0da4a4
Show file tree
Hide file tree
Showing 17 changed files with 347 additions and 739 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed
* CICP Refactor (breaking change!)
* Remove most references to "NCLX", as it is mostly an implementation detail, and the values are really from MPEG-CICP
* Eliminate avifProfileFormat: having an ICC profile is not mutually exclusive with signaling CICP
* CICP is now always available in an avifImage, set to unspecified by default
* Added --cicp as an alias for --nclx (semi-deprecated)
* Setting CICP via avifenc no longer overrides ICC profiles, they co-exist
* Simplified avifenc argument parsing / warnings logic
* avifenc/avifdec/avifdump now all display CICP when dumping AVIF information
* nclx colr box contents are guaranteed to override AV1 bitstream CICP (as MIAF standard specifies)
* Added comments explaining various decisions and citing standards
* Removed ICC inspection code regarding chroma-derived mtxCoeffs; this was overdesigned. Now just honor the assoc. colorPrimaries enum
* Reworked all examples in the README to reflect the new state of things, and clean out some cruft

## [0.7.3] - 2020-05-04
### Added
- avifenc: Lossless (--lossless, -l) mode, which sets new defaults and warns when anything would cause the AVIF to not be lossless
Expand Down
99 changes: 52 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,19 @@ if (decodeResult == AVIF_RESULT_OK) {
... image->width;
... image->height;
... image->depth; // If >8, all plane ptrs below are uint16_t*
... image->yuvFormat; // U and V planes might be smaller than Y based on format,
// use avifGetPixelFormatInfo() to find out in a generic way

// Option 1: Use YUV planes directly
... image->yuvPlanes;
... image->yuvRowBytes;
... image->yuvRange;
... image->yuvFormat; // U and V planes might be smaller than Y based on format,
// use avifGetPixelFormatInfo() to find out in a generic way
... image->matrixCoefficients; // specifies how to convert YUV planes to RGB
if (image->alphaPlane) { // Use alpha plane, if present.
... image->alphaPlane;
... image->alphaRowBytes;
... image->alphaRange; // Note: This might be limited range!
}

// Option 2: Convert to interleaved RGB(A)/BGR(A) using a libavif-allocated buffer.
avifRGBImage rgb;
Expand All @@ -67,26 +74,11 @@ if (decodeResult == AVIF_RESULT_OK) {
... rgb.rowBytes; // all channels are always full range
// Use your own buffer; no need to call avifRGBImageFreePixels()

// Use alpha plane, if present.
// Note: This might be limited range!
if (image->alphaPlane) {
... image->alphaPlane;
... image->alphaRowBytes;
... image->alphaRange;
}

// Optional: query color profile
if (image->profileFormat == AVIF_PROFILE_FORMAT_ICC) {
// ICC profile present
... image->icc.data;
... image->icc.size;
} else if (image->profileFormat == AVIF_PROFILE_FORMAT_NCLX) {
// NCLX profile present
... image->nclx.colourPrimaries;
... image->nclx.transferCharacteristics;
... image->nclx.matrixCoefficients;
... image->nclx.fullRangeFlag;
}
// Color profile information
... image->icc.data; // * If present and you support ICC,
... image->icc.size; // honor this ICC profile payload
... image->colorPrimaries; // * Otherwise, leverage these two values,
... image->transferCharacteristics; // if not set to unspecified

// Optional: Exif and XMP metadata querying
if(image->exif.size > 0) {
Expand Down Expand Up @@ -144,15 +136,22 @@ if (decodeResult == AVIF_RESULT_OK) {
... decoder->image->yuvFormat; // U and V planes might be smaller than Y based on format,
// use avifGetPixelFormatInfo() to find out in a generic way
// See Basic Decoding example for color profile and metadata querying
// Option 1: Use YUV planes directly
... decoder->image->yuvPlanes;
... decoder->image->yuvRowBytes;
... decoder->image->yuvRange;
... decoder->image->yuvFormat; // U and V planes might be smaller than Y based on format,
// use avifGetPixelFormatInfo() to find out in a generic way
... decoder->image->matrixCoefficients; // specifies how to convert YUV planes to RGB
if (decoder->image->alphaPlane) { // Use alpha plane, if present.
... decoder->image->alphaPlane;
... decoder->image->alphaRowBytes;
... decoder->image->alphaRange; // Note: This might be limited range!
}
// Option 2: Convert to interleaved RGB(A)/BGR(A) using a libavif-allocated buffer.
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, image);
avifRGBImageSetDefaults(&rgb, decoder->image);
rgb.format = ...; // See choices in avif.h
rgb.depth = ...; // [8, 10, 12, 16]; Does not need to match image->depth.
// If >8, rgb->pixels is uint16_t*
Expand All @@ -164,23 +163,33 @@ if (decodeResult == AVIF_RESULT_OK) {
// Option 3: Convert directly into your own pre-existing interleaved RGB(A)/BGR(A) buffer
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, image);
avifRGBImageSetDefaults(&rgb, decoder->image);
rgb.format = ...; // See choices in avif.h
rgb.depth = ...; // [8, 10, 12, 16]; Does not need to match image->depth.
// If >8, rgb->pixels is uint16_t*
rgb.pixels = ...; // Point at your RGB(A)/BGR(A) pixels here
rgb.rowBytes = ...;
avifImageYUVToRGB(image, &rgb);
avifImageYUVToRGB(decoder->image, &rgb);
... rgb.pixels; // Pixels in interleaved rgbFormat chosen above;
... rgb.rowBytes; // all channels are always full range
// Use your own buffer; no need to call avifRGBImageFreePixels()
// Use alpha plane, if present.
// Note: This might be limited range!
if (decoder->image->alphaPlane) {
... image->alphaPlane;
... image->alphaRowBytes;
... image->alphaRange;
// Color profile information
... decoder->image->icc.data; // * If present and you support ICC,
... decoder->image->icc.size; // honor this ICC profile payload
... decoder->image->colorPrimaries; // * Otherwise, leverage these two values,
... decoder->image->transferCharacteristics; // if not set to unspecified
// Optional: Exif and XMP metadata querying
if(decoder->image->exif.size > 0) {
// Parse Exif payload
... decoder->image->exif.data;
... decoder->image->exif.size;
}
if(decoder->image->xmp.size > 0) {
// Parse XMP document
... decoder->image->xmp.data;
... decoder->image->xmp.size;
}
// Timing and frame information
Expand Down Expand Up @@ -212,16 +221,19 @@ int depth = 8;
avifPixelFormat format = AVIF_PIXEL_FORMAT_YUV420;
avifImage * image = avifImageCreate(width, height, depth, format);

// (Semi-)optional: Describe the color profile, YUV<->RGB conversion, and range.
// These default to "unspecified" and full range. You should at least set the
// matrixCoefficients to indicate how you would like YUV<->RGB conversion to be done.
image->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709;
image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_BT709;
image->yuvRange = AVIF_RANGE_FULL;

// Option 1: Populate YUV planes
avifImageAllocatePlanes(image, AVIF_PLANES_YUV);
... image->yuvPlanes;
... image->yuvRowBytes;

// Option 2: Populate RGB planes (if YUV planes are absent, RGB->YUV conversion will automatically happen)
avifImageAllocatePlanes(image, AVIF_PLANES_RGB);
... image->rgbPlanes;
... image->rgbRowBytes;

// Option 2: Convert from interleaved RGB(A)/BGR(A) using a libavif-allocated buffer.
uint32_t rgbDepth = ...; // [8, 10, 12, 16]; Does not need to match image->depth.
// If >8, rgb->pixels is uint16_t*
Expand Down Expand Up @@ -250,16 +262,9 @@ avifImageRGBToYUV(image, rgb); // if alpha is present, it will also be copied/co
avifImageAllocatePlanes(image, AVIF_PLANES_A);
... image->alphaPlane;
... image->alphaRowBytes;
... image->alphaRange;

// Optional: Set color profile based on NCLX box
avifNclxColorProfile nclx;
nclx.colourPrimaries = AVIF_NCLX_COLOUR_PRIMARIES_BT709;
nclx.transferCharacteristics = AVIF_NCLX_TRANSFER_CHARACTERISTICS_SRGB;
nclx.matrixCoefficients = AVIF_NCLX_MATRIX_COEFFICIENTS_BT709;
nclx.fullRangeFlag = AVIF_NCLX_FULL_RANGE;
avifImageSetProfileNCLX(image, &nclx);

// Optional: Set color profile based on ICC profile
// Optional: Set an ICC profile
uint8_t * icc = ...; // raw ICC profile data
size_t iccSize = ...; // Length of raw ICC profile data
avifImageSetProfileICC(image, icc, iccSize);
Expand Down
75 changes: 25 additions & 50 deletions apps/avifenc.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ static void syntax(void)
printf(" -l,--lossless : Set all defaults to encode losslessly, and emit warnings when settings/input don't allow for it\n");
printf(" -d,--depth D : Output depth [8,10,12]. (JPEG/PNG only; For y4m, depth is retained)\n");
printf(" -y,--yuv FORMAT : Output format [default=444, 422, 420]. (JPEG/PNG only; For y4m, format is retained)\n");
printf(" -n,--nclx P/T/M : Set nclx colr box values (3 raw numbers, use -r to set range flag)\n");
printf(" P = enum avifNclxColourPrimaries\n");
printf(" T = enum avifNclxTransferCharacteristics\n");
printf(" M = enum avifNclxMatrixCoefficients\n");
printf(" --cicp,--nclx P/T/M : Set CICP values (nclx colr box) (3 raw numbers, use -r to set range flag)\n");
printf(" P = enum avifColorPrimaries\n");
printf(" T = enum avifTransferCharacteristics\n");
printf(" M = enum avifMatrixCoefficients\n");
printf(" (use 2 for any you wish to leave unspecified)\n");
printf(" -r,--range RANGE : YUV range [limited or l, full or f]. (JPEG/PNG only, default: full; For y4m, range is retained)\n");
printf(" --min Q : Set min quantizer for color (%d-%d, where %d is lossless)\n",
AVIF_QUANTIZER_BEST_QUALITY,
Expand Down Expand Up @@ -79,17 +80,16 @@ static const char * quantizerString(int quantizer)
return "Low";
}

static avifBool parseNCLX(avifNclxColorProfile * nclx, const char * arg)
static avifBool parseCICP(int cicp[3], const char * arg)
{
char buffer[128];
strncpy(buffer, arg, 127);
buffer[127] = 0;

int values[3];
int index = 0;
char * token = strtok(buffer, "/");
while (token != NULL) {
values[index] = atoi(token);
cicp[index] = atoi(token);
++index;
if (index >= 3) {
break;
Expand All @@ -99,10 +99,6 @@ static avifBool parseNCLX(avifNclxColorProfile * nclx, const char * arg)
}

if (index == 3) {
nclx->colourPrimaries = (uint16_t)values[0];
nclx->transferCharacteristics = (uint16_t)values[1];
nclx->matrixCoefficients = (uint16_t)values[2];
nclx->range = AVIF_RANGE_FULL; // This will be set later
return AVIF_TRUE;
}
return AVIF_FALSE;
Expand Down Expand Up @@ -155,13 +151,12 @@ int main(int argc, char * argv[])
uint8_t imirAxis = 0xff; // sentinel value indicating "unused"
avifCodecChoice codecChoice = AVIF_CODEC_CHOICE_AUTO;
avifRange requestedRange = AVIF_RANGE_FULL;
avifBool requestedRangeSet = AVIF_FALSE;
avifBool nclxSet = AVIF_FALSE;
avifBool lossless = AVIF_FALSE;
avifEncoder * encoder = NULL;

avifNclxColorProfile nclx;
memset(&nclx, 0, sizeof(avifNclxColorProfile));
avifColorPrimaries colorPrimaries = AVIF_COLOR_PRIMARIES_UNSPECIFIED;
avifTransferCharacteristics transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_UNSPECIFIED;
avifMatrixCoefficients matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_UNSPECIFIED;

int argIndex = 1;
while (argIndex < argc) {
Expand Down Expand Up @@ -231,12 +226,15 @@ int main(int argc, char * argv[])
if (maxQuantizerAlpha > AVIF_QUANTIZER_WORST_QUALITY) {
maxQuantizerAlpha = AVIF_QUANTIZER_WORST_QUALITY;
}
} else if (!strcmp(arg, "-n") || !strcmp(arg, "--nclx")) {
} else if (!strcmp(arg, "--cicp") || !strcmp(arg, "--nclx")) {
NEXTARG();
if (!parseNCLX(&nclx, arg)) {
int cicp[3];
if (!parseCICP(cicp, arg)) {
return 1;
}
nclxSet = AVIF_TRUE;
colorPrimaries = (avifColorPrimaries)cicp[0];
transferCharacteristics = (avifTransferCharacteristics)cicp[1];
matrixCoefficients = (avifMatrixCoefficients)cicp[2];
} else if (!strcmp(arg, "-r") || !strcmp(arg, "--range")) {
NEXTARG();
if (!strcmp(arg, "limited") || !strcmp(arg, "l")) {
Expand All @@ -247,7 +245,6 @@ int main(int argc, char * argv[])
fprintf(stderr, "ERROR: Unknown range: %s\n", arg);
return 1;
}
requestedRangeSet = AVIF_TRUE;
} else if (!strcmp(arg, "-s") || !strcmp(arg, "--speed")) {
NEXTARG();
if (!strcmp(arg, "default") || !strcmp(arg, "d")) {
Expand Down Expand Up @@ -306,15 +303,15 @@ int main(int argc, char * argv[])
lossless = AVIF_TRUE;

// Set defaults, and warn later on if anything looks incorrect
requestedFormat = AVIF_PIXEL_FORMAT_YUV444; // don't subsample GBR
requestedFormat = AVIF_PIXEL_FORMAT_YUV444; // don't subsample when using AVIF_MATRIX_COEFFICIENTS_IDENTITY
minQuantizer = AVIF_QUANTIZER_LOSSLESS; // lossless
maxQuantizer = AVIF_QUANTIZER_LOSSLESS; // lossless
minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; // lossless
maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS; // lossless
codecChoice = AVIF_CODEC_CHOICE_AOM; // rav1e doesn't support lossless transform yet:
// https://github.com/xiph/rav1e/issues/151
requestedRange = AVIF_RANGE_FULL; // avoid limited range
requestedRangeSet = AVIF_TRUE;
matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY; // this is key for lossless
} else {
// Positional argument
if (!inputFilename) {
Expand Down Expand Up @@ -349,39 +346,17 @@ int main(int argc, char * argv[])
goto cleanup;
}

if (lossless && (inputFormat != AVIF_APP_FILE_FORMAT_Y4M)) {
if (!nclxSet) { // don't stomp on the user's cmdline nclx values
// Assume SRGB unless they tell us otherwise via --nclx
nclx.colourPrimaries = AVIF_NCLX_COLOUR_PRIMARIES_BT709;
nclx.transferCharacteristics = AVIF_NCLX_TRANSFER_CHARACTERISTICS_SRGB;
nclx.matrixCoefficients = AVIF_NCLX_MATRIX_COEFFICIENTS_IDENTITY; // this is key for lossless
nclx.range = AVIF_RANGE_FULL;
nclxSet = AVIF_TRUE;
}
}

// Set range and nclx in advance so any upcoming RGB -> YUV use the proper coefficients
if (requestedRangeSet) {
avif->yuvRange = requestedRange;
}
if (nclxSet) {
nclx.range = avif->yuvRange;
avifImageSetProfileNCLX(avif, &nclx);
}
// Set these in advance so any upcoming RGB -> YUV use the proper coefficients
avif->colorPrimaries = colorPrimaries;
avif->transferCharacteristics = transferCharacteristics;
avif->matrixCoefficients = matrixCoefficients;
avif->yuvRange = requestedRange;

if (inputFormat == AVIF_APP_FILE_FORMAT_Y4M) {
if (requestedRangeSet) {
fprintf(stderr, "WARNING: Ignoring range (-r) value when encoding from y4m content.\n");
}
if (!y4mRead(avif, inputFilename)) {
returnCode = 1;
goto cleanup;
}
if (nclxSet && (nclx.range != avif->yuvRange)) {
// Update the NCLX profile based on the new range from the y4m file
nclx.range = avif->yuvRange;
avifImageSetProfileNCLX(avif, &nclx);
}
sourceDepth = avif->depth;
sourceWasRGB = AVIF_FALSE;
} else if (inputFormat == AVIF_APP_FILE_FORMAT_JPEG) {
Expand Down Expand Up @@ -438,7 +413,7 @@ int main(int argc, char * argv[])
avifBool depthMatches = (sourceDepth == avif->depth);
avifBool using444 = (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV444);
avifBool usingFullRange = (avif->yuvRange == AVIF_RANGE_FULL);
avifBool usingIdentityMatrix = (nclxSet && (nclx.matrixCoefficients == AVIF_NCLX_MATRIX_COEFFICIENTS_IDENTITY));
avifBool usingIdentityMatrix = (avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY);

// Guess if the enduser is asking for lossless and enable it so that warnings can be emitted
if (!lossless && losslessColorQP && (!hasAlpha || losslessAlphaQP)) {
Expand Down Expand Up @@ -488,7 +463,7 @@ int main(int argc, char * argv[])
}

if (!usingIdentityMatrix) {
fprintf(stderr, "WARNING: [--lossless] Input data was RGB and nclx matrixCoefficients isn't set to identity (--nclx x/x/0); Output might not be lossless.\n");
fprintf(stderr, "WARNING: [--lossless] Input data was RGB and matrixCoefficients isn't set to identity (--cicp x/x/0); Output might not be lossless.\n");
lossless = AVIF_FALSE;
}
}
Expand Down
8 changes: 2 additions & 6 deletions apps/shared/avifjpeg.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,7 @@ avifBool avifJPEGRead(avifImage * avif, const char * inputFilename, avifPixelFor
unsigned int iccDataLen;
if (read_icc_profile(&cinfo, &iccDataTmp, &iccDataLen)) {
iccData = iccDataTmp;
if (avif->profileFormat == AVIF_PROFILE_FORMAT_NONE) {
avifImageSetProfileICC(avif, iccDataTmp, (size_t)iccDataLen);
} else {
fprintf(stderr, "WARNING: JPEG contains ICC profile which is being overridden with --nclx\n");
}
avifImageSetProfileICC(avif, iccDataTmp, (size_t)iccDataLen);
}

avif->width = cinfo.output_width;
Expand Down Expand Up @@ -148,7 +144,7 @@ avifBool avifJPEGWrite(avifImage * avif, const char * outputFilename, int jpegQu
jpeg_set_quality(&cinfo, jpegQuality, TRUE);
jpeg_start_compress(&cinfo, TRUE);

if (avif->profileFormat == AVIF_PROFILE_FORMAT_ICC) {
if (avif->icc.data && (avif->icc.size > 0)) {
write_icc_profile(&cinfo, avif->icc.data, (unsigned int)avif->icc.size);
}

Expand Down
Loading

0 comments on commit a0da4a4

Please sign in to comment.