├── .gitmodules ├── .travis.yml ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── apps ├── avifdec.c ├── avifenc.c └── shared │ ├── avifutil.c │ ├── avifutil.h │ ├── y4m.c │ └── y4m.h ├── appveyor.yml ├── examples └── avif_example1.c ├── ext ├── CMakeLists.txt └── gb │ ├── gb_math.c │ └── gb_math.h ├── include └── avif │ ├── avif.h │ └── internal.h └── src ├── avif.c ├── codec_aom.c ├── colr.c ├── mem.c ├── rawdata.c ├── read.c ├── reformat.c ├── stream.c ├── utils.c └── write.c /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/aom"] 2 | path = ext/aom 3 | url = https://github.com/joedrago/aom.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | os: linux 4 | 5 | matrix: 6 | include: 7 | - name: "GCC Debug" 8 | compiler: gcc 9 | addons: 10 | apt: 11 | packages: 12 | - nasm 13 | before_script: 14 | - mkdir build 15 | - cd build 16 | - cmake -DCMAKE_BUILD_TYPE=Debug .. 17 | script: 18 | - make 19 | 20 | - name: "GCC Release" 21 | compiler: gcc 22 | addons: 23 | apt: 24 | packages: 25 | - nasm 26 | before_script: 27 | - mkdir build 28 | - cd build 29 | - cmake -DCMAKE_BUILD_TYPE=Release .. 30 | script: 31 | - make 32 | 33 | - name: "Clang Debug" 34 | compiler: clang 35 | addons: 36 | apt: 37 | packages: 38 | - nasm 39 | before_script: 40 | - mkdir build 41 | - cd build 42 | - cmake -DCMAKE_BUILD_TYPE=Debug .. 43 | script: 44 | - make 45 | 46 | - name: "Clang Release" 47 | compiler: clang 48 | addons: 49 | apt: 50 | packages: 51 | - nasm 52 | before_script: 53 | - mkdir build 54 | - cd build 55 | - cmake -DCMAKE_BUILD_TYPE=Release .. 56 | script: 57 | - make 58 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ### Added 9 | - `avifPixelFormatToString()` convenience function for debugging/printing 10 | - `avifenc` and `avifdec` "apps" which show basic bidirectional conversion to y4m 11 | - Remove all calls to `convertXYZToXYY()` as they were all unnecessary 12 | - Make calling `avifImageYUVToRGB()` upon reading an avif optional 13 | 14 | ## [0.1.3] - 2019-04-23 15 | ### Changed 16 | - `ftyp` - Change `major_brand` to `avif` 17 | - `ftyp` - Reorder `compatible_brands`, add `MA1A` or `MA1B` when appropriate 18 | - Write `meta` box before `mdat` box for streaming friendliness 19 | 20 | ## [0.1.2] - 2019-04-18 21 | ### Added 22 | - `AVIF_NCLX_COLOUR_PRIMARIES_P3` (convenient mirrored value) 23 | - `avifNclxColourPrimariesFind()` - Finds a builtin avifNclxColourPrimaries and name by a set of primaries 24 | 25 | ### Changed 26 | - Fixed enum name copypasta for `AVIF_NCLX_COLOUR_PRIMARIES_EG432_1` 27 | - Fix UV limited ranges when doing full<->limited range conversion 28 | 29 | ## [0.1.1] - 2019-04-15 30 | ### Added 31 | - Added `appveyor.yml` (exported from Appveyor) 32 | - Move `ext/aom` to a proper submodule 33 | - Update AOM to commit [3e3b9342a](https://aomedia.googlesource.com/aom/+/3e3b9342a20147ec6e4f89aa290e20277c1260ce) with minor CMake changes 34 | 35 | ### Changed 36 | - Added static library artifact zip to Windows x64 builds (Appveyor) 37 | - Updated README to explain libavif's goals and a little more build info 38 | - Fix clang warning in `avifVersion()` signature 39 | 40 | ## [0.1.0] - 2019-04-12 41 | ### Added 42 | - First version. Plenty of bugfixes and features await! 43 | - `ext/aom` based off AOM commit [3563b12b](https://aomedia.googlesource.com/aom/+/3563b12b766639ba445eb0e62a225a4419594aef) with minor CMake changes 44 | - An interest and willingness to maintain this file. 45 | - Constants `AVIF_VERSION`, `AVIF_VERSION_MAJOR`, `AVIF_VERSION_MINOR`, `AVIF_VERSION_PATCH` 46 | - `avifVersion()` function 47 | 48 | [Unreleased]: https://github.com/joedrago/avif/compare/v0.1.3...HEAD 49 | [0.1.3]: https://github.com/joedrago/avif/compare/v0.1.2...v0.1.3 50 | [0.1.2]: https://github.com/joedrago/avif/compare/v0.1.1...v0.1.2 51 | [0.1.1]: https://github.com/joedrago/avif/compare/v0.1.0...v0.1.1 52 | [0.1.0]: https://github.com/joedrago/avif/releases/tag/v0.1.0 53 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Joe Drago. All rights reserved. 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | cmake_minimum_required(VERSION 3.5) 5 | project(avif) 6 | 7 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 8 | add_definitions(-std=c99) # Enforce C99 for gcc 9 | endif() 10 | 11 | add_subdirectory(ext) 12 | 13 | include_directories( 14 | include 15 | "${CMAKE_CURRENT_SOURCE_DIR}/ext/aom" 16 | "${AOM_BINARY_DIR}" 17 | ) 18 | 19 | if(NOT AVIF_EXTERNAL_GB) 20 | include_directories("${CMAKE_CURRENT_SOURCE_DIR}/ext/gb") 21 | endif() 22 | 23 | set(AVIF_SRCS 24 | include/avif/avif.h 25 | include/avif/internal.h 26 | src/avif.c 27 | src/colr.c 28 | src/mem.c 29 | src/rawdata.c 30 | src/read.c 31 | src/reformat.c 32 | src/stream.c 33 | src/utils.c 34 | src/write.c 35 | ) 36 | 37 | # TODO: Support other codec implementations here 38 | set(AVIF_SRCS ${AVIF_SRCS} 39 | src/codec_aom.c 40 | ) 41 | 42 | add_library(avif STATIC ${AVIF_SRCS}) 43 | target_link_libraries(avif aom gb) 44 | 45 | option(AVIF_BUILD_EXAMPLES "Build avif Examples." OFF) 46 | if(AVIF_BUILD_EXAMPLES) 47 | add_executable(avif_example1 examples/avif_example1.c) 48 | target_link_libraries(avif_example1 avif) 49 | endif() 50 | 51 | option(AVIF_BUILD_APPS "Build avif apps." OFF) 52 | if(AVIF_BUILD_APPS) 53 | include_directories(apps/shared) 54 | add_executable(avifenc 55 | apps/avifenc.c 56 | apps/shared/y4m.c 57 | apps/shared/avifutil.c 58 | ) 59 | target_link_libraries(avifenc avif) 60 | add_executable(avifdec 61 | apps/avifdec.c 62 | apps/shared/y4m.c 63 | apps/shared/avifutil.c 64 | ) 65 | target_link_libraries(avifdec avif) 66 | endif() 67 | 68 | macro(avif_set_folder_safe target folder) 69 | if(TARGET ${target}) 70 | set_target_properties(${target} PROPERTIES FOLDER ${folder}) 71 | endif() 72 | endmacro() 73 | 74 | if(WIN32) 75 | option(AVIF_BUILD_STATIC "Build static avif library (with codec/gb included)" OFF) 76 | if(AVIF_BUILD_STATIC) 77 | add_custom_command( 78 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/avif_static_x64.lib 79 | COMMAND link.exe /lib /nologo /MACHINE:x64 /OUT:${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/avif_static_x64.lib $ $ $ 80 | DEPENDS avif aom 81 | COMMENT "Creating static avif library..." 82 | ) 83 | 84 | add_custom_target(avif_static ALL 85 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/avif_static_x64.lib 86 | ) 87 | endif() 88 | 89 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 90 | 91 | avif_set_folder_safe(aom "ext/avif") 92 | avif_set_folder_safe(aom_av1 "ext/avif") 93 | avif_set_folder_safe(aom_av1_common "ext/avif") 94 | avif_set_folder_safe(aom_av1_common_avx2_intrinsics "ext/avif") 95 | avif_set_folder_safe(aom_av1_common_sse2_intrinsics "ext/avif") 96 | avif_set_folder_safe(aom_av1_common_sse4_intrinsics "ext/avif") 97 | avif_set_folder_safe(aom_av1_common_ssse3_intrinsics "ext/avif") 98 | avif_set_folder_safe(aom_av1_decoder "ext/avif") 99 | avif_set_folder_safe(aom_av1_encoder "ext/avif") 100 | avif_set_folder_safe(aom_av1_encoder_avx2_intrinsics "ext/avif") 101 | avif_set_folder_safe(aom_av1_encoder_sse2 "ext/avif") 102 | avif_set_folder_safe(aom_av1_encoder_sse2_intrinsics "ext/avif") 103 | avif_set_folder_safe(aom_av1_encoder_sse3_intrinsics "ext/avif") 104 | avif_set_folder_safe(aom_av1_encoder_sse42_intrinsics "ext/avif") 105 | avif_set_folder_safe(aom_av1_encoder_sse4_intrinsics "ext/avif") 106 | avif_set_folder_safe(aom_av1_encoder_ssse3 "ext/avif") 107 | avif_set_folder_safe(aom_av1_encoder_ssse3_intrinsics "ext/avif") 108 | avif_set_folder_safe(aom_dsp "ext/avif") 109 | avif_set_folder_safe(aom_dsp_common "ext/avif") 110 | avif_set_folder_safe(aom_dsp_common_avx2_intrinsics "ext/avif") 111 | avif_set_folder_safe(aom_dsp_common_sse2 "ext/avif") 112 | avif_set_folder_safe(aom_dsp_common_sse2_intrinsics "ext/avif") 113 | avif_set_folder_safe(aom_dsp_common_sse4_1_intrinsics "ext/avif") 114 | avif_set_folder_safe(aom_dsp_common_ssse3 "ext/avif") 115 | avif_set_folder_safe(aom_dsp_common_ssse3_intrinsics "ext/avif") 116 | avif_set_folder_safe(aom_dsp_decoder "ext/avif") 117 | avif_set_folder_safe(aom_dsp_encoder "ext/avif") 118 | avif_set_folder_safe(aom_dsp_encoder_avx "ext/avif") 119 | avif_set_folder_safe(aom_dsp_encoder_avx2_intrinsics "ext/avif") 120 | avif_set_folder_safe(aom_dsp_encoder_sse2 "ext/avif") 121 | avif_set_folder_safe(aom_dsp_encoder_sse2_intrinsics "ext/avif") 122 | avif_set_folder_safe(aom_dsp_encoder_sse4_1_intrinsics "ext/avif") 123 | avif_set_folder_safe(aom_dsp_encoder_ssse3 "ext/avif") 124 | avif_set_folder_safe(aom_dsp_encoder_ssse3_intrinsics "ext/avif") 125 | avif_set_folder_safe(aom_mem "ext/avif") 126 | avif_set_folder_safe(aom_ports "ext/avif") 127 | avif_set_folder_safe(aom_rtcd "ext/avif") 128 | avif_set_folder_safe(aom_scale "ext/avif") 129 | avif_set_folder_safe(aom_util "ext/avif") 130 | avif_set_folder_safe(aom_version "ext/avif") 131 | avif_set_folder_safe(aom_version_check "ext/avif") 132 | avif_set_folder_safe(avif "ext/avif") 133 | avif_set_folder_safe(dist "ext/avif") 134 | 135 | if(NOT AVIF_EXTERNAL_GB) 136 | avif_set_folder_safe(gb "ext/avif") 137 | endif() 138 | endif() 139 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Joe Drago. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS PROJECT HAS MOVED 2 | 3 | This project is now officially a part of AOM, and all future updates will be here: 4 | 5 | https://github.com/AOMediaCodec/libavif 6 | 7 | This is now a stale repo. 8 | --- 9 | --- 10 | --- 11 | --- 12 | --- 13 | --- 14 | 15 | # libavif [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/joedrago/avif?branch=master&svg=true)](https://ci.appveyor.com/project/joedrago/avif) [![Travis Build Status](https://travis-ci.com/joedrago/avif.svg?branch=master)](https://travis-ci.com/joedrago/avif) 16 | 17 | This library aims to be a friendly, portable C implementation of the AV1 Image File Format, as described here: 18 | 19 | https://aomediacodec.github.io/av1-avif/ 20 | 21 | It is a work-in-progress, but can already encode and decode all AOM supported YUV formats and bit depths (with alpha). 22 | 23 | For now, it is recommended that you checkout/use [tagged releases](https://github.com/joedrago/avif/tags) instead of just using the master branch. I will regularly create new versions as bugfixes and features are added. 24 | 25 | # Build Notes 26 | 27 | Building libavif requires [NASM](https://nasm.us/) and [CMake](https://cmake.org/). 28 | 29 | Make sure nasm is available and in your PATH on your machine, then use CMake to do a basic build (Debug or Release). 30 | 31 | # Prebuilt Library (Windows) 32 | 33 | If you're building on Windows with VS2017 and want to try out libavif without going through the build process, static library builds for both Debug and Release are available on [Appveyor](https://ci.appveyor.com/project/joedrago/avif). 34 | 35 | --- 36 | 37 | # License 38 | 39 | Released under the BSD License. 40 | 41 | Copyright 2019 Joe Drago. All rights reserved. 42 | 43 | Redistribution and use in source and binary forms, with or without 44 | modification, are permitted provided that the following conditions are met: 45 | 46 | 1. Redistributions of source code must retain the above copyright notice, this 47 | list of conditions and the following disclaimer. 48 | 49 | 2. Redistributions in binary form must reproduce the above copyright notice, 50 | this list of conditions and the following disclaimer in the documentation 51 | and/or other materials provided with the distribution. 52 | 53 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 54 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 55 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 56 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 57 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 58 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 59 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 60 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 61 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 62 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 63 | -------------------------------------------------------------------------------- /apps/avifdec.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/avif.h" 5 | 6 | #include "avifutil.h" 7 | #include "y4m.h" 8 | 9 | #include 10 | #include 11 | 12 | int syntax(void) 13 | { 14 | printf("Syntax: avifdec [options] input.avif output.y4m\n"); 15 | printf("Options:\n"); 16 | printf(" -h,--help : Show syntax help\n"); 17 | printf("\n"); 18 | return 0; 19 | } 20 | 21 | int main(int argc, char * argv[]) 22 | { 23 | const char * inputFilename = NULL; 24 | const char * outputFilename = NULL; 25 | 26 | if (argc < 2) { 27 | return syntax(); 28 | } 29 | 30 | avifBool showHelp = AVIF_FALSE; 31 | 32 | int argIndex = 1; 33 | const char * filenames[2] = { NULL, NULL }; 34 | while (argIndex < argc) { 35 | const char * arg = argv[argIndex]; 36 | 37 | if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) { 38 | return syntax(); 39 | } else { 40 | // Positional argument 41 | if (!inputFilename) { 42 | inputFilename = arg; 43 | } else if (!outputFilename) { 44 | outputFilename = arg; 45 | } else { 46 | fprintf(stderr, "Too many positional arguments: %s\n", arg); 47 | return 1; 48 | } 49 | } 50 | 51 | ++argIndex; 52 | } 53 | 54 | if (!inputFilename || !outputFilename) { 55 | return syntax(); 56 | } 57 | 58 | FILE * inputFile = fopen(inputFilename, "rb"); 59 | if (!inputFile) { 60 | fprintf(stderr, "Cannot open file for read: %s\n", inputFilename); 61 | return 1; 62 | } 63 | fseek(inputFile, 0, SEEK_END); 64 | size_t inputFileSize = ftell(inputFile); 65 | fseek(inputFile, 0, SEEK_SET); 66 | 67 | if (inputFileSize < 1) { 68 | fprintf(stderr, "File too small: %s\n", inputFilename); 69 | fclose(inputFile); 70 | return 1; 71 | } 72 | 73 | avifRawData raw = AVIF_RAW_DATA_EMPTY; 74 | avifRawDataRealloc(&raw, inputFileSize); 75 | if (fread(raw.data, 1, inputFileSize, inputFile) != inputFileSize) { 76 | fprintf(stderr, "Failed to read %zu bytes: %s\n", inputFileSize, inputFilename); 77 | fclose(inputFile); 78 | avifRawDataFree(&raw); 79 | return 1; 80 | } 81 | 82 | fclose(inputFile); 83 | inputFile = NULL; 84 | 85 | avifImage * avif = avifImageCreateEmpty(); 86 | avifResult decodeResult = avifImageRead(avif, &raw); 87 | if (decodeResult == AVIF_RESULT_OK) { 88 | printf("Image decoded: %s\n", inputFilename); 89 | printf("Image details:\n"); 90 | avifImageDump(avif); 91 | y4mWrite(avif, outputFilename); 92 | } else { 93 | printf("ERROR: Failed to decode image: %s\n", avifResultToString(decodeResult)); 94 | } 95 | avifRawDataFree(&raw); 96 | avifImageDestroy(avif); 97 | return 0; 98 | } 99 | -------------------------------------------------------------------------------- /apps/avifenc.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/avif.h" 5 | 6 | #include "avifutil.h" 7 | #include "y4m.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #define NEXTARG() \ 14 | if (((argIndex + 1) == argc) || (argv[argIndex + 1][0] == '-')) { \ 15 | fprintf(stderr, "%s requires an argument.", arg); \ 16 | return 1; \ 17 | } \ 18 | arg = argv[++argIndex] 19 | 20 | static int syntax(void) 21 | { 22 | printf("Syntax: avifenc [options] input.y4m output.avif\n"); 23 | printf("Options:\n"); 24 | printf(" -h,--help : Show syntax help\n"); 25 | printf(" -j,--jobs J : Number of jobs (worker threads, default: 1)\n"); 26 | printf(" -n,--nclx P/T/M/R : Set nclx colr box values (4 raw numbers)\n"); 27 | printf(" P = enum avifNclxColourPrimaries\n"); 28 | printf(" T = enum avifNclxTransferCharacteristics\n"); 29 | printf(" M = enum avifNclxMatrixCoefficients\n"); 30 | printf(" R = avifNclxRangeFlag (any nonzero value becomes AVIF_NCLX_FULL_RANGE)\n"); 31 | printf(" -q,--quality Q : Set quality (%d-%d, where %d is lossless)\n", AVIF_BEST_QUALITY, AVIF_WORST_QUALITY, AVIF_BEST_QUALITY); 32 | printf("\n"); 33 | return 0; 34 | } 35 | 36 | // This is *very* arbitrary, I just want to set people's expectations a bit 37 | static const char * qualityString(int quality) 38 | { 39 | if (quality == 0) { 40 | return "Lossless"; 41 | } 42 | if (quality <= 12) { 43 | return "High"; 44 | } 45 | if (quality <= 32) { 46 | return "Medium"; 47 | } 48 | if (quality == AVIF_WORST_QUALITY) { 49 | return "Worst"; 50 | } 51 | return "Low"; 52 | } 53 | 54 | static avifBool parseNCLX(avifNclxColorProfile * nclx, const char * arg) 55 | { 56 | char buffer[128]; 57 | strncpy(buffer, arg, 127); 58 | buffer[127] = 0; 59 | 60 | int values[4]; 61 | int index = 0; 62 | char * token = strtok(buffer, "/"); 63 | while (token != NULL) { 64 | values[index] = atoi(token); 65 | ++index; 66 | if (index >= 4) { 67 | break; 68 | } 69 | 70 | token = strtok(NULL, "/"); 71 | } 72 | 73 | if (index == 4) { 74 | nclx->colourPrimaries = values[0]; 75 | nclx->transferCharacteristics = values[1]; 76 | nclx->matrixCoefficients = values[2]; 77 | nclx->fullRangeFlag = values[3] ? AVIF_NCLX_FULL_RANGE : AVIF_NCLX_LIMITED_RANGE; 78 | return AVIF_TRUE; 79 | } 80 | return AVIF_FALSE; 81 | } 82 | 83 | int main(int argc, char * argv[]) 84 | { 85 | const char * inputFilename = NULL; 86 | const char * outputFilename = NULL; 87 | 88 | if (argc < 2) { 89 | return syntax(); 90 | } 91 | 92 | avifBool showHelp = AVIF_FALSE; 93 | int jobs = 1; 94 | int quality = AVIF_BEST_QUALITY; 95 | avifBool nclxSet = AVIF_FALSE; 96 | avifNclxColorProfile nclx; 97 | 98 | int argIndex = 1; 99 | const char * filenames[2] = { NULL, NULL }; 100 | while (argIndex < argc) { 101 | const char * arg = argv[argIndex]; 102 | 103 | if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) { 104 | return syntax(); 105 | } else if (!strcmp(arg, "-j") || !strcmp(arg, "--jobs")) { 106 | NEXTARG(); 107 | jobs = atoi(arg); 108 | if (jobs < 1) { 109 | jobs = 1; 110 | } 111 | } else if (!strcmp(arg, "-q") || !strcmp(arg, "--quality")) { 112 | NEXTARG(); 113 | quality = atoi(arg); 114 | if (quality < AVIF_BEST_QUALITY) { 115 | quality = AVIF_BEST_QUALITY; 116 | } 117 | if (quality > AVIF_WORST_QUALITY) { 118 | quality = AVIF_WORST_QUALITY; 119 | } 120 | } else if (!strcmp(arg, "-n") || !strcmp(arg, "--nclx")) { 121 | NEXTARG(); 122 | if (!parseNCLX(&nclx, arg)) { 123 | return 1; 124 | } 125 | nclxSet = AVIF_TRUE; 126 | } else { 127 | // Positional argument 128 | if (!inputFilename) { 129 | inputFilename = arg; 130 | } else if (!outputFilename) { 131 | outputFilename = arg; 132 | } else { 133 | fprintf(stderr, "Too many positional arguments: %s\n", arg); 134 | return 1; 135 | } 136 | } 137 | 138 | ++argIndex; 139 | } 140 | 141 | if (!inputFilename || !outputFilename) { 142 | return syntax(); 143 | } 144 | 145 | int returnCode = 0; 146 | avifImage * avif = avifImageCreateEmpty(); 147 | avifRawData raw = AVIF_RAW_DATA_EMPTY; 148 | 149 | if (!y4mRead(avif, inputFilename)) { 150 | avifImageDestroy(avif); 151 | returnCode = 1; 152 | goto cleanup; 153 | } 154 | printf("Successfully loaded: %s\n", inputFilename); 155 | 156 | if (nclxSet) { 157 | avif->profileFormat = AVIF_PROFILE_FORMAT_NCLX; 158 | memcpy(&avif->nclx, &nclx, sizeof(nclx)); 159 | } 160 | 161 | printf("AVIF to be written:\n"); 162 | avifImageDump(avif); 163 | 164 | printf("Encoding with quality %d (%s), %d worker thread(s), please wait...\n", quality, qualityString(quality), jobs); 165 | avifResult encodeResult = avifImageWrite(avif, &raw, jobs, quality); 166 | if (encodeResult != AVIF_RESULT_OK) { 167 | fprintf(stderr, "ERROR: Failed to encode image: %s\n", avifResultToString(encodeResult)); 168 | goto cleanup; 169 | } 170 | 171 | printf("Encoded successfully.\n"); 172 | printf(" * ColorOBU size: %zu bytes\n", avif->ioStats.colorOBUSize); 173 | FILE * f = fopen(outputFilename, "wb"); 174 | if (!f) { 175 | fprintf(stderr, "ERROR: Failed to open file for write: %s\n", outputFilename); 176 | goto cleanup; 177 | } 178 | fwrite(raw.data, 1, raw.size, f); 179 | fclose(f); 180 | printf("Wrote: %s\n", outputFilename); 181 | 182 | cleanup: 183 | avifImageDestroy(avif); 184 | avifRawDataFree(&raw); 185 | return returnCode; 186 | } 187 | -------------------------------------------------------------------------------- /apps/shared/avifutil.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avifutil.h" 5 | 6 | #include 7 | 8 | void avifImageDump(avifImage * avif) 9 | { 10 | printf(" * Resolution : %dx%d\n", avif->width, avif->height); 11 | printf(" * Bit Depth : %d\n", avif->depth); 12 | printf(" * Format : %s\n", avifPixelFormatToString(avif->yuvFormat)); 13 | switch (avif->profileFormat) { 14 | case AVIF_PROFILE_FORMAT_NONE: 15 | printf(" * Color Profile: None\n"); 16 | break; 17 | case AVIF_PROFILE_FORMAT_ICC: 18 | printf(" * Color Profile: ICC (%zu bytes)\n", avif->icc.size); 19 | break; 20 | case AVIF_PROFILE_FORMAT_NCLX: 21 | printf(" * Color Profile: nclx - P:%d / T:%d / M:%d / R:%s\n", 22 | avif->nclx.colourPrimaries, avif->nclx.transferCharacteristics, avif->nclx.matrixCoefficients, 23 | avif->nclx.fullRangeFlag ? "full" : "limited"); 24 | break; 25 | } 26 | printf("\n"); 27 | } 28 | -------------------------------------------------------------------------------- /apps/shared/avifutil.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/avif.h" 5 | 6 | void avifImageDump(avifImage * avif); 7 | -------------------------------------------------------------------------------- /apps/shared/y4m.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | // This is a barebones y4m reader/writer for basic libavif testing. It is NOT comprehensive! 5 | 6 | #include "y4m.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | avifBool y4mColorSpaceToFormatAndDepth(const char * formatString, avifPixelFormat * format, int * depth) 13 | { 14 | if (!strcmp(formatString, "C420jpeg")) { 15 | *format = AVIF_PIXEL_FORMAT_YUV420; 16 | *depth = 8; 17 | return AVIF_TRUE; 18 | } 19 | if (!strcmp(formatString, "C444p10")) { 20 | *format = AVIF_PIXEL_FORMAT_YUV444; 21 | *depth = 10; 22 | return AVIF_TRUE; 23 | } 24 | if (!strcmp(formatString, "C422p10")) { 25 | *format = AVIF_PIXEL_FORMAT_YUV422; 26 | *depth = 10; 27 | return AVIF_TRUE; 28 | } 29 | if (!strcmp(formatString, "C420p10")) { 30 | *format = AVIF_PIXEL_FORMAT_YUV420; 31 | *depth = 10; 32 | return AVIF_TRUE; 33 | } 34 | if (!strcmp(formatString, "C422p10")) { 35 | *format = AVIF_PIXEL_FORMAT_YUV422; 36 | *depth = 10; 37 | return AVIF_TRUE; 38 | } 39 | if (!strcmp(formatString, "C444p12")) { 40 | *format = AVIF_PIXEL_FORMAT_YUV444; 41 | *depth = 12; 42 | return AVIF_TRUE; 43 | } 44 | if (!strcmp(formatString, "C422p12")) { 45 | *format = AVIF_PIXEL_FORMAT_YUV422; 46 | *depth = 12; 47 | return AVIF_TRUE; 48 | } 49 | if (!strcmp(formatString, "C420p12")) { 50 | *format = AVIF_PIXEL_FORMAT_YUV420; 51 | *depth = 12; 52 | return AVIF_TRUE; 53 | } 54 | if (!strcmp(formatString, "C422p12")) { 55 | *format = AVIF_PIXEL_FORMAT_YUV422; 56 | *depth = 12; 57 | return AVIF_TRUE; 58 | } 59 | if (!strcmp(formatString, "C444")) { 60 | *format = AVIF_PIXEL_FORMAT_YUV444; 61 | *depth = 8; 62 | return AVIF_TRUE; 63 | } 64 | if (!strcmp(formatString, "C422")) { 65 | *format = AVIF_PIXEL_FORMAT_YUV422; 66 | *depth = 8; 67 | return AVIF_TRUE; 68 | } 69 | if (!strcmp(formatString, "C420")) { 70 | *format = AVIF_PIXEL_FORMAT_YUV420; 71 | *depth = 8; 72 | return AVIF_TRUE; 73 | } 74 | return AVIF_FALSE; 75 | } 76 | 77 | avifBool getHeaderString(uint8_t * p, uint8_t * end, char * out, size_t maxChars) 78 | { 79 | uint8_t * headerEnd = p; 80 | while ((*headerEnd != ' ') && (*headerEnd != '\n')) { 81 | if (headerEnd >= end) { 82 | return AVIF_FALSE; 83 | } 84 | ++headerEnd; 85 | } 86 | size_t formatLen = headerEnd - p; 87 | if (formatLen > maxChars) { 88 | return AVIF_FALSE; 89 | } 90 | 91 | strncpy(out, p, formatLen); 92 | out[formatLen] = 0; 93 | return AVIF_TRUE; 94 | } 95 | 96 | #define ADVANCE(BYTES) { p += BYTES; if (p >= end) goto cleanup; } 97 | 98 | avifBool y4mRead(avifImage * avif, const char * inputFilename) 99 | { 100 | FILE * inputFile = fopen(inputFilename, "rb"); 101 | if (!inputFile) { 102 | fprintf(stderr, "Cannot open file for read: %s\n", inputFilename); 103 | return AVIF_FALSE; 104 | } 105 | fseek(inputFile, 0, SEEK_END); 106 | size_t inputFileSize = ftell(inputFile); 107 | fseek(inputFile, 0, SEEK_SET); 108 | 109 | if (inputFileSize < 10) { 110 | fprintf(stderr, "File too small: %s\n", inputFilename); 111 | fclose(inputFile); 112 | return AVIF_FALSE; 113 | } 114 | 115 | avifRawData raw = AVIF_RAW_DATA_EMPTY; 116 | avifRawDataRealloc(&raw, inputFileSize); 117 | if (fread(raw.data, 1, inputFileSize, inputFile) != inputFileSize) { 118 | fprintf(stderr, "Failed to read %zu bytes: %s\n", inputFileSize, inputFilename); 119 | fclose(inputFile); 120 | avifRawDataFree(&raw); 121 | return AVIF_FALSE; 122 | } 123 | 124 | avifBool result = AVIF_FALSE; 125 | 126 | fclose(inputFile); 127 | inputFile = NULL; 128 | 129 | uint8_t * end = raw.data + raw.size; 130 | uint8_t * p = raw.data; 131 | 132 | if (memcmp(p, "YUV4MPEG2 ", 10) != 0) { 133 | fprintf(stderr, "Not a y4m file: %s\n", inputFilename); 134 | avifRawDataFree(&raw); 135 | return AVIF_FALSE; 136 | } 137 | ADVANCE(10); // skip past header 138 | 139 | char tmpBuffer[32]; 140 | 141 | int width = -1; 142 | int height = -1; 143 | int depth = -1; 144 | avifPixelFormat format = AVIF_PIXEL_FORMAT_NONE; 145 | avifNclxRangeFlag rangeFlag = AVIF_NCLX_LIMITED_RANGE; 146 | while (p != end) { 147 | switch (*p) { 148 | case 'W': // width 149 | width = atoi(p + 1); 150 | break; 151 | case 'H': // height 152 | height = atoi(p + 1); 153 | break; 154 | case 'C': // color space 155 | if (!getHeaderString(p, end, tmpBuffer, 31)) { 156 | fprintf(stderr, "Bad y4m header: %s\n", inputFilename); 157 | goto cleanup; 158 | } 159 | if (!y4mColorSpaceToFormatAndDepth(tmpBuffer, &format, &depth)) { 160 | fprintf(stderr, "Unsupported y4m pixel format: %s\n", inputFilename); 161 | goto cleanup; 162 | } 163 | break; 164 | case 'X': 165 | if (!getHeaderString(p, end, tmpBuffer, 31)) { 166 | fprintf(stderr, "Bad y4m header: %s\n", inputFilename); 167 | goto cleanup; 168 | } 169 | if (!strcmp(tmpBuffer, "XCOLORRANGE=FULL")) { 170 | rangeFlag = AVIF_NCLX_FULL_RANGE; 171 | } 172 | default: 173 | break; 174 | } 175 | 176 | // Advance past header section 177 | while ((*p != '\n') && (*p != ' ')) { 178 | ADVANCE(1); 179 | } 180 | if (*p == '\n') { 181 | // Done with y4m header 182 | break; 183 | } 184 | 185 | ADVANCE(1); 186 | } 187 | 188 | if (*p != '\n') { 189 | fprintf(stderr, "Truncated y4m header (no newline): %s\n", inputFilename); 190 | goto cleanup; 191 | } 192 | ADVANCE(1); // advance past newline 193 | 194 | size_t remainingBytes = end - p; 195 | if (remainingBytes < 6) { 196 | fprintf(stderr, "Truncated y4m (no room for frame header): %s\n", inputFilename); 197 | goto cleanup; 198 | } 199 | if (memcmp(p, "FRAME", 5) != 0) { 200 | fprintf(stderr, "Truncated y4m (no frame): %s\n", inputFilename); 201 | goto cleanup; 202 | } 203 | 204 | // Advance past frame header 205 | // TODO: Parse frame overrides similarly to header parsing above? 206 | while (*p != '\n') { 207 | ADVANCE(1); 208 | } 209 | if (*p != '\n') { 210 | fprintf(stderr, "Invalid y4m frame header: %s\n", inputFilename); 211 | goto cleanup; 212 | } 213 | ADVANCE(1); // advance past newline 214 | 215 | if ((width < 1) || 216 | (height < 1) || 217 | ((depth != 8) && (depth != 10) && (depth != 12)) || 218 | (format == AVIF_PIXEL_FORMAT_NONE)) 219 | { 220 | fprintf(stderr, "Failed to parse y4m header (not enough information): %s\n", inputFilename); 221 | goto cleanup; 222 | } 223 | 224 | avifImageFreePlanes(avif, AVIF_PLANES_YUV | AVIF_PLANES_A); 225 | avif->width = width; 226 | avif->height = height; 227 | avif->depth = depth; 228 | avif->yuvFormat = format; 229 | avif->yuvRange = rangeFlag; 230 | avifImageAllocatePlanes(avif, AVIF_PLANES_YUV); 231 | 232 | avifPixelFormatInfo info; 233 | avifGetPixelFormatInfo(avif->yuvFormat, &info); 234 | 235 | uint32_t planeBytes[3]; 236 | planeBytes[0] = avif->yuvRowBytes[0] * avif->height; 237 | planeBytes[1] = avif->yuvRowBytes[1] * (avif->height >> info.chromaShiftY); 238 | planeBytes[2] = avif->yuvRowBytes[2] * (avif->height >> info.chromaShiftY); 239 | 240 | uint32_t bytesNeeded = planeBytes[0] + planeBytes[1] + planeBytes[2]; 241 | remainingBytes = end - p; 242 | if (bytesNeeded > remainingBytes) { 243 | fprintf(stderr, "Not enough bytes in y4m for first frame: %s\n", inputFilename); 244 | goto cleanup; 245 | } 246 | 247 | for (int i = 0; i < 3; ++i) { 248 | memcpy(avif->yuvPlanes[i], p, planeBytes[i]); 249 | p += planeBytes[i]; 250 | } 251 | 252 | result = AVIF_TRUE; 253 | cleanup: 254 | avifRawDataFree(&raw); 255 | return result; 256 | } 257 | 258 | avifBool y4mWrite(avifImage * avif, const char * outputFilename) 259 | { 260 | avifBool swapUV = AVIF_FALSE; 261 | char * y4mHeaderFormat = NULL; 262 | switch (avif->depth) { 263 | case 8: 264 | switch (avif->yuvFormat) { 265 | case AVIF_PIXEL_FORMAT_YUV444: 266 | y4mHeaderFormat = "C444 XYSCSS=444"; 267 | break; 268 | case AVIF_PIXEL_FORMAT_YUV422: 269 | y4mHeaderFormat = "C422 XYSCSS=422"; 270 | break; 271 | case AVIF_PIXEL_FORMAT_YUV420: 272 | y4mHeaderFormat = "C420jpeg XYSCSS=420JPEG"; 273 | break; 274 | case AVIF_PIXEL_FORMAT_YV12: 275 | y4mHeaderFormat = "C420jpeg XYSCSS=420JPEG"; 276 | swapUV = AVIF_TRUE; 277 | break; 278 | } 279 | break; 280 | case 10: 281 | switch (avif->yuvFormat) { 282 | case AVIF_PIXEL_FORMAT_YUV444: 283 | y4mHeaderFormat = "C444p10 XYSCSS=444P10"; 284 | break; 285 | case AVIF_PIXEL_FORMAT_YUV422: 286 | y4mHeaderFormat = "C422p10 XYSCSS=422P10"; 287 | break; 288 | case AVIF_PIXEL_FORMAT_YUV420: 289 | y4mHeaderFormat = "C420p10 XYSCSS=420P10"; 290 | break; 291 | case AVIF_PIXEL_FORMAT_YV12: 292 | y4mHeaderFormat = "C422p10 XYSCSS=422P10"; 293 | swapUV = AVIF_TRUE; 294 | break; 295 | } 296 | break; 297 | case 12: 298 | switch (avif->yuvFormat) { 299 | case AVIF_PIXEL_FORMAT_YUV444: 300 | y4mHeaderFormat = "C444p12 XYSCSS=444P12"; 301 | break; 302 | case AVIF_PIXEL_FORMAT_YUV422: 303 | y4mHeaderFormat = "C422p12 XYSCSS=422P12"; 304 | break; 305 | case AVIF_PIXEL_FORMAT_YUV420: 306 | y4mHeaderFormat = "C420p12 XYSCSS=420P12"; 307 | break; 308 | case AVIF_PIXEL_FORMAT_YV12: 309 | y4mHeaderFormat = "C422p12 XYSCSS=422P12"; 310 | swapUV = AVIF_TRUE; 311 | break; 312 | } 313 | break; 314 | default: 315 | fprintf(stderr, "ERROR: y4mWrite unsupported depth: %d\n", avif->depth); 316 | return AVIF_FALSE; 317 | } 318 | 319 | if (y4mHeaderFormat == NULL) { 320 | fprintf(stderr, "ERROR: unsupported format\n"); 321 | return AVIF_FALSE; 322 | } 323 | 324 | const char * rangeString = "XCOLORRANGE=FULL"; 325 | if ((avif->profileFormat == AVIF_PROFILE_FORMAT_NCLX) && !avif->nclx.fullRangeFlag) { 326 | rangeString = "XCOLORRANGE=LIMITED"; 327 | } 328 | 329 | avifPixelFormatInfo info; 330 | avifGetPixelFormatInfo(avif->yuvFormat, &info); 331 | 332 | FILE * f = fopen(outputFilename, "wb"); 333 | if (!f) { 334 | fprintf(stderr, "Cannot open file for write: %s\n", outputFilename); 335 | return AVIF_FALSE; 336 | } 337 | 338 | fprintf(f, "YUV4MPEG2 W%d H%d F25:1 Ip A0:0 %s %s\n", avif->width, avif->height, y4mHeaderFormat, rangeString); 339 | 340 | fprintf(f, "FRAME\n"); 341 | uint8_t * planes[3]; 342 | uint32_t planeBytes[3]; 343 | planes[0] = avif->yuvPlanes[0]; 344 | planes[1] = avif->yuvPlanes[1]; 345 | planes[2] = avif->yuvPlanes[2]; 346 | planeBytes[0] = avif->yuvRowBytes[0] * avif->height; 347 | planeBytes[1] = avif->yuvRowBytes[1] * (avif->height >> info.chromaShiftY); 348 | planeBytes[2] = avif->yuvRowBytes[2] * (avif->height >> info.chromaShiftY); 349 | if (swapUV) { 350 | uint8_t * tmpPtr; 351 | uint32_t tmp; 352 | tmpPtr = planes[1]; 353 | tmp = planeBytes[1]; 354 | planes[1] = planes[2]; 355 | planeBytes[1] = planeBytes[2]; 356 | planes[2] = tmpPtr; 357 | planeBytes[2] = tmp; 358 | } 359 | 360 | for (int i = 0; i < 3; ++i) { 361 | fwrite(planes[i], 1, planeBytes[i], f); 362 | } 363 | 364 | fclose(f); 365 | printf("Wrote: %s\n", outputFilename); 366 | return AVIF_TRUE; 367 | } 368 | -------------------------------------------------------------------------------- /apps/shared/y4m.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/avif.h" 5 | 6 | avifBool y4mRead(avifImage * avif, const char * inputFilename); 7 | avifBool y4mWrite(avifImage * avif, const char * outputFilename); 8 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | branches: 3 | only: 4 | - master 5 | image: Visual Studio 2017 6 | configuration: 7 | - Debug 8 | - Release 9 | init: 10 | - cmd: >- 11 | set arch=Win64 12 | 13 | if "%arch%"=="Win64" ( set arch= Win64) 14 | 15 | echo %arch% 16 | 17 | echo %APPVEYOR_BUILD_WORKER_IMAGE% 18 | 19 | if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" ( set generator="Visual Studio 15 2017%arch%" ) 20 | 21 | if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" ( set generator="Visual Studio 14 2015%arch%" ) 22 | 23 | if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2013" ( set generator="Visual Studio 12 2013%arch%" ) 24 | 25 | echo %generator% 26 | install: 27 | - cmd: >- 28 | cd %APPVEYOR_BUILD_FOLDER% 29 | 30 | git submodule update --init --recursive 31 | before_build: 32 | - cmd: >- 33 | appveyor DownloadFile "https://github.com/joedrago/nasm_mirror/raw/master/nasm-2.14.02-win64.zip" -FileName "nasm.zip" 34 | 35 | 7z x "nasm.zip" > nul 36 | 37 | move nasm-* NASM 38 | 39 | set PATH=%PATH%;%CD%\NASM; 40 | 41 | nasm -v 42 | 43 | mkdir build 44 | 45 | cd build 46 | 47 | cmake --version 48 | 49 | cmake .. -G %generator% -DAVIF_BUILD_STATIC=1 -DAVIF_BUILD_APPS=1 50 | after_build: 51 | - cmd: >- 52 | copy %CONFIGURATION%\avifenc.exe . 53 | 54 | copy %CONFIGURATION%\avifdec.exe . 55 | 56 | mkdir lib 57 | 58 | set ARTIFACT_ZIP=avif_vs2017_x64_%APPVEYOR_REPO_COMMIT:~0,8%_%CONFIGURATION%.zip 59 | 60 | copy %CONFIGURATION%\avif_static_x64.lib lib\avif_x64_%CONFIGURATION%.lib 61 | 62 | 7z a %ARTIFACT_ZIP% lib 63 | 64 | cd .. 65 | 66 | 7z a build\%ARTIFACT_ZIP% include examples 67 | build: 68 | project: C:/projects/avif/build/avif.sln 69 | parallel: true 70 | verbosity: minimal 71 | artifacts: 72 | - path: 'build\avif_vs2017_x64_*.zip' 73 | name: Static Lib and Includes 74 | - path: 'build\*.exe' 75 | name: Basic apps 76 | -------------------------------------------------------------------------------- /examples/avif_example1.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/avif.h" 5 | 6 | #include 7 | #include 8 | 9 | int main(int argc, char * argv[]) 10 | { 11 | (void)argc; 12 | (void)argv; 13 | 14 | printf("avif version: %s\n", avifVersion()); 15 | 16 | #if 1 17 | int width = 32; 18 | int height = 32; 19 | int depth = 8; 20 | 21 | // Encode an orange, 8-bit, full opacity image 22 | avifImage * image = avifImageCreate(width, height, depth, AVIF_PIXEL_FORMAT_YUV444); 23 | avifImageAllocatePlanes(image, AVIF_PLANES_RGB | AVIF_PLANES_A); 24 | for (int j = 0; j < height; ++j) { 25 | for (int i = 0; i < width; ++i) { 26 | image->rgbPlanes[0][i + (j * image->rgbRowBytes[0])] = 255; // R 27 | image->rgbPlanes[1][i + (j * image->rgbRowBytes[1])] = 128; // G 28 | image->rgbPlanes[2][i + (j * image->rgbRowBytes[2])] = 0; // B 29 | image->alphaPlane[i + (j * image->alphaRowBytes)] = 255; // A 30 | } 31 | } 32 | 33 | // uint8_t * fakeICC = "abcdefg"; 34 | // uint32_t fakeICCSize = (uint32_t)strlen(fakeICC); 35 | // apgImageSetICC(image, fakeICC, fakeICCSize); 36 | 37 | avifRawData raw = AVIF_RAW_DATA_EMPTY; 38 | avifResult res = avifImageWrite(image, &raw, 1, 50); 39 | 40 | #if 0 41 | // debug 42 | { 43 | FILE * f = fopen("out.avif", "wb"); 44 | if (f) { 45 | fwrite(raw.data, 1, raw.size, f); 46 | fclose(f); 47 | } 48 | } 49 | #endif 50 | 51 | if (res == AVIF_RESULT_OK) { 52 | // Decode it 53 | avifImage * decoded = avifImageCreateEmpty(); 54 | avifResult decodeResult = avifImageRead(decoded, &raw); 55 | if (decodeResult == AVIF_RESULT_OK) { 56 | avifImageYUVToRGB(decoded); 57 | for (int j = 0; j < height; ++j) { 58 | for (int i = 0; i < width; ++i) { 59 | for (int plane = 0; plane < 3; ++plane) { 60 | uint32_t src = image->rgbPlanes[plane][i + (j * image->rgbRowBytes[plane])]; 61 | uint32_t dst = decoded->rgbPlanes[plane][i + (j * decoded->rgbRowBytes[plane])]; 62 | if (src != dst) { 63 | printf("(%d,%d,p%d) %d != %d\n", i, j, plane, src, dst); 64 | } 65 | } 66 | } 67 | } 68 | } 69 | avifImageDestroy(decoded); 70 | } 71 | 72 | avifImageDestroy(image); 73 | #else /* if 1 */ 74 | 75 | FILE * f = fopen("test.avif", "rb"); 76 | if (!f) 77 | return 0; 78 | 79 | fseek(f, 0, SEEK_END); 80 | uint32_t size = ftell(f); 81 | fseek(f, 0, SEEK_SET); 82 | avifRawData raw = AVIF_RAW_DATA_EMPTY; 83 | avifRawDataRealloc(&raw, size); 84 | fread(raw.data, 1, size, f); 85 | fclose(f); 86 | 87 | avifImage * decoded = avifImageCreate(); 88 | avifResult decodeResult = avifImageRead(decoded, &raw); 89 | avifRawDataFree(&raw); 90 | #endif /* if 1 */ 91 | return 0; 92 | } 93 | -------------------------------------------------------------------------------- /ext/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Joe Drago. All rights reserved. 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | 4 | set(AOM_MINIMAL_BUILD TRUE) 5 | add_subdirectory(aom) 6 | 7 | if(NOT AVIF_EXTERNAL_GB) 8 | include_directories(gb) 9 | add_library(gb 10 | gb/gb_math.c 11 | gb/gb_math.h 12 | ) 13 | endif() 14 | -------------------------------------------------------------------------------- /ext/gb/gb_math.c: -------------------------------------------------------------------------------- 1 | #define GB_MATH_IMPLEMENTATION 2 | #include "gb_math.h" 3 | -------------------------------------------------------------------------------- /include/avif/avif.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #ifndef AVIF_AVIF_H 5 | #define AVIF_AVIF_H 6 | 7 | #include 8 | #include 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | // --------------------------------------------------------------------------- 15 | // Constants 16 | 17 | #define AVIF_VERSION_MAJOR 0 18 | #define AVIF_VERSION_MINOR 1 19 | #define AVIF_VERSION_PATCH 3 20 | #define AVIF_VERSION (AVIF_VERSION_MAJOR * 10000) + (AVIF_VERSION_MINOR * 100) + AVIF_VERSION_PATCH 21 | 22 | typedef int avifBool; 23 | #define AVIF_TRUE 1 24 | #define AVIF_FALSE 0 25 | 26 | #define AVIF_LOSSLESS 0 27 | #define AVIF_BEST_QUALITY 0 28 | #define AVIF_WORST_QUALITY 63 29 | 30 | #define AVIF_PLANE_COUNT_RGB 3 31 | #define AVIF_PLANE_COUNT_YUV 3 32 | 33 | enum avifPlanesFlags 34 | { 35 | AVIF_PLANES_RGB = (1 << 0), 36 | AVIF_PLANES_YUV = (1 << 1), 37 | AVIF_PLANES_A = (1 << 2), 38 | 39 | AVIF_PLANES_ALL = 0xff 40 | }; 41 | 42 | enum avifChannelIndex 43 | { 44 | // rgbPlanes 45 | AVIF_CHAN_R = 0, 46 | AVIF_CHAN_G = 1, 47 | AVIF_CHAN_B = 2, 48 | 49 | // yuvPlanes - These are always correct, even if UV is flipped when encoded (YV12) 50 | AVIF_CHAN_Y = 0, 51 | AVIF_CHAN_U = 1, 52 | AVIF_CHAN_V = 2 53 | }; 54 | 55 | // --------------------------------------------------------------------------- 56 | // Utils 57 | 58 | const char * avifVersion(void); 59 | 60 | // Yes, clamp macros are nasty. Do not use them. 61 | #define AVIF_CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) 62 | 63 | float avifRoundf(float v); 64 | 65 | uint16_t avifHTONS(uint16_t s); 66 | uint16_t avifNTOHS(uint16_t s); 67 | uint32_t avifHTONL(uint32_t l); 68 | uint32_t avifNTOHL(uint32_t l); 69 | 70 | // --------------------------------------------------------------------------- 71 | // Memory management 72 | 73 | void * avifAlloc(size_t size); 74 | void avifFree(void * p); 75 | 76 | // --------------------------------------------------------------------------- 77 | // avifResult 78 | 79 | typedef enum avifResult 80 | { 81 | AVIF_RESULT_OK = 0, 82 | AVIF_RESULT_UNKNOWN_ERROR, 83 | AVIF_RESULT_INVALID_FTYP, 84 | AVIF_RESULT_NO_CONTENT, 85 | AVIF_RESULT_NO_YUV_FORMAT_SELECTED, 86 | AVIF_RESULT_REFORMAT_FAILED, 87 | AVIF_RESULT_UNSUPPORTED_DEPTH, 88 | AVIF_RESULT_ENCODE_COLOR_FAILED, 89 | AVIF_RESULT_ENCODE_ALPHA_FAILED, 90 | AVIF_RESULT_BMFF_PARSE_FAILED, 91 | AVIF_RESULT_NO_AV1_ITEMS_FOUND, 92 | AVIF_RESULT_DECODE_COLOR_FAILED, 93 | AVIF_RESULT_DECODE_ALPHA_FAILED, 94 | AVIF_RESULT_COLOR_ALPHA_SIZE_MISMATCH, 95 | AVIF_RESULT_ISPE_SIZE_MISMATCH, 96 | AVIF_UNSUPPORTED_PIXEL_FORMAT 97 | } avifResult; 98 | 99 | const char * avifResultToString(avifResult result); 100 | 101 | // --------------------------------------------------------------------------- 102 | // avifRawData: Generic raw memory storage 103 | 104 | // Note: you can use this struct directly (without the functions) if you're 105 | // passing data into avif*() functions, but you should use avifRawDataFree() 106 | // if any avif*() function populates one of these. 107 | 108 | typedef struct avifRawData 109 | { 110 | uint8_t * data; 111 | size_t size; 112 | } avifRawData; 113 | 114 | // Initialize avifRawData on the stack with this 115 | #define AVIF_RAW_DATA_EMPTY { NULL, 0 } 116 | 117 | void avifRawDataRealloc(avifRawData * raw, size_t newSize); 118 | void avifRawDataSet(avifRawData * raw, const uint8_t * data, size_t len); 119 | void avifRawDataConcat(avifRawData * dst, avifRawData ** srcs, int srcsCount); 120 | void avifRawDataFree(avifRawData * raw); 121 | 122 | // --------------------------------------------------------------------------- 123 | // avifPixelFormat 124 | 125 | typedef enum avifPixelFormat 126 | { 127 | // No pixels are present 128 | AVIF_PIXEL_FORMAT_NONE = 0, 129 | 130 | AVIF_PIXEL_FORMAT_YUV444, 131 | AVIF_PIXEL_FORMAT_YUV422, 132 | AVIF_PIXEL_FORMAT_YUV420, 133 | AVIF_PIXEL_FORMAT_YV12 134 | } avifPixelFormat; 135 | const char * avifPixelFormatToString(avifPixelFormat format); 136 | 137 | typedef struct avifPixelFormatInfo 138 | { 139 | int chromaShiftX; 140 | int chromaShiftY; 141 | int aomIndexU; // maps U plane to AOM-side plane index 142 | int aomIndexV; // maps V plane to AOM-side plane index 143 | } avifPixelFormatInfo; 144 | 145 | void avifGetPixelFormatInfo(avifPixelFormat format, avifPixelFormatInfo * info); 146 | 147 | // --------------------------------------------------------------------------- 148 | // avifNclxColorProfile 149 | 150 | typedef enum avifNclxColourPrimaries 151 | { 152 | // This is actually reserved, but libavif uses it as a sentinel value. 153 | AVIF_NCLX_COLOUR_PRIMARIES_UNKNOWN = 0, 154 | 155 | AVIF_NCLX_COLOUR_PRIMARIES_BT709 = 1, 156 | AVIF_NCLX_COLOUR_PRIMARIES_BT1361_0 = 1, 157 | AVIF_NCLX_COLOUR_PRIMARIES_IEC61966_2_1 = 1, 158 | AVIF_NCLX_COLOUR_PRIMARIES_SRGB = 1, 159 | AVIF_NCLX_COLOUR_PRIMARIES_SYCC = 1, 160 | AVIF_NCLX_COLOUR_PRIMARIES_IEC61966_2_4 = 1, 161 | AVIF_NCLX_COLOUR_PRIMARIES_UNSPECIFIED = 2, 162 | AVIF_NCLX_COLOUR_PRIMARIES_BT470_6M = 4, 163 | AVIF_NCLX_COLOUR_PRIMARIES_BT601_7_625 = 5, 164 | AVIF_NCLX_COLOUR_PRIMARIES_BT470_6G = 5, 165 | AVIF_NCLX_COLOUR_PRIMARIES_BT601_7_525 = 6, 166 | AVIF_NCLX_COLOUR_PRIMARIES_BT1358 = 6, 167 | AVIF_NCLX_COLOUR_PRIMARIES_ST240 = 7, 168 | AVIF_NCLX_COLOUR_PRIMARIES_GENERIC_FILM = 8, 169 | AVIF_NCLX_COLOUR_PRIMARIES_BT2020 = 9, 170 | AVIF_NCLX_COLOUR_PRIMARIES_BT2100 = 9, 171 | AVIF_NCLX_COLOUR_PRIMARIES_XYZ = 10, 172 | AVIF_NCLX_COLOUR_PRIMARIES_ST428 = 10, 173 | AVIF_NCLX_COLOUR_PRIMARIES_RP431_2 = 11, 174 | AVIF_NCLX_COLOUR_PRIMARIES_EG432_1 = 12, 175 | AVIF_NCLX_COLOUR_PRIMARIES_P3 = 12, 176 | AVIF_NCLX_COLOUR_PRIMARIES_EBU3213E = 22 177 | } avifNclxColourPrimaries; 178 | 179 | // outPrimaries: rX, rY, gX, gY, bX, bY, wX, wY 180 | void avifNclxColourPrimariesGetValues(avifNclxColourPrimaries ancp, float outPrimaries[8]); 181 | avifNclxColourPrimaries avifNclxColourPrimariesFind(float inPrimaries[8], const char ** outName); 182 | 183 | typedef enum avifNclxTransferCharacteristics 184 | { 185 | // This is actually reserved, but libavif uses it as a sentinel value. 186 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_UNKNOWN = 0, 187 | 188 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_BT709 = 1, 189 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_BT1361 = 1, 190 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_UNSPECIFIED = 2, 191 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_GAMMA22 = 4, 192 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_GAMMA28 = 5, 193 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_BT601 = 6, 194 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_ST240 = 7, 195 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_LINEAR = 8, 196 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_LOG_100_1 = 9, 197 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_LOG_100_SQRT = 10, 198 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_IEC61966 = 11, 199 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_BT1361_EXTENDED = 12, 200 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_61966_2_1 = 13, 201 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_SRGB = 13, 202 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_SYCC = 13, 203 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_BT2020_10BIT = 14, 204 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_BT2020_12BIT = 15, 205 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_ST2084 = 16, 206 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_BT2100_PQ = 16, 207 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_ST428 = 17, 208 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_STD_B67 = 18, 209 | AVIF_NCLX_TRANSFER_CHARACTERISTICS_BT2100_HLG = 18 210 | } avifNclxTransferCharacteristics; 211 | 212 | typedef enum avifNclxMatrixCoefficients 213 | { 214 | AVIF_NCLX_MATRIX_COEFFICIENTS_IDENTITY = 0, 215 | AVIF_NCLX_MATRIX_COEFFICIENTS_BT709 = 1, 216 | AVIF_NCLX_MATRIX_COEFFICIENTS_BT1361_0 = 1, 217 | AVIF_NCLX_MATRIX_COEFFICIENTS_SRGB = 1, 218 | AVIF_NCLX_MATRIX_COEFFICIENTS_SYCC = 1, 219 | AVIF_NCLX_MATRIX_COEFFICIENTS_UNSPECIFIED = 2, 220 | AVIF_NCLX_MATRIX_COEFFICIENTS_USFC_73682 = 4, 221 | AVIF_NCLX_MATRIX_COEFFICIENTS_BT470_6B = 5, 222 | AVIF_NCLX_MATRIX_COEFFICIENTS_BT601_7_625 = 5, 223 | AVIF_NCLX_MATRIX_COEFFICIENTS_BT601_7_525 = 6, 224 | AVIF_NCLX_MATRIX_COEFFICIENTS_BT1700_NTSC = 6, 225 | AVIF_NCLX_MATRIX_COEFFICIENTS_ST170 = 6, 226 | AVIF_NCLX_MATRIX_COEFFICIENTS_ST240 = 7, 227 | AVIF_NCLX_MATRIX_COEFFICIENTS_BT2020_NCL = 9, 228 | AVIF_NCLX_MATRIX_COEFFICIENTS_BT2100 = 9, 229 | AVIF_NCLX_MATRIX_COEFFICIENTS_BT2020_CL = 10, 230 | AVIF_NCLX_MATRIX_COEFFICIENTS_ST2085 = 11, 231 | AVIF_NCLX_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL = 12, 232 | AVIF_NCLX_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL = 13, 233 | AVIF_NCLX_MATRIX_COEFFICIENTS_ICTCP = 14 234 | } avifNclxMatrixCoefficients; 235 | 236 | // for fullRangeFlag 237 | typedef enum avifNclxRangeFlag 238 | { 239 | AVIF_NCLX_LIMITED_RANGE = 0, 240 | AVIF_NCLX_FULL_RANGE = 0x80 241 | } avifNclxRangeFlag; 242 | 243 | typedef struct avifNclxColorProfile 244 | { 245 | uint16_t colourPrimaries; 246 | uint16_t transferCharacteristics; 247 | uint16_t matrixCoefficients; 248 | uint8_t fullRangeFlag; 249 | } avifNclxColorProfile; 250 | 251 | // --------------------------------------------------------------------------- 252 | // avifRange 253 | 254 | typedef enum avifRange 255 | { 256 | AVIF_RANGE_LIMITED = 0, 257 | AVIF_RANGE_FULL, 258 | } avifRange; 259 | 260 | // --------------------------------------------------------------------------- 261 | // avifProfileFormat 262 | 263 | typedef enum avifProfileFormat 264 | { 265 | // No color profile present 266 | AVIF_PROFILE_FORMAT_NONE = 0, 267 | 268 | // icc represents an ICC profile chunk (inside a colr box) 269 | AVIF_PROFILE_FORMAT_ICC, 270 | 271 | // nclx represents a valid nclx colr box 272 | AVIF_PROFILE_FORMAT_NCLX 273 | } avifProfileFormat; 274 | 275 | // --------------------------------------------------------------------------- 276 | // avifImage 277 | 278 | typedef struct avifImage 279 | { 280 | // Image information 281 | int width; 282 | int height; 283 | int depth; // all planes (RGB/YUV/A) must share this depth; if depth>8, all planes are uint16_t internally 284 | 285 | uint8_t * rgbPlanes[AVIF_PLANE_COUNT_RGB]; 286 | uint32_t rgbRowBytes[AVIF_PLANE_COUNT_RGB]; 287 | 288 | avifPixelFormat yuvFormat; 289 | avifRange yuvRange; 290 | uint8_t * yuvPlanes[AVIF_PLANE_COUNT_YUV]; 291 | uint32_t yuvRowBytes[AVIF_PLANE_COUNT_YUV]; 292 | 293 | uint8_t * alphaPlane; 294 | uint32_t alphaRowBytes; 295 | 296 | // Profile information 297 | avifProfileFormat profileFormat; 298 | avifRawData icc; 299 | avifNclxColorProfile nclx; 300 | 301 | // Useful stats from the most recent read/write 302 | struct IOStats 303 | { 304 | size_t colorOBUSize; 305 | size_t alphaOBUSize; 306 | } ioStats; 307 | } avifImage; 308 | 309 | avifImage * avifImageCreate(int width, int height, int depth, avifPixelFormat yuvFormat); 310 | avifImage * avifImageCreateEmpty(void); // helper for making an image to decode into 311 | void avifImageDestroy(avifImage * image); 312 | 313 | void avifImageSetProfileNone(avifImage * image); 314 | void avifImageSetProfileICC(avifImage * image, uint8_t * icc, size_t iccSize); 315 | void avifImageSetProfileNCLX(avifImage * image, avifNclxColorProfile * nclx); 316 | 317 | void avifImageAllocatePlanes(avifImage * image, uint32_t planes); // Ignores any pre-existing planes 318 | void avifImageFreePlanes(avifImage * image, uint32_t planes); // Ignores already-freed planes 319 | avifResult avifImageRead(avifImage * image, avifRawData * input); 320 | 321 | // avifImageWrite notes: 322 | // * if returns AVIF_RESULT_OK, output must be freed with avifRawDataFree() 323 | // * if (numThreads < 2), multithreading is disabled 324 | // * quality range: [AVIF_BEST_QUALITY - AVIF_WORST_QUALITY] 325 | avifResult avifImageWrite(avifImage * image, avifRawData * output, int numThreads, int quality); 326 | 327 | // Used by avifImageRead/avifImageWrite 328 | avifResult avifImageRGBToYUV(avifImage * image); 329 | avifResult avifImageYUVToRGB(avifImage * image); 330 | 331 | // Helpers 332 | avifBool avifImageUsesU16(avifImage * image); 333 | 334 | #ifdef __cplusplus 335 | } // extern "C" 336 | #endif 337 | 338 | #endif // ifndef AVIF_AVIF_H 339 | -------------------------------------------------------------------------------- /include/avif/internal.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #ifndef AVIF_INTERNAL_H 5 | #define AVIF_INTERNAL_H 6 | 7 | #include "avif/avif.h" 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | // Yes, clamp macros are nasty. Do not use them. 14 | #define AVIF_CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) 15 | 16 | // Used by stream related things. 17 | #define CHECK(A) if (!(A)) return AVIF_FALSE; 18 | 19 | // --------------------------------------------------------------------------- 20 | // URNs 21 | 22 | #define URN_ALPHA0 "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha" 23 | #define URN_ALPHA1 "urn:mpeg:hevc:2015:auxid:1" 24 | 25 | // --------------------------------------------------------------------------- 26 | // Utils 27 | 28 | float avifRoundf(float v); 29 | 30 | uint16_t avifHTONS(uint16_t s); 31 | uint16_t avifNTOHS(uint16_t s); 32 | uint32_t avifHTONL(uint32_t l); 33 | uint32_t avifNTOHL(uint32_t l); 34 | uint64_t avifHTON64(uint64_t l); 35 | uint64_t avifNTOH64(uint64_t l); 36 | 37 | int avifFullToLimitedY(int depth, int v); 38 | int avifFullToLimitedUV(int depth, int v); 39 | int avifLimitedToFullY(int depth, int v); 40 | int avifLimitedToFullUV(int depth, int v); 41 | 42 | void avifCalcYUVCoefficients(avifImage * image, float * outR, float * outG, float * outB); 43 | 44 | // --------------------------------------------------------------------------- 45 | // Memory management 46 | 47 | void * avifAlloc(size_t size); 48 | void avifFree(void * p); 49 | 50 | // --------------------------------------------------------------------------- 51 | // avifCodec (abstraction layer to use different AV1 implementations) 52 | 53 | typedef struct avifCodecConfigurationBox 54 | { 55 | // [skipped; is constant] unsigned int (1)marker = 1; 56 | // [skipped; is constant] unsigned int (7)version = 1; 57 | 58 | uint8_t seqProfile; // unsigned int (3) seq_profile; 59 | uint8_t seqLevelIdx0; // unsigned int (5) seq_level_idx_0; 60 | uint8_t seqTier0; // unsigned int (1) seq_tier_0; 61 | uint8_t highBitdepth; // unsigned int (1) high_bitdepth; 62 | uint8_t twelveBit; // unsigned int (1) twelve_bit; 63 | uint8_t monochrome; // unsigned int (1) monochrome; 64 | uint8_t chromaSubsamplingX; // unsigned int (1) chroma_subsampling_x; 65 | uint8_t chromaSubsamplingY; // unsigned int (1) chroma_subsampling_y; 66 | uint8_t chromaSamplePosition; // unsigned int (2) chroma_sample_position; 67 | 68 | // unsigned int (3)reserved = 0; 69 | // unsigned int (1)initial_presentation_delay_present; 70 | // if (initial_presentation_delay_present) { 71 | // unsigned int (4)initial_presentation_delay_minus_one; 72 | // } else { 73 | // unsigned int (4)reserved = 0; 74 | // } 75 | } avifCodecConfigurationBox; 76 | 77 | typedef enum avifCodecPlanes 78 | { 79 | AVIF_CODEC_PLANES_COLOR = 0, // YUV 80 | AVIF_CODEC_PLANES_ALPHA, 81 | 82 | AVIF_CODEC_PLANES_COUNT 83 | } avifCodecPlanes; 84 | 85 | typedef struct avifCodecImageSize 86 | { 87 | uint32_t width; 88 | uint32_t height; 89 | } avifCodecImageSize; 90 | 91 | struct avifCodecInternal; 92 | 93 | typedef struct avifCodec 94 | { 95 | struct avifCodecInternal * internal; // up to each codec to use how it wants 96 | } avifCodec; 97 | 98 | avifCodec * avifCodecCreate(); 99 | void avifCodecDestroy(avifCodec * codec); 100 | 101 | avifBool avifCodecDecode(avifCodec * codec, avifCodecPlanes planes, avifRawData * obu); 102 | avifCodecImageSize avifCodecGetImageSize(avifCodec * codec, avifCodecPlanes planes); // should return 0s if absent 103 | avifBool avifCodecAlphaLimitedRange(avifCodec * codec); // returns AVIF_TRUE if an alpha plane exists and was encoded with limited range 104 | avifResult avifCodecGetDecodedImage(avifCodec * codec, avifImage * image); 105 | avifResult avifCodecEncodeImage(avifCodec * codec, avifImage * image, int numThreads, int colorQuality, avifRawData * colorOBU, avifRawData * alphaOBU); // if either OBU* is null, skip its encode. alpha should always be lossless 106 | void avifCodecGetConfigurationBox(avifCodec * codec, avifCodecPlanes planes, avifCodecConfigurationBox * outConfig); 107 | 108 | // --------------------------------------------------------------------------- 109 | // avifStream 110 | 111 | typedef size_t avifBoxMarker; 112 | 113 | typedef struct avifStream 114 | { 115 | avifRawData * raw; 116 | size_t offset; 117 | } avifStream; 118 | 119 | typedef struct avifBoxHeader 120 | { 121 | size_t size; 122 | uint8_t type[4]; 123 | } avifBoxHeader; 124 | 125 | uint8_t * avifStreamCurrent(avifStream * stream); 126 | 127 | void avifStreamStart(avifStream * stream, avifRawData * raw); 128 | 129 | // Read 130 | avifBool avifStreamHasBytesLeft(avifStream * stream, size_t byteCount); 131 | size_t avifStreamRemainingBytes(avifStream * stream); 132 | size_t avifStreamOffset(avifStream * stream); 133 | void avifStreamSetOffset(avifStream * stream, size_t offset); 134 | avifBool avifStreamSkip(avifStream * stream, size_t byteCount); 135 | avifBool avifStreamRead(avifStream * stream, uint8_t * data, size_t size); 136 | avifBool avifStreamReadU16(avifStream * stream, uint16_t * v); 137 | avifBool avifStreamReadU32(avifStream * stream, uint32_t * v); 138 | avifBool avifStreamReadUX8(avifStream * stream, uint64_t * v, uint64_t factor); // Reads a factor*8 sized uint, saves in v 139 | avifBool avifStreamReadU64(avifStream * stream, uint64_t * v); 140 | avifBool avifStreamReadString(avifStream * stream, char * output, size_t outputSize); 141 | avifBool avifStreamReadBoxHeader(avifStream * stream, avifBoxHeader * header); 142 | avifBool avifStreamReadVersionAndFlags(avifStream * stream, uint8_t * version, uint8_t * flags); // flags is an optional uint8_t[3] 143 | avifBool avifStreamReadAndEnforceVersion(avifStream * stream, uint8_t enforcedVersion); // currently discards flags 144 | 145 | // Write 146 | void avifStreamFinishWrite(avifStream * stream); 147 | void avifStreamWrite(avifStream * stream, const uint8_t * data, size_t size); 148 | void avifStreamWriteChars(avifStream * stream, const char * chars, size_t size); 149 | avifBoxMarker avifStreamWriteBox(avifStream * stream, const char * type, int version /* -1 for "not a FullBox" */, size_t contentSize); 150 | void avifStreamFinishBox(avifStream * stream, avifBoxMarker marker); 151 | void avifStreamWriteU8(avifStream * stream, uint8_t v); 152 | void avifStreamWriteU16(avifStream * stream, uint16_t v); 153 | void avifStreamWriteU32(avifStream * stream, uint32_t v); 154 | void avifStreamWriteZeros(avifStream * stream, size_t byteCount); 155 | 156 | #ifdef __cplusplus 157 | } // extern "C" 158 | #endif 159 | 160 | #endif // ifndef AVIF_INTERNAL_H 161 | -------------------------------------------------------------------------------- /src/avif.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/avif.h" 5 | 6 | #include 7 | 8 | #define STR_HELPER(x) #x 9 | #define STR(x) STR_HELPER(x) 10 | #define AVIF_VERSION_STRING (STR(AVIF_VERSION_MAJOR) "." STR(AVIF_VERSION_MINOR) "." STR(AVIF_VERSION_PATCH)) 11 | 12 | const char * avifVersion(void) 13 | { 14 | return AVIF_VERSION_STRING; 15 | } 16 | 17 | const char * avifPixelFormatToString(avifPixelFormat format) 18 | { 19 | switch (format) { 20 | case AVIF_PIXEL_FORMAT_YUV444: return "YUV444"; 21 | case AVIF_PIXEL_FORMAT_YUV420: return "YUV420"; 22 | case AVIF_PIXEL_FORMAT_YUV422: return "YUV422"; 23 | case AVIF_PIXEL_FORMAT_YV12: return "YV12"; 24 | case AVIF_PIXEL_FORMAT_NONE: 25 | default: 26 | break; 27 | } 28 | return "Unknown"; 29 | } 30 | 31 | void avifGetPixelFormatInfo(avifPixelFormat format, avifPixelFormatInfo * info) 32 | { 33 | memset(info, 0, sizeof(avifPixelFormatInfo)); 34 | info->aomIndexU = 1; 35 | info->aomIndexV = 2; 36 | 37 | switch (format) { 38 | case AVIF_PIXEL_FORMAT_YUV444: 39 | info->chromaShiftX = 0; 40 | info->chromaShiftY = 0; 41 | break; 42 | 43 | case AVIF_PIXEL_FORMAT_YUV422: 44 | info->chromaShiftX = 1; 45 | info->chromaShiftY = 0; 46 | break; 47 | 48 | case AVIF_PIXEL_FORMAT_YUV420: 49 | info->chromaShiftX = 1; 50 | info->chromaShiftY = 1; 51 | break; 52 | 53 | case AVIF_PIXEL_FORMAT_YV12: 54 | info->chromaShiftX = 1; 55 | info->chromaShiftY = 1; 56 | info->aomIndexU = 2; 57 | info->aomIndexV = 1; 58 | break; 59 | 60 | case AVIF_PIXEL_FORMAT_NONE: 61 | default: 62 | break; 63 | } 64 | } 65 | 66 | const char * avifResultToString(avifResult result) 67 | { 68 | switch (result) { 69 | case AVIF_RESULT_OK: return "OK"; 70 | case AVIF_RESULT_INVALID_FTYP: return "Invalid ftyp"; 71 | case AVIF_RESULT_NO_CONTENT: return "No content"; 72 | case AVIF_RESULT_NO_YUV_FORMAT_SELECTED: return "No YUV format selected"; 73 | case AVIF_RESULT_REFORMAT_FAILED: return "Reformat failed"; 74 | case AVIF_RESULT_UNSUPPORTED_DEPTH: return "Unsupported depth"; 75 | case AVIF_RESULT_ENCODE_COLOR_FAILED: return "Encoding of color planes failed"; 76 | case AVIF_RESULT_ENCODE_ALPHA_FAILED: return "Encoding of alpha plane failed"; 77 | case AVIF_RESULT_BMFF_PARSE_FAILED: return "BMFF parsing failed"; 78 | case AVIF_RESULT_NO_AV1_ITEMS_FOUND: return "No AV1 items found"; 79 | case AVIF_RESULT_DECODE_COLOR_FAILED: return "Decoding of color planes failed"; 80 | case AVIF_RESULT_DECODE_ALPHA_FAILED: return "Decoding of alpha plane failed"; 81 | case AVIF_RESULT_COLOR_ALPHA_SIZE_MISMATCH: return "Color and alpha planes size mismatch"; 82 | case AVIF_RESULT_ISPE_SIZE_MISMATCH: return "Plane sizes don't match ispe values"; 83 | case AVIF_UNSUPPORTED_PIXEL_FORMAT: return "Unsupported pixel format"; 84 | case AVIF_RESULT_UNKNOWN_ERROR: 85 | default: 86 | break; 87 | } 88 | return "Unknown Error"; 89 | } 90 | 91 | // This function assumes nothing in this struct needs to be freed; use avifImageClear() externally 92 | static void avifImageSetDefaults(avifImage * image) 93 | { 94 | memset(image, 0, sizeof(avifImage)); 95 | image->yuvRange = AVIF_RANGE_FULL; 96 | } 97 | 98 | avifImage * avifImageCreate(int width, int height, int depth, avifPixelFormat yuvFormat) 99 | { 100 | avifImage * image = (avifImage *)avifAlloc(sizeof(avifImage)); 101 | avifImageSetDefaults(image); 102 | image->width = width; 103 | image->height = height; 104 | image->depth = depth; 105 | image->yuvFormat = yuvFormat; 106 | return image; 107 | } 108 | 109 | avifImage * avifImageCreateEmpty(void) 110 | { 111 | return avifImageCreate(0, 0, 0, AVIF_PIXEL_FORMAT_NONE); 112 | } 113 | 114 | void avifImageDestroy(avifImage * image) 115 | { 116 | avifImageFreePlanes(image, AVIF_PLANES_ALL); 117 | avifRawDataFree(&image->icc); 118 | avifFree(image); 119 | } 120 | 121 | void avifImageSetProfileNone(avifImage * image) 122 | { 123 | image->profileFormat = AVIF_PROFILE_FORMAT_NONE; 124 | avifRawDataFree(&image->icc); 125 | } 126 | 127 | void avifImageSetProfileICC(avifImage * image, uint8_t * icc, size_t iccSize) 128 | { 129 | avifImageSetProfileNone(image); 130 | if (iccSize) { 131 | image->profileFormat = AVIF_PROFILE_FORMAT_ICC; 132 | avifRawDataSet(&image->icc, icc, iccSize); 133 | } 134 | } 135 | 136 | void avifImageSetProfileNCLX(avifImage * image, avifNclxColorProfile * nclx) 137 | { 138 | avifImageSetProfileNone(image); 139 | image->profileFormat = AVIF_PROFILE_FORMAT_NCLX; 140 | memcpy(&image->nclx, nclx, sizeof(avifNclxColorProfile)); 141 | } 142 | 143 | void avifImageAllocatePlanes(avifImage * image, uint32_t planes) 144 | { 145 | int channelSize = avifImageUsesU16(image) ? 2 : 1; 146 | int fullRowBytes = channelSize * image->width; 147 | int fullSize = fullRowBytes * image->height; 148 | if (planes & AVIF_PLANES_RGB) { 149 | if (!image->rgbPlanes[AVIF_CHAN_R]) { 150 | image->rgbRowBytes[AVIF_CHAN_R] = fullRowBytes; 151 | image->rgbPlanes[AVIF_CHAN_R] = avifAlloc(fullSize); 152 | memset(image->rgbPlanes[AVIF_CHAN_R], 0, fullSize); 153 | } 154 | if (!image->rgbPlanes[AVIF_CHAN_G]) { 155 | image->rgbRowBytes[AVIF_CHAN_G] = fullRowBytes; 156 | image->rgbPlanes[AVIF_CHAN_G] = avifAlloc(fullSize); 157 | memset(image->rgbPlanes[AVIF_CHAN_G], 0, fullSize); 158 | } 159 | if (!image->rgbPlanes[AVIF_CHAN_B]) { 160 | image->rgbRowBytes[AVIF_CHAN_B] = fullRowBytes; 161 | image->rgbPlanes[AVIF_CHAN_B] = avifAlloc(fullSize); 162 | memset(image->rgbPlanes[AVIF_CHAN_B], 0, fullSize); 163 | } 164 | } 165 | if ((planes & AVIF_PLANES_YUV) && (image->yuvFormat != AVIF_PIXEL_FORMAT_NONE)) { 166 | avifPixelFormatInfo info; 167 | avifGetPixelFormatInfo(image->yuvFormat, &info); 168 | 169 | int shiftedW = image->width; 170 | if (info.chromaShiftX) { 171 | shiftedW = (image->width + 1) >> info.chromaShiftX; 172 | } 173 | int shiftedH = image->height; 174 | if (info.chromaShiftY) { 175 | shiftedH = (image->height + 1) >> info.chromaShiftY; 176 | } 177 | 178 | int uvRowBytes = channelSize * shiftedW; 179 | int uvSize = uvRowBytes * shiftedH; 180 | 181 | if (!image->yuvPlanes[AVIF_CHAN_Y]) { 182 | image->yuvRowBytes[AVIF_CHAN_Y] = fullRowBytes; 183 | image->yuvPlanes[AVIF_CHAN_Y] = avifAlloc(fullSize); 184 | memset(image->yuvPlanes[AVIF_CHAN_Y], 0, fullSize); 185 | } 186 | if (!image->yuvPlanes[AVIF_CHAN_U]) { 187 | image->yuvRowBytes[AVIF_CHAN_U] = uvRowBytes; 188 | image->yuvPlanes[AVIF_CHAN_U] = avifAlloc(uvSize); 189 | memset(image->yuvPlanes[AVIF_CHAN_U], 0, uvSize); 190 | } 191 | if (!image->yuvPlanes[AVIF_CHAN_V]) { 192 | image->yuvRowBytes[AVIF_CHAN_V] = uvRowBytes; 193 | image->yuvPlanes[AVIF_CHAN_V] = avifAlloc(uvSize); 194 | memset(image->yuvPlanes[AVIF_CHAN_V], 0, uvSize); 195 | } 196 | } 197 | if (planes & AVIF_PLANES_A) { 198 | if (!image->alphaPlane) { 199 | image->alphaRowBytes = fullRowBytes; 200 | image->alphaPlane = avifAlloc(fullRowBytes * image->height); 201 | memset(image->alphaPlane, 0, fullRowBytes * image->height); 202 | } 203 | } 204 | } 205 | 206 | void avifImageFreePlanes(avifImage * image, uint32_t planes) 207 | { 208 | if (planes & AVIF_PLANES_RGB) { 209 | avifFree(image->rgbPlanes[AVIF_CHAN_R]); 210 | image->rgbPlanes[AVIF_CHAN_R] = NULL; 211 | image->rgbRowBytes[AVIF_CHAN_R] = 0; 212 | avifFree(image->rgbPlanes[AVIF_CHAN_G]); 213 | image->rgbPlanes[AVIF_CHAN_G] = NULL; 214 | image->rgbRowBytes[AVIF_CHAN_G] = 0; 215 | avifFree(image->rgbPlanes[AVIF_CHAN_G]); 216 | image->rgbPlanes[AVIF_CHAN_G] = NULL; 217 | image->rgbRowBytes[AVIF_CHAN_G] = 0; 218 | } 219 | if ((planes & AVIF_PLANES_YUV) && (image->yuvFormat != AVIF_PIXEL_FORMAT_NONE)) { 220 | avifFree(image->yuvPlanes[AVIF_CHAN_Y]); 221 | image->yuvPlanes[AVIF_CHAN_Y] = NULL; 222 | image->yuvRowBytes[AVIF_CHAN_Y] = 0; 223 | avifFree(image->yuvPlanes[AVIF_CHAN_U]); 224 | image->yuvPlanes[AVIF_CHAN_U] = NULL; 225 | image->yuvRowBytes[AVIF_CHAN_U] = 0; 226 | avifFree(image->yuvPlanes[AVIF_CHAN_V]); 227 | image->yuvPlanes[AVIF_CHAN_V] = NULL; 228 | image->yuvRowBytes[AVIF_CHAN_V] = 0; 229 | } 230 | if (planes & AVIF_PLANES_A) { 231 | avifFree(image->alphaPlane); 232 | image->alphaPlane = NULL; 233 | image->alphaRowBytes = 0; 234 | } 235 | } 236 | 237 | avifBool avifImageUsesU16(avifImage * image) 238 | { 239 | return (image->depth > 8) ? AVIF_TRUE : AVIF_FALSE; 240 | } 241 | -------------------------------------------------------------------------------- /src/codec_aom.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/internal.h" 5 | 6 | #include "aom/aom_decoder.h" 7 | #include "aom/aomdx.h" 8 | #include "aom/aom_encoder.h" 9 | #include "aom/aomcx.h" 10 | 11 | #include 12 | 13 | struct avifCodecInternal 14 | { 15 | avifBool decoderInitialized[AVIF_CODEC_PLANES_COUNT]; 16 | aom_codec_ctx_t decoders[AVIF_CODEC_PLANES_COUNT]; 17 | 18 | aom_image_t * images[AVIF_CODEC_PLANES_COUNT]; 19 | avifRawData encodedOBUs[AVIF_CODEC_PLANES_COUNT]; 20 | avifCodecConfigurationBox configs[AVIF_CODEC_PLANES_COUNT]; 21 | }; 22 | 23 | avifCodec * avifCodecCreate() 24 | { 25 | avifCodec * codec = (avifCodec *)avifAlloc(sizeof(avifCodec)); 26 | codec->internal = (struct avifCodecInternal *)avifAlloc(sizeof(struct avifCodecInternal)); 27 | memset(codec->internal, 0, sizeof(struct avifCodecInternal)); 28 | return codec; 29 | } 30 | 31 | void avifCodecDestroy(avifCodec * codec) 32 | { 33 | for (int plane = 0; plane < AVIF_CODEC_PLANES_COUNT; ++plane) { 34 | if (codec->internal->decoderInitialized[plane]) { 35 | aom_codec_destroy(&codec->internal->decoders[plane]); 36 | } 37 | avifRawDataFree(&codec->internal->encodedOBUs[plane]); 38 | } 39 | avifFree(codec->internal); 40 | avifFree(codec); 41 | } 42 | 43 | avifBool avifCodecDecode(avifCodec * codec, avifCodecPlanes planes, avifRawData * obu) 44 | { 45 | aom_codec_stream_info_t si; 46 | aom_codec_iface_t * decoder_interface = aom_codec_av1_dx(); 47 | if (aom_codec_dec_init(&codec->internal->decoders[planes], decoder_interface, NULL, 0)) { 48 | return AVIF_FALSE; 49 | } 50 | codec->internal->decoderInitialized[planes] = AVIF_TRUE; 51 | 52 | if (aom_codec_control(&codec->internal->decoders[planes], AV1D_SET_OUTPUT_ALL_LAYERS, 1)) { 53 | return AVIF_FALSE; 54 | } 55 | 56 | si.is_annexb = 0; 57 | if (aom_codec_peek_stream_info(decoder_interface, obu->data, obu->size, &si)) { 58 | return AVIF_FALSE; 59 | } 60 | 61 | if (aom_codec_decode(&codec->internal->decoders[planes], obu->data, obu->size, NULL)) { 62 | return AVIF_FALSE; 63 | } 64 | 65 | aom_codec_iter_t iter = NULL; 66 | codec->internal->images[planes] = aom_codec_get_frame(&codec->internal->decoders[planes], &iter); // It doesn't appear that I own this / need to free this 67 | return (codec->internal->images[planes]) ? AVIF_TRUE : AVIF_FALSE; 68 | } 69 | 70 | avifCodecImageSize avifCodecGetImageSize(avifCodec * codec, avifCodecPlanes planes) 71 | { 72 | avifCodecImageSize size; 73 | if (codec->internal->images[planes]) { 74 | size.width = codec->internal->images[planes]->d_w; 75 | size.height = codec->internal->images[planes]->d_h; 76 | } else { 77 | size.width = 0; 78 | size.height = 0; 79 | } 80 | return size; 81 | } 82 | 83 | avifBool avifCodecAlphaLimitedRange(avifCodec * codec) 84 | { 85 | aom_image_t * aomAlphaImage = codec->internal->images[AVIF_CODEC_PLANES_ALPHA]; 86 | if (aomAlphaImage && (aomAlphaImage->range == AOM_CR_STUDIO_RANGE)) { 87 | return AVIF_TRUE; 88 | } 89 | return AVIF_FALSE; 90 | } 91 | 92 | avifResult avifCodecGetDecodedImage(avifCodec * codec, avifImage * image) 93 | { 94 | aom_image_t * aomColorImage = codec->internal->images[AVIF_CODEC_PLANES_COLOR]; 95 | aom_image_t * aomAlphaImage = codec->internal->images[AVIF_CODEC_PLANES_ALPHA]; 96 | avifBool hasAlpha = aomAlphaImage ? AVIF_TRUE : AVIF_FALSE; 97 | 98 | avifPixelFormat yuvFormat = AVIF_PIXEL_FORMAT_NONE; 99 | switch (aomColorImage->fmt) { 100 | case AOM_IMG_FMT_I420: 101 | case AOM_IMG_FMT_AOMI420: 102 | case AOM_IMG_FMT_I42016: 103 | yuvFormat = AVIF_PIXEL_FORMAT_YUV420; 104 | break; 105 | case AOM_IMG_FMT_I422: 106 | case AOM_IMG_FMT_I42216: 107 | yuvFormat = AVIF_PIXEL_FORMAT_YUV422; 108 | break; 109 | case AOM_IMG_FMT_I444: 110 | case AOM_IMG_FMT_I44416: 111 | yuvFormat = AVIF_PIXEL_FORMAT_YUV444; 112 | break; 113 | case AOM_IMG_FMT_YV12: 114 | case AOM_IMG_FMT_AOMYV12: 115 | case AOM_IMG_FMT_YV1216: 116 | yuvFormat = AVIF_PIXEL_FORMAT_YV12; 117 | break; 118 | case AOM_IMG_FMT_NONE: 119 | default: 120 | break; 121 | } 122 | 123 | image->width = aomColorImage->d_w; 124 | image->height = aomColorImage->d_h; 125 | image->depth = aomColorImage->bit_depth; 126 | image->yuvFormat = yuvFormat; 127 | image->yuvRange = (aomColorImage->range == AOM_CR_STUDIO_RANGE) ? AVIF_RANGE_LIMITED : AVIF_RANGE_FULL; 128 | 129 | avifPixelFormatInfo formatInfo; 130 | avifGetPixelFormatInfo(yuvFormat, &formatInfo); 131 | 132 | int uvHeight = image->height >> formatInfo.chromaShiftY; 133 | avifImageAllocatePlanes(image, AVIF_PLANES_YUV); 134 | for (int yuvPlane = 0; yuvPlane < 3; ++yuvPlane) { 135 | int aomPlaneIndex = yuvPlane; 136 | int planeHeight = image->height; 137 | if (yuvPlane == AVIF_CHAN_U) { 138 | aomPlaneIndex = formatInfo.aomIndexU; 139 | planeHeight = uvHeight; 140 | } else if (yuvPlane == AVIF_CHAN_V) { 141 | aomPlaneIndex = formatInfo.aomIndexV; 142 | planeHeight = uvHeight; 143 | } 144 | 145 | for (int j = 0; j < planeHeight; ++j) { 146 | uint8_t * srcRow = &aomColorImage->planes[aomPlaneIndex][j * aomColorImage->stride[aomPlaneIndex]]; 147 | uint8_t * dstRow = &image->yuvPlanes[yuvPlane][j * image->yuvRowBytes[yuvPlane]]; 148 | memcpy(dstRow, srcRow, image->yuvRowBytes[yuvPlane]); 149 | } 150 | } 151 | 152 | if (hasAlpha) { 153 | avifImageAllocatePlanes(image, AVIF_PLANES_A); 154 | for (int j = 0; j < image->height; ++j) { 155 | uint8_t * srcAlphaRow = &aomAlphaImage->planes[0][j * aomAlphaImage->stride[0]]; 156 | uint8_t * dstAlphaRow = &image->alphaPlane[j * image->alphaRowBytes]; 157 | memcpy(dstAlphaRow, srcAlphaRow, image->alphaRowBytes); 158 | } 159 | } 160 | return AVIF_RESULT_OK; 161 | } 162 | 163 | static aom_img_fmt_t avifImageCalcAOMFmt(avifImage * image, avifBool alphaOnly, int * yShift) 164 | { 165 | *yShift = 0; 166 | 167 | aom_img_fmt_t fmt; 168 | if (alphaOnly) { 169 | // We're going monochrome, who cares about chroma quality 170 | fmt = AOM_IMG_FMT_I420; 171 | *yShift = 1; 172 | } else { 173 | switch (image->yuvFormat) { 174 | case AVIF_PIXEL_FORMAT_YUV444: 175 | fmt = AOM_IMG_FMT_I444; 176 | break; 177 | case AVIF_PIXEL_FORMAT_YUV422: 178 | fmt = AOM_IMG_FMT_I422; 179 | break; 180 | case AVIF_PIXEL_FORMAT_YUV420: 181 | fmt = AOM_IMG_FMT_I420; 182 | *yShift = 1; 183 | break; 184 | case AVIF_PIXEL_FORMAT_YV12: 185 | fmt = AOM_IMG_FMT_YV12; 186 | *yShift = 1; 187 | break; 188 | default: 189 | return AOM_IMG_FMT_NONE; 190 | } 191 | } 192 | 193 | if (image->depth > 8) { 194 | fmt |= AOM_IMG_FMT_HIGHBITDEPTH; 195 | } 196 | 197 | return fmt; 198 | } 199 | 200 | static avifBool encodeOBU(avifImage * image, avifBool alphaOnly, int numThreads, int quality, avifRawData * outputOBU, avifCodecConfigurationBox * outputConfig) 201 | { 202 | avifBool success = AVIF_FALSE; 203 | aom_codec_iface_t * encoder_interface = aom_codec_av1_cx(); 204 | aom_codec_ctx_t encoder; 205 | 206 | memset(outputConfig, 0, sizeof(avifCodecConfigurationBox)); 207 | 208 | int yShift = 0; 209 | aom_img_fmt_t aomFormat = avifImageCalcAOMFmt(image, alphaOnly, &yShift); 210 | if (aomFormat == AOM_IMG_FMT_NONE) { 211 | return AVIF_FALSE; 212 | } 213 | 214 | avifPixelFormatInfo formatInfo; 215 | avifGetPixelFormatInfo(image->yuvFormat, &formatInfo); 216 | 217 | struct aom_codec_enc_cfg cfg; 218 | aom_codec_enc_config_default(encoder_interface, &cfg, 0); 219 | 220 | // Profile 0. 8-bit and 10-bit 4:2:0 and 4:0:0 only. 221 | // Profile 1. 8-bit and 10-bit 4:4:4 222 | // Profile 2. 8-bit and 10-bit 4:2:2 223 | // 12-bit 4:0:0, 4:2:2 and 4:4:4 224 | if (image->depth == 12) { 225 | // Only profile 2 can handle 12 bit 226 | cfg.g_profile = 2; 227 | } else { 228 | // 8-bit or 10-bit 229 | 230 | if (alphaOnly) { 231 | // Assuming aomImage->monochrome makes it 4:0:0 232 | cfg.g_profile = 0; 233 | } else { 234 | switch (image->yuvFormat) { 235 | case AVIF_PIXEL_FORMAT_YUV444: cfg.g_profile = 1; break; 236 | case AVIF_PIXEL_FORMAT_YUV422: cfg.g_profile = 2; break; 237 | case AVIF_PIXEL_FORMAT_YUV420: cfg.g_profile = 0; break; 238 | case AVIF_PIXEL_FORMAT_YV12: cfg.g_profile = 0; break; 239 | case AVIF_PIXEL_FORMAT_NONE: 240 | default: 241 | break; 242 | } 243 | } 244 | } 245 | 246 | cfg.g_bit_depth = image->depth; 247 | cfg.g_input_bit_depth = image->depth; 248 | cfg.g_w = image->width; 249 | cfg.g_h = image->height; 250 | if (numThreads > 1) { 251 | cfg.g_threads = numThreads; 252 | } 253 | 254 | // TODO: Choose correct value from Annex A.3 table: https://aomediacodec.github.io/av1-spec/av1-spec.pdf 255 | uint8_t seqLevelIdx0 = 31; 256 | if ((image->width <= 8192) && (image->height <= 4352) && ((image->width * image->height) <= 8912896)) { 257 | // Image is 5.1 compatible 258 | seqLevelIdx0 = 13; // 5.1 259 | } 260 | 261 | outputConfig->seqProfile = cfg.g_profile; 262 | outputConfig->seqLevelIdx0 = seqLevelIdx0; 263 | outputConfig->seqTier0 = 0; 264 | outputConfig->highBitdepth = (image->depth > 8) ? 1 : 0; 265 | outputConfig->twelveBit = (image->depth == 12) ? 1 : 0; 266 | outputConfig->monochrome = alphaOnly ? 1 : 0; 267 | outputConfig->chromaSubsamplingX = formatInfo.chromaShiftX; 268 | outputConfig->chromaSubsamplingY = formatInfo.chromaShiftY; 269 | 270 | // TODO: choose the correct one from below: 271 | // * 0 - CSP_UNKNOWN Unknown (in this case the source video transfer function must be signaled outside the AV1 bitstream) 272 | // * 1 - CSP_VERTICAL Horizontally co-located with (0, 0) luma sample, vertical position in the middle between two luma samples 273 | // * 2 - CSP_COLOCATED co-located with (0, 0) luma sample 274 | // * 3 - CSP_RESERVED 275 | outputConfig->chromaSamplePosition = 0; 276 | 277 | avifBool lossless = (quality == AVIF_BEST_QUALITY) ? AVIF_TRUE : AVIF_FALSE; 278 | cfg.rc_min_quantizer = 0; 279 | if (lossless) { 280 | cfg.rc_max_quantizer = 0; 281 | } else { 282 | cfg.rc_max_quantizer = quality; 283 | } 284 | 285 | uint32_t encoderFlags = 0; 286 | if (image->depth > 8) { 287 | encoderFlags |= AOM_CODEC_USE_HIGHBITDEPTH; 288 | } 289 | aom_codec_enc_init(&encoder, encoder_interface, &cfg, encoderFlags); 290 | 291 | if (lossless) { 292 | aom_codec_control(&encoder, AV1E_SET_LOSSLESS, 1); 293 | } 294 | if (numThreads > 1) { 295 | aom_codec_control(&encoder, AV1E_SET_ROW_MT, 1); 296 | } 297 | 298 | int uvHeight = image->height >> yShift; 299 | aom_image_t * aomImage = aom_img_alloc(NULL, aomFormat, image->width, image->height, 16); 300 | 301 | if (alphaOnly) { 302 | aomImage->range = AOM_CR_FULL_RANGE; // Alpha is always full range 303 | aom_codec_control(&encoder, AV1E_SET_COLOR_RANGE, aomImage->range); 304 | aomImage->monochrome = 1; 305 | for (int j = 0; j < image->height; ++j) { 306 | uint8_t * srcAlphaRow = &image->alphaPlane[j * image->alphaRowBytes]; 307 | uint8_t * dstAlphaRow = &aomImage->planes[0][j * aomImage->stride[0]]; 308 | memcpy(dstAlphaRow, srcAlphaRow, image->alphaRowBytes); 309 | } 310 | 311 | for (int j = 0; j < uvHeight; ++j) { 312 | // Zero out U and V 313 | memset(&aomImage->planes[1][j * aomImage->stride[1]], 0, aomImage->stride[1]); 314 | memset(&aomImage->planes[2][j * aomImage->stride[2]], 0, aomImage->stride[2]); 315 | } 316 | } else { 317 | aomImage->range = (image->yuvRange == AVIF_RANGE_FULL) ? AOM_CR_FULL_RANGE : AOM_CR_STUDIO_RANGE; 318 | aom_codec_control(&encoder, AV1E_SET_COLOR_RANGE, aomImage->range); 319 | for (int yuvPlane = 0; yuvPlane < 3; ++yuvPlane) { 320 | int aomPlaneIndex = yuvPlane; 321 | int planeHeight = image->height; 322 | if (yuvPlane == AVIF_CHAN_U) { 323 | aomPlaneIndex = formatInfo.aomIndexU; 324 | planeHeight = uvHeight; 325 | } else if (yuvPlane == AVIF_CHAN_V) { 326 | aomPlaneIndex = formatInfo.aomIndexV; 327 | planeHeight = uvHeight; 328 | } 329 | 330 | for (int j = 0; j < planeHeight; ++j) { 331 | uint8_t * srcRow = &image->yuvPlanes[yuvPlane][j * image->yuvRowBytes[yuvPlane]]; 332 | uint8_t * dstRow = &aomImage->planes[aomPlaneIndex][j * aomImage->stride[aomPlaneIndex]]; 333 | memcpy(dstRow, srcRow, image->yuvRowBytes[yuvPlane]); 334 | } 335 | } 336 | } 337 | 338 | aom_codec_encode(&encoder, aomImage, 0, 1, 0); 339 | aom_codec_encode(&encoder, NULL, 0, 1, 0); // flush 340 | 341 | aom_codec_iter_t iter = NULL; 342 | for (;;) { 343 | const aom_codec_cx_pkt_t * pkt = aom_codec_get_cx_data(&encoder, &iter); 344 | if (pkt == NULL) 345 | break; 346 | if (pkt->kind == AOM_CODEC_CX_FRAME_PKT) { 347 | avifRawDataSet(outputOBU, pkt->data.frame.buf, pkt->data.frame.sz); 348 | success = AVIF_TRUE; 349 | break; 350 | } 351 | } 352 | 353 | aom_img_free(aomImage); 354 | aom_codec_destroy(&encoder); 355 | return success; 356 | } 357 | 358 | avifResult avifCodecEncodeImage(avifCodec * codec, avifImage * image, int numThreads, int colorQuality, avifRawData * colorOBU, avifRawData * alphaOBU) 359 | { 360 | if (colorOBU) { 361 | if (!encodeOBU(image, AVIF_FALSE, numThreads, colorQuality, colorOBU, &codec->internal->configs[AVIF_CODEC_PLANES_COLOR])) { 362 | return AVIF_RESULT_ENCODE_COLOR_FAILED; 363 | } 364 | } 365 | if (alphaOBU) { 366 | if (!encodeOBU(image, AVIF_TRUE, numThreads, AVIF_BEST_QUALITY, alphaOBU, &codec->internal->configs[AVIF_CODEC_PLANES_ALPHA])) { 367 | return AVIF_RESULT_ENCODE_COLOR_FAILED; 368 | } 369 | } 370 | return AVIF_RESULT_OK; 371 | } 372 | 373 | void avifCodecGetConfigurationBox(avifCodec * codec, avifCodecPlanes planes, avifCodecConfigurationBox * outConfig) 374 | { 375 | memcpy(outConfig, &codec->internal->configs[planes], sizeof(avifCodecConfigurationBox)); 376 | } 377 | -------------------------------------------------------------------------------- /src/colr.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/internal.h" 5 | 6 | #include "gb_math.h" 7 | 8 | #include 9 | 10 | struct avifColourPrimariesTable 11 | { 12 | int colourPrimariesEnum; 13 | const char * name; 14 | float primaries[8]; // rX, rY, gX, gY, bX, bY, wX, wY 15 | }; 16 | static const struct avifColourPrimariesTable table[] = { 17 | { AVIF_NCLX_COLOUR_PRIMARIES_BT709, "BT.709", { 0.64f, 0.33f, 0.3f, 0.6f, 0.15f, 0.06f, 0.3127f, 0.329f } }, 18 | { AVIF_NCLX_COLOUR_PRIMARIES_BT470_6M, "BT470-6 System M", { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f, 0.310f, 0.316f } }, 19 | { AVIF_NCLX_COLOUR_PRIMARIES_BT601_7_625, "BT.601-7 625", { 0.64f, 0.33f, 0.29f, 0.60f, 0.15f, 0.06f, 0.3127f, 0.3290f } }, 20 | { AVIF_NCLX_COLOUR_PRIMARIES_BT601_7_525, "BT.601-7 525", { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f, 0.3127f, 0.3290f } }, 21 | { AVIF_NCLX_COLOUR_PRIMARIES_ST240, "ST 240", { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f, 0.3127f, 0.3290f } }, 22 | { AVIF_NCLX_COLOUR_PRIMARIES_GENERIC_FILM, "Generic film", { 0.681f, 0.319f, 0.243f, 0.692f, 0.145f, 0.049f, 0.310f, 0.316f } }, 23 | { AVIF_NCLX_COLOUR_PRIMARIES_BT2020, "BT.2020", { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f, 0.3127f, 0.3290f } }, 24 | { AVIF_NCLX_COLOUR_PRIMARIES_XYZ, "XYZ", { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.3333f, 0.3333f } }, 25 | { AVIF_NCLX_COLOUR_PRIMARIES_RP431_2, "RP 431-2", { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f, 0.314f, 0.351f } }, 26 | { AVIF_NCLX_COLOUR_PRIMARIES_EG432_1, "EG 432-1 (P3)", { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f, 0.3127f, 0.3290f } }, 27 | { AVIF_NCLX_COLOUR_PRIMARIES_EBU3213E, "EBU 3213-E", { 0.630f, 0.340f, 0.295f, 0.605f, 0.155f, 0.077f, 0.3127f, 0.3290f } } 28 | }; 29 | static const int tableSize = sizeof(table) / sizeof(table[0]); 30 | 31 | void avifNclxColourPrimariesGetValues(avifNclxColourPrimaries ancp, float outPrimaries[8]) 32 | { 33 | for (int i = 0; i < tableSize; ++i) { 34 | if (table[i].colourPrimariesEnum == ancp) { 35 | memcpy(outPrimaries, table[i].primaries, sizeof(table[i].primaries)); 36 | return; 37 | } 38 | } 39 | 40 | // if we get here, the color primaries are unknown. Just return a reasonable default. 41 | memcpy(outPrimaries, table[0].primaries, sizeof(table[0].primaries)); 42 | } 43 | 44 | static avifBool matchesTo3RoundedPlaces(float a, float b) 45 | { 46 | return (fabsf(a - b) < 0.001f) ? AVIF_TRUE : AVIF_FALSE; 47 | } 48 | 49 | static avifBool primariesMatch(const float p1[8], const float p2[8]) 50 | { 51 | return matchesTo3RoundedPlaces(p1[0], p2[0]) && 52 | matchesTo3RoundedPlaces(p1[1], p2[1]) && 53 | matchesTo3RoundedPlaces(p1[2], p2[2]) && 54 | matchesTo3RoundedPlaces(p1[3], p2[3]) && 55 | matchesTo3RoundedPlaces(p1[4], p2[4]) && 56 | matchesTo3RoundedPlaces(p1[5], p2[5]) && 57 | matchesTo3RoundedPlaces(p1[6], p2[6]) && 58 | matchesTo3RoundedPlaces(p1[7], p2[7]); 59 | } 60 | 61 | avifNclxColourPrimaries avifNclxColourPrimariesFind(float inPrimaries[8], const char ** outName) 62 | { 63 | if (outName) { 64 | *outName = NULL; 65 | } 66 | 67 | for (int i = 0; i < tableSize; ++i) { 68 | if (primariesMatch(inPrimaries, table[i].primaries)) { 69 | if (outName) { 70 | *outName = table[i].name; 71 | } 72 | return table[i].colourPrimariesEnum; 73 | } 74 | } 75 | return AVIF_NCLX_COLOUR_PRIMARIES_UNKNOWN; 76 | } 77 | 78 | static float fixedToFloat(int32_t fixed) 79 | { 80 | float sign = 1.0f; 81 | if (fixed < 0) { 82 | sign = -1.0f; 83 | fixed *= -1; 84 | } 85 | return sign * ((float)((fixed >> 16) & 0xffff) + ((float)(fixed & 0xffff) / 65536.0f)); 86 | } 87 | 88 | #if 0 89 | static void convertXYZToXYY(float XYZ[3], float xyY[3], float whitePointX, float whitePointY) 90 | { 91 | float sum = XYZ[0] + XYZ[1] + XYZ[2]; 92 | if (sum <= 0.0f) { 93 | xyY[0] = whitePointX; 94 | xyY[1] = whitePointY; 95 | xyY[2] = 0.0f; 96 | return; 97 | } 98 | xyY[0] = XYZ[0] / sum; 99 | xyY[1] = XYZ[1] / sum; 100 | xyY[2] = XYZ[1]; 101 | } 102 | 103 | static void convertXYYToXYZ(float * xyY, float * XYZ) 104 | { 105 | if (xyY[2] <= 0.0f) { 106 | XYZ[0] = 0.0f; 107 | XYZ[1] = 0.0f; 108 | XYZ[2] = 0.0f; 109 | return; 110 | } 111 | XYZ[0] = (xyY[0] * xyY[2]) / xyY[1]; 112 | XYZ[1] = xyY[2]; 113 | XYZ[2] = ((1 - xyY[0] - xyY[1]) * xyY[2]) / xyY[1]; 114 | } 115 | 116 | static void convertMaxXYToXYZ(float x, float y, float * XYZ) 117 | { 118 | float xyY[3]; 119 | xyY[0] = x; 120 | xyY[1] = y; 121 | xyY[2] = 1.0f; 122 | convertXYYToXYZ(xyY, XYZ); 123 | } 124 | 125 | static void convertXYZToXY(float XYZ[3], float xy[2], float whitePointX, float whitePointY) 126 | { 127 | float xyY[3]; 128 | convertXYZToXYY(XYZ, xyY, whitePointX, whitePointY); 129 | xy[0] = xyY[0]; 130 | xy[1] = xyY[1]; 131 | } 132 | #endif /* if 0 */ 133 | 134 | static float calcMaxY(float r, float g, float b, gbMat3 * colorants) 135 | { 136 | gbVec3 rgb, XYZ; 137 | rgb.e[0] = r; 138 | rgb.e[1] = g; 139 | rgb.e[2] = b; 140 | gb_mat3_mul_vec3(&XYZ, colorants, rgb); 141 | return XYZ.y; 142 | } 143 | 144 | static avifBool readXYZ(uint8_t * data, size_t size, float xyz[3]) 145 | { 146 | avifRawData xyzData; 147 | xyzData.data = data; 148 | xyzData.size = size; 149 | avifStream s; 150 | avifStreamStart(&s, &xyzData); 151 | CHECK(avifStreamSkip(&s, 8)); 152 | 153 | int32_t fixedXYZ[3]; 154 | CHECK(avifStreamReadU32(&s, (uint32_t *)&fixedXYZ[0])); 155 | CHECK(avifStreamReadU32(&s, (uint32_t *)&fixedXYZ[1])); 156 | CHECK(avifStreamReadU32(&s, (uint32_t *)&fixedXYZ[2])); 157 | 158 | xyz[0] = fixedToFloat(fixedXYZ[0]); 159 | xyz[1] = fixedToFloat(fixedXYZ[1]); 160 | xyz[2] = fixedToFloat(fixedXYZ[2]); 161 | return AVIF_TRUE; 162 | } 163 | 164 | static avifBool readMat3(uint8_t * data, size_t size, gbMat3 * m) 165 | { 166 | avifRawData xyzData; 167 | xyzData.data = data; 168 | xyzData.size = size; 169 | avifStream s; 170 | avifStreamStart(&s, &xyzData); 171 | CHECK(avifStreamSkip(&s, 8)); 172 | 173 | for (int i = 0; i < 9; ++i) { 174 | int32_t fixedXYZ; 175 | CHECK(avifStreamReadU32(&s, (uint32_t *)&fixedXYZ)); 176 | m->e[i] = fixedToFloat(fixedXYZ); 177 | } 178 | return AVIF_TRUE; 179 | } 180 | 181 | static avifBool calcYUVInfoFromICC(avifRawData * icc, float coeffs[3]) 182 | { 183 | avifStream s; 184 | 185 | uint8_t iccMajorVersion; 186 | avifStreamStart(&s, icc); 187 | CHECK(avifStreamSkip(&s, 8)); // skip to version 188 | CHECK(avifStreamRead(&s, &iccMajorVersion, 1)); 189 | 190 | avifStreamStart(&s, icc); // start stream over 191 | CHECK(avifStreamSkip(&s, 128)); // skip past the ICC header 192 | 193 | uint32_t tagCount; 194 | CHECK(avifStreamReadU32(&s, &tagCount)); 195 | 196 | avifBool rXYZPresent = AVIF_FALSE; 197 | avifBool gXYZPresent = AVIF_FALSE; 198 | avifBool bXYZPresent = AVIF_FALSE; 199 | avifBool wtptPresent = AVIF_FALSE; 200 | avifBool chadPresent = AVIF_FALSE; 201 | gbMat3 colorants; 202 | gbMat3 chad, invChad; 203 | gbVec3 wtpt; 204 | 205 | for (uint32_t tagIndex = 0; tagIndex < tagCount; ++tagIndex) { 206 | uint8_t tagSignature[4]; 207 | uint32_t tagOffset; 208 | uint32_t tagSize; 209 | CHECK(avifStreamRead(&s, tagSignature, 4)); 210 | CHECK(avifStreamReadU32(&s, &tagOffset)); 211 | CHECK(avifStreamReadU32(&s, &tagSize)); 212 | if ((tagOffset + tagSize) > icc->size) { 213 | return AVIF_FALSE; 214 | } 215 | if (!memcmp(tagSignature, "rXYZ", 4)) { 216 | CHECK(readXYZ(icc->data + tagOffset, tagSize, &colorants.e[0])); 217 | rXYZPresent = AVIF_TRUE; 218 | } else if (!memcmp(tagSignature, "gXYZ", 4)) { 219 | CHECK(readXYZ(icc->data + tagOffset, tagSize, &colorants.e[3])); 220 | gXYZPresent = AVIF_TRUE; 221 | } else if (!memcmp(tagSignature, "bXYZ", 4)) { 222 | CHECK(readXYZ(icc->data + tagOffset, tagSize, &colorants.e[6])); 223 | bXYZPresent = AVIF_TRUE; 224 | } else if (!memcmp(tagSignature, "wtpt", 4)) { 225 | CHECK(readXYZ(icc->data + tagOffset, tagSize, &wtpt.e[0])); 226 | wtptPresent = AVIF_TRUE; 227 | } else if (!memcmp(tagSignature, "chad", 4)) { 228 | CHECK(readMat3(icc->data + tagOffset, tagSize, &chad)); 229 | chadPresent = AVIF_TRUE; 230 | } 231 | } 232 | 233 | if (!rXYZPresent || !gXYZPresent || !bXYZPresent || !wtptPresent) { 234 | return AVIF_FALSE; 235 | } 236 | 237 | // These are read in column order, transpose to fix 238 | gb_mat3_transpose(&colorants); 239 | gb_mat3_transpose(&chad); 240 | 241 | gb_mat3_inverse(&invChad, &chad); 242 | 243 | if (chadPresent) { 244 | // TODO: make sure ICC profiles with no chad still behave? 245 | 246 | gbMat3 tmpColorants; 247 | memcpy(&tmpColorants, &colorants, sizeof(tmpColorants)); 248 | gb_mat3_mul(&colorants, &tmpColorants, &invChad); 249 | 250 | // TODO: make sure older versions work well? 251 | if (iccMajorVersion >= 4) { 252 | gbVec3 tmp; 253 | memcpy(&tmp, &wtpt, sizeof(tmp)); 254 | gb_mat3_mul_vec3(&wtpt, &invChad, tmp); 255 | } 256 | } 257 | 258 | // white point and color primaries harvesting (unnecessary for YUV coefficients) 259 | #if 0 260 | float whitePoint[2]; 261 | convertXYZToXY(&wtpt.e[0], &whitePoint, 0.0f, 0.0f); 262 | 263 | float primaries[6]; 264 | { 265 | // transpose to get sets of 3-tuples for R, G, B 266 | gb_mat3_transpose(&colorants); 267 | 268 | convertXYZToXY(&colorants.e[0], &primaries[0], whitePoint[0], whitePoint[1]); 269 | convertXYZToXY(&colorants.e[3], &primaries[2], whitePoint[0], whitePoint[1]); 270 | convertXYZToXY(&colorants.e[6], &primaries[4], whitePoint[0], whitePoint[1]); 271 | 272 | // put it back 273 | gb_mat3_transpose(&colorants); 274 | } 275 | #endif 276 | 277 | // YUV coefficients are simply the brightest Y that a primary can be (where the white point's Y is 1.0) 278 | coeffs[0] = calcMaxY(1.0f, 0.0f, 0.0f, &colorants); 279 | coeffs[2] = calcMaxY(0.0f, 0.0f, 1.0f, &colorants); 280 | coeffs[1] = 1.0f - coeffs[0] - coeffs[2]; 281 | return AVIF_TRUE; 282 | } 283 | 284 | // From http://docs-hoffmann.de/ciexyz29082000.pdf, Section 11.4 285 | static void deriveXYZMatrix(gbMat3 * colorants, float primaries[8]) 286 | { 287 | gbVec3 U, W; 288 | gbMat3 P, PInv, D; 289 | 290 | P.col[0].x = primaries[0]; 291 | P.col[0].y = primaries[1]; 292 | P.col[0].z = 1 - primaries[0] - primaries[1]; 293 | P.col[1].x = primaries[2]; 294 | P.col[1].y = primaries[3]; 295 | P.col[1].z = 1 - primaries[2] - primaries[3]; 296 | P.col[2].x = primaries[4]; 297 | P.col[2].y = primaries[5]; 298 | P.col[2].z = 1 - primaries[4] - primaries[5]; 299 | 300 | gb_mat3_inverse(&PInv, &P); 301 | 302 | W.x = primaries[6]; 303 | W.y = primaries[7]; 304 | W.z = 1 - primaries[6] - primaries[7]; 305 | 306 | gb_mat3_mul_vec3(&U, &PInv, W); 307 | 308 | memset(&D, 0, sizeof(D)); 309 | D.col[0].x = U.x / W.y; 310 | D.col[1].y = U.y / W.y; 311 | D.col[2].z = U.z / W.y; 312 | 313 | gb_mat3_mul(colorants, &P, &D); 314 | gb_mat3_transpose(colorants); 315 | } 316 | 317 | static avifBool calcYUVInfoFromNCLX(avifNclxColorProfile * nclx, float coeffs[3]) 318 | { 319 | float primaries[8]; 320 | avifNclxColourPrimariesGetValues(nclx->colourPrimaries, primaries); 321 | 322 | gbMat3 colorants; 323 | deriveXYZMatrix(&colorants, primaries); 324 | 325 | // YUV coefficients are simply the brightest Y that a primary can be (where the white point's Y is 1.0) 326 | coeffs[0] = calcMaxY(1.0f, 0.0f, 0.0f, &colorants); 327 | coeffs[2] = calcMaxY(0.0f, 0.0f, 1.0f, &colorants); 328 | coeffs[1] = 1.0f - coeffs[0] - coeffs[2]; 329 | return AVIF_TRUE; 330 | } 331 | 332 | void avifCalcYUVCoefficients(avifImage * image, float * outR, float * outG, float * outB) 333 | { 334 | // sRGB (BT.709) defaults 335 | float kr = 0.2126f; 336 | float kb = 0.0722f; 337 | float kg = 1.0f - kr - kb; 338 | 339 | float coeffs[3]; 340 | if ((image->profileFormat == AVIF_PROFILE_FORMAT_ICC) && image->icc.data && image->icc.size) { 341 | if (calcYUVInfoFromICC(&image->icc, coeffs)) { 342 | kr = coeffs[0]; 343 | kg = coeffs[1]; 344 | kb = coeffs[2]; 345 | } 346 | } else if (image->profileFormat == AVIF_PROFILE_FORMAT_NCLX) { 347 | if (calcYUVInfoFromNCLX(&image->nclx, coeffs)) { 348 | kr = coeffs[0]; 349 | kg = coeffs[1]; 350 | kb = coeffs[2]; 351 | } 352 | } 353 | 354 | *outR = kr; 355 | *outG = kg; 356 | *outB = kb; 357 | } 358 | -------------------------------------------------------------------------------- /src/mem.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/avif.h" 5 | 6 | #include 7 | 8 | void * avifAlloc(size_t size) 9 | { 10 | return malloc(size); 11 | } 12 | 13 | void avifFree(void * p) 14 | { 15 | free(p); 16 | } 17 | -------------------------------------------------------------------------------- /src/rawdata.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/avif.h" 5 | 6 | #include 7 | 8 | void avifRawDataRealloc(avifRawData * raw, size_t newSize) 9 | { 10 | if (raw->size != newSize) { 11 | uint8_t * old = raw->data; 12 | size_t oldSize = raw->size; 13 | raw->data = avifAlloc(newSize); 14 | raw->size = newSize; 15 | if (oldSize) { 16 | size_t bytesToCopy = (oldSize < raw->size) ? oldSize : raw->size; 17 | memcpy(raw->data, old, bytesToCopy); 18 | avifFree(old); 19 | } 20 | } 21 | } 22 | 23 | void avifRawDataSet(avifRawData * raw, const uint8_t * data, size_t len) 24 | { 25 | if (len) { 26 | avifRawDataRealloc(raw, len); 27 | memcpy(raw->data, data, len); 28 | } else { 29 | avifFree(raw); 30 | } 31 | } 32 | 33 | void avifRawDataFree(avifRawData * raw) 34 | { 35 | avifFree(raw->data); 36 | raw->data = NULL; 37 | raw->size = 0; 38 | } 39 | 40 | void avifRawDataConcat(avifRawData * dst, avifRawData ** srcs, int srcsCount) 41 | { 42 | size_t totalSize = 0; 43 | for (int i = 0; i < srcsCount; ++i) { 44 | totalSize += srcs[i]->size; 45 | } 46 | 47 | avifRawDataRealloc(dst, totalSize); 48 | 49 | uint8_t * p = dst->data; 50 | for (int i = 0; i < srcsCount; ++i) { 51 | if (srcs[i]->size) { 52 | memcpy(p, srcs[i]->data, srcs[i]->size); 53 | p += srcs[i]->size; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/read.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/internal.h" 5 | 6 | #include 7 | 8 | // from the MIAF spec: 9 | // --- 10 | // Section 6.7 11 | // "α is an alpha plane value, scaled into the range of 0 (fully transparent) to 1 (fully opaque), inclusive" 12 | // --- 13 | // Section 7.3.5.2 14 | // "the sample values of the alpha plane divided by the maximum value (e.g. by 255 for 8-bit sample 15 | // values) provides the multiplier to be used to obtain the intensity for the associated master image" 16 | // --- 17 | // The define AVIF_FIX_STUDIO_ALPHA detects when the alpha OBU is incorrectly using studio range 18 | // and corrects it before returning the alpha pixels to the caller. 19 | #define AVIF_FIX_STUDIO_ALPHA 20 | 21 | #define AUXTYPE_SIZE 64 22 | #define MAX_COMPATIBLE_BRANDS 32 23 | #define MAX_ITEMS 8 24 | #define MAX_PROPERTIES 24 25 | 26 | // --------------------------------------------------------------------------- 27 | // Box data structures 28 | 29 | // ftyp 30 | typedef struct avifFileType 31 | { 32 | uint8_t majorBrand[4]; 33 | uint32_t minorVersion; 34 | uint8_t compatibleBrands[4 * MAX_COMPATIBLE_BRANDS]; 35 | int compatibleBrandsCount; 36 | } avifFileType; 37 | 38 | // ispe 39 | typedef struct avifImageSpatialExtents 40 | { 41 | uint32_t width; 42 | uint32_t height; 43 | } avifImageSpatialExtents; 44 | 45 | // auxC 46 | typedef struct avifAuxiliaryType 47 | { 48 | char auxType[AUXTYPE_SIZE]; 49 | } avifAuxiliaryType; 50 | 51 | // colr 52 | typedef struct avifColourInformationBox 53 | { 54 | avifProfileFormat format; 55 | uint8_t * icc; 56 | size_t iccSize; 57 | avifNclxColorProfile nclx; 58 | } avifColourInformationBox; 59 | 60 | // --------------------------------------------------------------------------- 61 | // Top-level structures 62 | 63 | // one "item" worth (all iref, iloc, iprp, etc refer to one of these) 64 | typedef struct avifItem 65 | { 66 | int id; 67 | uint8_t type[4]; 68 | uint32_t offset; 69 | uint32_t size; 70 | avifBool ispePresent; 71 | avifImageSpatialExtents ispe; 72 | avifBool auxCPresent; 73 | avifAuxiliaryType auxC; 74 | avifBool colrPresent; 75 | avifColourInformationBox colr; 76 | int thumbnailForID; // if non-zero, this item is a thumbnail for Item #{thumbnailForID} 77 | int auxForID; // if non-zero, this item is an auxC plane for Item #{auxForID} 78 | } avifItem; 79 | 80 | // Temporary storage for ipco contents until they can be associated and memcpy'd to an avifItem 81 | typedef struct avifProperty 82 | { 83 | uint8_t type[4]; 84 | avifImageSpatialExtents ispe; 85 | avifAuxiliaryType auxC; 86 | avifColourInformationBox colr; 87 | } avifProperty; 88 | 89 | typedef struct avifData 90 | { 91 | // TODO: Everything in here using a MAX_* constant is a bit lazy; it should all be dynamic 92 | 93 | avifFileType ftyp; 94 | avifItem items[MAX_ITEMS]; 95 | avifProperty properties[MAX_PROPERTIES]; 96 | int propertyCount; 97 | } avifData; 98 | 99 | int findItemID(avifData * data, int itemID) 100 | { 101 | if (itemID == 0) { 102 | return -1; 103 | } 104 | 105 | for (int i = 0; i < MAX_ITEMS; ++i) { 106 | if (data->items[i].id == itemID) { 107 | return i; 108 | } 109 | } 110 | 111 | for (int i = 0; i < MAX_ITEMS; ++i) { 112 | if (data->items[i].id == 0) { 113 | data->items[i].id = itemID; 114 | return i; 115 | } 116 | } 117 | 118 | return -1; 119 | } 120 | 121 | // --------------------------------------------------------------------------- 122 | // URN 123 | 124 | static avifBool isAlphaURN(char * urn) 125 | { 126 | if (!strcmp(urn, URN_ALPHA0)) return AVIF_TRUE; 127 | if (!strcmp(urn, URN_ALPHA1)) return AVIF_TRUE; 128 | return AVIF_FALSE; 129 | } 130 | 131 | // --------------------------------------------------------------------------- 132 | // BMFF Parsing 133 | 134 | #define BEGIN_STREAM(VARNAME, PTR, SIZE) \ 135 | avifStream VARNAME; \ 136 | avifRawData VARNAME ## _rawData = { PTR, SIZE }; \ 137 | avifStreamStart(&VARNAME, &VARNAME ## _rawData) 138 | 139 | static avifBool avifParseItemLocationBox(avifData * data, uint8_t * raw, size_t rawLen) 140 | { 141 | BEGIN_STREAM(s, raw, rawLen); 142 | 143 | CHECK(avifStreamReadAndEnforceVersion(&s, 0)); 144 | 145 | uint8_t offsetSizeAndLengthSize; 146 | CHECK(avifStreamRead(&s, &offsetSizeAndLengthSize, 1)); 147 | uint8_t offsetSize = (offsetSizeAndLengthSize >> 4) & 0xf; // unsigned int(4) offset_size; 148 | uint8_t lengthSize = (offsetSizeAndLengthSize >> 0) & 0xf; // unsigned int(4) length_size; 149 | 150 | uint8_t baseOffsetSizeAndReserved; 151 | CHECK(avifStreamRead(&s, &baseOffsetSizeAndReserved, 1)); 152 | uint8_t baseOffsetSize = (baseOffsetSizeAndReserved >> 4) & 0xf; // unsigned int(4) base_offset_size; 153 | 154 | uint16_t itemCount; 155 | CHECK(avifStreamReadU16(&s, &itemCount)); // unsigned int(16) item_count; 156 | for (int i = 0; i < itemCount; ++i) { 157 | uint16_t itemID; // unsigned int(16) item_ID; 158 | CHECK(avifStreamReadU16(&s, &itemID)); // 159 | uint16_t dataReferenceIndex; // unsigned int(16) data_reference_index; 160 | CHECK(avifStreamReadU16(&s, &dataReferenceIndex)); // 161 | uint64_t baseOffset; // unsigned int(base_offset_size*8) base_offset; 162 | CHECK(avifStreamReadUX8(&s, &baseOffset, baseOffsetSize)); // 163 | uint16_t extentCount; // unsigned int(16) extent_count; 164 | CHECK(avifStreamReadU16(&s, &extentCount)); // 165 | if (extentCount == 1) { 166 | uint64_t extentOffset; // unsigned int(offset_size*8) extent_offset; 167 | CHECK(avifStreamReadUX8(&s, &extentOffset, offsetSize)); 168 | uint64_t extentLength; // unsigned int(offset_size*8) extent_length; 169 | CHECK(avifStreamReadUX8(&s, &extentLength, lengthSize)); 170 | 171 | int itemIndex = findItemID(data, itemID); 172 | if (itemIndex == -1) { 173 | return AVIF_FALSE; 174 | } 175 | data->items[itemIndex].id = itemID; 176 | data->items[itemIndex].offset = (uint32_t)(baseOffset + extentOffset); 177 | data->items[itemIndex].size = (uint32_t)extentLength; 178 | } else { 179 | // TODO: support more than one extent 180 | return AVIF_FALSE; 181 | } 182 | } 183 | return AVIF_TRUE; 184 | } 185 | 186 | static avifBool avifParseImageSpatialExtentsProperty(avifData * data, uint8_t * raw, size_t rawLen, int propertyIndex) 187 | { 188 | BEGIN_STREAM(s, raw, rawLen); 189 | CHECK(avifStreamReadAndEnforceVersion(&s, 0)); 190 | 191 | CHECK(avifStreamReadU32(&s, &data->properties[propertyIndex].ispe.width)); 192 | CHECK(avifStreamReadU32(&s, &data->properties[propertyIndex].ispe.height)); 193 | return AVIF_TRUE; 194 | } 195 | 196 | static avifBool avifParseAuxiliaryTypeProperty(avifData * data, uint8_t * raw, size_t rawLen, int propertyIndex) 197 | { 198 | BEGIN_STREAM(s, raw, rawLen); 199 | CHECK(avifStreamReadAndEnforceVersion(&s, 0)); 200 | 201 | CHECK(avifStreamReadString(&s, data->properties[propertyIndex].auxC.auxType, AUXTYPE_SIZE)); 202 | return AVIF_TRUE; 203 | } 204 | 205 | static avifBool avifParseColourInformationBox(avifData * data, uint8_t * raw, size_t rawLen, int propertyIndex) 206 | { 207 | BEGIN_STREAM(s, raw, rawLen); 208 | 209 | data->properties[propertyIndex].colr.format = AVIF_PROFILE_FORMAT_NONE; 210 | 211 | uint8_t colourType[4]; // unsigned int(32) colour_type; 212 | CHECK(avifStreamRead(&s, colourType, 4)); 213 | if (!memcmp(colourType, "rICC", 4) || !memcmp(colourType, "prof", 4)) { 214 | data->properties[propertyIndex].colr.format = AVIF_PROFILE_FORMAT_ICC; 215 | data->properties[propertyIndex].colr.icc = avifStreamCurrent(&s); 216 | data->properties[propertyIndex].colr.iccSize = avifStreamRemainingBytes(&s); 217 | } else if (!memcmp(colourType, "nclx", 4)) { 218 | CHECK(avifStreamReadU16(&s, &data->properties[propertyIndex].colr.nclx.colourPrimaries)); // unsigned int(16) colour_primaries; 219 | CHECK(avifStreamReadU16(&s, &data->properties[propertyIndex].colr.nclx.transferCharacteristics)); // unsigned int(16) transfer_characteristics; 220 | CHECK(avifStreamReadU16(&s, &data->properties[propertyIndex].colr.nclx.matrixCoefficients)); // unsigned int(16) matrix_coefficients; 221 | CHECK(avifStreamRead(&s, &data->properties[propertyIndex].colr.nclx.fullRangeFlag, 1)); // unsigned int(1) full_range_flag; unsigned int(7) reserved = 0; 222 | data->properties[propertyIndex].colr.nclx.fullRangeFlag |= 0x80; 223 | data->properties[propertyIndex].colr.format = AVIF_PROFILE_FORMAT_NCLX; 224 | } 225 | return AVIF_TRUE; 226 | } 227 | 228 | static avifBool avifParseItemPropertyContainerBox(avifData * data, uint8_t * raw, size_t rawLen) 229 | { 230 | BEGIN_STREAM(s, raw, rawLen); 231 | 232 | data->propertyCount = 0; 233 | 234 | while (avifStreamHasBytesLeft(&s, 1)) { 235 | avifBoxHeader header; 236 | CHECK(avifStreamReadBoxHeader(&s, &header)); 237 | 238 | if (data->propertyCount >= MAX_PROPERTIES) { 239 | return AVIF_FALSE; 240 | } 241 | int propertyIndex = data->propertyCount; 242 | ++data->propertyCount; 243 | 244 | memcpy(data->properties[propertyIndex].type, header.type, 4); 245 | if (!memcmp(header.type, "ispe", 4)) { 246 | CHECK(avifParseImageSpatialExtentsProperty(data, avifStreamCurrent(&s), header.size, propertyIndex)); 247 | } 248 | if (!memcmp(header.type, "auxC", 4)) { 249 | CHECK(avifParseAuxiliaryTypeProperty(data, avifStreamCurrent(&s), header.size, propertyIndex)); 250 | } 251 | if (!memcmp(header.type, "colr", 4)) { 252 | CHECK(avifParseColourInformationBox(data, avifStreamCurrent(&s), header.size, propertyIndex)); 253 | } 254 | 255 | CHECK(avifStreamSkip(&s, header.size)); 256 | } 257 | return AVIF_TRUE; 258 | } 259 | 260 | static avifBool avifParseItemPropertyAssociation(avifData * data, uint8_t * raw, size_t rawLen) 261 | { 262 | BEGIN_STREAM(s, raw, rawLen); 263 | 264 | uint8_t version; 265 | uint8_t flags[3]; 266 | CHECK(avifStreamReadVersionAndFlags(&s, &version, flags)); 267 | avifBool propertyIndexIsU16 = (flags[2] & 0x1) ? AVIF_TRUE : AVIF_FALSE; // is flags[2] correct? 268 | 269 | uint32_t entryCount; 270 | CHECK(avifStreamReadU32(&s, &entryCount)); 271 | for (uint32_t entryIndex = 0; entryIndex < entryCount; ++entryIndex) { 272 | unsigned int itemID; 273 | if (version < 1) { 274 | uint16_t tmp; 275 | CHECK(avifStreamReadU16(&s, &tmp)); 276 | itemID = tmp; 277 | } else { 278 | CHECK(avifStreamReadU32(&s, &itemID)); 279 | } 280 | uint8_t associationCount; 281 | CHECK(avifStreamRead(&s, &associationCount, 1)); 282 | for (uint8_t associationIndex = 0; associationIndex < associationCount; ++associationIndex) { 283 | avifBool essential = AVIF_FALSE; 284 | uint16_t propertyIndex = 0; 285 | if (propertyIndexIsU16) { 286 | CHECK(avifStreamReadU16(&s, &propertyIndex)); 287 | essential = (propertyIndex & 0x8000) ? AVIF_TRUE : AVIF_FALSE; 288 | propertyIndex &= 0x7fff; 289 | } else { 290 | uint8_t tmp; 291 | CHECK(avifStreamRead(&s, &tmp, 1)); 292 | essential = (tmp & 0x80) ? AVIF_TRUE : AVIF_FALSE; 293 | propertyIndex = tmp & 0x7f; 294 | } 295 | 296 | if (propertyIndex == 0) { 297 | // Not associated with any item 298 | continue; 299 | } 300 | --propertyIndex; // 1-indexed 301 | 302 | if (propertyIndex >= MAX_PROPERTIES) { 303 | return AVIF_FALSE; 304 | } 305 | 306 | int itemIndex = findItemID(data, itemID); 307 | if (itemIndex == -1) { 308 | return AVIF_FALSE; 309 | } 310 | 311 | // Associate property with item 312 | avifProperty * prop = &data->properties[propertyIndex]; 313 | if (!memcmp(prop->type, "ispe", 4)) { 314 | data->items[itemIndex].ispePresent = AVIF_TRUE; 315 | memcpy(&data->items[itemIndex].ispe, &prop->ispe, sizeof(avifImageSpatialExtents)); 316 | } else if (!memcmp(prop->type, "auxC", 4)) { 317 | data->items[itemIndex].auxCPresent = AVIF_TRUE; 318 | memcpy(&data->items[itemIndex].auxC, &prop->auxC, sizeof(avifAuxiliaryType)); 319 | } else if (!memcmp(prop->type, "colr", 4)) { 320 | data->items[itemIndex].colrPresent = AVIF_TRUE; 321 | memcpy(&data->items[itemIndex].colr, &prop->colr, sizeof(avifColourInformationBox)); 322 | } 323 | } 324 | } 325 | 326 | return AVIF_TRUE; 327 | } 328 | 329 | static avifBool avifParseItemPropertiesBox(avifData * data, uint8_t * raw, size_t rawLen) 330 | { 331 | BEGIN_STREAM(s, raw, rawLen); 332 | 333 | avifBoxHeader ipcoHeader; 334 | CHECK(avifStreamReadBoxHeader(&s, &ipcoHeader)); 335 | if (memcmp(ipcoHeader.type, "ipco", 4) != 0) { 336 | return AVIF_FALSE; 337 | } 338 | 339 | // Read all item properties inside of ItemPropertyContainerBox 340 | CHECK(avifParseItemPropertyContainerBox(data, avifStreamCurrent(&s), ipcoHeader.size)); 341 | CHECK(avifStreamSkip(&s, ipcoHeader.size)); 342 | 343 | // Now read all ItemPropertyAssociation until the end of the box, and make associations 344 | while (avifStreamHasBytesLeft(&s, 1)) { 345 | avifBoxHeader ipmaHeader; 346 | CHECK(avifStreamReadBoxHeader(&s, &ipmaHeader)); 347 | 348 | if (!memcmp(ipmaHeader.type, "ipma", 4)) { 349 | CHECK(avifParseItemPropertyAssociation(data, avifStreamCurrent(&s), ipmaHeader.size)); 350 | } else { 351 | // These must all be type ipma 352 | return AVIF_FALSE; 353 | } 354 | 355 | CHECK(avifStreamSkip(&s, ipmaHeader.size)); 356 | } 357 | return AVIF_TRUE; 358 | } 359 | 360 | static avifBool avifParseItemInfoEntry(avifData * data, uint8_t * raw, size_t rawLen) 361 | { 362 | BEGIN_STREAM(s, raw, rawLen); 363 | 364 | CHECK(avifStreamReadAndEnforceVersion(&s, 2)); // TODO: support version > 2? 2+ is required for item_type 365 | 366 | uint16_t itemID; // unsigned int(16) item_ID; 367 | CHECK(avifStreamReadU16(&s, &itemID)); // 368 | uint16_t itemProtectionIndex; // unsigned int(16) item_protection_index; 369 | CHECK(avifStreamReadU16(&s, &itemProtectionIndex)); // 370 | uint8_t itemType[4]; // unsigned int(32) item_type; 371 | CHECK(avifStreamRead(&s, itemType, 4)); // 372 | 373 | int itemIndex = findItemID(data, itemID); 374 | if (itemIndex == -1) { 375 | return AVIF_FALSE; 376 | } 377 | memcpy(data->items[itemIndex].type, itemType, sizeof(itemType)); 378 | 379 | return AVIF_TRUE; 380 | } 381 | 382 | static avifBool avifParseItemInfoBox(avifData * data, uint8_t * raw, size_t rawLen) 383 | { 384 | BEGIN_STREAM(s, raw, rawLen); 385 | 386 | uint8_t version; 387 | CHECK(avifStreamReadVersionAndFlags(&s, &version, NULL)); 388 | uint32_t entryCount; 389 | if (version == 0) { 390 | uint16_t tmp; 391 | CHECK(avifStreamReadU16(&s, &tmp)); // unsigned int(16) entry_count; 392 | entryCount = tmp; 393 | } else if (version == 1) { 394 | CHECK(avifStreamReadU32(&s, &entryCount)); // unsigned int(16) entry_count; 395 | } else { 396 | return AVIF_FALSE; 397 | } 398 | 399 | for (uint32_t entryIndex = 0; entryIndex < entryCount; ++entryIndex) { 400 | avifBoxHeader infeHeader; 401 | CHECK(avifStreamReadBoxHeader(&s, &infeHeader)); 402 | 403 | if (!memcmp(infeHeader.type, "infe", 4)) { 404 | CHECK(avifParseItemInfoEntry(data, avifStreamCurrent(&s), infeHeader.size)); 405 | } else { 406 | // These must all be type ipma 407 | return AVIF_FALSE; 408 | } 409 | 410 | CHECK(avifStreamSkip(&s, infeHeader.size)); 411 | } 412 | 413 | return AVIF_TRUE; 414 | } 415 | 416 | static avifBool avifParseItemReferenceBox(avifData * data, uint8_t * raw, size_t rawLen) 417 | { 418 | BEGIN_STREAM(s, raw, rawLen); 419 | 420 | uint8_t version; 421 | CHECK(avifStreamReadVersionAndFlags(&s, &version, NULL)); 422 | 423 | while (avifStreamHasBytesLeft(&s, 1)) { 424 | avifBoxHeader irefHeader; 425 | CHECK(avifStreamReadBoxHeader(&s, &irefHeader)); 426 | 427 | uint32_t fromID = 0; 428 | if (version == 0) { 429 | uint16_t tmp; 430 | CHECK(avifStreamReadU16(&s, &tmp)); // unsigned int(16) from_item_ID; 431 | fromID = tmp; 432 | } else if (version == 1) { 433 | CHECK(avifStreamReadU32(&s, &fromID)); // unsigned int(32) from_item_ID; 434 | } else { 435 | // unsupported iref version, skip it 436 | break; 437 | } 438 | 439 | uint16_t referenceCount = 0; 440 | CHECK(avifStreamReadU16(&s, &referenceCount)); // unsigned int(16) reference_count; 441 | 442 | for (uint16_t refIndex = 0; refIndex < referenceCount; ++refIndex) { 443 | uint32_t toID = 0; 444 | if (version == 0) { 445 | uint16_t tmp; 446 | CHECK(avifStreamReadU16(&s, &tmp)); // unsigned int(16) to_item_ID; 447 | toID = tmp; 448 | } else if (version == 1) { 449 | CHECK(avifStreamReadU32(&s, &toID)); // unsigned int(32) to_item_ID; 450 | } else { 451 | // unsupported iref version, skip it 452 | break; 453 | } 454 | 455 | // Read this reference as "{fromID} is a {irefType} for {toID}" 456 | if (fromID && toID) { 457 | int itemIndex = findItemID(data, fromID); 458 | if (itemIndex == -1) { 459 | return AVIF_FALSE; 460 | } 461 | if (!memcmp(irefHeader.type, "thmb", 4)) { 462 | data->items[itemIndex].thumbnailForID = toID; 463 | } 464 | if (!memcmp(irefHeader.type, "auxl", 4)) { 465 | data->items[itemIndex].auxForID = toID; 466 | } 467 | } 468 | } 469 | } 470 | 471 | return AVIF_TRUE; 472 | } 473 | 474 | static avifBool avifParseMetaBox(avifData * data, uint8_t * raw, size_t rawLen) 475 | { 476 | BEGIN_STREAM(s, raw, rawLen); 477 | 478 | CHECK(avifStreamReadAndEnforceVersion(&s, 0)); 479 | 480 | while (avifStreamHasBytesLeft(&s, 1)) { 481 | avifBoxHeader header; 482 | CHECK(avifStreamReadBoxHeader(&s, &header)); 483 | 484 | if (!memcmp(header.type, "iloc", 4)) { 485 | CHECK(avifParseItemLocationBox(data, avifStreamCurrent(&s), header.size)); 486 | } else if (!memcmp(header.type, "iprp", 4)) { 487 | CHECK(avifParseItemPropertiesBox(data, avifStreamCurrent(&s), header.size)); 488 | } else if (!memcmp(header.type, "iinf", 4)) { 489 | CHECK(avifParseItemInfoBox(data, avifStreamCurrent(&s), header.size)); 490 | } else if (!memcmp(header.type, "iref", 4)) { 491 | CHECK(avifParseItemReferenceBox(data, avifStreamCurrent(&s), header.size)); 492 | } 493 | 494 | CHECK(avifStreamSkip(&s, header.size)); 495 | } 496 | return AVIF_TRUE; 497 | } 498 | 499 | static avifBool avifParseFileTypeBox(avifData * data, uint8_t * raw, size_t rawLen) 500 | { 501 | BEGIN_STREAM(s, raw, rawLen); 502 | 503 | CHECK(avifStreamRead(&s, data->ftyp.majorBrand, 4)); 504 | CHECK(avifStreamReadU32(&s, &data->ftyp.minorVersion)); 505 | 506 | size_t compatibleBrandsBytes = avifStreamRemainingBytes(&s); 507 | if ((compatibleBrandsBytes % 4) != 0) { 508 | return AVIF_FALSE; 509 | } 510 | if (compatibleBrandsBytes > (4 * MAX_COMPATIBLE_BRANDS)) { 511 | // TODO: stop clamping and resize this 512 | compatibleBrandsBytes = (4 * MAX_COMPATIBLE_BRANDS); 513 | } 514 | CHECK(avifStreamRead(&s, data->ftyp.compatibleBrands, compatibleBrandsBytes)); 515 | data->ftyp.compatibleBrandsCount = (int)compatibleBrandsBytes / 4; 516 | 517 | return AVIF_TRUE; 518 | } 519 | 520 | static avifBool avifParse(avifData * data, uint8_t * raw, size_t rawLen) 521 | { 522 | BEGIN_STREAM(s, raw, rawLen); 523 | 524 | while (avifStreamHasBytesLeft(&s, 1)) { 525 | avifBoxHeader header; 526 | CHECK(avifStreamReadBoxHeader(&s, &header)); 527 | 528 | if (!memcmp(header.type, "ftyp", 4)) { 529 | CHECK(avifParseFileTypeBox(data, avifStreamCurrent(&s), header.size)); 530 | } else if (!memcmp(header.type, "meta", 4)) { 531 | CHECK(avifParseMetaBox(data, avifStreamCurrent(&s), header.size)); 532 | } 533 | 534 | CHECK(avifStreamSkip(&s, header.size)); 535 | } 536 | return AVIF_TRUE; 537 | } 538 | 539 | // --------------------------------------------------------------------------- 540 | 541 | avifResult avifImageRead(avifImage * image, avifRawData * input) 542 | { 543 | avifCodec * codec = NULL; 544 | 545 | // ----------------------------------------------------------------------- 546 | // Parse BMFF boxes 547 | 548 | avifData data; 549 | memset(&data, 0, sizeof(data)); 550 | if (!avifParse(&data, input->data, input->size)) { 551 | return AVIF_RESULT_BMFF_PARSE_FAILED; 552 | } 553 | 554 | avifBool avifCompatible = (memcmp(data.ftyp.majorBrand, "avif", 4) == 0) ? AVIF_TRUE : AVIF_FALSE; 555 | if (!avifCompatible) { 556 | for (int compatibleBrandIndex = 0; compatibleBrandIndex < data.ftyp.compatibleBrandsCount; ++compatibleBrandIndex) { 557 | uint8_t * compatibleBrand = &data.ftyp.compatibleBrands[4 * compatibleBrandIndex]; 558 | if (!memcmp(compatibleBrand, "avif", 4)) { 559 | avifCompatible = AVIF_TRUE; 560 | break; 561 | } 562 | } 563 | } 564 | if (!avifCompatible) { 565 | return AVIF_RESULT_INVALID_FTYP; 566 | } 567 | 568 | // ----------------------------------------------------------------------- 569 | 570 | avifRawData colorOBU = AVIF_RAW_DATA_EMPTY; 571 | avifRawData alphaOBU = AVIF_RAW_DATA_EMPTY; 572 | avifItem * colorOBUItem = NULL; 573 | avifItem * alphaOBUItem = NULL; 574 | 575 | // Find the colorOBU item 576 | for (int itemIndex = 0; itemIndex < MAX_ITEMS; ++itemIndex) { 577 | avifItem * item = &data.items[itemIndex]; 578 | if (!item->id || !item->size) { 579 | break; 580 | } 581 | if (item->offset > input->size) { 582 | break; 583 | } 584 | if ((item->offset + item->size) > input->size) { 585 | break; 586 | } 587 | if (memcmp(item->type, "av01", 4)) { 588 | // probably exif or some other data 589 | continue; 590 | } 591 | if (item->thumbnailForID != 0) { 592 | // It's a thumbnail, skip it 593 | continue; 594 | } 595 | 596 | colorOBUItem = item; 597 | colorOBU.data = input->data + item->offset; 598 | colorOBU.size = item->size; 599 | break; 600 | } 601 | 602 | // Find the alphaOBU item, if any 603 | if (colorOBUItem) { 604 | for (int itemIndex = 0; itemIndex < MAX_ITEMS; ++itemIndex) { 605 | avifItem * item = &data.items[itemIndex]; 606 | if (!item->id || !item->size) { 607 | break; 608 | } 609 | if (item->offset > input->size) { 610 | break; 611 | } 612 | if ((item->offset + item->size) > input->size) { 613 | break; 614 | } 615 | if (memcmp(item->type, "av01", 4)) { 616 | // probably exif or some other data 617 | continue; 618 | } 619 | if (item->thumbnailForID != 0) { 620 | // It's a thumbnail, skip it 621 | continue; 622 | } 623 | 624 | if (isAlphaURN(item->auxC.auxType) && (item->auxForID == colorOBUItem->id)) { 625 | alphaOBUItem = item; 626 | alphaOBU.data = input->data + item->offset; 627 | alphaOBU.size = item->size; 628 | break; 629 | } 630 | } 631 | } 632 | 633 | if (colorOBU.size == 0) { 634 | return AVIF_RESULT_NO_AV1_ITEMS_FOUND; 635 | } 636 | avifBool hasAlpha = (alphaOBU.size > 0) ? AVIF_TRUE : AVIF_FALSE; 637 | 638 | codec = avifCodecCreate(); 639 | if (!avifCodecDecode(codec, AVIF_CODEC_PLANES_COLOR, &colorOBU)) { 640 | avifCodecDestroy(codec); 641 | return AVIF_RESULT_DECODE_COLOR_FAILED; 642 | } 643 | avifCodecImageSize colorPlanesSize = avifCodecGetImageSize(codec, AVIF_CODEC_PLANES_COLOR); 644 | 645 | avifCodecImageSize alphaPlanesSize; 646 | memset(&alphaPlanesSize, 0, sizeof(alphaPlanesSize)); 647 | if (hasAlpha) { 648 | if (!avifCodecDecode(codec, AVIF_CODEC_PLANES_ALPHA, &alphaOBU)) { 649 | avifCodecDestroy(codec); 650 | return AVIF_RESULT_DECODE_ALPHA_FAILED; 651 | } 652 | alphaPlanesSize = avifCodecGetImageSize(codec, AVIF_CODEC_PLANES_ALPHA); 653 | 654 | if ((colorPlanesSize.width != alphaPlanesSize.width) || (colorPlanesSize.height != alphaPlanesSize.height)) { 655 | avifCodecDestroy(codec); 656 | return AVIF_RESULT_COLOR_ALPHA_SIZE_MISMATCH; 657 | } 658 | } 659 | 660 | if ((colorOBUItem && colorOBUItem->ispePresent && ((colorOBUItem->ispe.width != colorPlanesSize.width) || (colorOBUItem->ispe.height != colorPlanesSize.height))) || 661 | (alphaOBUItem && alphaOBUItem->ispePresent && ((alphaOBUItem->ispe.width != alphaPlanesSize.width) || (alphaOBUItem->ispe.height != alphaPlanesSize.height)))) 662 | { 663 | avifCodecDestroy(codec); 664 | return AVIF_RESULT_ISPE_SIZE_MISMATCH; 665 | } 666 | 667 | if (colorOBUItem->colrPresent) { 668 | if (colorOBUItem->colr.format == AVIF_PROFILE_FORMAT_ICC) { 669 | avifImageSetProfileICC(image, colorOBUItem->colr.icc, colorOBUItem->colr.iccSize); 670 | } else if (colorOBUItem->colr.format == AVIF_PROFILE_FORMAT_NCLX) { 671 | avifImageSetProfileNCLX(image, &colorOBUItem->colr.nclx); 672 | } 673 | } 674 | 675 | avifImageFreePlanes(image, AVIF_PLANES_ALL); 676 | 677 | avifResult imageResult = avifCodecGetDecodedImage(codec, image); 678 | if (imageResult != AVIF_RESULT_OK) { 679 | avifCodecDestroy(codec); 680 | return imageResult; 681 | } 682 | 683 | #if defined(AVIF_FIX_STUDIO_ALPHA) 684 | if (hasAlpha && avifCodecAlphaLimitedRange(codec)) { 685 | // Naughty! Alpha planes are supposed to be full range. Correct that here. 686 | if (avifImageUsesU16(image)) { 687 | for (int j = 0; j < image->height; ++j) { 688 | for (int i = 0; i < image->height; ++i) { 689 | uint16_t * alpha = (uint16_t *)&image->alphaPlane[(i * 2) + (j * image->alphaRowBytes)]; 690 | *alpha = (uint16_t)avifLimitedToFullY(image->depth, *alpha); 691 | } 692 | } 693 | } else { 694 | for (int j = 0; j < image->height; ++j) { 695 | for (int i = 0; i < image->height; ++i) { 696 | uint8_t * alpha = &image->alphaPlane[i + (j * image->alphaRowBytes)]; 697 | *alpha = (uint8_t)avifLimitedToFullY(image->depth, *alpha); 698 | } 699 | } 700 | } 701 | } 702 | #endif 703 | 704 | if (codec) { 705 | avifCodecDestroy(codec); 706 | } 707 | 708 | image->ioStats.colorOBUSize = colorOBU.size; 709 | image->ioStats.alphaOBUSize = alphaOBU.size; 710 | return AVIF_RESULT_OK; 711 | } 712 | -------------------------------------------------------------------------------- /src/reformat.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/internal.h" 5 | 6 | #include 7 | 8 | typedef struct avifReformatState 9 | { 10 | // YUV coefficients 11 | float kr; 12 | float kg; 13 | float kb; 14 | 15 | avifPixelFormatInfo formatInfo; 16 | avifBool usesU16; 17 | } avifReformatState; 18 | 19 | struct YUVBlock 20 | { 21 | float y; 22 | float u; 23 | float v; 24 | }; 25 | 26 | static avifBool avifPrepareReformatState(avifImage * image, avifReformatState * state) 27 | { 28 | if (image->yuvFormat == AVIF_PIXEL_FORMAT_NONE) { 29 | return AVIF_FALSE; 30 | } 31 | avifGetPixelFormatInfo(image->yuvFormat, &state->formatInfo); 32 | avifCalcYUVCoefficients(image, &state->kr, &state->kg, &state->kb); 33 | state->usesU16 = avifImageUsesU16(image); 34 | return AVIF_TRUE; 35 | } 36 | 37 | static int yuvToUNorm(int chan, avifRange range, int depth, float maxChannel, float v) 38 | { 39 | if (chan != AVIF_CHAN_Y) { 40 | v += 0.5f; 41 | } 42 | v = AVIF_CLAMP(v, 0.0f, 1.0f); 43 | int unorm = (int)avifRoundf(v * maxChannel); 44 | if (range == AVIF_RANGE_LIMITED) { 45 | if (chan == AVIF_CHAN_Y) { 46 | unorm = avifFullToLimitedY(depth, unorm); 47 | } else { 48 | unorm = avifFullToLimitedUV(depth, unorm); 49 | } 50 | } 51 | return unorm; 52 | } 53 | 54 | avifResult avifImageRGBToYUV(avifImage * image) 55 | { 56 | if (!image->rgbPlanes[AVIF_CHAN_R] || !image->rgbPlanes[AVIF_CHAN_G] || !image->rgbPlanes[AVIF_CHAN_B]) { 57 | return AVIF_RESULT_REFORMAT_FAILED; 58 | } 59 | 60 | avifReformatState state; 61 | if (!avifPrepareReformatState(image, &state)) { 62 | return AVIF_RESULT_REFORMAT_FAILED; 63 | } 64 | 65 | avifImageAllocatePlanes(image, AVIF_PLANES_YUV); 66 | 67 | const float kr = state.kr; 68 | const float kg = state.kg; 69 | const float kb = state.kb; 70 | 71 | struct YUVBlock yuvBlock[2][2]; 72 | float rgbPixel[3]; 73 | float maxChannel = (float)((1 << image->depth) - 1); 74 | for (int outerJ = 0; outerJ < image->height; outerJ += 2) { 75 | for (int outerI = 0; outerI < image->width; outerI += 2) { 76 | 77 | int blockW = 2, blockH = 2; 78 | if ((outerI + 1) >= image->width) { 79 | blockW = 1; 80 | } 81 | if ((outerJ + 1) >= image->height) { 82 | blockH = 1; 83 | } 84 | 85 | // Convert an entire 2x2 block to YUV, and populate any fully sampled channels as we go 86 | for (int bJ = 0; bJ < blockH; ++bJ) { 87 | for (int bI = 0; bI < blockW; ++bI) { 88 | int i = outerI + bI; 89 | int j = outerJ + bJ; 90 | 91 | // Unpack RGB into normalized float 92 | if (state.usesU16) { 93 | rgbPixel[0] = *((uint16_t *)(&image->rgbPlanes[AVIF_CHAN_R][(i * 2) + (j * image->rgbRowBytes[AVIF_CHAN_R])])) / maxChannel; 94 | rgbPixel[1] = *((uint16_t *)(&image->rgbPlanes[AVIF_CHAN_G][(i * 2) + (j * image->rgbRowBytes[AVIF_CHAN_G])])) / maxChannel; 95 | rgbPixel[2] = *((uint16_t *)(&image->rgbPlanes[AVIF_CHAN_B][(i * 2) + (j * image->rgbRowBytes[AVIF_CHAN_B])])) / maxChannel; 96 | } else { 97 | rgbPixel[0] = image->rgbPlanes[AVIF_CHAN_R][i + (j * image->rgbRowBytes[AVIF_CHAN_R])] / maxChannel; 98 | rgbPixel[1] = image->rgbPlanes[AVIF_CHAN_G][i + (j * image->rgbRowBytes[AVIF_CHAN_G])] / maxChannel; 99 | rgbPixel[2] = image->rgbPlanes[AVIF_CHAN_B][i + (j * image->rgbRowBytes[AVIF_CHAN_B])] / maxChannel; 100 | } 101 | 102 | // RGB -> YUV conversion 103 | float Y = (kr * rgbPixel[0]) + (kg * rgbPixel[1]) + (kb * rgbPixel[2]); 104 | yuvBlock[bI][bJ].y = Y; 105 | yuvBlock[bI][bJ].u = (rgbPixel[2] - Y) / (2 * (1 - kb)); 106 | yuvBlock[bI][bJ].v = (rgbPixel[0] - Y) / (2 * (1 - kr)); 107 | 108 | if (state.usesU16) { 109 | uint16_t * pY = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_Y][(i * 2) + (j * image->yuvRowBytes[AVIF_CHAN_Y])]; 110 | *pY = (uint16_t)yuvToUNorm(AVIF_CHAN_Y, image->yuvRange, image->depth, maxChannel, yuvBlock[bI][bJ].y); 111 | if (!state.formatInfo.chromaShiftX && !state.formatInfo.chromaShiftY) { 112 | // YUV444, full chroma 113 | uint16_t * pU = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_U][(i * 2) + (j * image->yuvRowBytes[AVIF_CHAN_U])]; 114 | *pU = (uint16_t)yuvToUNorm(AVIF_CHAN_U, image->yuvRange, image->depth, maxChannel, yuvBlock[bI][bJ].u); 115 | uint16_t * pV = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_V][(i * 2) + (j * image->yuvRowBytes[AVIF_CHAN_V])]; 116 | *pV = (uint16_t)yuvToUNorm(AVIF_CHAN_V, image->yuvRange, image->depth, maxChannel, yuvBlock[bI][bJ].v); 117 | } 118 | } else { 119 | image->yuvPlanes[AVIF_CHAN_Y][i + (j * image->yuvRowBytes[AVIF_CHAN_Y])] = (uint8_t)yuvToUNorm(AVIF_CHAN_Y, image->yuvRange, image->depth, maxChannel, yuvBlock[bI][bJ].y); 120 | if (!state.formatInfo.chromaShiftX && !state.formatInfo.chromaShiftY) { 121 | // YUV444, full chroma 122 | image->yuvPlanes[AVIF_CHAN_U][i + (j * image->yuvRowBytes[AVIF_CHAN_U])] = (uint8_t)yuvToUNorm(AVIF_CHAN_U, image->yuvRange, image->depth, maxChannel, yuvBlock[bI][bJ].u); 123 | image->yuvPlanes[AVIF_CHAN_V][i + (j * image->yuvRowBytes[AVIF_CHAN_V])] = (uint8_t)yuvToUNorm(AVIF_CHAN_V, image->yuvRange, image->depth, maxChannel, yuvBlock[bI][bJ].v); 124 | } 125 | } 126 | } 127 | } 128 | 129 | // Populate any subsampled channels with averages from the 2x2 block 130 | if (state.formatInfo.chromaShiftX && state.formatInfo.chromaShiftY) { 131 | // YUV420, average 4 samples (2x2) 132 | 133 | float sumU = 0.0f; 134 | float sumV = 0.0f; 135 | for (int bJ = 0; bJ < blockH; ++bJ) { 136 | for (int bI = 0; bI < blockW; ++bI) { 137 | sumU += yuvBlock[bI][bJ].u; 138 | sumV += yuvBlock[bI][bJ].v; 139 | } 140 | } 141 | float totalSamples = (float)(blockW * blockH); 142 | float avgU = sumU / totalSamples; 143 | float avgV = sumV / totalSamples; 144 | 145 | int uvI = outerI >> state.formatInfo.chromaShiftX; 146 | int uvJ = outerJ >> state.formatInfo.chromaShiftY; 147 | if (state.usesU16) { 148 | uint16_t * pU = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_U][(uvI * 2) + (uvJ * image->yuvRowBytes[AVIF_CHAN_U])]; 149 | *pU = (uint16_t)yuvToUNorm(AVIF_CHAN_U, image->yuvRange, image->depth, maxChannel, avgU); 150 | uint16_t * pV = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_V][(uvI * 2) + (uvJ * image->yuvRowBytes[AVIF_CHAN_V])]; 151 | *pV = (uint16_t)yuvToUNorm(AVIF_CHAN_V, image->yuvRange, image->depth, maxChannel, avgV); 152 | } else { 153 | image->yuvPlanes[AVIF_CHAN_U][uvI + (uvJ * image->yuvRowBytes[AVIF_CHAN_U])] = (uint8_t)yuvToUNorm(AVIF_CHAN_U, image->yuvRange, image->depth, maxChannel, avgU); 154 | image->yuvPlanes[AVIF_CHAN_V][uvI + (uvJ * image->yuvRowBytes[AVIF_CHAN_V])] = (uint8_t)yuvToUNorm(AVIF_CHAN_V, image->yuvRange, image->depth, maxChannel, avgV); 155 | } 156 | } else if (state.formatInfo.chromaShiftX && !state.formatInfo.chromaShiftY) { 157 | // YUV422, average 2 samples (1x2), twice 158 | 159 | for (int bJ = 0; bJ < blockH; ++bJ) { 160 | float sumU = 0.0f; 161 | float sumV = 0.0f; 162 | for (int bI = 0; bI < blockW; ++bI) { 163 | sumU += yuvBlock[bI][bJ].u; 164 | sumV += yuvBlock[bI][bJ].v; 165 | } 166 | float totalSamples = (float)blockW; 167 | float avgU = sumU / totalSamples; 168 | float avgV = sumV / totalSamples; 169 | 170 | int uvI = outerI >> state.formatInfo.chromaShiftX; 171 | int uvJ = outerJ + bJ; 172 | if (state.usesU16) { 173 | uint16_t * pU = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_U][(uvI * 2) + (uvJ * image->yuvRowBytes[AVIF_CHAN_U])]; 174 | *pU = (uint16_t)yuvToUNorm(AVIF_CHAN_U, image->yuvRange, image->depth, maxChannel, avgU); 175 | uint16_t * pV = (uint16_t *)&image->yuvPlanes[AVIF_CHAN_V][(uvI * 2) + (uvJ * image->yuvRowBytes[AVIF_CHAN_V])]; 176 | *pV = (uint16_t)yuvToUNorm(AVIF_CHAN_V, image->yuvRange, image->depth, maxChannel, avgV); 177 | } else { 178 | image->yuvPlanes[AVIF_CHAN_U][uvI + (uvJ * image->yuvRowBytes[AVIF_CHAN_U])] = (uint8_t)yuvToUNorm(AVIF_CHAN_U, image->yuvRange, image->depth, maxChannel, avgU); 179 | image->yuvPlanes[AVIF_CHAN_V][uvI + (uvJ * image->yuvRowBytes[AVIF_CHAN_V])] = (uint8_t)yuvToUNorm(AVIF_CHAN_V, image->yuvRange, image->depth, maxChannel, avgV); 180 | } 181 | } 182 | } 183 | } 184 | } 185 | 186 | return AVIF_RESULT_OK; 187 | } 188 | 189 | avifResult avifImageYUVToRGB(avifImage * image) 190 | { 191 | if (!image->yuvPlanes[AVIF_CHAN_Y] || !image->yuvPlanes[AVIF_CHAN_U] || !image->yuvPlanes[AVIF_CHAN_V]) { 192 | return AVIF_RESULT_REFORMAT_FAILED; 193 | } 194 | 195 | avifReformatState state; 196 | if (!avifPrepareReformatState(image, &state)) { 197 | return AVIF_RESULT_REFORMAT_FAILED; 198 | } 199 | 200 | avifImageAllocatePlanes(image, AVIF_PLANES_RGB); 201 | 202 | const float kr = state.kr; 203 | const float kg = state.kg; 204 | const float kb = state.kb; 205 | 206 | int yuvUNorm[3]; 207 | float yuvPixel[3]; 208 | float rgbPixel[3]; 209 | float maxChannel = (float)((1 << image->depth) - 1); 210 | for (int j = 0; j < image->height; ++j) { 211 | for (int i = 0; i < image->width; ++i) { 212 | // Unpack YUV into unorm 213 | int uvI = i >> state.formatInfo.chromaShiftX; 214 | int uvJ = j >> state.formatInfo.chromaShiftY; 215 | if (state.usesU16) { 216 | yuvUNorm[0] = *((uint16_t *)&image->yuvPlanes[AVIF_CHAN_Y][(i * 2) + (j * image->yuvRowBytes[AVIF_CHAN_Y])]); 217 | yuvUNorm[1] = *((uint16_t *)&image->yuvPlanes[AVIF_CHAN_U][(uvI * 2) + (uvJ * image->yuvRowBytes[AVIF_CHAN_U])]); 218 | yuvUNorm[2] = *((uint16_t *)&image->yuvPlanes[AVIF_CHAN_V][(uvI * 2) + (uvJ * image->yuvRowBytes[AVIF_CHAN_V])]); 219 | } else { 220 | yuvUNorm[0] = image->yuvPlanes[AVIF_CHAN_Y][i + (j * image->yuvRowBytes[AVIF_CHAN_Y])]; 221 | yuvUNorm[1] = image->yuvPlanes[AVIF_CHAN_U][uvI + (uvJ * image->yuvRowBytes[AVIF_CHAN_U])]; 222 | yuvUNorm[2] = image->yuvPlanes[AVIF_CHAN_V][uvI + (uvJ * image->yuvRowBytes[AVIF_CHAN_V])]; 223 | 224 | } 225 | 226 | // adjust for limited/full color range, if need be 227 | if (image->yuvRange == AVIF_RANGE_LIMITED) { 228 | yuvUNorm[0] = avifLimitedToFullY(image->depth, yuvUNorm[0]); 229 | yuvUNorm[1] = avifLimitedToFullUV(image->depth, yuvUNorm[1]); 230 | yuvUNorm[2] = avifLimitedToFullUV(image->depth, yuvUNorm[2]); 231 | } 232 | 233 | // Convert unorm to float 234 | yuvPixel[0] = yuvUNorm[0] / maxChannel; 235 | yuvPixel[1] = yuvUNorm[1] / maxChannel; 236 | yuvPixel[2] = yuvUNorm[2] / maxChannel; 237 | yuvPixel[1] -= 0.5f; 238 | yuvPixel[2] -= 0.5f; 239 | 240 | float Y = yuvPixel[0]; 241 | float Cb = yuvPixel[1]; 242 | float Cr = yuvPixel[2]; 243 | 244 | float R = Y + (2 * (1 - kr)) * Cr; 245 | float B = Y + (2 * (1 - kb)) * Cb; 246 | float G = Y - ( 247 | (2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb))) 248 | / 249 | kg); 250 | 251 | rgbPixel[0] = AVIF_CLAMP(R, 0.0f, 1.0f); 252 | rgbPixel[1] = AVIF_CLAMP(G, 0.0f, 1.0f); 253 | rgbPixel[2] = AVIF_CLAMP(B, 0.0f, 1.0f); 254 | 255 | if (state.usesU16) { 256 | uint16_t * pR = (uint16_t *)&image->rgbPlanes[AVIF_CHAN_R][(i * 2) + (j * image->rgbRowBytes[AVIF_CHAN_R])]; 257 | *pR = (uint16_t)avifRoundf(rgbPixel[0] * maxChannel); 258 | uint16_t * pG = (uint16_t *)&image->rgbPlanes[AVIF_CHAN_G][(i * 2) + (j * image->rgbRowBytes[AVIF_CHAN_G])]; 259 | *pG = (uint16_t)avifRoundf(rgbPixel[1] * maxChannel); 260 | uint16_t * pB = (uint16_t *)&image->rgbPlanes[AVIF_CHAN_B][(i * 2) + (j * image->rgbRowBytes[AVIF_CHAN_B])]; 261 | *pB = (uint16_t)avifRoundf(rgbPixel[2] * maxChannel); 262 | } else { 263 | image->rgbPlanes[AVIF_CHAN_R][i + (j * image->rgbRowBytes[AVIF_CHAN_R])] = (uint8_t)avifRoundf(rgbPixel[0] * maxChannel); 264 | image->rgbPlanes[AVIF_CHAN_G][i + (j * image->rgbRowBytes[AVIF_CHAN_G])] = (uint8_t)avifRoundf(rgbPixel[1] * maxChannel); 265 | image->rgbPlanes[AVIF_CHAN_B][i + (j * image->rgbRowBytes[AVIF_CHAN_B])] = (uint8_t)avifRoundf(rgbPixel[2] * maxChannel); 266 | } 267 | } 268 | } 269 | return AVIF_RESULT_OK; 270 | } 271 | 272 | int avifLimitedToFullY(int depth, int v) 273 | { 274 | switch (depth) { 275 | case 8: 276 | v = ((v - 16) * 255) / (235 - 16); 277 | v = AVIF_CLAMP(v, 0, 255); 278 | return v; 279 | case 10: 280 | v = ((v - 64) * 1023) / (940 - 64); 281 | v = AVIF_CLAMP(v, 0, 1023); 282 | return v; 283 | case 12: 284 | v = ((v - 256) * 4095) / (3760 - 256); 285 | v = AVIF_CLAMP(v, 0, 4095); 286 | return v; 287 | } 288 | return v; 289 | } 290 | 291 | int avifLimitedToFullUV(int depth, int v) 292 | { 293 | switch (depth) { 294 | case 8: 295 | v = ((v - 16) * 255) / (240 - 16); 296 | v = AVIF_CLAMP(v, 0, 255); 297 | return v; 298 | case 10: 299 | v = ((v - 64) * 1023) / (960 - 64); 300 | v = AVIF_CLAMP(v, 0, 1023); 301 | return v; 302 | case 12: 303 | v = ((v - 256) * 4095) / (3840 - 256); 304 | v = AVIF_CLAMP(v, 0, 4095); 305 | return v; 306 | } 307 | return v; 308 | } 309 | 310 | int avifFullToLimitedY(int depth, int v) 311 | { 312 | switch (depth) { 313 | case 8: 314 | v = ((v * (235 - 16)) / 255) + 16; 315 | v = AVIF_CLAMP(v, 16, 235); 316 | return v; 317 | case 10: 318 | v = ((v * (940 - 64)) / 1023) + 64; 319 | v = AVIF_CLAMP(v, 64, 940); 320 | return v; 321 | case 12: 322 | v = ((v * (3760 - 256)) / 4095) + 256; 323 | v = AVIF_CLAMP(v, 256, 3760); 324 | return v; 325 | } 326 | return v; 327 | } 328 | 329 | int avifFullToLimitedUV(int depth, int v) 330 | { 331 | switch (depth) { 332 | case 8: 333 | v = ((v * (240 - 16)) / 255) + 16; 334 | v = AVIF_CLAMP(v, 16, 240); 335 | return v; 336 | case 10: 337 | v = ((v * (960 - 64)) / 1023) + 64; 338 | v = AVIF_CLAMP(v, 64, 960); 339 | return v; 340 | case 12: 341 | v = ((v * (3840 - 256)) / 4095) + 256; 342 | v = AVIF_CLAMP(v, 256, 3840); 343 | return v; 344 | } 345 | return v; 346 | } 347 | -------------------------------------------------------------------------------- /src/stream.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/internal.h" 5 | 6 | #include 7 | 8 | uint8_t * avifStreamCurrent(avifStream * stream) 9 | { 10 | return stream->raw->data + stream->offset; 11 | } 12 | 13 | void avifStreamStart(avifStream * stream, avifRawData * raw) 14 | { 15 | stream->raw = raw; 16 | stream->offset = 0; 17 | } 18 | 19 | // --------------------------------------------------------------------------- 20 | // Read 21 | 22 | avifBool avifStreamHasBytesLeft(avifStream * stream, size_t byteCount) 23 | { 24 | return (stream->offset + byteCount) <= stream->raw->size; 25 | } 26 | 27 | size_t avifStreamRemainingBytes(avifStream * stream) 28 | { 29 | return stream->raw->size - stream->offset; 30 | } 31 | 32 | size_t avifStreamOffset(avifStream * stream) 33 | { 34 | return stream->offset; 35 | } 36 | 37 | void avifStreamSetOffset(avifStream * stream, size_t offset) 38 | { 39 | stream->offset = offset; 40 | if (stream->offset > stream->raw->size) { 41 | stream->offset = stream->raw->size; 42 | } 43 | } 44 | 45 | avifBool avifStreamSkip(avifStream * stream, size_t byteCount) 46 | { 47 | if (!avifStreamHasBytesLeft(stream, byteCount)) { 48 | return AVIF_FALSE; 49 | } 50 | stream->offset += byteCount; 51 | return AVIF_TRUE; 52 | } 53 | 54 | avifBool avifStreamRead(avifStream * stream, uint8_t * data, size_t size) 55 | { 56 | if (!avifStreamHasBytesLeft(stream, size)) { 57 | return AVIF_FALSE; 58 | } 59 | 60 | memcpy(data, stream->raw->data + stream->offset, size); 61 | stream->offset += size; 62 | return AVIF_TRUE; 63 | } 64 | 65 | avifBool avifStreamReadUX8(avifStream * stream, uint64_t * v, uint64_t factor) 66 | { 67 | if (factor == 0) { 68 | // Don't read anything, just set to 0 69 | *v = 0; 70 | } else if (factor == 1) { 71 | uint8_t tmp; 72 | CHECK(avifStreamRead(stream, &tmp, 1)); 73 | *v = tmp; 74 | } else if (factor == 2) { 75 | uint16_t tmp; 76 | CHECK(avifStreamReadU16(stream, &tmp)); 77 | *v = tmp; 78 | } else if (factor == 4) { 79 | uint32_t tmp; 80 | CHECK(avifStreamReadU32(stream, &tmp)); 81 | *v = tmp; 82 | } else if (factor == 8) { 83 | uint64_t tmp; 84 | CHECK(avifStreamReadU64(stream, &tmp)); 85 | *v = tmp; 86 | } else { 87 | // Unsupported factor 88 | return AVIF_FALSE; 89 | } 90 | return AVIF_TRUE; 91 | } 92 | 93 | avifBool avifStreamReadU16(avifStream * stream, uint16_t * v) 94 | { 95 | CHECK(avifStreamRead(stream, (uint8_t *)v, sizeof(uint16_t))); 96 | *v = avifNTOHS(*v); 97 | return AVIF_TRUE; 98 | } 99 | 100 | avifBool avifStreamReadU32(avifStream * stream, uint32_t * v) 101 | { 102 | CHECK(avifStreamRead(stream, (uint8_t *)v, sizeof(uint32_t))); 103 | *v = avifNTOHL(*v); 104 | return AVIF_TRUE; 105 | } 106 | 107 | avifBool avifStreamReadU64(avifStream * stream, uint64_t * v) 108 | { 109 | CHECK(avifStreamRead(stream, (uint8_t *)v, sizeof(uint64_t))); 110 | *v = avifNTOH64(*v); 111 | return AVIF_TRUE; 112 | } 113 | 114 | avifBool avifStreamReadString(avifStream * stream, char * output, size_t outputSize) 115 | { 116 | // Check for the presence of a null terminator in the stream. 117 | size_t remainingBytes = avifStreamRemainingBytes(stream); 118 | uint8_t * p = avifStreamCurrent(stream); 119 | avifBool foundNullTerminator = AVIF_FALSE; 120 | for (size_t i = 0; i < remainingBytes; ++i) { 121 | if (p[i] == 0) { 122 | foundNullTerminator = AVIF_TRUE; 123 | break; 124 | } 125 | } 126 | if (!foundNullTerminator) { 127 | return AVIF_FALSE; 128 | } 129 | 130 | char * streamString = (char *)p; 131 | size_t stringLen = strlen(streamString); 132 | stream->offset += stringLen + 1; // update the stream to have read the "whole string" in 133 | 134 | // clamp to our output buffer 135 | if (stringLen >= outputSize) { 136 | stringLen = outputSize - 1; 137 | } 138 | memcpy(output, streamString, stringLen); 139 | output[stringLen] = 0; 140 | return AVIF_TRUE; 141 | } 142 | 143 | avifBool avifStreamReadBoxHeader(avifStream * stream, avifBoxHeader * header) 144 | { 145 | size_t startOffset = stream->offset; 146 | 147 | uint32_t smallSize; 148 | CHECK(avifStreamReadU32(stream, &smallSize)); 149 | CHECK(avifStreamRead(stream, header->type, 4)); 150 | 151 | uint64_t size = smallSize; 152 | if (size == 1) { 153 | CHECK(avifStreamReadU64(stream, &size)); 154 | } 155 | 156 | if (!memcmp(header->type, "uuid", 4)) { 157 | CHECK(avifStreamSkip(stream, 16)); 158 | } 159 | 160 | header->size = (size_t)(size - (stream->offset - startOffset)); 161 | return AVIF_TRUE; 162 | } 163 | 164 | avifBool avifStreamReadVersionAndFlags(avifStream * stream, uint8_t * version, uint8_t * flags) 165 | { 166 | uint8_t versionAndFlags[4]; 167 | CHECK(avifStreamRead(stream, versionAndFlags, 4)); 168 | if (version) { 169 | *version = versionAndFlags[0]; 170 | } 171 | if (flags) { 172 | memcpy(flags, &versionAndFlags[1], 3); 173 | } 174 | return AVIF_TRUE; 175 | } 176 | 177 | avifBool avifStreamReadAndEnforceVersion(avifStream * stream, uint8_t enforcedVersion) 178 | { 179 | uint8_t version; 180 | CHECK(avifStreamReadVersionAndFlags(stream, &version, NULL)); 181 | return (version == enforcedVersion) ? AVIF_TRUE : AVIF_FALSE; 182 | } 183 | 184 | // --------------------------------------------------------------------------- 185 | // Write 186 | 187 | #define AVIF_STREAM_BUFFER_INCREMENT (1024 * 1024) 188 | static void makeRoom(avifStream * stream, size_t size) 189 | { 190 | size_t neededSize = stream->offset + size; 191 | size_t newSize = stream->raw->size; 192 | while (newSize < neededSize) { 193 | newSize += AVIF_STREAM_BUFFER_INCREMENT; 194 | } 195 | if (stream->raw->size != newSize) { 196 | avifRawDataRealloc(stream->raw, newSize); 197 | } 198 | } 199 | 200 | void avifStreamFinishWrite(avifStream * stream) 201 | { 202 | if (stream->raw->size != stream->offset) { 203 | if (stream->offset) { 204 | stream->raw->size = stream->offset; 205 | } else { 206 | avifRawDataFree(stream->raw); 207 | } 208 | } 209 | } 210 | 211 | void avifStreamWrite(avifStream * stream, const uint8_t * data, size_t size) 212 | { 213 | if (!size) { 214 | return; 215 | } 216 | 217 | makeRoom(stream, size); 218 | memcpy(stream->raw->data + stream->offset, data, size); 219 | stream->offset += size; 220 | } 221 | 222 | void avifStreamWriteChars(avifStream * stream, const char * chars, size_t size) 223 | { 224 | avifStreamWrite(stream, (uint8_t *)chars, size); 225 | } 226 | 227 | avifBoxMarker avifStreamWriteBox(avifStream * stream, const char * type, int version, size_t contentSize) 228 | { 229 | avifBoxMarker marker = stream->offset; 230 | size_t headerSize = sizeof(uint32_t) + 4 /* size of type */; 231 | if (version != -1) { 232 | headerSize += 4; 233 | } 234 | 235 | makeRoom(stream, headerSize); 236 | memset(stream->raw->data + stream->offset, 0, headerSize); 237 | if (version != -1) { 238 | stream->raw->data[stream->offset + 8] = version; 239 | } 240 | uint32_t noSize = avifNTOHL((uint32_t)(headerSize + contentSize)); 241 | memcpy(stream->raw->data + stream->offset, &noSize, sizeof(uint32_t)); 242 | memcpy(stream->raw->data + stream->offset + 4, type, 4); 243 | stream->offset += headerSize; 244 | 245 | return marker; 246 | } 247 | 248 | void avifStreamFinishBox(avifStream * stream, avifBoxMarker marker) 249 | { 250 | uint32_t noSize = avifNTOHL((uint32_t)(stream->offset - marker)); 251 | memcpy(stream->raw->data + marker, &noSize, sizeof(uint32_t)); 252 | } 253 | 254 | void avifStreamWriteU8(avifStream * stream, uint8_t v) 255 | { 256 | size_t size = sizeof(uint8_t); 257 | makeRoom(stream, size); 258 | memcpy(stream->raw->data + stream->offset, &v, size); 259 | stream->offset += size; 260 | } 261 | 262 | void avifStreamWriteU16(avifStream * stream, uint16_t v) 263 | { 264 | size_t size = sizeof(uint16_t); 265 | v = avifHTONS(v); 266 | makeRoom(stream, size); 267 | memcpy(stream->raw->data + stream->offset, &v, size); 268 | stream->offset += size; 269 | } 270 | 271 | void avifStreamWriteU32(avifStream * stream, uint32_t v) 272 | { 273 | size_t size = sizeof(uint32_t); 274 | v = avifHTONL(v); 275 | makeRoom(stream, size); 276 | memcpy(stream->raw->data + stream->offset, &v, size); 277 | stream->offset += size; 278 | } 279 | 280 | void avifStreamWriteZeros(avifStream * stream, size_t byteCount) 281 | { 282 | makeRoom(stream, byteCount); 283 | uint8_t * p = stream->raw->data + stream->offset; 284 | uint8_t * end = p + byteCount; 285 | while (p != end) { 286 | *p = 0; 287 | ++p; 288 | } 289 | stream->offset += byteCount; 290 | } 291 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/internal.h" 5 | 6 | #include 7 | #include 8 | 9 | float avifRoundf(float v) 10 | { 11 | return floorf(v + 0.5f); 12 | } 13 | 14 | // Thanks, Rob Pike! https://commandcenter.blogspot.nl/2012/04/byte-order-fallacy.html 15 | 16 | uint16_t avifHTONS(uint16_t s) 17 | { 18 | uint8_t data[2]; 19 | data[0] = (s >> 8) & 0xff; 20 | data[1] = (s >> 0) & 0xff; 21 | uint16_t result; 22 | memcpy(&result, data, sizeof(uint16_t)); 23 | return result; 24 | } 25 | 26 | uint16_t avifNTOHS(uint16_t s) 27 | { 28 | uint8_t data[2]; 29 | memcpy(&data, &s, sizeof(data)); 30 | 31 | return (uint16_t)((data[1] << 0) 32 | | (data[0] << 8)); 33 | } 34 | 35 | uint32_t avifHTONL(uint32_t l) 36 | { 37 | uint8_t data[4]; 38 | data[0] = (l >> 24) & 0xff; 39 | data[1] = (l >> 16) & 0xff; 40 | data[2] = (l >> 8) & 0xff; 41 | data[3] = (l >> 0) & 0xff; 42 | uint32_t result; 43 | memcpy(&result, data, sizeof(uint32_t)); 44 | return result; 45 | } 46 | 47 | uint32_t avifNTOHL(uint32_t l) 48 | { 49 | uint8_t data[4]; 50 | memcpy(&data, &l, sizeof(data)); 51 | 52 | return ((uint32_t)data[3] << 0) 53 | | ((uint32_t)data[2] << 8) 54 | | ((uint32_t)data[1] << 16) 55 | | ((uint32_t)data[0] << 24); 56 | } 57 | 58 | uint64_t avifHTON64(uint64_t l) 59 | { 60 | uint8_t data[8]; 61 | data[0] = (l >> 56) & 0xff; 62 | data[1] = (l >> 48) & 0xff; 63 | data[2] = (l >> 40) & 0xff; 64 | data[3] = (l >> 32) & 0xff; 65 | data[4] = (l >> 24) & 0xff; 66 | data[5] = (l >> 16) & 0xff; 67 | data[6] = (l >> 8) & 0xff; 68 | data[7] = (l >> 0) & 0xff; 69 | uint64_t result; 70 | memcpy(&result, data, sizeof(uint64_t)); 71 | return result; 72 | } 73 | 74 | uint64_t avifNTOH64(uint64_t l) 75 | { 76 | uint8_t data[8]; 77 | memcpy(&data, &l, sizeof(data)); 78 | 79 | return ((uint64_t)data[7] << 0) 80 | | ((uint64_t)data[6] << 8) 81 | | ((uint64_t)data[5] << 16) 82 | | ((uint64_t)data[4] << 24) 83 | | ((uint64_t)data[3] << 32) 84 | | ((uint64_t)data[2] << 40) 85 | | ((uint64_t)data[1] << 48) 86 | | ((uint64_t)data[0] << 56); 87 | } 88 | -------------------------------------------------------------------------------- /src/write.c: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Joe Drago. All rights reserved. 2 | // SPDX-License-Identifier: BSD-2-Clause 3 | 4 | #include "avif/internal.h" 5 | 6 | #include 7 | 8 | #define MAX_ASSOCIATIONS 16 9 | struct ipmaArray 10 | { 11 | uint8_t associations[MAX_ASSOCIATIONS]; 12 | uint8_t count; 13 | }; 14 | static void ipmaPush(struct ipmaArray * ipma, uint8_t assoc) 15 | { 16 | ipma->associations[ipma->count] = assoc; 17 | ++ipma->count; 18 | } 19 | 20 | static const char alphaURN[] = URN_ALPHA0; 21 | static const size_t alphaURNSize = sizeof(alphaURN); 22 | 23 | static avifBool avifImageIsOpaque(avifImage * image); 24 | static void writeConfigBox(avifStream * s, avifCodecConfigurationBox * cfg); 25 | 26 | avifResult avifImageWrite(avifImage * image, avifRawData * output, int numThreads, int quality) 27 | { 28 | if ((image->depth != 8) && (image->depth != 10) && (image->depth != 12)) { 29 | return AVIF_RESULT_UNSUPPORTED_DEPTH; 30 | } 31 | 32 | avifResult result = AVIF_RESULT_UNKNOWN_ERROR; 33 | avifRawData colorOBU = AVIF_RAW_DATA_EMPTY; 34 | avifRawData alphaOBU = AVIF_RAW_DATA_EMPTY; 35 | avifCodec * codec = avifCodecCreate(); 36 | 37 | avifStream s; 38 | avifStreamStart(&s, output); 39 | 40 | // ----------------------------------------------------------------------- 41 | // Reformat pixels, if need be 42 | 43 | if (!image->width || !image->height || !image->depth) { 44 | result = AVIF_RESULT_NO_CONTENT; 45 | goto writeCleanup; 46 | } 47 | 48 | if ((image->yuvFormat == AVIF_PIXEL_FORMAT_NONE) || !image->yuvPlanes[AVIF_CHAN_Y] || !image->yuvPlanes[AVIF_CHAN_U] || !image->yuvPlanes[AVIF_CHAN_V]) { 49 | if (!image->rgbPlanes[AVIF_CHAN_R] || !image->rgbPlanes[AVIF_CHAN_G] || !image->rgbPlanes[AVIF_CHAN_B]) { 50 | result = AVIF_RESULT_NO_CONTENT; 51 | goto writeCleanup; 52 | } 53 | 54 | avifImageFreePlanes(image, AVIF_PLANES_YUV); 55 | if (image->yuvFormat == AVIF_PIXEL_FORMAT_NONE) { 56 | result = AVIF_RESULT_NO_YUV_FORMAT_SELECTED; 57 | goto writeCleanup; 58 | } 59 | avifImageRGBToYUV(image); 60 | } 61 | 62 | // ----------------------------------------------------------------------- 63 | // Encode AV1 OBUs 64 | 65 | avifRawData * alphaOBUPtr = &alphaOBU; 66 | if (avifImageIsOpaque(image)) { 67 | alphaOBUPtr = NULL; 68 | } 69 | 70 | avifResult encodeResult = avifCodecEncodeImage(codec, image, numThreads, quality, &colorOBU, alphaOBUPtr); 71 | if (encodeResult != AVIF_RESULT_OK) { 72 | result = encodeResult; 73 | goto writeCleanup; 74 | } 75 | avifBool hasAlpha = (alphaOBU.size > 0) ? AVIF_TRUE : AVIF_FALSE; 76 | 77 | // ----------------------------------------------------------------------- 78 | // Write ftyp 79 | 80 | avifBoxMarker ftyp = avifStreamWriteBox(&s, "ftyp", -1, 0); 81 | avifStreamWriteChars(&s, "avif", 4); // unsigned int(32) major_brand; 82 | avifStreamWriteU32(&s, 0); // unsigned int(32) minor_version; 83 | avifStreamWriteChars(&s, "avif", 4); // unsigned int(32) compatible_brands[]; 84 | avifStreamWriteChars(&s, "mif1", 4); // ... compatible_brands[] 85 | avifStreamWriteChars(&s, "miaf", 4); // ... compatible_brands[] 86 | if ((image->depth == 8) || (image->depth == 10)) { 87 | if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) { 88 | avifStreamWriteChars(&s, "MA1B", 4); // ... compatible_brands[] 89 | } else if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) { 90 | avifStreamWriteChars(&s, "MA1A", 4); // ... compatible_brands[] 91 | } 92 | } 93 | avifStreamFinishBox(&s, ftyp); 94 | 95 | // ----------------------------------------------------------------------- 96 | // Start meta 97 | 98 | avifBoxMarker meta = avifStreamWriteBox(&s, "meta", 0, 0); 99 | 100 | // ----------------------------------------------------------------------- 101 | // Write hdlr 102 | 103 | avifBoxMarker hdlr = avifStreamWriteBox(&s, "hdlr", 0, 0); 104 | avifStreamWriteU32(&s, 0); // unsigned int(32) pre_defined = 0; 105 | avifStreamWriteChars(&s, "pict", 4); // unsigned int(32) handler_type; 106 | avifStreamWriteZeros(&s, 12); // const unsigned int(32)[3] reserved = 0; 107 | avifStreamWriteChars(&s, "libavif", 8); // string name; (writing null terminator) 108 | avifStreamFinishBox(&s, hdlr); 109 | 110 | // ----------------------------------------------------------------------- 111 | // Write pitm 112 | 113 | avifStreamWriteBox(&s, "pitm", 0, sizeof(uint16_t)); 114 | avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID; 115 | 116 | // ----------------------------------------------------------------------- 117 | // Write iloc 118 | 119 | // Remember where we want to store the offsets to the mdat OBU offsets to adjust them later. 120 | size_t colorOBUOffsetOffset = 0; 121 | size_t alphaOBUOffsetOffset = 0; 122 | 123 | avifBoxMarker iloc = avifStreamWriteBox(&s, "iloc", 0, 0); 124 | 125 | // iloc header 126 | uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0); // unsigned int(4) offset_size; unsigned int(4) length_size; 127 | avifStreamWrite(&s, &offsetSizeAndLengthSize, 1); // 128 | avifStreamWriteZeros(&s, 1); // unsigned int(4) base_offset_size; unsigned int(4) reserved; 129 | avifStreamWriteU16(&s, hasAlpha ? 2 : 1); // unsigned int(16) item_count; 130 | 131 | // Item ID #1 (Color OBU) 132 | avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID; 133 | avifStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; 134 | avifStreamWriteU16(&s, 1); // unsigned int(16) extent_count; 135 | colorOBUOffsetOffset = avifStreamOffset(&s); // 136 | avifStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; 137 | avifStreamWriteU32(&s, (uint32_t)colorOBU.size); // unsigned int(length_size*8) extent_length; 138 | 139 | if (hasAlpha) { 140 | avifStreamWriteU16(&s, 2); // unsigned int(16) item_ID; 141 | avifStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index; 142 | avifStreamWriteU16(&s, 1); // unsigned int(16) extent_count; 143 | alphaOBUOffsetOffset = avifStreamOffset(&s); // 144 | avifStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset; 145 | avifStreamWriteU32(&s, (uint32_t)alphaOBU.size); // unsigned int(length_size*8) extent_length; 146 | } 147 | 148 | avifStreamFinishBox(&s, iloc); 149 | 150 | // ----------------------------------------------------------------------- 151 | // Write iinf 152 | 153 | avifBoxMarker iinf = avifStreamWriteBox(&s, "iinf", 0, 0); 154 | avifStreamWriteU16(&s, hasAlpha ? 2 : 1); // unsigned int(16) entry_count; 155 | 156 | avifBoxMarker infe0 = avifStreamWriteBox(&s, "infe", 2, 0); 157 | avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID; 158 | avifStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index; 159 | avifStreamWriteChars(&s, "av01", 4); // unsigned int(32) item_type; 160 | avifStreamWriteChars(&s, "Color", 6); // string item_name; (writing null terminator) 161 | avifStreamFinishBox(&s, infe0); 162 | if (hasAlpha) { 163 | avifBoxMarker infe1 = avifStreamWriteBox(&s, "infe", 2, 0); 164 | avifStreamWriteU16(&s, 2); // unsigned int(16) item_ID; 165 | avifStreamWriteU16(&s, 0); // unsigned int(16) item_protection_index; 166 | avifStreamWriteChars(&s, "av01", 4); // unsigned int(32) item_type; 167 | avifStreamWriteChars(&s, "Alpha", 6); // string item_name; (writing null terminator) 168 | avifStreamFinishBox(&s, infe1); 169 | } 170 | avifStreamFinishBox(&s, iinf); 171 | 172 | // ----------------------------------------------------------------------- 173 | // Write iref (auxl) for alpha, if any 174 | 175 | if (hasAlpha) { 176 | avifBoxMarker iref = avifStreamWriteBox(&s, "iref", 0, 0); 177 | avifBoxMarker auxl = avifStreamWriteBox(&s, "auxl", -1, 0); 178 | avifStreamWriteU16(&s, 2); // unsigned int(16) from_item_ID; 179 | avifStreamWriteU16(&s, 1); // unsigned int(16) reference_count; 180 | avifStreamWriteU16(&s, 1); // unsigned int(16) to_item_ID; 181 | avifStreamFinishBox(&s, auxl); 182 | avifStreamFinishBox(&s, iref); 183 | } 184 | 185 | // ----------------------------------------------------------------------- 186 | // Write iprp->ipco->ispe 187 | 188 | avifBoxMarker iprp = avifStreamWriteBox(&s, "iprp", -1, 0); 189 | { 190 | uint8_t ipcoIndex = 0; 191 | struct ipmaArray ipmaColor; 192 | memset(&ipmaColor, 0, sizeof(ipmaColor)); 193 | struct ipmaArray ipmaAlpha; 194 | memset(&ipmaAlpha, 0, sizeof(ipmaAlpha)); 195 | 196 | avifBoxMarker ipco = avifStreamWriteBox(&s, "ipco", -1, 0); 197 | { 198 | avifBoxMarker ispe = avifStreamWriteBox(&s, "ispe", 0, 0); 199 | avifStreamWriteU32(&s, image->width); // unsigned int(32) image_width; 200 | avifStreamWriteU32(&s, image->height); // unsigned int(32) image_height; 201 | avifStreamFinishBox(&s, ispe); 202 | ++ipcoIndex; 203 | ipmaPush(&ipmaColor, ipcoIndex); // ipma is 1-indexed, doing this afterwards is correct 204 | ipmaPush(&ipmaAlpha, ipcoIndex); // Alpha shares the ispe prop 205 | 206 | if (image->profileFormat == AVIF_PROFILE_FORMAT_NCLX) { 207 | avifBoxMarker colr = avifStreamWriteBox(&s, "colr", -1, 0); 208 | avifStreamWriteChars(&s, "nclx", 4); // unsigned int(32) colour_type; 209 | avifStreamWriteU16(&s, image->nclx.colourPrimaries); // unsigned int(16) colour_primaries; 210 | avifStreamWriteU16(&s, image->nclx.transferCharacteristics); // unsigned int(16) transfer_characteristics; 211 | avifStreamWriteU16(&s, image->nclx.matrixCoefficients); // unsigned int(16) matrix_coefficients; 212 | avifStreamWriteU8(&s, image->nclx.fullRangeFlag & 0x80); // unsigned int(1) full_range_flag; unsigned int(7) reserved = 0; 213 | avifStreamFinishBox(&s, colr); 214 | ++ipcoIndex; 215 | ipmaPush(&ipmaColor, ipcoIndex); 216 | } else if ((image->profileFormat == AVIF_PROFILE_FORMAT_ICC) && image->icc.data && (image->icc.data > 0)) { 217 | avifBoxMarker colr = avifStreamWriteBox(&s, "colr", -1, 0); 218 | avifStreamWriteChars(&s, "prof", 4); // unsigned int(32) colour_type; 219 | avifStreamWrite(&s, image->icc.data, image->icc.size); 220 | avifStreamFinishBox(&s, colr); 221 | ++ipcoIndex; 222 | ipmaPush(&ipmaColor, ipcoIndex); 223 | } 224 | 225 | avifBoxMarker pixiC = avifStreamWriteBox(&s, "pixi", 0, 0); 226 | avifStreamWriteU8(&s, 3); // unsigned int (8) num_channels; 227 | avifStreamWriteU8(&s, image->depth); // unsigned int (8) bits_per_channel; 228 | avifStreamWriteU8(&s, image->depth); // unsigned int (8) bits_per_channel; 229 | avifStreamWriteU8(&s, image->depth); // unsigned int (8) bits_per_channel; 230 | avifStreamFinishBox(&s, pixiC); 231 | ++ipcoIndex; 232 | ipmaPush(&ipmaColor, ipcoIndex); 233 | 234 | avifCodecConfigurationBox colorConfig; 235 | avifCodecGetConfigurationBox(codec, AVIF_CODEC_PLANES_COLOR, &colorConfig); 236 | writeConfigBox(&s, &colorConfig); 237 | ++ipcoIndex; 238 | ipmaPush(&ipmaColor, ipcoIndex); 239 | 240 | if (hasAlpha) { 241 | avifBoxMarker pixiA = avifStreamWriteBox(&s, "pixi", 0, 0); 242 | avifStreamWriteU8(&s, 1); // unsigned int (8) num_channels; 243 | avifStreamWriteU8(&s, image->depth); // unsigned int (8) bits_per_channel; 244 | avifStreamFinishBox(&s, pixiA); 245 | ++ipcoIndex; 246 | ipmaPush(&ipmaAlpha, ipcoIndex); 247 | 248 | avifCodecConfigurationBox alphaConfig; 249 | avifCodecGetConfigurationBox(codec, AVIF_CODEC_PLANES_ALPHA, &alphaConfig); 250 | writeConfigBox(&s, &alphaConfig); 251 | ++ipcoIndex; 252 | ipmaPush(&ipmaAlpha, ipcoIndex); 253 | 254 | avifBoxMarker auxC = avifStreamWriteBox(&s, "auxC", 0, 0); 255 | avifStreamWriteChars(&s, alphaURN, alphaURNSize); // string aux_type; 256 | avifStreamFinishBox(&s, auxC); 257 | ++ipcoIndex; 258 | ipmaPush(&ipmaAlpha, ipcoIndex); 259 | } 260 | } 261 | avifStreamFinishBox(&s, ipco); 262 | 263 | avifBoxMarker ipma = avifStreamWriteBox(&s, "ipma", 0, 0); 264 | { 265 | int ipmaCount = hasAlpha ? 2 : 1; 266 | avifStreamWriteU32(&s, ipmaCount); // unsigned int(32) entry_count; 267 | 268 | avifStreamWriteU16(&s, 1); // unsigned int(16) item_ID; 269 | avifStreamWriteU8(&s, ipmaColor.count); // unsigned int(8) association_count; 270 | for (int i = 0; i < ipmaColor.count; ++i) { 271 | avifStreamWriteU8(&s, ipmaColor.associations[i]); // bit(1) essential; unsigned int(7) property_index; 272 | } 273 | 274 | if (hasAlpha) { 275 | avifStreamWriteU16(&s, 2); // unsigned int(16) item_ID; 276 | avifStreamWriteU8(&s, ipmaAlpha.count); // unsigned int(8) association_count; 277 | for (int i = 0; i < ipmaAlpha.count; ++i) { 278 | avifStreamWriteU8(&s, ipmaAlpha.associations[i]); // bit(1) essential; unsigned int(7) property_index; 279 | } 280 | } 281 | } 282 | avifStreamFinishBox(&s, ipma); 283 | } 284 | avifStreamFinishBox(&s, iprp); 285 | 286 | // ----------------------------------------------------------------------- 287 | // Finish meta box 288 | 289 | avifStreamFinishBox(&s, meta); 290 | 291 | // ----------------------------------------------------------------------- 292 | // Write mdat 293 | 294 | avifBoxMarker mdat = avifStreamWriteBox(&s, "mdat", -1, 0); 295 | uint32_t colorOBUOffset = (uint32_t)s.offset; 296 | avifStreamWrite(&s, colorOBU.data, colorOBU.size); 297 | uint32_t alphaOBUOffset = (uint32_t)s.offset; 298 | avifStreamWrite(&s, alphaOBU.data, alphaOBU.size); 299 | avifStreamFinishBox(&s, mdat); 300 | 301 | // ----------------------------------------------------------------------- 302 | // Finish up stream 303 | 304 | // Set offsets needed in meta box based on where we eventually wrote mdat 305 | size_t prevOffset = avifStreamOffset(&s); 306 | if (colorOBUOffsetOffset != 0) { 307 | avifStreamSetOffset(&s, colorOBUOffsetOffset); 308 | avifStreamWriteU32(&s, colorOBUOffset); 309 | } 310 | if (alphaOBUOffsetOffset != 0) { 311 | avifStreamSetOffset(&s, alphaOBUOffsetOffset); 312 | avifStreamWriteU32(&s, alphaOBUOffset); 313 | } 314 | avifStreamSetOffset(&s, prevOffset); 315 | 316 | // Close write stream 317 | avifStreamFinishWrite(&s); 318 | 319 | // ----------------------------------------------------------------------- 320 | // IO stats 321 | 322 | image->ioStats.colorOBUSize = colorOBU.size; 323 | image->ioStats.alphaOBUSize = alphaOBU.size; 324 | 325 | result = AVIF_RESULT_OK; 326 | 327 | // ----------------------------------------------------------------------- 328 | // Cleanup 329 | 330 | writeCleanup: 331 | if (codec) { 332 | avifCodecDestroy(codec); 333 | } 334 | avifRawDataFree(&colorOBU); 335 | avifRawDataFree(&alphaOBU); 336 | return result; 337 | } 338 | 339 | static avifBool avifImageIsOpaque(avifImage * image) 340 | { 341 | if (!image->alphaPlane) { 342 | return AVIF_TRUE; 343 | } 344 | 345 | int maxChannel = (1 << image->depth) - 1; 346 | if (avifImageUsesU16(image)) { 347 | for (int j = 0; j < image->height; ++j) { 348 | for (int i = 0; i < image->width; ++i) { 349 | uint16_t * p = (uint16_t *)&image->alphaPlane[(i * 2) + (j * image->alphaRowBytes)]; 350 | if (*p != maxChannel) { 351 | return AVIF_FALSE; 352 | } 353 | } 354 | } 355 | } else { 356 | for (int j = 0; j < image->height; ++j) { 357 | for (int i = 0; i < image->width; ++i) { 358 | if (image->alphaPlane[i + (j * image->alphaRowBytes)] != maxChannel) { 359 | return AVIF_FALSE; 360 | } 361 | } 362 | } 363 | } 364 | return AVIF_TRUE; 365 | } 366 | 367 | static void writeConfigBox(avifStream * s, avifCodecConfigurationBox * cfg) 368 | { 369 | avifBoxMarker av1C = avifStreamWriteBox(s, "av1C", -1, 0); 370 | 371 | // unsigned int (1) marker = 1; 372 | // unsigned int (7) version = 1; 373 | avifStreamWriteU8(s, 0x80 | 0x1); 374 | 375 | // unsigned int (3) seq_profile; 376 | // unsigned int (5) seq_level_idx_0; 377 | avifStreamWriteU8(s, ((cfg->seqProfile & 0x7) << 5) | (cfg->seqLevelIdx0 & 0x1f)); 378 | 379 | uint8_t bits = 0; 380 | bits |= (cfg->seqTier0 & 0x1) << 7; // unsigned int (1) seq_tier_0; 381 | bits |= (cfg->highBitdepth & 0x1) << 6; // unsigned int (1) high_bitdepth; 382 | bits |= (cfg->twelveBit & 0x1) << 5; // unsigned int (1) twelve_bit; 383 | bits |= (cfg->monochrome & 0x1) << 4; // unsigned int (1) monochrome; 384 | bits |= (cfg->chromaSubsamplingX & 0x1) << 3; // unsigned int (1) chroma_subsampling_x; 385 | bits |= (cfg->chromaSubsamplingY & 0x1) << 2; // unsigned int (1) chroma_subsampling_y; 386 | bits |= (cfg->chromaSamplePosition & 0x3); // unsigned int (2) chroma_sample_position; 387 | avifStreamWriteU8(s, bits); 388 | 389 | // unsigned int (3) reserved = 0; 390 | // unsigned int (1) initial_presentation_delay_present; 391 | // if (initial_presentation_delay_present) { 392 | // unsigned int (4) initial_presentation_delay_minus_one; 393 | // } else { 394 | // unsigned int (4) reserved = 0; 395 | // } 396 | avifStreamWriteU8(s, 0); 397 | 398 | avifStreamFinishBox(s, av1C); 399 | } 400 | --------------------------------------------------------------------------------