// File: crn_dxt_image.cpp
// See Copyright Notice and license at the end of inc/crnlib.h
#include "crn_core.h"
#include "crn_dxt_image.h"
#if CRNLIB_SUPPORT_SQUISH
#include "squish\squish.h"
#endif
#include "crn_ryg_dxt.hpp"
#include "crn_dxt_fast.h"
#include "crn_console.h"
#include "crn_threading.h"

#if CRNLIB_SUPPORT_ATI_COMPRESS
#ifdef _DLL
#pragma comment(lib, "ATI_Compress_MT_DLL_VC8.lib")
#else
#pragma comment(lib, "ATI_Compress_MT_VC8.lib")
#endif
#include "..\ext\ATI_Compress\ATI_Compress.h"
#endif

#include "crn_rg_etc1.h"
#include "crn_etc.h"
#define CRNLIB_USE_RG_ETC1 1

namespace crnlib {
dxt_image::dxt_image()
    : m_pElements(NULL),
      m_width(0),
      m_height(0),
      m_blocks_x(0),
      m_blocks_y(0),
      m_total_blocks(0),
      m_total_elements(0),
      m_num_elements_per_block(0),
      m_bytes_per_block(0),
      m_format(cDXTInvalid) {
  utils::zero_object(m_element_type);
  utils::zero_object(m_element_component_index);
}

dxt_image::dxt_image(const dxt_image& other)
    : m_pElements(NULL) {
  *this = other;
}

dxt_image& dxt_image::operator=(const dxt_image& rhs) {
  if (this == &rhs)
    return *this;

  clear();

  m_width = rhs.m_width;
  m_height = rhs.m_height;
  m_blocks_x = rhs.m_blocks_x;
  m_blocks_y = rhs.m_blocks_y;
  m_num_elements_per_block = rhs.m_num_elements_per_block;
  m_bytes_per_block = rhs.m_bytes_per_block;
  m_format = rhs.m_format;
  m_total_blocks = rhs.m_total_blocks;
  m_total_elements = rhs.m_total_elements;
  m_pElements = NULL;
  memcpy(m_element_type, rhs.m_element_type, sizeof(m_element_type));
  memcpy(m_element_component_index, rhs.m_element_component_index, sizeof(m_element_component_index));

  if (rhs.m_pElements) {
    m_elements.resize(m_total_elements);
    memcpy(&m_elements[0], rhs.m_pElements, sizeof(element) * m_total_elements);
    m_pElements = &m_elements[0];
  }

  return *this;
}

void dxt_image::clear() {
  m_elements.clear();
  m_width = 0;
  m_height = 0;
  m_blocks_x = 0;
  m_blocks_y = 0;
  m_num_elements_per_block = 0;
  m_bytes_per_block = 0;
  m_format = cDXTInvalid;
  utils::zero_object(m_element_type);
  utils::zero_object(m_element_component_index);
  m_total_blocks = 0;
  m_total_elements = 0;
  m_pElements = NULL;
}

bool dxt_image::init_internal(dxt_format fmt, uint width, uint height) {
  CRNLIB_ASSERT((fmt != cDXTInvalid) && (width > 0) && (height > 0));

  clear();

  m_width = width;
  m_height = height;

  m_blocks_x = (m_width + 3) >> cDXTBlockShift;
  m_blocks_y = (m_height + 3) >> cDXTBlockShift;

  m_num_elements_per_block = 2;
  if ((fmt == cDXT1) || (fmt == cDXT1A) || (fmt == cDXT5A) || (fmt == cETC1) || (fmt == cETC2) || (fmt == cETC1S))
    m_num_elements_per_block = 1;

  m_total_blocks = m_blocks_x * m_blocks_y;
  m_total_elements = m_total_blocks * m_num_elements_per_block;

  CRNLIB_ASSUME((uint)cDXT1BytesPerBlock == (uint)cETC1BytesPerBlock);
  m_bytes_per_block = cDXT1BytesPerBlock * m_num_elements_per_block;

  m_format = fmt;

  switch (m_format) {
    case cDXT1:
    case cDXT1A: {
      m_element_type[0] = cColorDXT1;
      m_element_component_index[0] = -1;
      break;
    }
    case cDXT3: {
      m_element_type[0] = cAlphaDXT3;
      m_element_type[1] = cColorDXT1;
      m_element_component_index[0] = 3;
      m_element_component_index[1] = -1;
      break;
    }
    case cDXT5: {
      m_element_type[0] = cAlphaDXT5;
      m_element_type[1] = cColorDXT1;
      m_element_component_index[0] = 3;
      m_element_component_index[1] = -1;
      break;
    }
    case cDXT5A: {
      m_element_type[0] = cAlphaDXT5;
      m_element_component_index[0] = 3;
      break;
    }
    case cDXN_XY: {
      m_element_type[0] = cAlphaDXT5;
      m_element_type[1] = cAlphaDXT5;
      m_element_component_index[0] = 0;
      m_element_component_index[1] = 1;
      break;
    }
    case cDXN_YX: {
      m_element_type[0] = cAlphaDXT5;
      m_element_type[1] = cAlphaDXT5;
      m_element_component_index[0] = 1;
      m_element_component_index[1] = 0;
      break;
    }
    case cETC1:
    case cETC1S: {
      m_element_type[0] = cColorETC1;
      m_element_component_index[0] = -1;
      break;
    }
    case cETC2: {
      m_element_type[0] = cColorETC2;
      m_element_component_index[0] = -1;
      break;
    }
    case cETC2A:
    case cETC2AS: {
      m_element_type[0] = cAlphaETC2;
      m_element_type[1] = cColorETC2;
      m_element_component_index[0] = 3;
      m_element_component_index[1] = -1;
      break;
    }
    default: {
      CRNLIB_ASSERT(0);
      clear();
      return false;
    }
  }

  return true;
}

bool dxt_image::init(dxt_format fmt, uint width, uint height, bool clear_elements) {
  if (!init_internal(fmt, width, height))
    return false;

  m_elements.resize(m_total_elements);
  m_pElements = &m_elements[0];

  if (clear_elements)
    memset(m_pElements, 0, sizeof(element) * m_total_elements);

  return true;
}

bool dxt_image::init(dxt_format fmt, uint width, uint height, uint num_elements, element* pElements, bool create_copy) {
  CRNLIB_ASSERT(num_elements && pElements);

  if (!init_internal(fmt, width, height))
    return false;

  if (num_elements != m_total_elements) {
    clear();
    return false;
  }

  if (create_copy) {
    m_elements.resize(m_total_elements);
    m_pElements = &m_elements[0];

    memcpy(m_pElements, pElements, m_total_elements * sizeof(element));
  } else
    m_pElements = pElements;

  return true;
}

struct init_task_params {
  dxt_format m_fmt;
  const image_u8* m_pImg;
  const dxt_image::pack_params* m_pParams;
  crn_thread_id_t m_main_thread;
  atomic32_t m_canceled;
};

void dxt_image::init_task(uint64 data, void* pData_ptr) {
  const uint thread_index = static_cast<uint>(data);
  init_task_params* pInit_params = static_cast<init_task_params*>(pData_ptr);

  const image_u8& img = *pInit_params->m_pImg;
  const pack_params& p = *pInit_params->m_pParams;
  const bool is_main_thread = (crn_get_current_thread_id() == pInit_params->m_main_thread);

  uint block_index = 0;

  set_block_pixels_context optimizer_context;
  int prev_progress_percentage = -1;

  for (uint block_y = 0; block_y < m_blocks_y; block_y++) {
    const uint pixel_ofs_y = block_y * cDXTBlockSize;

    for (uint block_x = 0; block_x < m_blocks_x; block_x++, block_index++) {
      if (pInit_params->m_canceled)
        return;

      if (p.m_pProgress_callback && is_main_thread && ((block_index & 63) == 63)) {
        const uint progress_percentage = p.m_progress_start + ((block_index * p.m_progress_range + get_total_blocks() / 2) / get_total_blocks());
        if ((int)progress_percentage != prev_progress_percentage) {
          prev_progress_percentage = progress_percentage;
          if (!(p.m_pProgress_callback)(progress_percentage, p.m_pProgress_callback_user_data_ptr)) {
            atomic_exchange32(&pInit_params->m_canceled, CRNLIB_TRUE);
            return;
          }
        }
      }

      if (p.m_num_helper_threads) {
        if ((block_index % (p.m_num_helper_threads + 1)) != thread_index)
          continue;
      }

      color_quad_u8 pixels[cDXTBlockSize * cDXTBlockSize];

      const uint pixel_ofs_x = block_x * cDXTBlockSize;

      for (uint y = 0; y < cDXTBlockSize; y++) {
        const uint iy = math::minimum(pixel_ofs_y + y, img.get_height() - 1);

        for (uint x = 0; x < cDXTBlockSize; x++) {
          const uint ix = math::minimum(pixel_ofs_x + x, img.get_width() - 1);

          pixels[x + y * cDXTBlockSize] = img(ix, iy);
        }
      }

      set_block_pixels(block_x, block_y, pixels, p, optimizer_context);
    }
  }
}

#if CRNLIB_SUPPORT_ATI_COMPRESS
bool dxt_image::init_ati_compress(dxt_format fmt, const image_u8& img, const pack_params& p) {
  image_u8 tmp_img(img);
  for (uint y = 0; y < img.get_height(); y++) {
    for (uint x = 0; x < img.get_width(); x++) {
      color_quad_u8 c(img(x, y));
      std::swap(c.r, c.b);
      tmp_img(x, y) = c;
    }
  }

  ATI_TC_Texture src_tex;
  utils::zero_object(src_tex);
  src_tex.dwSize = sizeof(ATI_TC_Texture);
  src_tex.dwWidth = tmp_img.get_width();
  src_tex.dwHeight = tmp_img.get_height();
  src_tex.dwPitch = tmp_img.get_pitch_in_bytes();
  src_tex.format = ATI_TC_FORMAT_ARGB_8888;
  src_tex.dwDataSize = src_tex.dwPitch * tmp_img.get_height();
  src_tex.pData = (ATI_TC_BYTE*)tmp_img.get_ptr();

  ATI_TC_Texture dst_tex;
  utils::zero_object(dst_tex);
  dst_tex.dwSize = sizeof(ATI_TC_Texture);
  dst_tex.dwWidth = tmp_img.get_width();
  dst_tex.dwHeight = tmp_img.get_height();
  dst_tex.dwDataSize = get_size_in_bytes();
  dst_tex.pData = (ATI_TC_BYTE*)get_element_ptr();

  switch (fmt) {
    case cDXT1:
    case cDXT1A:
      dst_tex.format = ATI_TC_FORMAT_DXT1;
      break;
    case cDXT3:
      dst_tex.format = ATI_TC_FORMAT_DXT3;
      break;
    case cDXT5:
      dst_tex.format = ATI_TC_FORMAT_DXT5;
      break;
    case cDXT5A:
      dst_tex.format = ATI_TC_FORMAT_ATI1N;
      break;
    case cDXN_XY:
      dst_tex.format = ATI_TC_FORMAT_ATI2N_XY;
      break;
    case cDXN_YX:
      dst_tex.format = ATI_TC_FORMAT_ATI2N;
      break;
    default: {
      CRNLIB_ASSERT(false);
      return false;
    }
  }

  ATI_TC_CompressOptions options;
  utils::zero_object(options);
  options.dwSize = sizeof(ATI_TC_CompressOptions);

  if (fmt == cDXT1A) {
    options.bDXT1UseAlpha = true;
    options.nAlphaThreshold = (ATI_TC_BYTE)p.m_dxt1a_alpha_threshold;
  }
  options.bDisableMultiThreading = (p.m_num_helper_threads == 0);
  switch (p.m_quality) {
    case cCRNDXTQualityFast:
      options.nCompressionSpeed = ATI_TC_Speed_Fast;
      break;
    case cCRNDXTQualitySuperFast:
      options.nCompressionSpeed = ATI_TC_Speed_SuperFast;
      break;
    default:
      options.nCompressionSpeed = ATI_TC_Speed_Normal;
      break;
  }

  if (p.m_perceptual) {
    options.bUseChannelWeighting = true;
    options.fWeightingRed = .212671f;
    options.fWeightingGreen = .715160f;
    options.fWeightingBlue = .072169f;
  }

  ATI_TC_ERROR err = ATI_TC_ConvertTexture(&src_tex, &dst_tex, &options, NULL, NULL, NULL);
  return err == ATI_TC_OK;
}
#endif

bool dxt_image::init(dxt_format fmt, const image_u8& img, const pack_params& p) {
  if (!init(fmt, img.get_width(), img.get_height(), false))
    return false;

#if CRNLIB_SUPPORT_ATI_COMPRESS
  if (p.m_compressor == cCRNDXTCompressorATI)
    return init_ati_compress(fmt, img, p);
#endif

  task_pool* pPool = p.m_pTask_pool;

  task_pool tmp_pool;
  if (!pPool) {
    if (!tmp_pool.init(p.m_num_helper_threads))
      return false;
    pPool = &tmp_pool;
  }

  init_task_params init_params;
  init_params.m_fmt = fmt;
  init_params.m_pImg = &img;
  init_params.m_pParams = &p;
  init_params.m_main_thread = crn_get_current_thread_id();
  init_params.m_canceled = false;

  for (uint i = 0; i <= p.m_num_helper_threads; i++)
    pPool->queue_object_task(this, &dxt_image::init_task, i, &init_params);

  pPool->join();

  if (init_params.m_canceled)
    return false;

  return true;
}

bool dxt_image::unpack(image_u8& img) const {
  if (!m_total_elements)
    return false;

  img.resize(m_width, m_height);

  color_quad_u8 pixels[cDXTBlockSize * cDXTBlockSize];
  for (uint i = 0; i < cDXTBlockSize * cDXTBlockSize; i++)
    pixels[i].set(0, 0, 0, 255);

  bool all_blocks_valid = true;
  for (uint block_y = 0; block_y < m_blocks_y; block_y++) {
    const uint pixel_ofs_y = block_y * cDXTBlockSize;
    const uint limit_y = math::minimum<uint>(cDXTBlockSize, img.get_height() - pixel_ofs_y);

    for (uint block_x = 0; block_x < m_blocks_x; block_x++) {
      if (!get_block_pixels(block_x, block_y, pixels))
        all_blocks_valid = false;

      const uint pixel_ofs_x = block_x * cDXTBlockSize;

      const uint limit_x = math::minimum<uint>(cDXTBlockSize, img.get_width() - pixel_ofs_x);

      for (uint y = 0; y < limit_y; y++) {
        const uint iy = pixel_ofs_y + y;

        for (uint x = 0; x < limit_x; x++) {
          const uint ix = pixel_ofs_x + x;

          img(ix, iy) = pixels[x + (y << cDXTBlockShift)];
        }
      }
    }
  }

  if (!all_blocks_valid)
    console::error("dxt_image::unpack: One or more invalid blocks encountered!");

  img.reset_comp_flags();
  img.set_component_valid(0, false);
  img.set_component_valid(1, false);
  img.set_component_valid(2, false);
  for (uint i = 0; i < m_num_elements_per_block; i++) {
    if (m_element_component_index[i] < 0) {
      img.set_component_valid(0, true);
      img.set_component_valid(1, true);
      img.set_component_valid(2, true);
    } else
      img.set_component_valid(m_element_component_index[i], true);
  }

  img.set_component_valid(3, get_dxt_format_has_alpha(m_format));

  return true;
}

void dxt_image::endian_swap() {
  utils::endian_switch_words(reinterpret_cast<uint16*>(m_elements.get_ptr()), m_elements.size_in_bytes() / sizeof(uint16));
}

const dxt_image::element& dxt_image::get_element(uint block_x, uint block_y, uint element_index) const {
  CRNLIB_ASSERT((block_x < m_blocks_x) && (block_y < m_blocks_y) && (element_index < m_num_elements_per_block));
  return m_pElements[(block_x + block_y * m_blocks_x) * m_num_elements_per_block + element_index];
}

dxt_image::element& dxt_image::get_element(uint block_x, uint block_y, uint element_index) {
  CRNLIB_ASSERT((block_x < m_blocks_x) && (block_y < m_blocks_y) && (element_index < m_num_elements_per_block));
  return m_pElements[(block_x + block_y * m_blocks_x) * m_num_elements_per_block + element_index];
}

bool dxt_image::has_alpha() const {
  switch (m_format) {
    case cDXT1: {
      for (uint i = 0; i < m_total_elements; i++) {
        const dxt1_block& blk = *(dxt1_block*)&m_pElements[i];

        if (blk.get_low_color() <= blk.get_high_color()) {
          for (uint y = 0; y < cDXTBlockSize; y++)
            for (uint x = 0; x < cDXTBlockSize; x++)
              if (blk.get_selector(x, y) == 3)
                return true;
        }
      }

      break;
    }
    case cDXT1A:
    case cDXT3:
    case cDXT5:
    case cDXT5A:
    case cETC2A:
    case cETC2AS:
      return true;
    default:
      break;
  }

  return false;
}

color_quad_u8 dxt_image::get_pixel(uint x, uint y) const {
  CRNLIB_ASSERT((x < m_width) && (y < m_height));

  const uint block_x = x >> cDXTBlockShift;
  const uint block_y = y >> cDXTBlockShift;

  const element* pElement = reinterpret_cast<const element*>(&get_element(block_x, block_y, 0));

  color_quad_u8 result(0, 0, 0, 255);

  for (uint element_index = 0; element_index < m_num_elements_per_block; element_index++, pElement++) {
    switch (m_element_type[element_index]) {
      case cColorETC1: {
        const etc1_block& block = *reinterpret_cast<const etc1_block*>(&get_element(block_x, block_y, element_index));

        const bool diff_flag = block.get_diff_bit();
        const bool flip_flag = block.get_flip_bit();
        const uint table_index0 = block.get_inten_table(0);
        const uint table_index1 = block.get_inten_table(1);
        color_quad_u8 subblock_colors0[4], subblock_colors1[4];

        if (diff_flag) {
          const uint16 base_color5 = block.get_base5_color();
          const uint16 delta_color3 = block.get_delta3_color();
          etc1_block::get_diff_subblock_colors(subblock_colors0, base_color5, table_index0);
          etc1_block::get_diff_subblock_colors(subblock_colors1, base_color5, delta_color3, table_index1);
        } else {
          const uint16 base_color4_0 = block.get_base4_color(0);
          etc1_block::get_abs_subblock_colors(subblock_colors0, base_color4_0, table_index0);
          const uint16 base_color4_1 = block.get_base4_color(1);
          etc1_block::get_abs_subblock_colors(subblock_colors1, base_color4_1, table_index1);
        }

        const uint bx = x & 3;
        const uint by = y & 3;

        const uint selector_index = block.get_selector(bx, by);
        if (flip_flag) {
          if (by <= 2)
            result = subblock_colors0[selector_index];
          else
            result = subblock_colors1[selector_index];
        } else {
          if (bx <= 2)
            result = subblock_colors0[selector_index];
          else
            result = subblock_colors1[selector_index];
        }

        break;
      }
      case cColorDXT1: {
        const dxt1_block* pBlock = reinterpret_cast<const dxt1_block*>(&get_element(block_x, block_y, element_index));

        const uint l = pBlock->get_low_color();
        const uint h = pBlock->get_high_color();

        color_quad_u8 c0(dxt1_block::unpack_color(static_cast<uint16>(l), true));
        color_quad_u8 c1(dxt1_block::unpack_color(static_cast<uint16>(h), true));

        const uint s = pBlock->get_selector(x & 3, y & 3);

        if (l > h) {
          switch (s) {
            case 0:
              result.set_noclamp_rgb(c0.r, c0.g, c0.b);
              break;
            case 1:
              result.set_noclamp_rgb(c1.r, c1.g, c1.b);
              break;
            case 2:
              result.set_noclamp_rgb((c0.r * 2 + c1.r) / 3, (c0.g * 2 + c1.g) / 3, (c0.b * 2 + c1.b) / 3);
              break;
            case 3:
              result.set_noclamp_rgb((c1.r * 2 + c0.r) / 3, (c1.g * 2 + c0.g) / 3, (c1.b * 2 + c0.b) / 3);
              break;
          }
        } else {
          switch (s) {
            case 0:
              result.set_noclamp_rgb(c0.r, c0.g, c0.b);
              break;
            case 1:
              result.set_noclamp_rgb(c1.r, c1.g, c1.b);
              break;
            case 2:
              result.set_noclamp_rgb((c0.r + c1.r) >> 1U, (c0.g + c1.g) >> 1U, (c0.b + c1.b) >> 1U);
              break;
            case 3: {
              if (m_format <= cDXT1A)
                result.set_noclamp_rgba(0, 0, 0, 0);
              else
                result.set_noclamp_rgb(0, 0, 0);
              break;
            }
          }
        }

        break;
      }
      case cAlphaDXT5: {
        const int comp_index = m_element_component_index[element_index];

        const dxt5_block* pBlock = reinterpret_cast<const dxt5_block*>(&get_element(block_x, block_y, element_index));

        const uint l = pBlock->get_low_alpha();
        const uint h = pBlock->get_high_alpha();

        const uint s = pBlock->get_selector(x & 3, y & 3);

        if (l > h) {
          switch (s) {
            case 0:
              result[comp_index] = static_cast<uint8>(l);
              break;
            case 1:
              result[comp_index] = static_cast<uint8>(h);
              break;
            case 2:
              result[comp_index] = static_cast<uint8>((l * 6 + h) / 7);
              break;
            case 3:
              result[comp_index] = static_cast<uint8>((l * 5 + h * 2) / 7);
              break;
            case 4:
              result[comp_index] = static_cast<uint8>((l * 4 + h * 3) / 7);
              break;
            case 5:
              result[comp_index] = static_cast<uint8>((l * 3 + h * 4) / 7);
              break;
            case 6:
              result[comp_index] = static_cast<uint8>((l * 2 + h * 5) / 7);
              break;
            case 7:
              result[comp_index] = static_cast<uint8>((l + h * 6) / 7);
              break;
          }
        } else {
          switch (s) {
            case 0:
              result[comp_index] = static_cast<uint8>(l);
              break;
            case 1:
              result[comp_index] = static_cast<uint8>(h);
              break;
            case 2:
              result[comp_index] = static_cast<uint8>((l * 4 + h) / 5);
              break;
            case 3:
              result[comp_index] = static_cast<uint8>((l * 3 + h * 2) / 5);
              break;
            case 4:
              result[comp_index] = static_cast<uint8>((l * 2 + h * 3) / 5);
              break;
            case 5:
              result[comp_index] = static_cast<uint8>((l + h * 4) / 5);
              break;
            case 6:
              result[comp_index] = 0;
              break;
            case 7:
              result[comp_index] = 255;
              break;
          }
        }

        break;
      }
      case cAlphaDXT3: {
        const int comp_index = m_element_component_index[element_index];

        const dxt3_block* pBlock = reinterpret_cast<const dxt3_block*>(&get_element(block_x, block_y, element_index));

        result[comp_index] = static_cast<uint8>(pBlock->get_alpha(x & 3, y & 3, true));

        break;
      }
      default:
        break;
    }
  }

  return result;
}

uint dxt_image::get_pixel_alpha(uint x, uint y, uint element_index) const {
  CRNLIB_ASSERT((x < m_width) && (y < m_height) && (element_index < m_num_elements_per_block));

  const uint block_x = x >> cDXTBlockShift;
  const uint block_y = y >> cDXTBlockShift;

  switch (m_element_type[element_index]) {
    case cColorDXT1: {
      if (m_format <= cDXT1A) {
        const dxt1_block* pBlock = reinterpret_cast<const dxt1_block*>(&get_element(block_x, block_y, element_index));

        const uint l = pBlock->get_low_color();
        const uint h = pBlock->get_high_color();

        if (l <= h) {
          uint s = pBlock->get_selector(x & 3, y & 3);

          return (s == 3) ? 0 : 255;
        } else {
          return 255;
        }
      }

      break;
    }
    case cAlphaDXT5: {
      const dxt5_block* pBlock = reinterpret_cast<const dxt5_block*>(&get_element(block_x, block_y, element_index));

      const uint l = pBlock->get_low_alpha();
      const uint h = pBlock->get_high_alpha();

      const uint s = pBlock->get_selector(x & 3, y & 3);

      if (l > h) {
        switch (s) {
          case 0:
            return l;
          case 1:
            return h;
          case 2:
            return (l * 6 + h) / 7;
          case 3:
            return (l * 5 + h * 2) / 7;
          case 4:
            return (l * 4 + h * 3) / 7;
          case 5:
            return (l * 3 + h * 4) / 7;
          case 6:
            return (l * 2 + h * 5) / 7;
          case 7:
            return (l + h * 6) / 7;
        }
      } else {
        switch (s) {
          case 0:
            return l;
          case 1:
            return h;
          case 2:
            return (l * 4 + h) / 5;
          case 3:
            return (l * 3 + h * 2) / 5;
          case 4:
            return (l * 2 + h * 3) / 5;
          case 5:
            return (l + h * 4) / 5;
          case 6:
            return 0;
          case 7:
            return 255;
        }
      }
    }
    case cAlphaDXT3: {
      const dxt3_block* pBlock = reinterpret_cast<const dxt3_block*>(&get_element(block_x, block_y, element_index));

      return pBlock->get_alpha(x & 3, y & 3, true);
    }
    default:
      break;
  }

  return 255;
}

void dxt_image::set_pixel(uint x, uint y, const color_quad_u8& c, bool perceptual) {
  CRNLIB_ASSERT((x < m_width) && (y < m_height));

  const uint block_x = x >> cDXTBlockShift;
  const uint block_y = y >> cDXTBlockShift;

  element* pElement = &get_element(block_x, block_y, 0);

  for (uint element_index = 0; element_index < m_num_elements_per_block; element_index++, pElement++) {
    switch (m_element_type[element_index]) {
      case cColorETC1: {
        etc1_block& block = *reinterpret_cast<etc1_block*>(&get_element(block_x, block_y, element_index));

        const bool diff_flag = block.get_diff_bit();
        const bool flip_flag = block.get_flip_bit();
        const uint table_index0 = block.get_inten_table(0);
        const uint table_index1 = block.get_inten_table(1);
        color_quad_u8 subblock_colors0[4], subblock_colors1[4];

        if (diff_flag) {
          const uint16 base_color5 = block.get_base5_color();
          const uint16 delta_color3 = block.get_delta3_color();
          etc1_block::get_diff_subblock_colors(subblock_colors0, base_color5, table_index0);
          etc1_block::get_diff_subblock_colors(subblock_colors1, base_color5, delta_color3, table_index1);
        } else {
          const uint16 base_color4_0 = block.get_base4_color(0);
          etc1_block::get_abs_subblock_colors(subblock_colors0, base_color4_0, table_index0);
          const uint16 base_color4_1 = block.get_base4_color(1);
          etc1_block::get_abs_subblock_colors(subblock_colors1, base_color4_1, table_index1);
        }

        const uint bx = x & 3;
        const uint by = y & 3;

        color_quad_u8* pColors = subblock_colors1;
        if (flip_flag) {
          if (by <= 2)
            pColors = subblock_colors0;
        } else {
          if (bx <= 2)
            pColors = subblock_colors0;
        }

        uint best_error = UINT_MAX;
        uint best_selector = 0;

        for (uint i = 0; i < 4; i++) {
          uint error = color::color_distance(perceptual, pColors[i], c, false);
          if (error < best_error) {
            best_error = error;
            best_selector = i;
          }
        }

        block.set_selector(bx, by, best_selector);
        break;
      }
      case cColorDXT1: {
        dxt1_block* pDXT1_block = reinterpret_cast<dxt1_block*>(pElement);

        color_quad_u8 colors[cDXT1SelectorValues];
        const uint n = pDXT1_block->get_block_colors(colors, static_cast<uint16>(pDXT1_block->get_low_color()), static_cast<uint16>(pDXT1_block->get_high_color()));

        if ((m_format == cDXT1A) && (c.a < 128))
          pDXT1_block->set_selector(x & 3, y & 3, 3);
        else {
          uint best_error = UINT_MAX;
          uint best_selector = 0;

          for (uint i = 0; i < n; i++) {
            uint error = color::color_distance(perceptual, colors[i], c, false);
            if (error < best_error) {
              best_error = error;
              best_selector = i;
            }
          }

          pDXT1_block->set_selector(x & 3, y & 3, best_selector);
        }

        break;
      }
      case cAlphaDXT5: {
        dxt5_block* pDXT5_block = reinterpret_cast<dxt5_block*>(pElement);

        uint values[cDXT5SelectorValues];
        dxt5_block::get_block_values(values, pDXT5_block->get_low_alpha(), pDXT5_block->get_high_alpha());

        const int comp_index = m_element_component_index[element_index];

        uint best_error = UINT_MAX;
        uint best_selector = 0;

        for (uint i = 0; i < cDXT5SelectorValues; i++) {
          uint error = labs((int)values[i] - (int)c[comp_index]);  // no need to square

          if (error < best_error) {
            best_error = error;
            best_selector = i;
          }
        }

        pDXT5_block->set_selector(x & 3, y & 3, best_selector);

        break;
      }
      case cAlphaDXT3: {
        const int comp_index = m_element_component_index[element_index];

        dxt3_block* pDXT3_block = reinterpret_cast<dxt3_block*>(pElement);

        pDXT3_block->set_alpha(x & 3, y & 3, c[comp_index], true);

        break;
      }
      default:
        break;
    }
  }  // element_index
}

bool dxt_image::get_block_pixels(uint block_x, uint block_y, color_quad_u8* pPixels) const {
  bool success = true;
  const element* pElement = &get_element(block_x, block_y, 0);

  for (uint element_index = 0; element_index < m_num_elements_per_block; element_index++, pElement++) {
    switch (m_element_type[element_index]) {
      case cColorETC1: {
        const etc1_block& block = *reinterpret_cast<const etc1_block*>(&get_element(block_x, block_y, element_index));
// Preserve alpha if the format is something weird (like ETC1 for color and DXT5A for alpha) - which isn't currently supported.
#if CRNLIB_USE_RG_ETC1
        if (!rg_etc1::unpack_etc1_block(&block, (uint32*)pPixels, m_format != cETC1))
          success = false;
#else
        if (!unpack_etc1(block, pPixels, m_format != cETC1))
          success = false;
#endif

        break;
      }
      case cColorETC2: {
        const etc1_block& block = *reinterpret_cast<const etc1_block*>(&get_element(block_x, block_y, element_index));
        if (!rg_etc1::unpack_etc2_color(&block, (uint32*)pPixels, m_format != cETC2))
          success = false;
        break;
      }
      case cAlphaETC2: {
        const etc1_block& block = *reinterpret_cast<const etc1_block*>(&get_element(block_x, block_y, element_index));
        if (!rg_etc1::unpack_etc2_alpha(&block, (uint32*)pPixels, m_element_component_index[element_index]))
          success = false;
        break;
      }
      case cColorDXT1: {
        const dxt1_block* pDXT1_block = reinterpret_cast<const dxt1_block*>(pElement);

        color_quad_u8 colors[cDXT1SelectorValues];
        pDXT1_block->get_block_colors(colors, static_cast<uint16>(pDXT1_block->get_low_color()), static_cast<uint16>(pDXT1_block->get_high_color()));

        for (uint i = 0; i < cDXTBlockSize * cDXTBlockSize; i++) {
          uint s = pDXT1_block->get_selector(i & 3, i >> 2);

          pPixels[i].r = colors[s].r;
          pPixels[i].g = colors[s].g;
          pPixels[i].b = colors[s].b;

          if (m_format <= cDXT1A)
            pPixels[i].a = colors[s].a;
        }

        break;
      }
      case cAlphaDXT5: {
        const dxt5_block* pDXT5_block = reinterpret_cast<const dxt5_block*>(pElement);

        uint values[cDXT5SelectorValues];
        dxt5_block::get_block_values(values, pDXT5_block->get_low_alpha(), pDXT5_block->get_high_alpha());

        const int comp_index = m_element_component_index[element_index];

        for (uint i = 0; i < cDXTBlockSize * cDXTBlockSize; i++) {
          uint s = pDXT5_block->get_selector(i & 3, i >> 2);

          pPixels[i][comp_index] = static_cast<uint8>(values[s]);
        }

        break;
      }
      case cAlphaDXT3: {
        const dxt3_block* pDXT3_block = reinterpret_cast<const dxt3_block*>(pElement);

        const int comp_index = m_element_component_index[element_index];

        for (uint i = 0; i < cDXTBlockSize * cDXTBlockSize; i++) {
          uint a = pDXT3_block->get_alpha(i & 3, i >> 2, true);

          pPixels[i][comp_index] = static_cast<uint8>(a);
        }

        break;
      }
      default:
        break;
    }
  }  // element_index
  return success;
}

void dxt_image::set_block_pixels(uint block_x, uint block_y, const color_quad_u8* pPixels, const pack_params& p) {
  set_block_pixels_context context;
  set_block_pixels(block_x, block_y, pPixels, p, context);
}

void dxt_image::set_block_pixels(
    uint block_x, uint block_y, const color_quad_u8* pPixels, const pack_params& p,
    set_block_pixels_context& context) {
  element* pElement = &get_element(block_x, block_y, 0);

  if (m_format == cETC1) {
    etc1_block& dst_block = *reinterpret_cast<etc1_block*>(pElement);

#if CRNLIB_USE_RG_ETC1
    rg_etc1::etc1_quality etc_quality = rg_etc1::cHighQuality;
    if (p.m_quality <= cCRNDXTQualityFast)
      etc_quality = rg_etc1::cLowQuality;
    else if (p.m_quality <= cCRNDXTQualityNormal)
      etc_quality = rg_etc1::cMediumQuality;

    rg_etc1::etc1_pack_params pack_params;
    pack_params.m_dithering = p.m_dithering;
    //pack_params.m_perceptual = p.m_perceptual;
    pack_params.m_quality = etc_quality;
    rg_etc1::pack_etc1_block(&dst_block, (uint32*)pPixels, pack_params);
#else
    crn_etc_quality etc_quality = cCRNETCQualitySlow;
    if (p.m_quality <= cCRNDXTQualityFast)
      etc_quality = cCRNETCQualityFast;
    else if (p.m_quality <= cCRNDXTQualityNormal)
      etc_quality = cCRNETCQualityMedium;

    crn_etc1_pack_params pack_params;
    pack_params.m_perceptual = p.m_perceptual;
    pack_params.m_quality = etc_quality;
    pack_params.m_dithering = p.m_dithering;

    pack_etc1_block(dst_block, pPixels, pack_params, context.m_etc1_optimizer);
#endif
  } else if (m_format == cETC2) {
    etc1_block& dst_block = *reinterpret_cast<etc1_block*>(pElement);
    rg_etc1::etc1_pack_params pack_params;
    pack_params.m_dithering = p.m_dithering;
    pack_params.m_quality = p.m_quality <= cCRNDXTQualityFast ? rg_etc1::cLowQuality : p.m_quality <= cCRNDXTQualityNormal ? rg_etc1::cMediumQuality : rg_etc1::cHighQuality;
    rg_etc1::pack_etc1_block(&dst_block, (uint32*)pPixels, pack_params);

  } else if (m_format == cETC2A) {
    rg_etc1::etc1_quality etc_quality = p.m_quality <= cCRNDXTQualityFast ? rg_etc1::cLowQuality : p.m_quality <= cCRNDXTQualityNormal ? rg_etc1::cMediumQuality : rg_etc1::cHighQuality;
    for (uint element_index = 0; element_index < m_num_elements_per_block; element_index++, pElement++) {
      if (m_element_type[element_index] == cAlphaETC2) {
        rg_etc1::etc2a_pack_params pack_params;
        pack_params.m_quality = etc_quality;
        pack_params.comp_index = m_element_component_index[element_index];
        rg_etc1::pack_etc2_alpha(pElement, (uint32*)pPixels, pack_params);
      } else {
        rg_etc1::etc1_pack_params pack_params;
        pack_params.m_dithering = p.m_dithering;
        pack_params.m_quality = etc_quality;
        rg_etc1::pack_etc1_block(pElement, (uint32*)pPixels, pack_params);
      }
    }

  } else if (m_format == cETC1S) {
    crn_etc1_pack_params pack_params;
    pack_params.m_perceptual = p.m_perceptual;
    pack_params.m_dithering = p.m_dithering;
    pack_params.m_quality = p.m_quality <= cCRNDXTQualityFast ? cCRNETCQualityFast : p.m_quality <= cCRNDXTQualityNormal ? cCRNETCQualityMedium : cCRNETCQualitySlow;
    pack_etc1s_block(*(etc1_block*)pElement, pPixels, pack_params);

  } else if (m_format == cETC2AS) {
    for (uint element_index = 0; element_index < m_num_elements_per_block; element_index++, pElement++) {
      if (m_element_type[element_index] == cAlphaETC2) {
        rg_etc1::etc2a_pack_params pack_params;
        pack_params.m_quality = p.m_quality <= cCRNDXTQualityFast ? rg_etc1::cLowQuality : p.m_quality <= cCRNDXTQualityNormal ? rg_etc1::cMediumQuality : rg_etc1::cHighQuality;
        pack_params.comp_index = m_element_component_index[element_index];
        rg_etc1::pack_etc2_alpha(pElement, (uint32*)pPixels, pack_params);
      } else {
        crn_etc1_pack_params pack_params;
        pack_params.m_perceptual = p.m_perceptual;
        pack_params.m_dithering = p.m_dithering;
        pack_params.m_quality = p.m_quality <= cCRNDXTQualityFast ? cCRNETCQualityFast : p.m_quality <= cCRNDXTQualityNormal ? cCRNETCQualityMedium : cCRNETCQualitySlow;
        pack_etc1s_block(*(etc1_block*)pElement, pPixels, pack_params);
      }
    }

  } else
#if CRNLIB_SUPPORT_SQUISH
      if ((p.m_compressor == cCRNDXTCompressorSquish) && ((m_format == cDXT1) || (m_format == cDXT1A) || (m_format == cDXT3) || (m_format == cDXT5) || (m_format == cDXT5A))) {
    uint squish_flags = 0;
    if ((m_format == cDXT1) || (m_format == cDXT1A))
      squish_flags = squish::kDxt1;
    else if (m_format == cDXT3)
      squish_flags = squish::kDxt3;
    else if (m_format == cDXT5A)
      squish_flags = squish::kDxt5A;
    else
      squish_flags = squish::kDxt5;

    if (p.m_perceptual)
      squish_flags |= squish::kColourMetricPerceptual;
    else
      squish_flags |= squish::kColourMetricUniform;

    if (p.m_quality >= cCRNDXTQualityBetter)
      squish_flags |= squish::kColourIterativeClusterFit;
    else if (p.m_quality == cCRNDXTQualitySuperFast)
      squish_flags |= squish::kColourRangeFit;

    color_quad_u8 pixels[cDXTBlockSize * cDXTBlockSize];

    memcpy(pixels, pPixels, sizeof(color_quad_u8) * cDXTBlockSize * cDXTBlockSize);

    if (m_format == cDXT1) {
      for (uint i = 0; i < cDXTBlockSize * cDXTBlockSize; i++)
        pixels[i].a = 255;
    } else if (m_format == cDXT1A) {
      for (uint i = 0; i < cDXTBlockSize * cDXTBlockSize; i++)
        if (pixels[i].a < p.m_dxt1a_alpha_threshold)
          pixels[i].a = 0;
        else
          pixels[i].a = 255;
    }

    squish::Compress(reinterpret_cast<const squish::u8*>(pixels), pElement, squish_flags);
  }

  else
#endif  // CRNLIB_SUPPORT_SQUISH
      // RYG doesn't support DXT1A
      if ((p.m_compressor == cCRNDXTCompressorRYG) && ((m_format == cDXT1) || (m_format == cDXT5) || (m_format == cDXT5A))) {
    color_quad_u8 pixels[cDXTBlockSize * cDXTBlockSize];

    for (uint i = 0; i < cDXTBlockSize * cDXTBlockSize; i++) {
      pixels[i].r = pPixels[i].b;
      pixels[i].g = pPixels[i].g;
      pixels[i].b = pPixels[i].r;

      if (m_format == cDXT1)
        pixels[i].a = 255;
      else
        pixels[i].a = pPixels[i].a;
    }

    if (m_format == cDXT5A)
      ryg_dxt::sCompressDXT5ABlock((sU8*)pElement, (const sU32*)pixels);
    else
      ryg_dxt::sCompressDXTBlock((sU8*)pElement, (const sU32*)pixels, m_format == cDXT5, 0);
  } else if ((p.m_compressor == cCRNDXTCompressorCRNF) && (m_format != cDXT1A)) {
    for (uint element_index = 0; element_index < m_num_elements_per_block; element_index++, pElement++) {
      switch (m_element_type[element_index]) {
        case cColorDXT1: {
          dxt1_block* pDXT1_block = reinterpret_cast<dxt1_block*>(pElement);
          dxt_fast::compress_color_block(pDXT1_block, pPixels, p.m_quality >= cCRNDXTQualityNormal);

          break;
        }
        case cAlphaDXT5: {
          dxt5_block* pDXT5_block = reinterpret_cast<dxt5_block*>(pElement);
          dxt_fast::compress_alpha_block(pDXT5_block, pPixels, m_element_component_index[element_index]);

          break;
        }
        case cAlphaDXT3: {
          const int comp_index = m_element_component_index[element_index];

          dxt3_block* pDXT3_block = reinterpret_cast<dxt3_block*>(pElement);

          for (uint i = 0; i < cDXTBlockSize * cDXTBlockSize; i++)
            pDXT3_block->set_alpha(i & 3, i >> 2, pPixels[i][comp_index], true);

          break;
        }
        default:
          break;
      }
    }
  } else {
    dxt1_endpoint_optimizer& dxt1_optimizer = context.m_dxt1_optimizer;
    dxt5_endpoint_optimizer& dxt5_optimizer = context.m_dxt5_optimizer;

    for (uint element_index = 0; element_index < m_num_elements_per_block; element_index++, pElement++) {
      switch (m_element_type[element_index]) {
        case cColorDXT1: {
          dxt1_block* pDXT1_block = reinterpret_cast<dxt1_block*>(pElement);

          bool pixels_have_alpha = false;
          if (m_format == cDXT1A) {
            for (uint i = 0; i < cDXTBlockSize * cDXTBlockSize; i++)
              if (pPixels[i].a < p.m_dxt1a_alpha_threshold) {
                pixels_have_alpha = true;
                break;
              }
          }

          dxt1_endpoint_optimizer::results results;
          uint8 selectors[cDXTBlockSize * cDXTBlockSize];
          results.m_pSelectors = selectors;

          dxt1_endpoint_optimizer::params params;
          params.m_block_index = block_x + block_y * m_blocks_x;
          params.m_quality = p.m_quality;
          params.m_perceptual = p.m_perceptual;
          params.m_grayscale_sampling = p.m_grayscale_sampling;
          params.m_pixels_have_alpha = pixels_have_alpha;
          params.m_use_alpha_blocks = p.m_use_both_block_types;
          params.m_use_transparent_indices_for_black = p.m_use_transparent_indices_for_black;
          params.m_dxt1a_alpha_threshold = p.m_dxt1a_alpha_threshold;
          params.m_pPixels = pPixels;
          params.m_num_pixels = cDXTBlockSize * cDXTBlockSize;
          params.m_endpoint_caching = p.m_endpoint_caching;

          if ((m_format != cDXT1) && (m_format != cDXT1A))
            params.m_use_alpha_blocks = false;

          if (!dxt1_optimizer.compute(params, results)) {
            CRNLIB_ASSERT(0);
            break;
          }

          pDXT1_block->set_low_color(results.m_low_color);
          pDXT1_block->set_high_color(results.m_high_color);

          for (uint i = 0; i < cDXTBlockSize * cDXTBlockSize; i++)
            pDXT1_block->set_selector(i & 3, i >> 2, selectors[i]);

          break;
        }
        case cAlphaDXT5: {
          dxt5_block* pDXT5_block = reinterpret_cast<dxt5_block*>(pElement);

          dxt5_endpoint_optimizer::results results;

          uint8 selectors[cDXTBlockSize * cDXTBlockSize];
          results.m_pSelectors = selectors;

          dxt5_endpoint_optimizer::params params;
          params.m_block_index = block_x + block_y * m_blocks_x;
          params.m_pPixels = pPixels;
          params.m_num_pixels = cDXTBlockSize * cDXTBlockSize;
          params.m_comp_index = m_element_component_index[element_index];
          params.m_quality = p.m_quality;
          params.m_use_both_block_types = p.m_use_both_block_types;

          if (!dxt5_optimizer.compute(params, results)) {
            CRNLIB_ASSERT(0);
            break;
          }

          pDXT5_block->set_low_alpha(results.m_first_endpoint);
          pDXT5_block->set_high_alpha(results.m_second_endpoint);

          for (uint i = 0; i < cDXTBlockSize * cDXTBlockSize; i++)
            pDXT5_block->set_selector(i & 3, i >> 2, selectors[i]);

          break;
        }
        case cAlphaDXT3: {
          const int comp_index = m_element_component_index[element_index];

          dxt3_block* pDXT3_block = reinterpret_cast<dxt3_block*>(pElement);

          for (uint i = 0; i < cDXTBlockSize * cDXTBlockSize; i++)
            pDXT3_block->set_alpha(i & 3, i >> 2, pPixels[i][comp_index], true);

          break;
        }
        default:
          break;
      }
    }
  }
}

void dxt_image::get_block_endpoints(uint block_x, uint block_y, uint element_index, uint& packed_low_endpoint, uint& packed_high_endpoint) const {
  const element& block = get_element(block_x, block_y, element_index);

  switch (m_element_type[element_index]) {
    case cColorETC1: {
      const etc1_block& src_block = *reinterpret_cast<const etc1_block*>(&block);
      if (src_block.get_diff_bit()) {
        packed_low_endpoint = src_block.get_base5_color();
        packed_high_endpoint = src_block.get_delta3_color();
      } else {
        packed_low_endpoint = src_block.get_base4_color(0);
        packed_high_endpoint = src_block.get_base4_color(1);
      }

      break;
    }
    case cColorDXT1: {
      const dxt1_block& block1 = *reinterpret_cast<const dxt1_block*>(&block);

      packed_low_endpoint = block1.get_low_color();
      packed_high_endpoint = block1.get_high_color();

      break;
    }
    case cAlphaDXT5: {
      const dxt5_block& block5 = *reinterpret_cast<const dxt5_block*>(&block);

      packed_low_endpoint = block5.get_low_alpha();
      packed_high_endpoint = block5.get_high_alpha();

      break;
    }
    case cAlphaDXT3: {
      packed_low_endpoint = 0;
      packed_high_endpoint = 255;

      break;
    }
    default:
      break;
  }
}

int dxt_image::get_block_endpoints(uint block_x, uint block_y, uint element_index, color_quad_u8& low_endpoint, color_quad_u8& high_endpoint, bool scaled) const {
  uint l = 0, h = 0;
  get_block_endpoints(block_x, block_y, element_index, l, h);

  switch (m_element_type[element_index]) {
    case cColorETC1: {
      const etc1_block& src_block = *reinterpret_cast<const etc1_block*>(&get_element(block_x, block_y, element_index));
      if (src_block.get_diff_bit()) {
        low_endpoint = etc1_block::unpack_color5(static_cast<uint16>(l), scaled);
        etc1_block::unpack_color5(high_endpoint, static_cast<uint16>(l), static_cast<uint16>(h), scaled);
      } else {
        low_endpoint = etc1_block::unpack_color4(static_cast<uint16>(l), scaled);
        high_endpoint = etc1_block::unpack_color4(static_cast<uint16>(h), scaled);
      }

      return -1;
    }
    case cColorDXT1: {
      uint r, g, b;

      dxt1_block::unpack_color(r, g, b, static_cast<uint16>(l), scaled);
      low_endpoint.r = static_cast<uint8>(r);
      low_endpoint.g = static_cast<uint8>(g);
      low_endpoint.b = static_cast<uint8>(b);

      dxt1_block::unpack_color(r, g, b, static_cast<uint16>(h), scaled);
      high_endpoint.r = static_cast<uint8>(r);
      high_endpoint.g = static_cast<uint8>(g);
      high_endpoint.b = static_cast<uint8>(b);

      return -1;
    }
    case cAlphaDXT5: {
      const int component = m_element_component_index[element_index];

      low_endpoint[component] = static_cast<uint8>(l);
      high_endpoint[component] = static_cast<uint8>(h);

      return component;
    }
    case cAlphaDXT3: {
      const int component = m_element_component_index[element_index];

      low_endpoint[component] = static_cast<uint8>(l);
      high_endpoint[component] = static_cast<uint8>(h);

      return component;
    }
    default:
      break;
  }

  return 0;
}

uint dxt_image::get_block_colors(uint block_x, uint block_y, uint element_index, color_quad_u8* pColors, uint subblock_index) {
  const element& block = get_element(block_x, block_y, element_index);

  switch (m_element_type[element_index]) {
    case cColorETC1: {
      const etc1_block& src_block = *reinterpret_cast<const etc1_block*>(&get_element(block_x, block_y, element_index));
      const uint table_index0 = src_block.get_inten_table(0);
      const uint table_index1 = src_block.get_inten_table(1);
      if (src_block.get_diff_bit()) {
        const uint16 base_color5 = src_block.get_base5_color();
        const uint16 delta_color3 = src_block.get_delta3_color();
        if (subblock_index)
          etc1_block::get_diff_subblock_colors(pColors, base_color5, delta_color3, table_index1);
        else
          etc1_block::get_diff_subblock_colors(pColors, base_color5, table_index0);
      } else {
        if (subblock_index) {
          const uint16 base_color4_1 = src_block.get_base4_color(1);
          etc1_block::get_abs_subblock_colors(pColors, base_color4_1, table_index1);
        } else {
          const uint16 base_color4_0 = src_block.get_base4_color(0);
          etc1_block::get_abs_subblock_colors(pColors, base_color4_0, table_index0);
        }
      }

      break;
    }
    case cColorDXT1: {
      const dxt1_block& block1 = *reinterpret_cast<const dxt1_block*>(&block);
      return dxt1_block::get_block_colors(pColors, static_cast<uint16>(block1.get_low_color()), static_cast<uint16>(block1.get_high_color()));
    }
    case cAlphaDXT5: {
      const dxt5_block& block5 = *reinterpret_cast<const dxt5_block*>(&block);

      uint values[cDXT5SelectorValues];

      const uint n = dxt5_block::get_block_values(values, block5.get_low_alpha(), block5.get_high_alpha());

      const int comp_index = m_element_component_index[element_index];
      for (uint i = 0; i < n; i++)
        pColors[i][comp_index] = static_cast<uint8>(values[i]);

      return n;
    }
    case cAlphaDXT3: {
      const int comp_index = m_element_component_index[element_index];
      for (uint i = 0; i < 16; i++)
        pColors[i][comp_index] = static_cast<uint8>((i << 4) | i);

      return 16;
    }
    default:
      break;
  }

  return 0;
}

uint dxt_image::get_subblock_index(uint x, uint y, uint element_index) const {
  if (m_element_type[element_index] != cColorETC1)
    return 0;

  const uint block_x = x >> cDXTBlockShift;
  const uint block_y = y >> cDXTBlockShift;

  const element& block = get_element(block_x, block_y, element_index);

  const etc1_block& src_block = *reinterpret_cast<const etc1_block*>(&block);
  if (src_block.get_flip_bit()) {
    return ((y & 3) >= 2) ? 1 : 0;
  } else {
    return ((x & 3) >= 2) ? 1 : 0;
  }
}

uint dxt_image::get_total_subblocks(uint element_index) const {
  return (m_element_type[element_index] == cColorETC1) ? 2 : 0;
}

uint dxt_image::get_selector(uint x, uint y, uint element_index) const {
  CRNLIB_ASSERT((x < m_width) && (y < m_height));

  const uint block_x = x >> cDXTBlockShift;
  const uint block_y = y >> cDXTBlockShift;

  const element& block = get_element(block_x, block_y, element_index);

  switch (m_element_type[element_index]) {
    case cColorETC1: {
      const etc1_block& src_block = *reinterpret_cast<const etc1_block*>(&block);
      return src_block.get_selector(x & 3, y & 3);
    }
    case cColorDXT1: {
      const dxt1_block& block1 = *reinterpret_cast<const dxt1_block*>(&block);
      return block1.get_selector(x & 3, y & 3);
    }
    case cAlphaDXT5: {
      const dxt5_block& block5 = *reinterpret_cast<const dxt5_block*>(&block);
      return block5.get_selector(x & 3, y & 3);
    }
    case cAlphaDXT3: {
      const dxt3_block& block3 = *reinterpret_cast<const dxt3_block*>(&block);
      return block3.get_alpha(x & 3, y & 3, false);
    }
    default:
      break;
  }

  return 0;
}

void dxt_image::change_dxt1_to_dxt1a() {
  if (m_format == cDXT1)
    m_format = cDXT1A;
}

void dxt_image::flip_col(uint x) {
  const uint other_x = (m_blocks_x - 1) - x;
  for (uint y = 0; y < m_blocks_y; y++) {
    for (uint e = 0; e < get_elements_per_block(); e++) {
      element tmp[2] = {get_element(x, y, e), get_element(other_x, y, e)};

      for (uint i = 0; i < 2; i++) {
        switch (get_element_type(e)) {
          case cColorDXT1:
            reinterpret_cast<dxt1_block*>(&tmp[i])->flip_x();
            break;
          case cAlphaDXT3:
            reinterpret_cast<dxt3_block*>(&tmp[i])->flip_x();
            break;
          case cAlphaDXT5:
            reinterpret_cast<dxt5_block*>(&tmp[i])->flip_x();
            break;
          default:
            CRNLIB_ASSERT(0);
            break;
        }
      }

      get_element(x, y, e) = tmp[1];
      get_element(other_x, y, e) = tmp[0];
    }
  }
}

void dxt_image::flip_row(uint y) {
  const uint other_y = (m_blocks_y - 1) - y;
  for (uint x = 0; x < m_blocks_x; x++) {
    for (uint e = 0; e < get_elements_per_block(); e++) {
      element tmp[2] = {get_element(x, y, e), get_element(x, other_y, e)};

      for (uint i = 0; i < 2; i++) {
        switch (get_element_type(e)) {
          case cColorDXT1:
            reinterpret_cast<dxt1_block*>(&tmp[i])->flip_y();
            break;
          case cAlphaDXT3:
            reinterpret_cast<dxt3_block*>(&tmp[i])->flip_y();
            break;
          case cAlphaDXT5:
            reinterpret_cast<dxt5_block*>(&tmp[i])->flip_y();
            break;
          default:
            CRNLIB_ASSERT(0);
            break;
        }
      }

      get_element(x, y, e) = tmp[1];
      get_element(x, other_y, e) = tmp[0];
    }
  }
}

bool dxt_image::can_flip(uint axis_index) {
  if (m_format == cETC1 || m_format == cETC2 || m_format == cETC2A || m_format == cETC1S || m_format == cETC2AS) {
    // Can't reliably flip ETCn textures (because of asymmetry in the 555/333 differential coding of subblock colors).
    return false;
  }

  uint d;
  if (axis_index)
    d = m_height;
  else
    d = m_width;

  if (d & 3) {
    if (d > 4)
      return false;
  }

  return true;
}

bool dxt_image::flip_x() {
  if (m_format == cETC1 || m_format == cETC2 || m_format == cETC2A || m_format == cETC1S || m_format == cETC2AS) {
    // Can't reliably flip ETCn textures (because of asymmetry in the 555/333 differential coding of subblock colors).
    return false;
  }

  if ((m_width & 3) && (m_width > 4))
    return false;

  if (m_width == 1)
    return true;

  const uint mid_x = m_blocks_x / 2;

  for (uint x = 0; x < mid_x; x++)
    flip_col(x);

  if (m_blocks_x & 1) {
    const uint w = math::minimum(m_width, 4U);
    for (uint y = 0; y < m_blocks_y; y++) {
      for (uint e = 0; e < get_elements_per_block(); e++) {
        element tmp(get_element(mid_x, y, e));
        switch (get_element_type(e)) {
          case cColorDXT1:
            reinterpret_cast<dxt1_block*>(&tmp)->flip_x(w, 4);
            break;
          case cAlphaDXT3:
            reinterpret_cast<dxt3_block*>(&tmp)->flip_x(w, 4);
            break;
          case cAlphaDXT5:
            reinterpret_cast<dxt5_block*>(&tmp)->flip_x(w, 4);
            break;
          default:
            CRNLIB_ASSERT(0);
            break;
        }
        get_element(mid_x, y, e) = tmp;
      }
    }
  }

  return true;
}

bool dxt_image::flip_y() {
  if (m_format == cETC1 || m_format == cETC2 || m_format == cETC2A || m_format == cETC1S || m_format == cETC2AS) {
    // Can't reliably flip ETCn textures (because of asymmetry in the 555/333 differential coding of subblock colors).
    return false;
  }

  if ((m_height & 3) && (m_height > 4))
    return false;

  if (m_height == 1)
    return true;

  const uint mid_y = m_blocks_y / 2;

  for (uint y = 0; y < mid_y; y++)
    flip_row(y);

  if (m_blocks_y & 1) {
    const uint h = math::minimum(m_height, 4U);
    for (uint x = 0; x < m_blocks_x; x++) {
      for (uint e = 0; e < get_elements_per_block(); e++) {
        element tmp(get_element(x, mid_y, e));
        switch (get_element_type(e)) {
          case cColorDXT1:
            reinterpret_cast<dxt1_block*>(&tmp)->flip_y(4, h);
            break;
          case cAlphaDXT3:
            reinterpret_cast<dxt3_block*>(&tmp)->flip_y(4, h);
            break;
          case cAlphaDXT5:
            reinterpret_cast<dxt5_block*>(&tmp)->flip_y(4, h);
            break;
          default:
            CRNLIB_ASSERT(0);
            break;
        }
        get_element(x, mid_y, e) = tmp;
      }
    }
  }

  return true;
}

}  // namespace crnlib
