// 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)) 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: { m_element_type[0] = cColorETC1; m_element_component_index[0] = -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(data); init_task_params* pInit_params = static_cast(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(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(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(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: 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(&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(&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(&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(l), true)); color_quad_u8 c1(dxt1_block::unpack_color(static_cast(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(&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(l); break; case 1: result[comp_index] = static_cast(h); break; case 2: result[comp_index] = static_cast((l * 6 + h ) / 7); break; case 3: result[comp_index] = static_cast((l * 5 + h * 2) / 7); break; case 4: result[comp_index] = static_cast((l * 4 + h * 3) / 7); break; case 5: result[comp_index] = static_cast((l * 3 + h * 4) / 7); break; case 6: result[comp_index] = static_cast((l * 2 + h * 5) / 7); break; case 7: result[comp_index] = static_cast((l + h * 6) / 7); break; } } else { switch (s) { case 0: result[comp_index] = static_cast(l); break; case 1: result[comp_index] = static_cast(h); break; case 2: result[comp_index] = static_cast((l * 4 + h ) / 5); break; case 3: result[comp_index] = static_cast((l * 3 + h * 2) / 5); break; case 4: result[comp_index] = static_cast((l * 2 + h * 3) / 5); break; case 5: result[comp_index] = static_cast((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(&get_element(block_x, block_y, element_index)); result[comp_index] = static_cast(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(&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(&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(&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(&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(pElement); color_quad_u8 colors[cDXT1SelectorValues]; const uint n = pDXT1_block->get_block_colors(colors, static_cast(pDXT1_block->get_low_color()), static_cast(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(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(values[i] - 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(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(&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 cColorDXT1: { const dxt1_block* pDXT1_block = reinterpret_cast(pElement); color_quad_u8 colors[cDXT1SelectorValues]; pDXT1_block->get_block_colors(colors, static_cast(pDXT1_block->get_low_color()), static_cast(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(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(values[s]); } break; } case cAlphaDXT3: { const dxt3_block* pDXT3_block = reinterpret_cast(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(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(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 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(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, 0); 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(pElement); dxt_fast::compress_color_block(pDXT1_block, pPixels, p.m_quality >= cCRNDXTQualityNormal); break; } case cAlphaDXT5: { dxt5_block* pDXT5_block = reinterpret_cast(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(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(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; params.m_color_weights[0] = p.m_color_weights[0]; params.m_color_weights[1] = p.m_color_weights[1]; params.m_color_weights[2] = p.m_color_weights[2]; 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(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(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(&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(&block); packed_low_endpoint = block1.get_low_color(); packed_high_endpoint = block1.get_high_color(); break; } case cAlphaDXT5: { const dxt5_block& block5 = *reinterpret_cast(&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(&get_element(block_x, block_y, element_index)); if (src_block.get_diff_bit()) { low_endpoint = etc1_block::unpack_color5(static_cast(l), scaled); etc1_block::unpack_color5(high_endpoint, static_cast(l), static_cast(h), scaled); } else { low_endpoint = etc1_block::unpack_color4(static_cast(l), scaled); high_endpoint = etc1_block::unpack_color4(static_cast(h), scaled); } return -1; } case cColorDXT1: { uint r, g, b; dxt1_block::unpack_color(r, g, b, static_cast(l), scaled); low_endpoint.r = static_cast(r); low_endpoint.g = static_cast(g); low_endpoint.b = static_cast(b); dxt1_block::unpack_color(r, g, b, static_cast(h), scaled); high_endpoint.r = static_cast(r); high_endpoint.g = static_cast(g); high_endpoint.b = static_cast(b); return -1; } case cAlphaDXT5: { const int component = m_element_component_index[element_index]; low_endpoint[component] = static_cast(l); high_endpoint[component] = static_cast(h); return component; } case cAlphaDXT3: { const int component = m_element_component_index[element_index]; low_endpoint[component] = static_cast(l); high_endpoint[component] = static_cast(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(&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(&block); return dxt1_block::get_block_colors(pColors, static_cast(block1.get_low_color()), static_cast(block1.get_high_color())); } case cAlphaDXT5: { const dxt5_block& block5 = *reinterpret_cast(&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(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((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(&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(&block); return src_block.get_selector(x & 3, y & 3); } case cColorDXT1: { const dxt1_block& block1 = *reinterpret_cast(&block); return block1.get_selector(x & 3, y & 3); } case cAlphaDXT5: { const dxt5_block& block5 = *reinterpret_cast(&block); return block5.get_selector(x & 3, y & 3); } case cAlphaDXT3: { const dxt3_block& block3 = *reinterpret_cast(&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(&tmp[i])->flip_x(); break; case cAlphaDXT3: reinterpret_cast(&tmp[i])->flip_x(); break; case cAlphaDXT5: reinterpret_cast(&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(&tmp[i])->flip_y(); break; case cAlphaDXT3: reinterpret_cast(&tmp[i])->flip_y(); break; case cAlphaDXT5: reinterpret_cast(&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) { // Can't reliably flip ETC1 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) { // Can't reliably flip ETC1 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(&tmp)->flip_x(w, 4); break; case cAlphaDXT3: reinterpret_cast(&tmp)->flip_x(w, 4); break; case cAlphaDXT5: reinterpret_cast(&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) { // Can't reliably flip ETC1 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(&tmp)->flip_y(4, h); break; case cAlphaDXT3: reinterpret_cast(&tmp)->flip_y(4, h); break; case cAlphaDXT5: reinterpret_cast(&tmp)->flip_y(4, h); break; default: CRNLIB_ASSERT(0); break; } get_element(x, mid_y, e) = tmp; } } } return true; } } // namespace crnlib