bool process(convert_params& params, convert_stats& stats) {
  texture_type tex_type = params.m_texture_type;

  crn_comp_params comp_params(params.m_comp_params);
  crn_mipmap_params mipmap_params(params.m_mipmap_params);

  progress_params progress_state;
  progress_state.m_pParams = &params;
  progress_state.m_canceled = false;
  progress_state.m_start_percentage = 0;

  params.m_status = false;
  params.m_error_message.clear();

  if (params.m_pIntermediate_texture) {
    crnlib_delete(params.m_pIntermediate_texture);
    params.m_pIntermediate_texture = NULL;
  }

  params.m_pIntermediate_texture = crnlib_new<mipmapped_texture>(*params.m_pInput_texture);

  mipmapped_texture& work_tex = *params.m_pInput_texture;

  if ((params.m_unflip) && (work_tex.is_flipped())) {
    console::info("Unflipping texture");
    work_tex.unflip(true, true);
  }

  if (params.m_y_flip) {
    console::info("Flipping texture on Y axis");

    // This is awkward - if we're writing to KTX, then go ahead and properly update the work texture's orientation flags.
    // Otherwise, don't bother updating the orientation flags because the writer may then attempt to unflip the texture before writing to formats
    // that don't support flipped textures (ugh).
    const bool bOutputFormatSupportsFlippedTextures = params.m_dst_file_type == texture_file_types::cFormatKTX;
    if (!work_tex.flip_y(bOutputFormatSupportsFlippedTextures)) {
      console::warning("Failed flipping texture on Y axis");
    }
  }

  if ((params.m_dst_format != PIXEL_FMT_INVALID) && (pixel_format_helpers::is_alpha_only(params.m_dst_format))) {
    if ((work_tex.get_comp_flags() & pixel_format_helpers::cCompFlagAValid) == 0) {
      console::warning("Output format is alpha-only, but input doesn't have alpha, so setting alpha to luminance.");

      work_tex.convert(PIXEL_FMT_A8, crnlib::dxt_image::pack_params());

      if (tex_type == cTextureTypeNormalMap)
        tex_type = cTextureTypeRegularMap;
    }
  }

  pixel_format dst_format = params.m_dst_format;
  if (pixel_format_helpers::is_dxt(dst_format)) {
    if ((params.m_dst_file_type != texture_file_types::cFormatCRN) &&
        (params.m_dst_file_type != texture_file_types::cFormatDDS) &&
        (params.m_dst_file_type != texture_file_types::cFormatKTX)) {
      console::warning("Output file format does not support DXTc - automatically choosing a non-DXT pixel format.");
      dst_format = PIXEL_FMT_INVALID;
    }
  }

  if (dst_format == PIXEL_FMT_INVALID) {
    // Caller didn't specify a format to use, so try to pick something reasonable.
    // This is actually much trickier than it seems, and the current approach kind of sucks.
    dst_format = choose_pixel_format(params, comp_params, work_tex, tex_type);
  }

  if ((dst_format == PIXEL_FMT_DXT1) && (comp_params.get_flag(cCRNCompFlagDXT1AForTransparency)))
    dst_format = PIXEL_FMT_DXT1A;
  else if (dst_format == PIXEL_FMT_DXT1A)
    comp_params.set_flag(cCRNCompFlagDXT1AForTransparency, true);

  if ((dst_format == PIXEL_FMT_DXT1A) && (params.m_dst_file_type == texture_file_types::cFormatCRN)) {
    console::warning("CRN file format does not support DXT1A compressed textures - converting to DXT5 instead.");
    dst_format = PIXEL_FMT_DXT5;
  }

  const bool is_normal_map = (tex_type == cTextureTypeNormalMap);
  bool perceptual = comp_params.get_flag(cCRNCompFlagPerceptual);
  if (is_normal_map) {
    perceptual = false;
    mipmap_params.m_gamma_filtering = false;
  }

  if (pixel_format_helpers::is_pixel_format_non_srgb(dst_format)) {
    if (perceptual) {
      console::message("Output pixel format is swizzled or not RGB, disabling perceptual color metrics");
      perceptual = false;
    }
  }

  if (pixel_format_helpers::is_normal_map(dst_format)) {
    if (perceptual)
      console::message("Output pixel format is intended for normal maps, disabling perceptual color metrics");

    perceptual = false;
  }

  bool generate_mipmaps = texture_file_types::supports_mipmaps(params.m_dst_file_type);
  if ((params.m_write_mipmaps_to_multiple_files) &&
      ((params.m_dst_file_type != texture_file_types::cFormatCRN) && (params.m_dst_file_type != texture_file_types::cFormatDDS) && (params.m_dst_file_type != texture_file_types::cFormatKTX))) {
    generate_mipmaps = true;
  }

  if (params.m_param_debugging) {
    params.print();

    print_comp_params(comp_params);
    print_mipmap_params(mipmap_params);
  }

  if (!create_texture_mipmaps(work_tex, comp_params, mipmap_params, generate_mipmaps))
    return convert_error(params, "Failed creating texture mipmaps!");

  bool formats_differ = work_tex.get_format() != dst_format;
  if (formats_differ) {
    if (pixel_format_helpers::is_dxt1(work_tex.get_format()) && pixel_format_helpers::is_dxt1(dst_format))
      formats_differ = false;
  }

  bool status = false;

  timer t;
  t.start();

  if ((params.m_dst_file_type == texture_file_types::cFormatCRN) ||
      ((params.m_dst_file_type == texture_file_types::cFormatDDS) && (pixel_format_helpers::is_dxt(dst_format)) &&
       //((formats_differ) || (comp_params.m_target_bitrate > 0.0f) || (comp_params.m_quality_level < cCRNMaxQualityLevel))
       ((comp_params.m_target_bitrate > 0.0f) || (comp_params.m_quality_level < cCRNMaxQualityLevel)))) {
    status = write_compressed_texture(work_tex, params, comp_params, dst_format, progress_state, perceptual, stats);
  } else {
    if ((comp_params.m_target_bitrate > 0.0f) || (comp_params.m_quality_level < cCRNMaxQualityLevel)) {
      console::warning("Target bitrate/quality level is not supported for this output file format.\n");
    }
    status = convert_and_write_normal_texture(work_tex, params, comp_params, dst_format, progress_state, formats_differ, perceptual, stats);
  }

  console::progress("");

  if (progress_state.m_canceled) {
    params.m_canceled = true;
    return false;
  }

  double total_write_time = t.get_elapsed_secs();

  if (status) {
    if (params.m_param_debugging)
      console::info("Work texture format: %s, desired destination format: %s", pixel_format_helpers::get_pixel_format_string(work_tex.get_format()), pixel_format_helpers::get_pixel_format_string(dst_format));

    console::message("Texture successfully written in %3.3fs", total_write_time);
  } else {
    dynamic_string str;

    if (work_tex.get_last_error().is_empty())
      str.format("Failed writing texture to file \"%s\"", params.m_dst_filename.get_ptr());
    else
      str.format("Failed writing texture to file \"%s\", Reason: %s", params.m_dst_filename.get_ptr(), work_tex.get_last_error().get_ptr());

    return convert_error(params, str.get_ptr());
  }

  if (params.m_debugging) {
    crnlib_print_mem_stats();
  }

  params.m_status = true;
  return true;
}
      bool process(convert_params& params, convert_stats& stats)
      {
         texture_type tex_type = params.m_texture_type;

         crn_comp_params comp_params(params.m_comp_params);
         crn_mipmap_params mipmap_params(params.m_mipmap_params);

         progress_params progress_state;
         progress_state.m_pParams = &params;
         progress_state.m_canceled = false;
         progress_state.m_start_percentage = 0;

         params.m_status = false;
         params.m_error_message.clear();

         if (params.m_pIntermediate_texture)
         {
            crnlib_delete(params.m_pIntermediate_texture);
            params.m_pIntermediate_texture = NULL;
         }

         params.m_pIntermediate_texture = crnlib_new<dds_texture>(*params.m_pInput_texture);

         dds_texture& work_tex = *params.m_pInput_texture;

         if ((params.m_dst_format != PIXEL_FMT_INVALID) && (pixel_format_helpers::is_alpha_only(params.m_dst_format)))
         {
            if ((work_tex.get_comp_flags() & pixel_format_helpers::cCompFlagAValid) == 0)
            {
               console::warning(L"Output format is alpha-only, but input doesn't have alpha, so setting alpha to luminance.");

               work_tex.convert(PIXEL_FMT_A8, crnlib::dxt_image::pack_params());

               if (tex_type == cTextureTypeNormalMap)
                  tex_type = cTextureTypeRegularMap;
            }
         }

         pixel_format dst_format = params.m_dst_format;

         if (dst_format == PIXEL_FMT_INVALID)
         {
            // Caller didn't specify a format to use, so try to pick something reasonable.
            // This is actually much trickier than it seems, and the current approach kind of sucks.
            dst_format = choose_pixel_format(params, comp_params, work_tex, tex_type);
         }

         if ((dst_format == PIXEL_FMT_DXT1) && (comp_params.get_flag(cCRNCompFlagDXT1AForTransparency)))
            dst_format = PIXEL_FMT_DXT1A;
         else if (dst_format == PIXEL_FMT_DXT1A)
            comp_params.set_flag(cCRNCompFlagDXT1AForTransparency, true);

         const bool is_normal_map = (tex_type == cTextureTypeNormalMap);
         bool perceptual = comp_params.get_flag(cCRNCompFlagPerceptual);
         if (is_normal_map)
         {
            perceptual = false;
            mipmap_params.m_gamma_filtering = false;
         }

         if (pixel_format_helpers::is_pixel_format_non_srgb(dst_format))
         {
            if (perceptual)
            {
               //console::warning(L"Output pixel format is swizzled or not RGB, disabling perceptual color metrics");
               perceptual = false;
            }
         }

         if (pixel_format_helpers::is_normal_map(dst_format))
         {
            //if (perceptual)
               //console::warning(L"Output pixel format is intended for normal maps, disabling perceptual color metrics");

            perceptual = false;
         }

         bool generate_mipmaps = texture_file_types::supports_mipmaps(params.m_dst_file_type);
         if ((params.m_write_mipmaps_to_multiple_files) && ((params.m_dst_file_type != texture_file_types::cFormatCRN) && (params.m_dst_file_type != texture_file_types::cFormatDDS)))
         {
            generate_mipmaps = true;
         }

         if (params.m_param_debugging)
         {
            params.print();

            print_comp_params(comp_params);
            print_mipmap_params(mipmap_params);
         }

         if (!create_texture_mipmaps(work_tex, comp_params, mipmap_params, generate_mipmaps))
            return convert_error(params, L"Failed creating texture mipmaps!");

         bool formats_differ = work_tex.get_format() != dst_format;
         if (formats_differ)
         {
            if (pixel_format_helpers::is_dxt1(work_tex.get_format()) && pixel_format_helpers::is_dxt1(dst_format))
               formats_differ = false;
         }

         bool status = false;

         timer t;
         t.start();

         if ( (params.m_dst_file_type == texture_file_types::cFormatCRN) ||
               ( (params.m_dst_file_type == texture_file_types::cFormatDDS) && (pixel_format_helpers::is_dxt(dst_format)) &&
                 ((formats_differ) || (comp_params.m_target_bitrate > 0.0f) || (comp_params.m_quality_level < cCRNMaxQualityLevel))
               )
            )
         {
            status = write_compressed_texture(work_tex, params, comp_params, dst_format, progress_state, perceptual, stats);
         }
         else
         {
            status = convert_and_write_normal_texture(work_tex, params, comp_params, dst_format, progress_state, formats_differ, perceptual, stats);
         }

         console::progress(L"");

         if (progress_state.m_canceled)
         {
            params.m_canceled = true;
            return false;
         }

         double total_write_time = t.get_elapsed_secs();

         if (status)
         {
            if (params.m_param_debugging)
               console::info(L"Work texture format: %s, desired destination format: %s", pixel_format_helpers::get_pixel_format_string(work_tex.get_format()), pixel_format_helpers::get_pixel_format_string(dst_format));

            console::message(L"Texture successfully written in %3.3fs", total_write_time);
         }
         else
         {
            dynamic_wstring str;

            if (work_tex.get_last_error().is_empty())
               str.format(L"Failed writing texture to file \"%s\"", params.m_dst_filename.get_ptr());
            else
               str.format(L"Failed writing texture to file \"%s\", Reason: %s", params.m_dst_filename.get_ptr(), work_tex.get_last_error().get_ptr());

            return convert_error(params, str.get_ptr());
         }

         if (params.m_debugging)
         {
            crnlib_print_mem_stats();
         }

         params.m_status = true;
         return true;
      }