static struct heif_error x265_encode_image()

in libheif/plugins/encoder_x265.cc [643:916]


static struct heif_error x265_encode_image(void* encoder_raw, const struct heif_image* image,
                                           heif_image_input_class input_class)
{
  struct encoder_struct_x265* encoder = (struct encoder_struct_x265*) encoder_raw;

  // close previous encoder if there is still one hanging around
  if (encoder->encoder) {
    const x265_api* api = x265_api_get(encoder->bit_depth);
    api->encoder_close(encoder->encoder);
    encoder->encoder = nullptr;
  }


  int bit_depth = heif_image_get_bits_per_pixel_range(image, heif_channel_Y);
  bool isGreyscale = (heif_image_get_colorspace(image) == heif_colorspace_monochrome);
  heif_chroma chroma = heif_image_get_chroma_format(image);

  const x265_api* api = x265_api_get(bit_depth);
  if (api == nullptr) {
    struct heif_error err = {
        heif_error_Encoder_plugin_error,
        heif_suberror_Unsupported_bit_depth,
        kError_unsupported_bit_depth
    };
    return err;
  }

  x265_param* param = api->param_alloc();
  api->param_default_preset(param, encoder->preset.c_str(), encoder->tune.c_str());

  if (bit_depth == 8) api->param_apply_profile(param, "mainstillpicture");
  else if (bit_depth == 10) api->param_apply_profile(param, "main10-intra");
  else if (bit_depth == 12) api->param_apply_profile(param, "main12-intra");
  else {
    api->param_free(param);
    return heif_error_unsupported_parameter;
  }


  param->fpsNum = 1;
  param->fpsDenom = 1;


  // x265 cannot encode images smaller than one CTU size
  // https://bitbucket.org/multicoreware/x265/issues/475/x265-does-not-allow-image-sizes-smaller
  // -> use smaller CTU sizes for very small images
  const char* ctu = NULL;
  int ctuSize = 64;

#if 1
  while (ctuSize > 16 &&
         (heif_image_get_width(image, heif_channel_Y) < ctuSize ||
          heif_image_get_height(image, heif_channel_Y) < ctuSize)) {
    ctuSize /= 2;
  }

  if (ctuSize < 16) {
    api->param_free(param);
    struct heif_error err = {
        heif_error_Encoder_plugin_error,
        heif_suberror_Invalid_parameter_value,
        kError_unsupported_image_size
    };
    return err;
  }
#else
  // TODO: There seems to be a bug in x265 where increasing the CTU size between
  // multiple encoding jobs causes a segmentation fault. E.g. encoding multiple
  // times with a CTU of 16 works, the next encoding with a CTU of 32 crashes.
  // Use hardcoded value of 64 and reject images that are too small.

  if (heif_image_get_width(image, heif_channel_Y) < ctuSize ||
      heif_image_get_height(image, heif_channel_Y) < ctuSize) {
    api->param_free(param);
    struct heif_error err = {
      heif_error_Encoder_plugin_error,
      heif_suberror_Invalid_parameter_value,
      kError_unsupported_image_size
    };
    return err;
  }
#endif

  // ctuSize should be a power of 2 in [16;64]
  switch (ctuSize) {
    case 64:
      ctu = "64";
      break;
    case 32:
      ctu = "32";
      break;
    case 16:
      ctu = "16";
      break;
    default:
      struct heif_error err = {
          heif_error_Encoder_plugin_error,
          heif_suberror_Invalid_parameter_value,
          kError_unsupported_image_size
      };
      return err;
  }

  // BPG uses CQP. It does not seem to be better though.
  //  param->rc.rateControlMode = X265_RC_CQP;
  //  param->rc.qp = (100 - encoder->quality)/2;
  param->totalFrames = 1;

  if (isGreyscale) {
    param->internalCsp = X265_CSP_I400;
  }
  else if (chroma == heif_chroma_420) {
    param->internalCsp = X265_CSP_I420;
  }
  else if (chroma == heif_chroma_422) {
    param->internalCsp = X265_CSP_I422;
  }
  else if (chroma == heif_chroma_444) {
    param->internalCsp = X265_CSP_I444;
  }

  if (chroma != heif_chroma_monochrome) {
    int w = heif_image_get_width(image, heif_channel_Y);
    int h = heif_image_get_height(image, heif_channel_Y);
    if (chroma != heif_chroma_444) { w = (w + 1) / 2; }
    if (chroma == heif_chroma_420) { h = (h + 1) / 2; }

    assert(heif_image_get_width(image, heif_channel_Cb)==w);
    assert(heif_image_get_width(image, heif_channel_Cr)==w);
    assert(heif_image_get_height(image, heif_channel_Cb)==h);
    assert(heif_image_get_height(image, heif_channel_Cr)==h);
    (void) w;
    (void) h;
  }

  api->param_parse(param, "info", "0");
  api->param_parse(param, "limit-modes", "0");
  api->param_parse(param, "limit-refs", "0");
  api->param_parse(param, "ctu", ctu);
  api->param_parse(param, "rskip", "0");

  api->param_parse(param, "rect", "1");
  api->param_parse(param, "amp", "1");
  api->param_parse(param, "aq-mode", "1");
  api->param_parse(param, "psy-rd", "1.0");
  api->param_parse(param, "psy-rdoq", "1.0");

  struct heif_color_profile_nclx* nclx = nullptr;
  heif_error err = heif_image_get_nclx_color_profile(image, &nclx);
  if (err.code != heif_error_Ok) {
    nclx = nullptr;
  }

  // make sure NCLX profile is deleted at end of function
  auto nclx_deleter = std::unique_ptr<heif_color_profile_nclx, void (*)(heif_color_profile_nclx*)>(nclx, heif_nclx_color_profile_free);

  if (nclx) {
    api->param_parse(param, "range", nclx->full_range_flag ? "full" : "limited");
  }
  else {
    api->param_parse(param, "range", "full");
  }

  if (nclx &&
      (input_class == heif_image_input_class_normal ||
       input_class == heif_image_input_class_thumbnail)) {

    {
      std::stringstream sstr;
      sstr << nclx->color_primaries;
      api->param_parse(param, "colorprim", sstr.str().c_str());
    }

    {
      std::stringstream sstr;
      sstr << nclx->transfer_characteristics;
      api->param_parse(param, "transfer", sstr.str().c_str());
    }

    {
      std::stringstream sstr;
      sstr << nclx->matrix_coefficients;
      api->param_parse(param, "colormatrix", sstr.str().c_str());
    }
  }

  for (const auto& p : encoder->parameters) {
    if (p.name == heif_encoder_parameter_name_quality) {
      // quality=0   -> crf=50
      // quality=50  -> crf=25
      // quality=100 -> crf=0

      param->rc.rfConstant = (100 - p.value_int) / 2.0;
    }
    else if (p.name == heif_encoder_parameter_name_lossless) {
      param->bLossless = p.value_int;
    }
    else if (p.name == kParam_TU_intra_depth) {
      auto valString = std::to_string(p.value_int);
      api->param_parse(param, "tu-intra-depth", valString.c_str());
    }
    else if (p.name == kParam_complexity) {
      const int complexity = p.value_int;

      if (complexity >= 60) {
        api->param_parse(param, "rd-refine", "1"); // increases computation time
        api->param_parse(param, "rd", "6");
      }

      if (complexity >= 70) {
        api->param_parse(param, "cu-lossless", "1"); // increases computation time
      }

      if (complexity >= 90) {
        api->param_parse(param, "wpp", "0"); // setting to 0 significantly increases computation time
      }
    }
    else if (strncmp(p.name.c_str(), "x265:", 5) == 0) {
      std::string x265p = p.name.substr(5);
      api->param_parse(param, x265p.c_str(), p.value_string.c_str());
    }
  }

  param->logLevel = encoder->logLevel;


  param->sourceWidth = heif_image_get_width(image, heif_channel_Y);
  param->sourceHeight = heif_image_get_height(image, heif_channel_Y);
  param->internalBitDepth = bit_depth;

  param->sourceWidth = rounded_size(param->sourceWidth);
  param->sourceHeight = rounded_size(param->sourceHeight);

  // Note: it is ok to cast away the const, as the image content is not changed.
  // However, we have to guarantee that there are no plane pointers or stride values kept over calling the svt_encode_image() function.
  err = heif_image_extend_padding_to_size(const_cast<struct heif_image*>(image),
                                          param->sourceWidth,
                                          param->sourceHeight);
  if (err.code) {
    return err;
  }

  x265_picture* pic = api->picture_alloc();
  api->picture_init(param, pic);

  if (isGreyscale) {
    pic->planes[0] = (void*) heif_image_get_plane_readonly(image, heif_channel_Y, &pic->stride[0]);
  }
  else {
    pic->planes[0] = (void*) heif_image_get_plane_readonly(image, heif_channel_Y, &pic->stride[0]);
    pic->planes[1] = (void*) heif_image_get_plane_readonly(image, heif_channel_Cb, &pic->stride[1]);
    pic->planes[2] = (void*) heif_image_get_plane_readonly(image, heif_channel_Cr, &pic->stride[2]);
  }

  pic->bitDepth = bit_depth;


  encoder->bit_depth = bit_depth;

  encoder->encoder = api->encoder_open(param);

  api->encoder_encode(encoder->encoder,
                      &encoder->nals,
                      &encoder->num_nals,
                      pic,
                      NULL);

  api->picture_free(pic);
  api->param_free(param);

  encoder->nal_output_counter = 0;

  return heif_error_ok;
}