// Copyright 2019 Joe Drago. All rights reserved. // SPDX-License-Identifier: BSD-2-Clause #include "avifutil.h" #include #include #include #include "avifjpeg.h" #include "avifpng.h" #include "y4m.h" // |a| and |b| hold int32_t values. The int64_t type is used so that we can negate INT32_MIN without // overflowing int32_t. static int64_t calcGCD(int64_t a, int64_t b) { if (a < 0) { a *= -1; } if (b < 0) { b *= -1; } while (b != 0) { int64_t r = a % b; a = b; b = r; } return a; } static void printClapFraction(const char * name, int32_t n, int32_t d) { printf("%s: %d/%d", name, n, d); if (d != 0) { int64_t gcd = calcGCD(n, d); if (gcd > 1) { int32_t rn = (int32_t)(n / gcd); int32_t rd = (int32_t)(d / gcd); printf(" (%d/%d)", rn, rd); } } } static void avifImageDumpInternal(const avifImage * avif, uint32_t gridCols, uint32_t gridRows, avifBool alphaPresent, avifProgressiveState progressiveState) { uint32_t width = avif->width; uint32_t height = avif->height; if (gridCols && gridRows) { width *= gridCols; height *= gridRows; } printf(" * Resolution : %ux%u\n", width, height); printf(" * Bit Depth : %u\n", avif->depth); printf(" * Format : %s\n", avifPixelFormatToString(avif->yuvFormat)); if (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) { printf(" * Chroma Sam. Pos: %u\n", avif->yuvChromaSamplePosition); } printf(" * Alpha : %s\n", alphaPresent ? (avif->alphaPremultiplied ? "Premultiplied" : "Not premultiplied") : "Absent"); printf(" * Range : %s\n", (avif->yuvRange == AVIF_RANGE_FULL) ? "Full" : "Limited"); printf(" * Color Primaries: %u\n", avif->colorPrimaries); printf(" * Transfer Char. : %u\n", avif->transferCharacteristics); printf(" * Matrix Coeffs. : %u\n", avif->matrixCoefficients); if (avif->icc.size != 0) { printf(" * ICC Profile : Present (%" AVIF_FMT_ZU " bytes)\n", avif->icc.size); } else { printf(" * ICC Profile : Absent\n"); } if (avif->xmp.size != 0) { printf(" * XMP Metadata : Present (%" AVIF_FMT_ZU " bytes)\n", avif->xmp.size); } else { printf(" * XMP Metadata : Absent\n"); } if (avif->exif.size != 0) { printf(" * Exif Metadata : Present (%" AVIF_FMT_ZU " bytes)\n", avif->exif.size); } else { printf(" * Exif Metadata : Absent\n"); } if (avif->transformFlags == AVIF_TRANSFORM_NONE) { printf(" * Transformations: None\n"); } else { printf(" * Transformations:\n"); if (avif->transformFlags & AVIF_TRANSFORM_PASP) { printf(" * pasp (Aspect Ratio) : %d/%d\n", (int)avif->pasp.hSpacing, (int)avif->pasp.vSpacing); } if (avif->transformFlags & AVIF_TRANSFORM_CLAP) { printf(" * clap (Clean Aperture): "); printClapFraction("W", (int32_t)avif->clap.widthN, (int32_t)avif->clap.widthD); printf(", "); printClapFraction("H", (int32_t)avif->clap.heightN, (int32_t)avif->clap.heightD); printf(", "); printClapFraction("hOff", (int32_t)avif->clap.horizOffN, (int32_t)avif->clap.horizOffD); printf(", "); printClapFraction("vOff", (int32_t)avif->clap.vertOffN, (int32_t)avif->clap.vertOffD); printf("\n"); avifCropRect cropRect; avifDiagnostics diag; avifDiagnosticsClearError(&diag); avifBool validClap = avifCropRectConvertCleanApertureBox(&cropRect, &avif->clap, avif->width, avif->height, avif->yuvFormat, &diag); if (validClap) { printf(" * Valid, derived crop rect: X: %d, Y: %d, W: %d, H: %d\n", cropRect.x, cropRect.y, cropRect.width, cropRect.height); } else { printf(" * Invalid: %s\n", diag.error); } } if (avif->transformFlags & AVIF_TRANSFORM_IROT) { printf(" * irot (Rotation) : %u\n", avif->irot.angle); } if (avif->transformFlags & AVIF_TRANSFORM_IMIR) { printf(" * imir (Mirror) : %u (%s)\n", avif->imir.axis, (avif->imir.axis == 0) ? "top-to-bottom" : "left-to-right"); } } printf(" * Progressive : %s\n", avifProgressiveStateToString(progressiveState)); if (avif->clli.maxCLL > 0 || avif->clli.maxPALL > 0) { printf(" * CLLI : %hu, %hu\n", avif->clli.maxCLL, avif->clli.maxPALL); } } void avifImageDump(const avifImage * avif, uint32_t gridCols, uint32_t gridRows, avifProgressiveState progressiveState) { const avifBool alphaPresent = avif->alphaPlane && (avif->alphaRowBytes > 0); avifImageDumpInternal(avif, gridCols, gridRows, alphaPresent, progressiveState); } void avifContainerDump(const avifDecoder * decoder) { avifImageDumpInternal(decoder->image, 0, 0, decoder->alphaPresent, decoder->progressiveState); if ((decoder->progressiveState == AVIF_PROGRESSIVE_STATE_UNAVAILABLE) && (decoder->imageCount > 1)) { if (decoder->repetitionCount == AVIF_REPETITION_COUNT_INFINITE) { printf(" * Repeat Count : Infinite\n"); } else if (decoder->repetitionCount == AVIF_REPETITION_COUNT_UNKNOWN) { printf(" * Repeat Count : Unknown\n"); } else { printf(" * Repeat Count : %d\n", decoder->repetitionCount); } } } void avifPrintVersions(void) { char codecVersions[256]; avifCodecVersions(codecVersions); printf("Version: %s (%s)\n", avifVersion(), codecVersions); unsigned int libyuvVersion = avifLibYUVVersion(); if (libyuvVersion == 0) { printf("libyuv : unavailable\n"); } else { printf("libyuv : available (%u)\n", libyuvVersion); } printf("\n"); } avifAppFileFormat avifGuessFileFormat(const char * filename) { // Guess from the file header FILE * f = fopen(filename, "rb"); if (f) { uint8_t headerBuffer[144]; size_t bytesRead = fread(headerBuffer, 1, sizeof(headerBuffer), f); fclose(f); if (bytesRead > 0) { avifROData header; header.data = headerBuffer; header.size = bytesRead; if (avifPeekCompatibleFileType(&header)) { return AVIF_APP_FILE_FORMAT_AVIF; } static const uint8_t signatureJPEG[2] = { 0xFF, 0xD8 }; static const uint8_t signaturePNG[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; static const uint8_t signatureY4M[9] = { 0x59, 0x55, 0x56, 0x34, 0x4D, 0x50, 0x45, 0x47, 0x32 }; // "YUV4MPEG2" struct avifHeaderSignature { avifAppFileFormat format; const uint8_t * magic; size_t magicSize; } signatures[] = { { AVIF_APP_FILE_FORMAT_JPEG, signatureJPEG, sizeof(signatureJPEG) }, { AVIF_APP_FILE_FORMAT_PNG, signaturePNG, sizeof(signaturePNG) }, { AVIF_APP_FILE_FORMAT_Y4M, signatureY4M, sizeof(signatureY4M) } }; const size_t signaturesCount = sizeof(signatures) / sizeof(signatures[0]); for (size_t signatureIndex = 0; signatureIndex < signaturesCount; ++signatureIndex) { struct avifHeaderSignature * signature = &signatures[signatureIndex]; if (header.size < signature->magicSize) { continue; } if (!memcmp(header.data, signature->magic, signature->magicSize)) { return signature->format; } } // If none of these signatures match, bail out here. Guessing by extension won't help. return AVIF_APP_FILE_FORMAT_UNKNOWN; } } // If we get here, the file header couldn't be read for some reason. Guess from the extension. const char * fileExt = strrchr(filename, '.'); if (!fileExt) { return AVIF_APP_FILE_FORMAT_UNKNOWN; } ++fileExt; // skip past the dot char lowercaseExt[8]; // This only needs to fit up to "jpeg", so this is plenty const size_t fileExtLen = strlen(fileExt); if (fileExtLen >= sizeof(lowercaseExt)) { // >= accounts for NULL terminator return AVIF_APP_FILE_FORMAT_UNKNOWN; } for (size_t i = 0; i < fileExtLen; ++i) { lowercaseExt[i] = (char)tolower((unsigned char)fileExt[i]); } lowercaseExt[fileExtLen] = 0; if (!strcmp(lowercaseExt, "avif")) { return AVIF_APP_FILE_FORMAT_AVIF; } else if (!strcmp(lowercaseExt, "y4m")) { return AVIF_APP_FILE_FORMAT_Y4M; } else if (!strcmp(lowercaseExt, "jpg") || !strcmp(lowercaseExt, "jpeg")) { return AVIF_APP_FILE_FORMAT_JPEG; } else if (!strcmp(lowercaseExt, "png")) { return AVIF_APP_FILE_FORMAT_PNG; } return AVIF_APP_FILE_FORMAT_UNKNOWN; } avifAppFileFormat avifReadImage(const char * filename, avifPixelFormat requestedFormat, int requestedDepth, avifChromaDownsampling chromaDownsampling, avifBool ignoreColorProfile, avifBool ignoreExif, avifBool ignoreXMP, avifBool allowChangingCicp, avifImage * image, uint32_t * outDepth, avifAppSourceTiming * sourceTiming, struct y4mFrameIterator ** frameIter) { const avifAppFileFormat format = avifGuessFileFormat(filename); if (format == AVIF_APP_FILE_FORMAT_Y4M) { if (!y4mRead(filename, image, sourceTiming, frameIter)) { return AVIF_APP_FILE_FORMAT_UNKNOWN; } if (outDepth) { *outDepth = image->depth; } } else if (format == AVIF_APP_FILE_FORMAT_JPEG) { if (!avifJPEGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP)) { return AVIF_APP_FILE_FORMAT_UNKNOWN; } if (outDepth) { *outDepth = 8; } } else if (format == AVIF_APP_FILE_FORMAT_PNG) { if (!avifPNGRead(filename, image, requestedFormat, requestedDepth, chromaDownsampling, ignoreColorProfile, ignoreExif, ignoreXMP, allowChangingCicp, outDepth)) { return AVIF_APP_FILE_FORMAT_UNKNOWN; } } else { fprintf(stderr, "Unrecognized file format for input file: %s\n", filename); return AVIF_APP_FILE_FORMAT_UNKNOWN; } return format; } void avifImageFixXMP(avifImage * image) { // Zero bytes are forbidden in UTF-8 XML: https://en.wikipedia.org/wiki/Valid_characters_in_XML // Keeping zero bytes in XMP may lead to issues at encoding or decoding. // For example, the PNG specification forbids null characters in XMP. See avifPNGWrite(). // The XMP Specification Part 3 says "When XMP is encoded as UTF-8, // there are no zero bytes in the XMP packet" for GIF. // Consider a single trailing null character following a non-null character // as a programming error. Leave other null characters as is. // See the discussion at https://github.com/AOMediaCodec/libavif/issues/1333. if (image->xmp.size >= 2 && image->xmp.data[image->xmp.size - 1] == '\0' && image->xmp.data[image->xmp.size - 2] != '\0') { --image->xmp.size; } } void avifDumpDiagnostics(const avifDiagnostics * diag) { if (!*diag->error) { return; } printf("Diagnostics:\n"); printf(" * %s\n", diag->error); } // --------------------------------------------------------------------------- // avifQueryCPUCount (separated into OS implementations) #if defined(_WIN32) // Windows #include int avifQueryCPUCount(void) { int numCPU; SYSTEM_INFO sysinfo; GetSystemInfo(&sysinfo); numCPU = sysinfo.dwNumberOfProcessors; return numCPU; } #elif defined(__APPLE__) // Apple #include int avifQueryCPUCount() { int mib[4]; int numCPU; size_t len = sizeof(numCPU); /* set the mib for hw.ncpu */ mib[0] = CTL_HW; mib[1] = HW_AVAILCPU; // alternatively, try HW_NCPU; /* get the number of CPUs from the system */ sysctl(mib, 2, &numCPU, &len, NULL, 0); if (numCPU < 1) { mib[1] = HW_NCPU; sysctl(mib, 2, &numCPU, &len, NULL, 0); if (numCPU < 1) numCPU = 1; } return numCPU; } #elif defined(__EMSCRIPTEN__) // Emscripten int avifQueryCPUCount() { return 1; } #else // POSIX #include int avifQueryCPUCount() { int numCPU = (int)sysconf(_SC_NPROCESSORS_ONLN); return (numCPU > 0) ? numCPU : 1; } #endif