text: // Copyright 2019 Joe Drago. All rights reserved.
  // SPDX-License-Identifier: BSD-2-Clause
  
  #include "avif/avif.h"
  
  #include <string.h>
  
  static avifResult reformatRGBAToYUV444(avifImage * srcImage, avifImage * dstImage, int dstDepth)
  {
      // TODO: calculate coefficients
      const float kr = 0.2126f;
      const float kb = 0.0722f;
      const float kg = 1.0f - kr - kb;
  
      float yuvPixel[3];
      float rgbPixel[3];
      float srcMaxChannel = (float)((1 << srcImage->depth) - 1);
      float dstMaxChannel = (float)((1 << dstImage->depth) - 1);
      for (int j = 0; j < srcImage->height; ++j) {
          for (int i = 0; i < srcImage->width; ++i) {
              // Unpack RGB into normalized float
              rgbPixel[0] = srcImage->planes[0][i + (j * srcImage->strides[0])] / srcMaxChannel;
              rgbPixel[1] = srcImage->planes[1][i + (j * srcImage->strides[1])] / srcMaxChannel;
              rgbPixel[2] = srcImage->planes[2][i + (j * srcImage->strides[2])] / srcMaxChannel;
  
              // RGB -> YUV conversion
              float Y = (kr * rgbPixel[0]) + (kg * rgbPixel[1]) + (kb * rgbPixel[2]);
              yuvPixel[0] = Y;
              yuvPixel[1] = (rgbPixel[2] - Y) / (2 * (1 - kb));
              yuvPixel[2] = (rgbPixel[0] - Y) / (2 * (1 - kr));
  
              // Stuff YUV into unorm16 color layer
              yuvPixel[0] = AVIF_CLAMP(yuvPixel[0], 0.0f, 1.0f);
              yuvPixel[1] += 0.5f;
              yuvPixel[1] = AVIF_CLAMP(yuvPixel[1], 0.0f, 1.0f);
              yuvPixel[2] += 0.5f;
              yuvPixel[2] = AVIF_CLAMP(yuvPixel[2], 0.0f, 1.0f);
              for (int plane = 0; plane < 3; ++plane) {
                  dstImage->planes[plane][i + (j * dstImage->strides[plane])] = (uint16_t)avifRoundf(yuvPixel[plane] * dstMaxChannel);
              }
  
              // reformat alpha
              float alpha = (float)srcImage->planes[3][i + (j * srcImage->strides[3])] / srcMaxChannel;
              dstImage->planes[3][i + (j * dstImage->strides[3])] = (uint16_t)avifRoundf(alpha * dstMaxChannel);
          }
      }
  
      return AVIF_RESULT_OK;
  }
  
  static avifResult reformatYUV444ToRGBA(avifImage * srcImage, avifImage * dstImage, int dstDepth)
  {
      // TODO: calculate coefficients
      const float kr = 0.2126f;
      const float kb = 0.0722f;
      const float kg = 1.0f - kr - kb;
  
      float yuvPixel[3];
      float rgbPixel[3];
      float srcMaxChannel = (float)((1 << srcImage->depth) - 1);
      float dstMaxChannel = (float)((1 << dstImage->depth) - 1);
      for (int j = 0; j < srcImage->height; ++j) {
          for (int i = 0; i < srcImage->width; ++i) {
              // Unpack YUV into normalized float
              yuvPixel[0] = srcImage->planes[0][i + (j * srcImage->strides[0])] / srcMaxChannel;
              yuvPixel[1] = srcImage->planes[1][i + (j * srcImage->strides[1])] / srcMaxChannel;
              yuvPixel[2] = srcImage->planes[2][i + (j * srcImage->strides[2])] / srcMaxChannel;
              yuvPixel[1] -= 0.5f;
              yuvPixel[2] -= 0.5f;
  
              float Y  = yuvPixel[0];
              float Cb = yuvPixel[1];
              float Cr = yuvPixel[2];
  
              float R = Y + (2 * (1 - kr)) * Cr;
              float B = Y + (2 * (1 - kb)) * Cb;
              float G = Y - (
                  (2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb)))
                  /
                  kg);
  
              rgbPixel[0] = AVIF_CLAMP(R, 0.0f, 1.0f);
              rgbPixel[1] = AVIF_CLAMP(G, 0.0f, 1.0f);
              rgbPixel[2] = AVIF_CLAMP(B, 0.0f, 1.0f);
              for (int plane = 0; plane < 3; ++plane) {
                  dstImage->planes[plane][i + (j * dstImage->strides[plane])] = (uint16_t)avifRoundf(rgbPixel[plane] * dstMaxChannel);
              }
  
              // reformat alpha
              float alpha = (float)srcImage->planes[3][i + (j * srcImage->strides[3])] / srcMaxChannel;
              dstImage->planes[3][i + (j * dstImage->strides[3])] = (uint16_t)avifRoundf(alpha * dstMaxChannel);
          }
      }
  
      return AVIF_RESULT_OK;
  }
  
  static avifResult reformatRGBAToYUV420(avifImage * srcImage, avifImage * dstImage, int dstDepth)
  {
      // TODO: calculate coefficients
      const float kr = 0.2126f;
      const float kb = 0.0722f;
      const float kg = 1.0f - kr - kb;
  
      float yuvPixel[3];
      float rgbPixel[3];
      float srcMaxChannel = (float)((1 << srcImage->depth) - 1);
      float dstMaxChannel = (float)((1 << dstImage->depth) - 1);
      for (int j = 0; j < srcImage->height; ++j) {
          for (int i = 0; i < srcImage->width; ++i) {
              // Unpack RGB into normalized float
              rgbPixel[0] = srcImage->planes[0][i + (j * srcImage->strides[0])] / srcMaxChannel;
              rgbPixel[1] = srcImage->planes[1][i + (j * srcImage->strides[1])] / srcMaxChannel;
              rgbPixel[2] = srcImage->planes[2][i + (j * srcImage->strides[2])] / srcMaxChannel;
  
              // RGB -> YUV conversion
              float Y = (kr * rgbPixel[0]) + (kg * rgbPixel[1]) + (kb * rgbPixel[2]);
              yuvPixel[0] = Y;
              yuvPixel[1] = (rgbPixel[2] - Y) / (2 * (1 - kb));
              yuvPixel[2] = (rgbPixel[0] - Y) / (2 * (1 - kr));
  
              // Stuff YUV into unorm16 color layer
              yuvPixel[0] = AVIF_CLAMP(yuvPixel[0], 0.0f, 1.0f);
              yuvPixel[1] += 0.5f;
              yuvPixel[1] = AVIF_CLAMP(yuvPixel[1], 0.0f, 1.0f);
              yuvPixel[2] += 0.5f;
              yuvPixel[2] = AVIF_CLAMP(yuvPixel[2], 0.0f, 1.0f);
  
              dstImage->planes[0][i + (j * dstImage->strides[0])] = (uint16_t)avifRoundf(yuvPixel[0] * dstMaxChannel);
  
              int x = i >> 1;
              int y = j >> 1;
              dstImage->planes[1][x + (y * dstImage->strides[1])] = (uint16_t)avifRoundf(yuvPixel[1] * dstMaxChannel);
              dstImage->planes[2][x + (y * dstImage->strides[2])] = (uint16_t)avifRoundf(yuvPixel[2] * dstMaxChannel);
  
              // reformat alpha
              float alpha = (float)srcImage->planes[3][i + (j * srcImage->strides[3])] / srcMaxChannel;
              dstImage->planes[3][i + (j * dstImage->strides[3])] = (uint16_t)avifRoundf(alpha * dstMaxChannel);
          }
      }
  
      return AVIF_RESULT_OK;
  }
  
  static avifResult reformatYUV420ToRGBA(avifImage * srcImage, avifImage * dstImage, int dstDepth)
  {
      // TODO: calculate coefficients
      const float kr = 0.2126f;
      const float kb = 0.0722f;
      const float kg = 1.0f - kr - kb;
  
      float yuvPixel[3];
      float rgbPixel[3];
      float srcMaxChannel = (float)((1 << srcImage->depth) - 1);
      float dstMaxChannel = (float)((1 << dstImage->depth) - 1);
      for (int j = 0; j < srcImage->height; ++j) {
          for (int i = 0; i < srcImage->width; ++i) {
              // Unpack YUV into normalized float
              int x = i >> 1;
              int y = j >> 1;
              yuvPixel[0] = srcImage->planes[0][i + (j * srcImage->strides[0])] / srcMaxChannel;
              yuvPixel[1] = srcImage->planes[1][x + (y * srcImage->strides[1])] / srcMaxChannel;
              yuvPixel[2] = srcImage->planes[2][x + (y * srcImage->strides[2])] / srcMaxChannel;
              yuvPixel[1] -= 0.5f;
              yuvPixel[2] -= 0.5f;
  
              float Y  = yuvPixel[0];
              float Cb = yuvPixel[1];
              float Cr = yuvPixel[2];
  
              float R = Y + (2 * (1 - kr)) * Cr;
              float B = Y + (2 * (1 - kb)) * Cb;
              float G = Y - (
                  (2 * ((kr * (1 - kr) * Cr) + (kb * (1 - kb) * Cb)))
                  /
                  kg);
  
              rgbPixel[0] = AVIF_CLAMP(R, 0.0f, 1.0f);
              rgbPixel[1] = AVIF_CLAMP(G, 0.0f, 1.0f);
              rgbPixel[2] = AVIF_CLAMP(B, 0.0f, 1.0f);
              for (int plane = 0; plane < 3; ++plane) {
                  dstImage->planes[plane][i + (j * dstImage->strides[plane])] = (uint16_t)avifRoundf(rgbPixel[plane] * dstMaxChannel);
              }
  
              // reformat alpha
              float alpha = (float)srcImage->planes[3][i + (j * srcImage->strides[3])] / srcMaxChannel;
              dstImage->planes[3][i + (j * dstImage->strides[3])] = (uint16_t)avifRoundf(alpha * dstMaxChannel);
          }
      }
  
      return AVIF_RESULT_OK;
  }
  
  avifResult avifImageReformatPixels(avifImage * srcImage, avifImage * dstImage, avifPixelFormat dstPixelFormat, int dstDepth)
  {
      avifImageCreatePixels(dstImage, dstPixelFormat, srcImage->width, srcImage->height, dstDepth);
      switch (srcImage->pixelFormat) {
          case AVIF_PIXEL_FORMAT_RGBA:
              switch (dstPixelFormat) {
                  case AVIF_PIXEL_FORMAT_YUV444:
                      return reformatRGBAToYUV444(srcImage, dstImage, dstDepth);
                  case AVIF_PIXEL_FORMAT_YUV420:
                      return reformatRGBAToYUV420(srcImage, dstImage, dstDepth);
              }
          case AVIF_PIXEL_FORMAT_YUV444:
              switch (dstPixelFormat) {
                  case AVIF_PIXEL_FORMAT_RGBA:
                      return reformatYUV444ToRGBA(srcImage, dstImage, dstDepth);
              }
          case AVIF_PIXEL_FORMAT_YUV420:
              switch (dstPixelFormat) {
                  case AVIF_PIXEL_FORMAT_RGBA:
                      return reformatYUV420ToRGBA(srcImage, dstImage, dstDepth);
              }
      }
      return AVIF_RESULT_REFORMAT_FAILED;
  }
  