xenia-canary/third_party/crunch/crnlib/crn_mipmapped_texture.cpp

3375 lines
104 KiB
C++

// File: crn_dds_texture.cpp - Actually supports both .DDS and .KTX. Probably will rename this eventually.
// See Copyright Notice and license at the end of inc/crnlib.h
#include "crn_core.h"
#include "crn_mipmapped_texture.h"
#include "crn_cfile_stream.h"
#include "crn_image_utils.h"
#include "crn_console.h"
#include "crn_texture_comp.h"
#include "crn_ktx_texture.h"
#define CRND_HEADER_FILE_ONLY
#include "../inc/crn_decomp.h"
namespace crnlib
{
const vec2I g_vertical_cross_image_offsets[6] = { vec2I(2, 1), vec2I(0, 1), vec2I(1, 0), vec2I(1, 2), vec2I(1, 1), vec2I(1, 3) };
mip_level::mip_level() :
m_width(0),
m_height(0),
m_comp_flags(pixel_format_helpers::cDefaultCompFlags),
m_format(PIXEL_FMT_INVALID),
m_pImage(NULL),
m_pDXTImage(NULL),
m_orient_flags(cDefaultOrientationFlags)
{
}
mip_level::mip_level(const mip_level& other) :
m_width(0),
m_height(0),
m_comp_flags(pixel_format_helpers::cDefaultCompFlags),
m_format(PIXEL_FMT_INVALID),
m_pImage(NULL),
m_pDXTImage(NULL),
m_orient_flags(cDefaultOrientationFlags)
{
*this = other;
}
mip_level& mip_level::operator= (const mip_level& rhs)
{
clear();
m_width = rhs.m_width;
m_height = rhs.m_height;
m_comp_flags = rhs.m_comp_flags;
m_format = rhs.m_format;
m_orient_flags = rhs.m_orient_flags;
if (rhs.m_pImage)
m_pImage = crnlib_new<image_u8>(*rhs.m_pImage);
if (rhs.m_pDXTImage)
m_pDXTImage = crnlib_new<dxt_image>(*rhs.m_pDXTImage);
return *this;
}
mip_level::~mip_level()
{
crnlib_delete(m_pImage);
crnlib_delete(m_pDXTImage);
}
void mip_level::clear()
{
m_width = 0;
m_height = 0;
m_comp_flags = pixel_format_helpers::cDefaultCompFlags;
m_format = PIXEL_FMT_INVALID;
m_orient_flags = cDefaultOrientationFlags;
if (m_pImage)
{
crnlib_delete(m_pImage);
m_pImage = NULL;
}
if (m_pDXTImage)
{
crnlib_delete(m_pDXTImage);
m_pDXTImage = NULL;
}
}
void mip_level::assign(image_u8* p, pixel_format fmt, orientation_flags_t orient_flags)
{
CRNLIB_ASSERT(p);
clear();
m_pImage = p;
m_width = p->get_width();
m_height = p->get_height();
m_orient_flags = orient_flags;
if (fmt != PIXEL_FMT_INVALID)
m_format = fmt;
else
{
if (p->is_grayscale())
m_format = p->is_component_valid(3) ? PIXEL_FMT_A8L8 : PIXEL_FMT_L8;
else
m_format = p->is_component_valid(3) ? PIXEL_FMT_A8R8G8B8 : PIXEL_FMT_R8G8B8;
}
m_comp_flags = p->get_comp_flags(); //pixel_format_helpers::get_component_flags(m_format);
}
void mip_level::assign(dxt_image* p, pixel_format fmt, orientation_flags_t orient_flags)
{
CRNLIB_ASSERT(p);
clear();
m_pDXTImage = p;
m_width = p->get_width();
m_height = p->get_height();
m_orient_flags = orient_flags;
if (fmt != PIXEL_FMT_INVALID)
m_format = fmt;
else
m_format = pixel_format_helpers::from_dxt_format(p->get_format());
m_comp_flags = pixel_format_helpers::get_component_flags(m_format);
}
bool mip_level::pack_to_dxt(const image_u8& img, pixel_format fmt, bool cook, const dxt_image::pack_params& orig_params, orientation_flags_t orient_flags)
{
CRNLIB_ASSERT(pixel_format_helpers::is_dxt(fmt));
if (!pixel_format_helpers::is_dxt(fmt))
return false;
dxt_image::pack_params p(orig_params);
if (pixel_format_helpers::is_pixel_format_non_srgb(fmt) || (img.get_comp_flags() & pixel_format_helpers::cCompFlagNormalMap) || (img.get_comp_flags() & pixel_format_helpers::cCompFlagLumaChroma))
{
// Disable perceptual colorspace metrics when packing to swizzled or non-RGB pixel formats.
p.m_perceptual = false;
}
image_u8 tmp_img(img);
clear();
m_format = fmt;
if (cook)
cook_image(tmp_img);
if ((pixel_format_helpers::is_alpha_only(fmt)) && (!tmp_img.has_alpha()))
tmp_img.set_alpha_to_luma();
dxt_format dxt_fmt = pixel_format_helpers::get_dxt_format(fmt);
dxt_image* pDXT_image = crnlib_new<dxt_image>();
if (!pDXT_image->init(dxt_fmt, tmp_img, p))
{
clear();
return false;
}
assign(pDXT_image, fmt, orient_flags);
return true;
}
bool mip_level::pack_to_dxt(pixel_format fmt, bool cook, const dxt_image::pack_params& p)
{
CRNLIB_ASSERT(pixel_format_helpers::is_dxt(fmt));
if (!pixel_format_helpers::is_dxt(fmt))
return false;
image_u8 tmp_img;
image_u8* pImage = get_unpacked_image(tmp_img, cUnpackFlagUncook);
return pack_to_dxt(*pImage, fmt, cook, p, m_orient_flags);
}
bool mip_level::unpack_from_dxt(bool uncook)
{
if (!m_pDXTImage)
return false;
image_u8* pNew_img = crnlib_new<image_u8>();
image_u8* pImg = get_unpacked_image(*pNew_img, uncook ? cUnpackFlagUncook : 0);
pImg;
CRNLIB_ASSERT(pImg == pNew_img);
assign(pNew_img, PIXEL_FMT_INVALID, m_orient_flags);
return true;
}
bool mip_level::is_flipped() const
{
return ((m_orient_flags & (cOrientationFlagXFlipped | cOrientationFlagYFlipped)) != 0);
}
bool mip_level::is_x_flipped() const
{
return ((m_orient_flags & cOrientationFlagXFlipped) != 0);
}
bool mip_level::is_y_flipped() const
{
return ((m_orient_flags & cOrientationFlagYFlipped) != 0);
}
bool mip_level::can_unflip_without_unpacking() const
{
if (!is_valid())
return false;
if (!is_packed())
return true;
bool can_unflip = true;
if (m_orient_flags & cOrientationFlagXFlipped)
{
if (!m_pDXTImage->can_flip(0))
can_unflip = false;
}
if (m_orient_flags & cOrientationFlagYFlipped)
{
if (!m_pDXTImage->can_flip(1))
can_unflip = false;
}
return can_unflip;
}
bool mip_level::unflip(bool allow_unpacking_to_flip, bool uncook_if_necessary_to_unpack)
{
if (!is_valid())
return false;
if (!is_flipped())
return false;
if (is_packed())
{
if (can_unflip_without_unpacking())
{
if (m_orient_flags & cOrientationFlagXFlipped)
{
m_pDXTImage->flip_x();
m_orient_flags = static_cast<orientation_flags_t>(m_orient_flags & ~cOrientationFlagXFlipped);
}
if (m_orient_flags & cOrientationFlagYFlipped)
{
m_pDXTImage->flip_y();
m_orient_flags = static_cast<orientation_flags_t>(m_orient_flags & ~cOrientationFlagYFlipped);
}
return true;
}
if (!allow_unpacking_to_flip)
return false;
}
unpack_from_dxt(uncook_if_necessary_to_unpack);
if (m_orient_flags & cOrientationFlagXFlipped)
{
m_pImage->flip_x();
m_orient_flags = static_cast<orientation_flags_t>(m_orient_flags & ~cOrientationFlagXFlipped);
}
if (m_orient_flags & cOrientationFlagYFlipped)
{
m_pImage->flip_y();
m_orient_flags = static_cast<orientation_flags_t>(m_orient_flags & ~cOrientationFlagYFlipped);
}
return true;
}
bool mip_level::set_alpha_to_luma()
{
if (m_pDXTImage)
unpack_from_dxt(true);
m_pImage->set_alpha_to_luma();
m_comp_flags = m_pImage->get_comp_flags();
if (m_pImage->is_grayscale())
m_format = PIXEL_FMT_A8L8;
else
m_format = PIXEL_FMT_A8R8G8B8;
return true;
}
bool mip_level::convert(image_utils::conversion_type conv_type)
{
if (m_pDXTImage)
unpack_from_dxt(true);
image_utils::convert_image(*m_pImage, conv_type);
m_comp_flags = m_pImage->get_comp_flags();
if (m_pImage->is_grayscale())
m_format = m_pImage->has_alpha() ? PIXEL_FMT_A8L8 : PIXEL_FMT_L8;
else
m_format = m_pImage->has_alpha() ? PIXEL_FMT_A8R8G8B8 : PIXEL_FMT_R8G8B8;
return true;
}
bool mip_level::convert(pixel_format fmt, bool cook, const dxt_image::pack_params& p)
{
if (pixel_format_helpers::is_dxt(fmt))
return pack_to_dxt(fmt, cook, p);
image_u8 tmp_img;
image_u8* pImg = get_unpacked_image(tmp_img, cUnpackFlagUncook);
image_u8* pImage = crnlib_new<image_u8>();
pImage->set_comp_flags(pixel_format_helpers::get_component_flags(fmt));
if (!pImage->resize(pImg->get_width(), pImg->get_height()))
return false;
for (uint y = 0; y < pImg->get_height(); y++)
{
for (uint x = 0; x < pImg->get_width(); x++)
{
color_quad_u8 c((*pImg)(x, y));
if ((pixel_format_helpers::is_alpha_only(fmt)) && (!pImg->has_alpha()))
{
c.a = static_cast<uint8>(c.get_luma());
}
else
{
if (pImage->is_grayscale())
{
uint8 g = static_cast<uint8>(c.get_luma());
c.r = g;
c.g = g;
c.b = g;
}
if (!pImage->is_component_valid(3))
c.a = 255;
}
(*pImage)(x, y) = c;
}
}
assign(pImage, fmt, m_orient_flags);
return true;
}
void mip_level::cook_image(image_u8& img) const
{
image_utils::conversion_type conv_type = image_utils::get_conversion_type(true, m_format);
if (conv_type != image_utils::cConversion_Invalid)
image_utils::convert_image(img, conv_type);
}
void mip_level::uncook_image(image_u8& img) const
{
image_utils::conversion_type conv_type = image_utils::get_conversion_type(false, m_format);
if (conv_type != image_utils::cConversion_Invalid)
image_utils::convert_image(img, conv_type);
}
image_u8* mip_level::get_unpacked_image(image_u8& tmp, uint unpack_flags) const
{
if (!is_valid())
return NULL;
if (m_pDXTImage)
{
m_pDXTImage->unpack(tmp);
tmp.set_comp_flags(m_comp_flags);
if (unpack_flags & cUnpackFlagUncook)
uncook_image(tmp);
}
else if ((unpack_flags & cUnpackFlagUnflip) && (m_orient_flags & (cOrientationFlagXFlipped | cOrientationFlagYFlipped)))
tmp = *m_pImage;
else
return m_pImage;
if (unpack_flags & cUnpackFlagUnflip)
{
if (m_orient_flags & cOrientationFlagXFlipped) tmp.flip_x();
if (m_orient_flags & cOrientationFlagYFlipped) tmp.flip_y();
}
return &tmp;
}
bool mip_level::flip_x()
{
if (!is_valid())
return false;
if (m_pDXTImage)
return m_pDXTImage->flip_x();
else if (m_pImage)
{
m_pImage->flip_x();
return true;
}
return false;
}
bool mip_level::flip_y()
{
if (!is_valid())
return false;
if (m_pDXTImage)
return m_pDXTImage->flip_y();
else if (m_pImage)
{
m_pImage->flip_y();
return true;
}
return false;
}
// -------------------------------------------------------------------------
mipmapped_texture::mipmapped_texture() :
m_width(0),
m_height(0),
m_comp_flags(pixel_format_helpers::cDefaultCompFlags),
m_format(PIXEL_FMT_INVALID),
m_source_file_type(texture_file_types::cFormatInvalid)
{
}
mipmapped_texture::~mipmapped_texture()
{
free_all_mips();
}
void mipmapped_texture::clear()
{
free_all_mips();
m_name.clear();
m_width = 0;
m_height = 0;
m_comp_flags = pixel_format_helpers::cDefaultCompFlags;
m_format = PIXEL_FMT_INVALID;
m_source_file_type = texture_file_types::cFormatInvalid;
m_last_error.clear();
}
void mipmapped_texture::free_all_mips()
{
for (uint i = 0; i < m_faces.size(); i++)
for (uint j = 0; j < m_faces[i].size(); j++)
crnlib_delete(m_faces[i][j]);
m_faces.clear();
}
mipmapped_texture::mipmapped_texture(const mipmapped_texture& other) :
m_width(0),
m_height(0),
m_comp_flags(pixel_format_helpers::cDefaultCompFlags),
m_format(PIXEL_FMT_INVALID)
{
*this = other;
}
mipmapped_texture& mipmapped_texture::operator= (const mipmapped_texture& rhs)
{
if (this == &rhs)
return *this;
clear();
m_name = rhs.m_name;
m_width = rhs.m_width;
m_height = rhs.m_height;
m_comp_flags = rhs.m_comp_flags;
m_format = rhs.m_format;
m_faces.resize(rhs.m_faces.size());
for (uint i = 0; i < m_faces.size(); i++)
{
m_faces[i].resize(rhs.m_faces[i].size());
for (uint j = 0; j < rhs.m_faces[i].size(); j++)
m_faces[i][j] = crnlib_new<mip_level>(*rhs.m_faces[i][j]);
}
CRNLIB_ASSERT((!is_valid()) || check());
return *this;
}
bool mipmapped_texture::read_dds(data_stream_serializer& serializer)
{
if (!read_dds_internal(serializer))
{
clear();
return false;
}
return true;
}
bool mipmapped_texture::read_dds_internal(data_stream_serializer& serializer)
{
CRNLIB_ASSERT(serializer.get_little_endian());
clear();
set_last_error("Not a DDS file");
uint8 hdr[4];
if (!serializer.read(hdr, sizeof(hdr)))
return false;
if (memcmp(hdr, "DDS ", 4) != 0)
return false;
DDSURFACEDESC2 desc;
if (!serializer.read(&desc, sizeof(desc)))
return false;
if (!c_crnlib_little_endian_platform)
utils::endian_switch_dwords(reinterpret_cast<uint32*>(&desc), sizeof(desc) / sizeof(uint32));
if (desc.dwSize != sizeof(desc))
return false;
if ((!desc.dwHeight) || (!desc.dwWidth) || (desc.dwHeight > cDDSMaxImageDimensions) || (desc.dwWidth > cDDSMaxImageDimensions))
return false;
m_width = desc.dwWidth;
m_height = desc.dwHeight;
uint num_mip_levels = 1;
if ((desc.dwFlags & DDSD_MIPMAPCOUNT) && (desc.ddsCaps.dwCaps & DDSCAPS_MIPMAP) && (desc.dwMipMapCount))
{
num_mip_levels = desc.dwMipMapCount;
if (num_mip_levels > utils::compute_max_mips(desc.dwWidth, desc.dwHeight))
return false;
}
uint num_faces = 1;
if (desc.ddsCaps.dwCaps & DDSCAPS_COMPLEX)
{
if (desc.ddsCaps.dwCaps2 & DDSCAPS2_CUBEMAP)
{
const uint all_faces_mask = DDSCAPS2_CUBEMAP_POSITIVEX|DDSCAPS2_CUBEMAP_NEGATIVEX|DDSCAPS2_CUBEMAP_POSITIVEY|DDSCAPS2_CUBEMAP_NEGATIVEY|DDSCAPS2_CUBEMAP_POSITIVEZ|DDSCAPS2_CUBEMAP_NEGATIVEZ;
if ((desc.ddsCaps.dwCaps2 & all_faces_mask) != all_faces_mask)
{
set_last_error("Incomplete cubemaps unsupported");
return false;
}
num_faces = 6;
}
else if (desc.ddsCaps.dwCaps2 & DDSCAPS2_VOLUME)
{
set_last_error("Volume textures unsupported");
return false;
}
}
if (desc.ddpfPixelFormat.dwFlags & DDPF_PALETTEINDEXED8)
{
// It's difficult to even make P8 textures with existing tools:
// nvdxt just hangs
// dxtex.exe just makes all-white textures
// So screw it.
set_last_error("Palettized textures unsupported");
return false;
}
dxt_format dxt_fmt = cDXTInvalid;
if (desc.ddpfPixelFormat.dwFlags & DDPF_FOURCC)
{
// http://code.google.com/p/nvidia-texture-tools/issues/detail?id=41
// ATI2 YX: 0 (0x00000000)
// ATI2 XY: 1498952257 (0x59583241) (BC5)
// ATI Compressonator obeys this stuff, nvidia's tools (like readdxt) don't - oh great
switch (desc.ddpfPixelFormat.dwFourCC)
{
case PIXEL_FMT_DXT1:
{
m_format = PIXEL_FMT_DXT1;
dxt_fmt = cDXT1;
break;
}
case PIXEL_FMT_DXT2:
case PIXEL_FMT_DXT3:
{
m_format = PIXEL_FMT_DXT3;
dxt_fmt = cDXT3;
break;
}
case PIXEL_FMT_DXT4:
case PIXEL_FMT_DXT5:
{
switch (desc.ddpfPixelFormat.dwRGBBitCount)
{
case PIXEL_FMT_DXT5_CCxY:
m_format = PIXEL_FMT_DXT5_CCxY;
break;
case PIXEL_FMT_DXT5_xGxR:
m_format = PIXEL_FMT_DXT5_xGxR;
break;
case PIXEL_FMT_DXT5_xGBR:
m_format = PIXEL_FMT_DXT5_xGBR;
break;
case PIXEL_FMT_DXT5_AGBR:
m_format = PIXEL_FMT_DXT5_AGBR;
break;
default:
m_format = PIXEL_FMT_DXT5;
break;
}
dxt_fmt = cDXT5;
break;
}
case PIXEL_FMT_3DC:
{
if (desc.ddpfPixelFormat.dwRGBBitCount == CRNLIB_PIXEL_FMT_FOURCC('A', '2', 'X', 'Y'))
{
dxt_fmt = cDXN_XY;
m_format = PIXEL_FMT_DXN;
}
else
{
dxt_fmt = cDXN_YX; // aka ATI2
m_format = PIXEL_FMT_3DC;
}
break;
}
case PIXEL_FMT_DXT5A:
{
m_format = PIXEL_FMT_DXT5A;
dxt_fmt = cDXT5A;
break;
}
case PIXEL_FMT_ETC1:
{
m_format = PIXEL_FMT_ETC1;
dxt_fmt = cETC1;
break;
}
default:
{
dynamic_string err_msg(cVarArg, "Unsupported DDS FOURCC format: 0x%08X", desc.ddpfPixelFormat.dwFourCC);
set_last_error(err_msg.get_ptr());
return false;
}
}
}
else if ((desc.ddpfPixelFormat.dwRGBBitCount < 8) || (desc.ddpfPixelFormat.dwRGBBitCount > 32) || (desc.ddpfPixelFormat.dwRGBBitCount & 7))
{
set_last_error("Unsupported bit count");
return false;
}
else if (desc.ddpfPixelFormat.dwFlags & DDPF_RGB)
{
if (desc.ddpfPixelFormat.dwFlags & DDPF_LUMINANCE)
{
if (desc.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS)
m_format = PIXEL_FMT_A8L8;
else
m_format = PIXEL_FMT_L8;
}
else if (desc.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS)
m_format = PIXEL_FMT_A8R8G8B8;
else
m_format = PIXEL_FMT_R8G8B8;
}
else if (desc.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS)
{
if (desc.ddpfPixelFormat.dwFlags & DDPF_LUMINANCE)
m_format = PIXEL_FMT_A8L8;
else
m_format = PIXEL_FMT_A8;
}
else if (desc.ddpfPixelFormat.dwFlags & DDPF_LUMINANCE)
{
m_format = PIXEL_FMT_L8;
}
else if (desc.ddpfPixelFormat.dwFlags & DDPF_ALPHA)
{
m_format = PIXEL_FMT_A8;
}
else
{
set_last_error("Unsupported format");
return false;
}
m_comp_flags = pixel_format_helpers::get_component_flags(m_format);
uint bits_per_pixel = desc.ddpfPixelFormat.dwRGBBitCount;
if (desc.ddpfPixelFormat.dwFlags & DDPF_FOURCC)
bits_per_pixel = pixel_format_helpers::get_bpp(m_format);
set_last_error("Load failed");
uint default_pitch;
if (desc.ddpfPixelFormat.dwFlags & DDPF_FOURCC)
default_pitch = (((desc.dwWidth + 3) & ~3) * ((desc.dwHeight + 3) & ~3) * bits_per_pixel) >> 3;
else
default_pitch = (desc.dwWidth * bits_per_pixel) >> 3;
uint pitch = 0;
if ( ( desc.dwFlags & DDSD_PITCH ) && ( !( desc.dwFlags & DDSD_LINEARSIZE ) ) )
{
pitch = desc.lPitch;
}
if (!pitch)
pitch = default_pitch;
#if 0
else if (pitch & 3)
{
// MS's DDS docs say the pitch must be DWORD aligned - but this isn't always the case.
// ATI Compressonator writes images with non-DWORD aligned pitches, and the DDSWithoutD3DX sample from MS doesn't compute the proper DWORD aligned pitch when reading DDS
// files, so the docs must be wrong/outdated.
console::warning("DDS file's pitch is not divisible by 4 - trying to load anyway.");
}
#endif
// Check for obviously wacky source pitches (probably a corrupted/invalid file).
else if (pitch > default_pitch * 8)
{
set_last_error("Invalid pitch");
return false;
}
crnlib::vector<uint8> load_buf;
uint mask_size[4];
mask_size[0] = math::bitmask_size(desc.ddpfPixelFormat.dwRBitMask);
mask_size[1] = math::bitmask_size(desc.ddpfPixelFormat.dwGBitMask);
mask_size[2] = math::bitmask_size(desc.ddpfPixelFormat.dwBBitMask);
mask_size[3] = math::bitmask_size(desc.ddpfPixelFormat.dwRGBAlphaBitMask);
uint mask_ofs[4];
mask_ofs[0] = math::bitmask_ofs(desc.ddpfPixelFormat.dwRBitMask);
mask_ofs[1] = math::bitmask_ofs(desc.ddpfPixelFormat.dwGBitMask);
mask_ofs[2] = math::bitmask_ofs(desc.ddpfPixelFormat.dwBBitMask);
mask_ofs[3] = math::bitmask_ofs(desc.ddpfPixelFormat.dwRGBAlphaBitMask);
if ((desc.ddpfPixelFormat.dwFlags & DDPF_LUMINANCE) && (!mask_size[0]))
{
mask_size[0] = desc.ddpfPixelFormat.dwRGBBitCount >> 3;
if (desc.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS)
mask_size[0] /= 2;
}
m_faces.resize(num_faces);
bool dxt1_alpha = false;
for (uint face_index = 0; face_index < num_faces; face_index++)
{
m_faces[face_index].resize(num_mip_levels);
for (uint level_index = 0; level_index < num_mip_levels; level_index++)
{
const uint width = math::maximum<uint>(desc.dwWidth >> level_index, 1U);
const uint height = math::maximum<uint>(desc.dwHeight >> level_index, 1U);
mip_level* pMip = crnlib_new<mip_level>();
m_faces[face_index][level_index] = pMip;
if (desc.ddpfPixelFormat.dwFlags & DDPF_FOURCC)
{
const uint bytes_per_block = pixel_format_helpers::get_dxt_bytes_per_block(m_format);
const uint num_blocks_x = (width + 3) >> 2;
const uint num_blocks_y = (height + 3) >> 2;
const uint actual_level_pitch = num_blocks_x * num_blocks_y * bytes_per_block;
const uint level_pitch = level_index ? actual_level_pitch : pitch;
dxt_image* pDXTImage = crnlib_new<dxt_image>();
if (!pDXTImage->init(dxt_fmt, width, height, false))
{
crnlib_delete(pDXTImage);
CRNLIB_ASSERT(0);
return false;
}
CRNLIB_ASSERT(pDXTImage->get_element_vec().size() * sizeof(dxt_image::element) == actual_level_pitch);
if (!serializer.read(&pDXTImage->get_element_vec()[0], actual_level_pitch))
{
crnlib_delete(pDXTImage);
return false;
}
// DDS image in memory are always assumed to be little endian - the same as DDS itself.
//if (c_crnlib_big_endian_platform)
// utils::endian_switch_words(reinterpret_cast<uint16*>(&pDXTImage->get_element_vec()[0]), actual_level_pitch / sizeof(uint16));
if (level_pitch > actual_level_pitch)
{
if (!serializer.skip(level_pitch - actual_level_pitch))
{
crnlib_delete(pDXTImage);
return false;
}
}
if ((m_format == PIXEL_FMT_DXT1) && (!dxt1_alpha))
dxt1_alpha = pDXTImage->has_alpha();
pMip->assign(pDXTImage, m_format);
}
else
{
image_u8* pImage = crnlib_new<image_u8>(width, height);
pImage->set_comp_flags(m_comp_flags);
const uint bytes_per_pixel = desc.ddpfPixelFormat.dwRGBBitCount >> 3;
const uint actual_line_pitch = width * bytes_per_pixel;
const uint line_pitch = level_index ? actual_line_pitch : pitch;
if (load_buf.size() < line_pitch)
load_buf.resize(line_pitch);
color_quad_u8 q(0, 0, 0, 255);
for (uint y = 0; y < height; y++)
{
if (!serializer.read(&load_buf[0], line_pitch))
{
crnlib_delete(pImage);
return false;
}
color_quad_u8* pDst = pImage->get_scanline(y);
for (uint x = 0; x < width; x++)
{
const uint8* pPixel = &load_buf[x * bytes_per_pixel];
uint c = 0;
// Assumes DDS is always little endian.
for (uint l = 0; l < bytes_per_pixel; l++)
c |= (pPixel[l] << (l * 8U));
for (uint i = 0; i < 4; i++)
{
if (!mask_size[i])
continue;
uint mask = (1U << mask_size[i]) - 1U;
uint bits = (c >> mask_ofs[i]) & mask;
uint v = (bits * 255 + (mask >> 1)) / mask;
q.set_component(i, v);
}
if (desc.ddpfPixelFormat.dwFlags & DDPF_LUMINANCE)
{
q.g = q.r;
q.b = q.r;
}
*pDst++ = q;
}
}
pMip->assign(pImage, m_format);
CRNLIB_ASSERT(pMip->get_comp_flags() == m_comp_flags);
}
}
}
clear_last_error();
if (dxt1_alpha)
change_dxt1_to_dxt1a();
return true;
}
void mipmapped_texture::change_dxt1_to_dxt1a()
{
if (m_format != PIXEL_FMT_DXT1)
return;
m_format = PIXEL_FMT_DXT1A;
m_comp_flags = pixel_format_helpers::get_component_flags(m_format);
for (uint f = 0; f < m_faces.size(); f++)
{
for (uint l = 0; l < m_faces[f].size(); l++)
{
if (m_faces[f][l]->get_dxt_image())
{
m_faces[f][l]->set_format(m_format);
m_faces[f][l]->set_comp_flags(m_comp_flags);
m_faces[f][l]->get_dxt_image()->change_dxt1_to_dxt1a();
}
}
}
}
bool mipmapped_texture::check() const
{
uint levels = 0;
orientation_flags_t orient_flags = cDefaultOrientationFlags;
for (uint f = 0; f < m_faces.size(); f++)
{
if (!f)
{
levels = m_faces[f].size();
if ((levels) && (m_faces[f][0]))
orient_flags = m_faces[f][0]->get_orientation_flags();
}
else if (m_faces[f].size() != levels)
return false;
for (uint l = 0; l < m_faces[f].size(); l++)
{
mip_level* p = m_faces[f][l];
if (!p)
return false;
if (!p->is_valid())
return false;
if (p->get_orientation_flags() != orient_flags)
return false;
if (!l)
{
if (m_width != p->get_width())
return false;
if (m_height != p->get_height())
return false;
}
if (p->get_comp_flags() != m_comp_flags)
return false;
if (p->get_format() != m_format)
return false;
if (p->get_image())
{
if (pixel_format_helpers::is_dxt(p->get_format()))
return false;
if (p->get_image()->get_width() != p->get_width())
return false;
if (p->get_image()->get_height() != p->get_height())
return false;
if (p->get_image()->get_comp_flags() != m_comp_flags)
return false;
}
else if (!pixel_format_helpers::is_dxt(p->get_format()))
return false;
}
}
return true;
}
bool mipmapped_texture::write_dds(data_stream_serializer& serializer) const
{
if (!m_width)
{
set_last_error("Nothing to write");
return false;
}
set_last_error("write_dds() failed");
if (!serializer.write("DDS ", sizeof(uint32)))
return false;
DDSURFACEDESC2 desc;
utils::zero_object(desc);
desc.dwSize = sizeof(desc);
desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS;
desc.dwWidth = m_width;
desc.dwHeight = m_height;
desc.ddsCaps.dwCaps = DDSCAPS_TEXTURE;
desc.ddpfPixelFormat.dwSize = sizeof(desc.ddpfPixelFormat);
if (get_num_levels() > 1)
{
desc.dwMipMapCount = get_num_levels();
desc.dwFlags |= DDSD_MIPMAPCOUNT;
desc.ddsCaps.dwCaps |= (DDSCAPS_MIPMAP | DDSCAPS_COMPLEX);
}
if (get_num_faces() > 1)
{
desc.ddsCaps.dwCaps |= DDSCAPS_COMPLEX;
desc.ddsCaps.dwCaps2 |= DDSCAPS2_CUBEMAP;
desc.ddsCaps.dwCaps2 |= DDSCAPS2_CUBEMAP_POSITIVEX|DDSCAPS2_CUBEMAP_NEGATIVEX|DDSCAPS2_CUBEMAP_POSITIVEY|DDSCAPS2_CUBEMAP_NEGATIVEY|DDSCAPS2_CUBEMAP_POSITIVEZ|DDSCAPS2_CUBEMAP_NEGATIVEZ;
}
bool dxt_format = false;
if (pixel_format_helpers::is_dxt(m_format))
{
dxt_format = true;
desc.ddpfPixelFormat.dwFlags |= DDPF_FOURCC;
switch (m_format)
{
case PIXEL_FMT_ETC1:
{
desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_ETC1;
desc.ddpfPixelFormat.dwRGBBitCount = 0;
break;
}
case PIXEL_FMT_DXN:
{
desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_3DC;
desc.ddpfPixelFormat.dwRGBBitCount = PIXEL_FMT_DXN;
break;
}
case PIXEL_FMT_DXT1A:
{
desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_DXT1;
desc.ddpfPixelFormat.dwRGBBitCount = 0;
break;
}
case PIXEL_FMT_DXT5_CCxY:
{
desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_DXT5;
desc.ddpfPixelFormat.dwRGBBitCount = (uint32)PIXEL_FMT_DXT5_CCxY;
break;
}
case PIXEL_FMT_DXT5_xGxR:
{
desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_DXT5;
desc.ddpfPixelFormat.dwRGBBitCount = (uint32)PIXEL_FMT_DXT5_xGxR;
break;
}
case PIXEL_FMT_DXT5_xGBR:
{
desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_DXT5;
desc.ddpfPixelFormat.dwRGBBitCount = (uint32)PIXEL_FMT_DXT5_xGBR;
break;
}
case PIXEL_FMT_DXT5_AGBR:
{
desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_DXT5;
desc.ddpfPixelFormat.dwRGBBitCount = (uint32)PIXEL_FMT_DXT5_AGBR;
break;
}
default:
{
desc.ddpfPixelFormat.dwFourCC = (uint32)m_format;
desc.ddpfPixelFormat.dwRGBBitCount = 0;
break;
}
}
uint bits_per_pixel = pixel_format_helpers::get_bpp(m_format);
desc.lPitch = (((desc.dwWidth + 3) & ~3) * ((desc.dwHeight + 3) & ~3) * bits_per_pixel) >> 3;
desc.dwFlags |= DDSD_LINEARSIZE;
}
else
{
switch (m_format)
{
case PIXEL_FMT_A8R8G8B8:
{
desc.ddpfPixelFormat.dwFlags |= (DDPF_RGB | DDPF_ALPHAPIXELS);
desc.ddpfPixelFormat.dwRGBBitCount = 32;
desc.ddpfPixelFormat.dwRBitMask = 0xFF0000;
desc.ddpfPixelFormat.dwGBitMask = 0x00FF00;
desc.ddpfPixelFormat.dwBBitMask = 0x0000FF;
desc.ddpfPixelFormat.dwRGBAlphaBitMask = 0xFF000000;
break;
}
case PIXEL_FMT_R8G8B8:
{
desc.ddpfPixelFormat.dwFlags |= DDPF_RGB;
desc.ddpfPixelFormat.dwRGBBitCount = 24;
desc.ddpfPixelFormat.dwRBitMask = 0xFF0000;
desc.ddpfPixelFormat.dwGBitMask = 0x00FF00;
desc.ddpfPixelFormat.dwBBitMask = 0x0000FF;
break;
}
case PIXEL_FMT_A8:
{
desc.ddpfPixelFormat.dwFlags |= DDPF_ALPHA;
desc.ddpfPixelFormat.dwRGBBitCount = 8;
desc.ddpfPixelFormat.dwRGBAlphaBitMask = 0xFF;
break;
}
case PIXEL_FMT_L8:
{
desc.ddpfPixelFormat.dwFlags |= DDPF_LUMINANCE;
desc.ddpfPixelFormat.dwRGBBitCount = 8;
desc.ddpfPixelFormat.dwRBitMask = 0xFF;
break;
}
case PIXEL_FMT_A8L8:
{
desc.ddpfPixelFormat.dwFlags |= DDPF_ALPHAPIXELS | DDPF_LUMINANCE;
desc.ddpfPixelFormat.dwRGBBitCount = 16;
desc.ddpfPixelFormat.dwRBitMask = 0xFF;
desc.ddpfPixelFormat.dwRGBAlphaBitMask = 0xFF00;
break;
}
default:
{
CRNLIB_ASSERT(false);
return false;
}
}
uint bits_per_pixel = desc.ddpfPixelFormat.dwRGBBitCount;
desc.lPitch = (desc.dwWidth * bits_per_pixel) >> 3;
desc.dwFlags |= DDSD_LINEARSIZE;
}
if (!c_crnlib_little_endian_platform)
utils::endian_switch_dwords(reinterpret_cast<uint32*>(&desc), sizeof(desc) / sizeof(uint32));
if (!serializer.write(&desc, sizeof(desc)))
return false;
if (!c_crnlib_little_endian_platform)
utils::endian_switch_dwords(reinterpret_cast<uint32*>(&desc), sizeof(desc) / sizeof(uint32));
crnlib::vector<uint8> write_buf;
const bool can_unflip_packed_texture = can_unflip_without_unpacking();
if ((is_packed()) && (is_flipped()) && (!can_unflip_without_unpacking()))
{
console::warning("mipmapped_texture::write_dds: One or more faces/miplevels cannot be unflipped without unpacking. Writing flipped .DDS texture.");
}
for (uint face = 0; face < get_num_faces(); face++)
{
for (uint level = 0; level < get_num_levels(); level++)
{
const mip_level* pLevel = get_level(face, level);
if (dxt_format)
{
const uint width = pLevel->get_width();
const uint height = pLevel->get_height();
CRNLIB_ASSERT(width == math::maximum<uint>(1, m_width >> level));
CRNLIB_ASSERT(height == math::maximum<uint>(1, m_height >> level));
const dxt_image* p = pLevel->get_dxt_image();
dxt_image tmp;
if ((can_unflip_packed_texture) && (pLevel->get_orientation_flags() & (cOrientationFlagXFlipped | cOrientationFlagYFlipped)))
{
tmp = *p;
if (pLevel->get_orientation_flags() & cOrientationFlagXFlipped)
{
if (!tmp.flip_x())
console::warning("mipmapped_texture::write_dds: Unable to unflip compressed texture on X axis");
}
if (pLevel->get_orientation_flags() & cOrientationFlagYFlipped)
{
if (!tmp.flip_y())
console::warning("mipmapped_texture::write_dds: Unable to unflip compressed texture on Y axis");
}
p = &tmp;
}
const uint num_blocks_x = (width + 3) >> 2;
const uint num_blocks_y = (height + 3) >> 2;
CRNLIB_ASSERT(num_blocks_x * num_blocks_y * p->get_elements_per_block() == p->get_total_elements());
width, height, num_blocks_x, num_blocks_y;
const uint size_in_bytes = p->get_total_elements() * sizeof(dxt_image::element);
if (size_in_bytes > write_buf.size())
write_buf.resize(size_in_bytes);
memcpy(&write_buf[0], p->get_element_ptr(), size_in_bytes);
// DXT data is always little endian in memory, just like the DDS format.
// (Except for ETC1, which contains big endian 64-bit QWORD's).
//if (!c_crnlib_little_endian_platform)
// utils::endian_switch_words(reinterpret_cast<WORD*>(&write_buf[0]), size_in_bytes / sizeof(WORD));
if (!serializer.write(&write_buf[0], size_in_bytes))
return false;
}
else
{
const uint width = pLevel->get_width();
const uint height = pLevel->get_height();
const image_u8* p = pLevel->get_image();
image_u8 tmp;
if (pLevel->get_orientation_flags() & (cOrientationFlagXFlipped | cOrientationFlagYFlipped))
{
p = pLevel->get_unpacked_image(tmp, cUnpackFlagUnflip);
}
const uint bits_per_pixel = desc.ddpfPixelFormat.dwRGBBitCount;
const uint bytes_per_pixel = bits_per_pixel >> 3;
const uint pitch = width * bytes_per_pixel;
if (pitch > write_buf.size())
write_buf.resize(pitch);
for (uint y = 0; y < height; y++)
{
const color_quad_u8* pSrc = p->get_scanline(y);
const color_quad_u8* pEnd = pSrc + width;
uint8* pDst = &write_buf[0];
do
{
const color_quad_u8& c = *pSrc;
uint x = 0;
switch (m_format)
{
case PIXEL_FMT_A8R8G8B8:
{
x = (c.a << 24) | (c.r << 16) | (c.g << 8) | c.b;
break;
}
case PIXEL_FMT_R8G8B8:
{
x = (c.r << 16) | (c.g << 8) | c.b;
break;
}
case PIXEL_FMT_A8:
{
x = c.a;
break;
}
case PIXEL_FMT_A8L8:
{
x = (c.a << 8) | c.get_luma();
break;
}
case PIXEL_FMT_L8:
{
x = c.get_luma();
break;
}
default: break;
}
pDst[0] = static_cast<uint8>(x);
if (bytes_per_pixel > 1)
{
pDst[1] = static_cast<uint8>(x >> 8);
if (bytes_per_pixel > 2)
{
pDst[2] = static_cast<uint8>(x >> 16);
if (bytes_per_pixel > 3)
pDst[3] = static_cast<uint8>(x >> 24);
}
}
pSrc++;
pDst += bytes_per_pixel;
} while (pSrc != pEnd);
if (!serializer.write(&write_buf[0], pitch))
return false;
}
}
}
}
clear_last_error();
return true;
}
bool mipmapped_texture::read_ktx(data_stream_serializer& serializer)
{
clear();
set_last_error("Unable to read KTX file");
ktx_texture kt;
if (!kt.read_from_stream(serializer))
return false;
if ((kt.get_depth() > 1) || (kt.get_array_size() > 1))
{
set_last_error("read_ktx: Depth and array textures are not supported");
return false;
}
// Must be 1D, 2D, or a cubemap, with or without mipmaps.
m_width = kt.get_width();
m_height = kt.get_height();
uint num_mip_levels = kt.get_num_mips();
uint num_faces = kt.get_num_faces();
uint32 crnlib_fourcc = 0;
dynamic_string crnlib_fourcc_str;
if (kt.get_key_value_as_string("CRNLIB_FOURCC", crnlib_fourcc_str))
{
if (crnlib_fourcc_str.get_len() == 4)
{
for (int i = 3; i >= 0; i--)
crnlib_fourcc = (crnlib_fourcc << 8) | crnlib_fourcc_str[i];
}
}
const bool is_compressed_texture = kt.is_compressed();
dxt_format dxt_fmt = cDXTInvalid;
pixel_packer unpacker;
if (is_compressed_texture)
{
switch (kt.get_ogl_internal_fmt())
{
case KTX_ETC1_RGB8_OES:
dxt_fmt = cETC1;
break;
case KTX_RGB_S3TC:
case KTX_RGB4_S3TC:
case KTX_COMPRESSED_RGB_S3TC_DXT1_EXT:
case KTX_COMPRESSED_SRGB_S3TC_DXT1_EXT:
dxt_fmt = cDXT1;
break;
case KTX_COMPRESSED_RGBA_S3TC_DXT1_EXT:
case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
dxt_fmt = cDXT1A;
break;
case KTX_RGBA_S3TC:
case KTX_RGBA4_S3TC:
case KTX_COMPRESSED_RGBA_S3TC_DXT3_EXT:
case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
dxt_fmt = cDXT3;
break;
case KTX_COMPRESSED_RGBA_S3TC_DXT5_EXT:
case KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
case KTX_RGBA_DXT5_S3TC:
case KTX_RGBA4_DXT5_S3TC:
dxt_fmt = cDXT5;
break;
case KTX_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:
dxt_fmt = cDXN_YX;
if (crnlib_fourcc == PIXEL_FMT_DXN)
{
dxt_fmt = cDXN_XY;
}
break;
case KTX_COMPRESSED_LUMINANCE_LATC1_EXT:
dxt_fmt = cDXT5A;
break;
default:
set_last_error("Unsupported KTX internal format");
return false;
}
m_format = pixel_format_helpers::from_dxt_format(dxt_fmt);
if (m_format == PIXEL_FMT_INVALID)
{
set_last_error("Unsupported KTX internal compressed format");
return false;
}
if (crnlib_fourcc != 0)
{
switch (crnlib_fourcc)
{
case PIXEL_FMT_DXT5_CCxY:
case PIXEL_FMT_DXT5_xGxR:
case PIXEL_FMT_DXT5_xGBR:
case PIXEL_FMT_DXT5_AGBR:
{
if (dxt_fmt == cDXT5)
{
m_format = static_cast<pixel_format>(crnlib_fourcc);
}
break;
}
}
}
}
else
{
m_format = PIXEL_FMT_A8R8G8B8;
const uint type_size = get_ogl_type_size(kt.get_ogl_type());
const uint type_bits = type_size * 8;
// Normal component order: 1,2,3,4 (*last* component packed into LSB of output type)
// Reversed component order: 4,3,2,1 (*first* component packed into LSB of output type)
if (is_packed_pixel_ogl_type(kt.get_ogl_type()))
{
switch (kt.get_ogl_type())
{
// 24bpp packed formats
case KTX_UNSIGNED_BYTE_3_3_2: unpacker.init("B2G3R3"); m_format = PIXEL_FMT_R8G8B8; break;
case KTX_UNSIGNED_BYTE_2_3_3_REV: unpacker.init("R3G3B2"); m_format = PIXEL_FMT_R8G8B8; break;
case KTX_UNSIGNED_SHORT_5_6_5: unpacker.init("B5G6R5"); m_format = PIXEL_FMT_R8G8B8; break;
case KTX_UNSIGNED_SHORT_5_6_5_REV: unpacker.init("R5G6B5"); m_format = PIXEL_FMT_R8G8B8; break;
// 32bpp packed formats
case KTX_UNSIGNED_SHORT_4_4_4_4: unpacker.init("A4B4G4R4"); break;
case KTX_UNSIGNED_SHORT_4_4_4_4_REV: unpacker.init("R4G4B4A4"); break;
case KTX_UNSIGNED_SHORT_5_5_5_1: unpacker.init("A1B5G5R5"); break;
case KTX_UNSIGNED_SHORT_1_5_5_5_REV: unpacker.init("R5G5B5A1"); break;
case KTX_UNSIGNED_INT_8_8_8_8: unpacker.init("A8B8G8R8"); break;
case KTX_UNSIGNED_INT_8_8_8_8_REV: unpacker.init("R8G8B8A8"); break;
case KTX_UNSIGNED_INT_10_10_10_2: unpacker.init("A2B10G10R10"); break;
case KTX_UNSIGNED_INT_2_10_10_10_REV: unpacker.init("R10G10B10A2"); break;
case KTX_UNSIGNED_INT_5_9_9_9_REV: unpacker.init("R9G9B9A5"); break;
default:
set_last_error("Unsupported KTX packed pixel type");
return false;
}
unpacker.set_pixel_stride(get_ogl_type_size(kt.get_ogl_type()));
}
else
{
switch (kt.get_ogl_fmt())
{
case 1:
case KTX_RED:
case KTX_RED_INTEGER:
case KTX_R8:
case KTX_R8UI:
{
unpacker.init("R", -1, type_bits);
m_format = PIXEL_FMT_R8G8B8;
break;
}
case KTX_GREEN:
case KTX_GREEN_INTEGER:
{
unpacker.init("G", -1, type_bits);
m_format = PIXEL_FMT_R8G8B8;
break;
}
case KTX_BLUE:
case KTX_BLUE_INTEGER:
{
unpacker.init("B", -1, type_bits);
m_format = PIXEL_FMT_R8G8B8;
break;
}
case KTX_ALPHA:
{
unpacker.init("A", -1, type_bits);
m_format = PIXEL_FMT_A8;
break;
}
case KTX_LUMINANCE:
{
unpacker.init("Y", -1, type_bits);
m_format = PIXEL_FMT_L8;
break;
}
case 2:
case KTX_RG:
case KTX_RG8:
case KTX_RG_INTEGER:
{
unpacker.init("RG", -1, type_bits);
m_format = PIXEL_FMT_A8L8;
break;
}
case KTX_LUMINANCE_ALPHA:
{
unpacker.init("YA", -1, type_bits);
m_format = PIXEL_FMT_A8L8;
break;
}
case 3:
case KTX_SRGB:
case KTX_RGB:
case KTX_RGB_INTEGER:
case KTX_RGB8:
case KTX_SRGB8:
{
unpacker.init("RGB", -1, type_bits);
m_format = PIXEL_FMT_R8G8B8;
break;
}
case KTX_BGR:
case KTX_BGR_INTEGER:
{
unpacker.init("BGR", -1, type_bits);
m_format = PIXEL_FMT_R8G8B8;
break;
}
case 4:
case KTX_RGBA_INTEGER:
case KTX_RGBA:
case KTX_SRGB_ALPHA:
case KTX_SRGB8_ALPHA8:
case KTX_RGBA8:
{
unpacker.init("RGBA", -1, type_bits);
break;
}
case KTX_BGRA:
case KTX_BGRA_INTEGER:
{
unpacker.init("BGRA", -1, type_bits);
break;
}
default:
set_last_error("Unsupported KTX pixel format");
return false;
}
unpacker.set_pixel_stride(unpacker.get_num_comps() * get_ogl_type_size(kt.get_ogl_type()));
}
CRNLIB_ASSERT(unpacker.is_valid());
}
m_comp_flags = pixel_format_helpers::get_component_flags(m_format);
m_faces.resize(num_faces);
bool x_flipped = false;
bool y_flipped = true;
dynamic_string orient;
if ((kt.get_key_value_as_string("KTXorientation", orient)) && (orient.get_len() >= 7))
{
// 0123456
// "S=r,T=d"
if ((orient[0] == 'S') && (orient[1] == '=') && (orient[3] == ',') &&
(orient[4] == 'T') && (orient[5] == '='))
{
if (tolower(orient[2]) == 'l')
x_flipped = true;
else if (tolower(orient[2]) == 'r')
x_flipped = false;
if (tolower(orient[6]) == 'u')
y_flipped = true;
else if (tolower(orient[6]) == 'd')
y_flipped = false;
}
}
orientation_flags_t orient_flags = cDefaultOrientationFlags;
if (x_flipped) orient_flags = static_cast<orientation_flags_t>(orient_flags | cOrientationFlagXFlipped);
if (y_flipped) orient_flags = static_cast<orientation_flags_t>(orient_flags | cOrientationFlagYFlipped);
bool dxt1_alpha = false;
for (uint face_index = 0; face_index < num_faces; face_index++)
{
m_faces[face_index].resize(num_mip_levels);
for (uint level_index = 0; level_index < num_mip_levels; level_index++)
{
const uint width = math::maximum<uint>(m_width >> level_index, 1U);
const uint height = math::maximum<uint>(m_height >> level_index, 1U);
mip_level* pMip = crnlib_new<mip_level>();
m_faces[face_index][level_index] = pMip;
const crnlib::vector<uint8>& image_data = kt.get_image_data(level_index, 0, face_index, 0);
if (is_compressed_texture)
{
const uint bytes_per_block = pixel_format_helpers::get_dxt_bytes_per_block(m_format);
const uint num_blocks_x = (width + 3) >> 2;
const uint num_blocks_y = (height + 3) >> 2;
const uint level_pitch = num_blocks_x * num_blocks_y * bytes_per_block;
if (image_data.size() != level_pitch)
return false;
dxt_image* pDXTImage = crnlib_new<dxt_image>();
if (!pDXTImage->init(dxt_fmt, width, height, false))
{
crnlib_delete(pDXTImage);
CRNLIB_ASSERT(0);
return false;
}
CRNLIB_ASSERT(pDXTImage->get_element_vec().size() * sizeof(dxt_image::element) == level_pitch);
memcpy(&pDXTImage->get_element_vec()[0], image_data.get_ptr(), image_data.size());
if ((m_format == PIXEL_FMT_DXT1) && (!dxt1_alpha))
dxt1_alpha = pDXTImage->has_alpha();
pMip->assign(pDXTImage, m_format, orient_flags);
}
else
{
if (image_data.size() != (width * height * unpacker.get_pixel_stride()))
return false;
image_u8* pImage = crnlib_new<image_u8>(width, height);
pImage->set_comp_flags(m_comp_flags);
const uint8* pSrc = image_data.get_ptr();
color_quad_u8 q(0, 0, 0, 255);
for (uint y = 0; y < height; y++)
{
for (uint x = 0; x < width; x++)
{
color_quad_u8 c;
pSrc = static_cast<const uint8*>(unpacker.unpack(pSrc, c));
pImage->set_pixel_unclipped(x, y, c);
}
}
pMip->assign(pImage, m_format, orient_flags);
CRNLIB_ASSERT(pMip->get_comp_flags() == m_comp_flags);
}
}
}
clear_last_error();
if (dxt1_alpha)
change_dxt1_to_dxt1a();
return true;
}
bool mipmapped_texture::write_ktx(data_stream_serializer& serializer) const
{
if (!m_width)
{
set_last_error("Nothing to write");
return false;
}
set_last_error("write_ktx() failed");
uint32 ogl_internal_fmt = 0, ogl_fmt = 0, ogl_type = 0;
pixel_packer packer;
if (is_packed())
{
switch (get_format())
{
case PIXEL_FMT_DXT1:
{
ogl_internal_fmt = KTX_COMPRESSED_RGB_S3TC_DXT1_EXT;
break;
}
case PIXEL_FMT_DXT1A:
{
ogl_internal_fmt = KTX_COMPRESSED_RGBA_S3TC_DXT1_EXT;
break;
}
case PIXEL_FMT_DXT2:
case PIXEL_FMT_DXT3:
{
ogl_internal_fmt = KTX_COMPRESSED_RGBA_S3TC_DXT3_EXT;
break;
}
case PIXEL_FMT_DXT4:
case PIXEL_FMT_DXT5:
case PIXEL_FMT_DXT5_CCxY:
case PIXEL_FMT_DXT5_xGxR:
case PIXEL_FMT_DXT5_xGBR:
case PIXEL_FMT_DXT5_AGBR:
{
ogl_internal_fmt = KTX_COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
}
case PIXEL_FMT_3DC:
case PIXEL_FMT_DXN:
{
ogl_internal_fmt = KTX_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT;
break;
}
case PIXEL_FMT_DXT5A:
{
ogl_internal_fmt = KTX_COMPRESSED_LUMINANCE_LATC1_EXT;
break;
}
case PIXEL_FMT_ETC1:
{
ogl_internal_fmt = KTX_ETC1_RGB8_OES;
break;
}
default:
{
CRNLIB_ASSERT(0);
return false;
}
}
}
else
{
ogl_type = KTX_UNSIGNED_BYTE;
switch (get_format())
{
case PIXEL_FMT_R8G8B8: ogl_internal_fmt = KTX_RGB8; ogl_fmt = KTX_RGB; packer.init("R8G8B8"); break;
case PIXEL_FMT_L8: ogl_internal_fmt = KTX_LUMINANCE8; ogl_fmt = KTX_LUMINANCE; packer.init("G8"); break;
case PIXEL_FMT_A8: ogl_internal_fmt = KTX_ALPHA8; ogl_fmt = KTX_ALPHA; packer.init("A8"); break;
case PIXEL_FMT_A8L8: ogl_internal_fmt = KTX_LUMINANCE8_ALPHA8; ogl_fmt = KTX_LUMINANCE_ALPHA; packer.init("Y8A8"); break;
case PIXEL_FMT_A8R8G8B8: ogl_internal_fmt = KTX_RGBA8; ogl_fmt = KTX_RGBA; packer.init("R8G8B8A8"); break;
default:
{
CRNLIB_ASSERT(0);
return false;
}
}
}
ktx_texture kt;
bool success;
if (determine_texture_type() == cTextureTypeCubemap)
success = kt.init_cubemap(get_width(), get_num_levels(), ogl_internal_fmt, ogl_fmt, ogl_type);
else
success = kt.init_2D(get_width(), get_height(), get_num_levels(), ogl_internal_fmt, ogl_fmt, ogl_type);
if (!success)
return false;
dynamic_string fourcc_str(cVarArg, "%c%c%c%c", m_format & 0xFF, (m_format >> 8) & 0xFF, (m_format >> 16) & 0xFF, (m_format >> 24) & 0xFF);
kt.add_key_value("CRNLIB_FOURCC", fourcc_str.get_ptr());
const mip_level* pLevel0 = get_level(0, 0);
dynamic_string ktx_orient_str(cVarArg, "S=%c,T=%c", (pLevel0->get_orientation_flags() & cOrientationFlagXFlipped) ? 'l' : 'r', (pLevel0->get_orientation_flags() & cOrientationFlagYFlipped) ? 'u' : 'd');
kt.add_key_value("KTXorientation", ktx_orient_str.get_ptr());
for (uint face_index = 0; face_index < get_num_faces(); face_index++)
{
for (uint level_index = 0; level_index < get_num_levels(); level_index++)
{
const mip_level* pLevel = get_level(face_index, level_index);
const uint mip_width = pLevel->get_width();
const uint mip_height = pLevel->get_height();
if (is_packed())
{
const dxt_image* p = pLevel->get_dxt_image();
kt.add_image(face_index, level_index, p->get_element_ptr(), p->get_size_in_bytes());
}
else
{
const image_u8* p = pLevel->get_image();
crnlib::vector<uint8> tmp(mip_width * mip_height * packer.get_pixel_stride());
uint8* pDst = tmp.get_ptr();
for (uint y = 0; y < mip_height; y++)
for (uint x = 0; x < mip_width; x++)
pDst = (uint8*)packer.pack(p->get_unclamped(x, y), pDst);
kt.add_image(face_index, level_index, tmp.get_ptr(), tmp.size_in_bytes());
}
}
}
if (!kt.write_to_stream(serializer))
return false;
clear_last_error();
return true;
}
void mipmapped_texture::assign(face_vec& faces)
{
CRNLIB_ASSERT(!faces.empty());
if (faces.empty())
return;
free_all_mips();
#ifdef CRNLIB_BUILD_DEBUG
for (uint i = 1; i < faces.size(); i++)
CRNLIB_ASSERT(faces[i].size() == faces[0].size());
#endif
mip_level* p = faces[0][0];
m_width = p->get_width();
m_height = p->get_height();
m_comp_flags = p->get_comp_flags();
m_format = p->get_format();
m_faces.swap(faces);
CRNLIB_ASSERT(check());
}
void mipmapped_texture::assign(mip_level* pLevel)
{
face_vec faces(1, mip_ptr_vec(1, pLevel));
assign(faces);
}
void mipmapped_texture::assign(image_u8* p, pixel_format fmt, orientation_flags_t orient_flags)
{
mip_level* pLevel = crnlib_new<mip_level>();
pLevel->assign(p, fmt, orient_flags);
assign(pLevel);
}
void mipmapped_texture::assign(dxt_image* p, pixel_format fmt, orientation_flags_t orient_flags)
{
mip_level* pLevel = crnlib_new<mip_level>();
pLevel->assign(p, fmt, orient_flags);
assign(pLevel);
}
void mipmapped_texture::set(texture_file_types::format source_file_type, const mipmapped_texture& mipmapped_texture)
{
clear();
*this = mipmapped_texture;
m_source_file_type = source_file_type;
}
image_u8* mipmapped_texture::get_level_image(uint face, uint level, image_u8& img, uint unpack_flags) const
{
if (!is_valid())
return NULL;
const mip_level* pLevel = get_level(face, level);
return pLevel->get_unpacked_image(img, unpack_flags);
}
void mipmapped_texture::swap(mipmapped_texture& img)
{
utils::swap(m_width, img.m_width);
utils::swap(m_height, img.m_height);
utils::swap(m_comp_flags, img.m_comp_flags);
utils::swap(m_format, img.m_format);
m_faces.swap(img.m_faces);
m_last_error.swap(img.m_last_error);
utils::swap(m_source_file_type, img.m_source_file_type);
CRNLIB_ASSERT(check());
}
texture_type mipmapped_texture::determine_texture_type() const
{
if (!is_valid())
return cTextureTypeUnknown;
if (get_num_faces() == 6)
return cTextureTypeCubemap;
else if (is_vertical_cross())
return cTextureTypeVerticalCrossCubemap;
else if (is_normal_map())
return cTextureTypeNormalMap;
return cTextureTypeRegularMap;
}
void mipmapped_texture::discard_mips()
{
for (uint f = 0; f < m_faces.size(); f++)
{
if (m_faces[f].size() > 1)
{
for (uint l = 1; l < m_faces[f].size(); l++)
crnlib_delete(m_faces[f][l]);
m_faces[f].resize(1);
}
}
CRNLIB_ASSERT(check());
}
void mipmapped_texture::init(uint width, uint height, uint levels, uint faces, pixel_format fmt, const char* pName, orientation_flags_t orient_flags)
{
clear();
CRNLIB_ASSERT((width > 0) && (height > 0) && (levels > 0));
CRNLIB_ASSERT((faces == 1) || (faces == 6));
m_width = width;
m_height = height;
m_comp_flags = pixel_format_helpers::get_component_flags(fmt);
m_format = fmt;
if (pName)
m_name.set(pName);
m_faces.resize(faces);
for (uint f = 0; f < faces; f++)
{
m_faces[f].resize(levels);
for (uint l = 0; l < levels; l++)
{
m_faces[f][l] = crnlib_new<mip_level>();
const uint mip_width = math::maximum(1U, width >> l);
const uint mip_height = math::maximum(1U, height >> l);
if (pixel_format_helpers::is_dxt(fmt))
{
dxt_image* p = crnlib_new<dxt_image>();
p->init(pixel_format_helpers::get_dxt_format(fmt), mip_width, mip_height, true);
m_faces[f][l]->assign(p, m_format, orient_flags);
}
else
{
image_u8* p = crnlib_new<image_u8>(mip_width, mip_height);
p->set_comp_flags(m_comp_flags);
m_faces[f][l]->assign(p, m_format, orient_flags);
}
}
}
CRNLIB_ASSERT(check());
}
void mipmapped_texture::discard_mipmaps()
{
if (!is_valid())
return;
discard_mips();
}
bool mipmapped_texture::convert(pixel_format fmt, bool cook, const dxt_image::pack_params& p)
{
if (!is_valid())
return false;
if (fmt == get_format())
return true;
uint total_pixels = 0;
for (uint f = 0; f < m_faces.size(); f++)
for (uint l = 0; l < m_faces[f].size(); l++)
total_pixels += m_faces[f][l]->get_total_pixels();
uint num_pixels_processed = 0;
uint progress_start = p.m_progress_start;
for (uint f = 0; f < m_faces.size(); f++)
{
for (uint l = 0; l < m_faces[f].size(); l++)
{
const uint num_pixels = m_faces[f][l]->get_total_pixels();
uint progress_range = (num_pixels * p.m_progress_range) / total_pixels;
dxt_image::pack_params tmp_params(p);
tmp_params.m_progress_start = math::clamp<uint>(progress_start, 0, p.m_progress_range);
tmp_params.m_progress_range = math::clamp<uint>(progress_range, 0, p.m_progress_range - tmp_params.m_progress_start);
progress_start += tmp_params.m_progress_range;
if (!m_faces[f][l]->convert(fmt, cook, tmp_params))
{
clear();
return false;
}
num_pixels_processed += num_pixels;
}
}
m_format = get_level(0, 0)->get_format();
m_comp_flags = get_level(0, 0)->get_comp_flags();
CRNLIB_ASSERT(check());
if (p.m_pProgress_callback)
{
if (!p.m_pProgress_callback(p.m_progress_start + p.m_progress_range, p.m_pProgress_callback_user_data_ptr))
return false;
}
return true;
}
bool mipmapped_texture::convert(pixel_format fmt, const dxt_image::pack_params& p)
{
return convert(fmt, true, p);
}
bool mipmapped_texture::convert(pixel_format fmt, bool cook, const dxt_image::pack_params& p, int qdxt_quality, bool hierarchical)
{
if ((!pixel_format_helpers::is_dxt(fmt)) || (fmt == PIXEL_FMT_DXT3) || (fmt == PIXEL_FMT_ETC1))
{
// QDXT doesn't support DXT3 or ETC1 yet.
return convert(fmt, cook, p);
}
mipmapped_texture src_tex(*this);
if (src_tex.is_packed())
src_tex.unpack_from_dxt(true);
if (cook)
{
mipmapped_texture cooked_tex(src_tex);
for (uint f = 0; f < m_faces.size(); f++)
for (uint l = 0; l < m_faces[f].size(); l++)
src_tex.m_faces[f][l]->cook_image(*cooked_tex.m_faces[f][l]->get_image());
src_tex.swap(cooked_tex);
}
qdxt1_params q1_params;
q1_params.init(p, qdxt_quality, hierarchical);
qdxt5_params q5_params;
q5_params.init(p, qdxt_quality, hierarchical);
if (pixel_format_helpers::is_pixel_format_non_srgb(fmt) || (m_comp_flags & pixel_format_helpers::cCompFlagNormalMap) || (m_comp_flags & pixel_format_helpers::cCompFlagLumaChroma))
{
// Disable perceptual colorspace metrics when packing to swizzled or non-RGB pixel formats.
q1_params.m_perceptual = false;
}
task_pool tp;
if (!tp.init(p.m_num_helper_threads))
return false;
mipmapped_texture packed_tex;
qdxt_state state(tp);
if (!src_tex.qdxt_pack_init(state, packed_tex, q1_params, q5_params, fmt, false))
return false;
if (!src_tex.qdxt_pack(state, packed_tex, q1_params, q5_params))
return false;
swap(packed_tex);
return true;
}
bool mipmapped_texture::is_packed() const
{
CRNLIB_ASSERT(is_valid());
if (!is_valid())
return false;
return get_level(0, 0)->is_packed();
}
bool mipmapped_texture::set_alpha_to_luma()
{
CRNLIB_ASSERT(is_valid());
if (!is_valid())
return false;
if (is_packed())
unpack_from_dxt(true);
for (uint f = 0; f < m_faces.size(); f++)
for (uint l = 0; l < get_num_levels(); l++)
get_level(f, l)->set_alpha_to_luma();
m_format = get_level(0, 0)->get_format();
m_comp_flags = get_level(0, 0)->get_comp_flags();
CRNLIB_ASSERT(check());
return true;
}
bool mipmapped_texture::convert(image_utils::conversion_type conv_type)
{
CRNLIB_ASSERT(is_valid());
if (!is_valid())
return false;
if (is_packed())
unpack_from_dxt(true);
for (uint f = 0; f < m_faces.size(); f++)
for (uint l = 0; l < get_num_levels(); l++)
get_level(f, l)->convert(conv_type);
m_format = get_level(0, 0)->get_format();
m_comp_flags = get_level(0, 0)->get_comp_flags();
CRNLIB_ASSERT(check());
return true;
}
bool mipmapped_texture::unpack_from_dxt(bool uncook)
{
CRNLIB_ASSERT(is_valid());
if (!is_valid())
return false;
CRNLIB_ASSERT(pixel_format_helpers::is_dxt(m_format));
if (!pixel_format_helpers::is_dxt(m_format))
return false;
for (uint f = 0; f < m_faces.size(); f++)
for (uint l = 0; l < get_num_levels(); l++)
if (!get_level(f, l)->unpack_from_dxt(uncook))
return false;
m_format = get_level(0, 0)->get_format();
m_comp_flags = get_level(0, 0)->get_comp_flags();
CRNLIB_ASSERT(check());
return true;
}
bool mipmapped_texture::has_alpha() const
{
CRNLIB_ASSERT(is_valid());
if (!is_valid())
return false;
if (pixel_format_helpers::has_alpha(m_format))
return true;
if ((m_format == PIXEL_FMT_DXT1) && (get_level(0, 0)->get_dxt_image()))
{
// Try scanning DXT1 mip levels to find blocks with transparent pixels.
for (uint f = 0; f < get_num_faces(); f++)
if (get_level(f, 0)->get_dxt_image()->has_alpha())
return true;
}
return false;
}
bool mipmapped_texture::is_normal_map() const
{
CRNLIB_ASSERT(is_valid());
if (!is_valid())
return false;
if (pixel_format_helpers::is_normal_map(get_format()))
return true;
const mip_level* pLevel = get_level(0, 0);
if (pLevel->get_image())
return image_utils::is_normal_map(*pLevel->get_image(), m_name.get_ptr());
image_u8 tmp;
pLevel->get_dxt_image()->unpack(tmp);
return image_utils::is_normal_map(tmp, m_name.get_ptr());
}
bool mipmapped_texture::is_vertical_cross() const
{
CRNLIB_ASSERT(is_valid());
if (!is_valid())
return false;
if (get_num_faces() > 1)
return false;
if (!((math::is_power_of_2(m_height)) && (!math::is_power_of_2(m_width)) && (m_height / 4U == m_width / 3U)))
return false;
return true;
}
bool mipmapped_texture::resize(uint new_width, uint new_height, const resample_params& params)
{
CRNLIB_ASSERT(is_valid());
if (!is_valid())
return false;
CRNLIB_ASSERT((new_width >= 1) && (new_height >= 1));
face_vec faces(get_num_faces());
for (uint f = 0; f < faces.size(); f++)
{
faces[f].resize(1);
faces[f][0] = crnlib_new<mip_level>();
}
for (uint f = 0; f < faces.size(); f++)
{
image_u8 tmp;
image_u8* pImg = get_level(f, 0)->get_unpacked_image(tmp, cUnpackFlagUncook);
image_u8* pMip = crnlib_new<image_u8>();
image_utils::resample_params rparams;
rparams.m_dst_width = new_width;
rparams.m_dst_height = new_height;
rparams.m_filter_scale = params.m_filter_scale;
rparams.m_first_comp = 0;
rparams.m_num_comps = pImg->is_component_valid(3) ? 4 : 3;
rparams.m_srgb = params.m_srgb;
rparams.m_wrapping = params.m_wrapping;
rparams.m_pFilter = params.m_pFilter;
rparams.m_multithreaded = params.m_multithreaded;
if (!image_utils::resample(*pImg, *pMip, rparams))
{
crnlib_delete(pMip);
for (uint f = 0; f < faces.size(); f++)
for (uint l = 0; l < faces[f].size(); l++)
crnlib_delete(faces[f][l]);
return false;
}
if (params.m_renormalize)
image_utils::renorm_normal_map(*pMip);
pMip->set_comp_flags(pImg->get_comp_flags());
faces[f][0]->assign(pMip, PIXEL_FMT_INVALID, get_level(f, 0)->get_orientation_flags());
}
assign(faces);
CRNLIB_ASSERT(check());
return true;
}
bool mipmapped_texture::generate_mipmaps(const generate_mipmap_params& params, bool force)
{
CRNLIB_ASSERT(is_valid());
if (!is_valid())
return false;
uint num_levels = 1;
{
uint width = get_width();
uint height = get_height();
while ((width > params.m_min_mip_size) || (height > params.m_min_mip_size))
{
width >>= 1U;
height >>= 1U;
num_levels++;
}
}
if ((params.m_max_mips > 0) && (num_levels > params.m_max_mips))
num_levels = params.m_max_mips;
if ((force) && (get_num_levels() > 1))
discard_mipmaps();
if (num_levels == get_num_levels())
return true;
face_vec faces(get_num_faces());
for (uint f = 0; f < faces.size(); f++)
{
faces[f].resize(num_levels);
for (uint l = 0; l < num_levels; l++)
faces[f][l] = crnlib_new<mip_level>();
}
for (uint f = 0; f < faces.size(); f++)
{
image_u8 tmp;
image_u8* pImg = get_level(f, 0)->get_unpacked_image(tmp, cUnpackFlagUncook);
for (uint l = 0; l < num_levels; l++)
{
const uint mip_width = math::maximum<uint>(1U, get_width() >> l);
const uint mip_height = math::maximum<uint>(1U, get_height() >> l);
image_u8* pMip = crnlib_new<image_u8>();
if (!l)
*pMip = *pImg;
else
{
image_utils::resample_params rparams;
rparams.m_dst_width = mip_width;
rparams.m_dst_height = mip_height;
rparams.m_filter_scale = params.m_filter_scale;
rparams.m_first_comp = 0;
rparams.m_num_comps = pImg->is_component_valid(3) ? 4 : 3;
rparams.m_srgb = params.m_srgb;
rparams.m_wrapping = params.m_wrapping;
rparams.m_pFilter = params.m_pFilter;
rparams.m_multithreaded = params.m_multithreaded;
if (!image_utils::resample(*pImg, *pMip, rparams))
{
crnlib_delete(pMip);
for (uint f = 0; f < faces.size(); f++)
for (uint l = 0; l < faces[f].size(); l++)
crnlib_delete(faces[f][l]);
return false;
}
if (params.m_renormalize)
image_utils::renorm_normal_map(*pMip);
pMip->set_comp_flags(pImg->get_comp_flags());
}
faces[f][l]->assign(pMip, PIXEL_FMT_INVALID, get_level(f, 0)->get_orientation_flags());
}
}
assign(faces);
CRNLIB_ASSERT(check());
return true;
}
bool mipmapped_texture::crop(uint x, uint y, uint width, uint height)
{
CRNLIB_ASSERT(is_valid());
if (!is_valid())
return false;
if (get_num_faces() > 1)
return false;
if ((width < 1) || (height < 1))
return false;
image_u8 tmp;
image_u8* pImg = get_level(0, 0)->get_unpacked_image(tmp, cUnpackFlagUncook | cUnpackFlagUnflip);
image_u8* pMip = crnlib_new<image_u8>(width, height);
if (!pImg->extract_block(pMip->get_ptr(), x, y, width, height))
return false;
face_vec faces(1);
faces[0].resize(1);
faces[0][0] = crnlib_new<mip_level>();
pMip->set_comp_flags(pImg->get_comp_flags());
faces[0][0]->assign(pMip);
assign(faces);
CRNLIB_ASSERT(check());
return true;
}
bool mipmapped_texture::vertical_cross_to_cubemap()
{
if (!is_vertical_cross())
return false;
const uint face_width = get_height() / 4;
bool alpha_is_valid = has_alpha();
mipmapped_texture cubemap;
pixel_format fmt = alpha_is_valid ? PIXEL_FMT_A8R8G8B8 : PIXEL_FMT_R8G8B8;
cubemap.init(face_width, face_width, 1, 6, fmt, m_name.get_ptr(), cDefaultOrientationFlags);
// +x -x +y -y +z -z
// 0 1 2
// 0 +y
// 1 -x +z +x
// 2 -y
// 3 -z
for (uint face_index = 0; face_index < 6; face_index++)
{
const mip_level* pSrc = get_level(0, 0);
image_u8 tmp_img;
image_u8* pSrc_image = pSrc->get_unpacked_image(tmp_img, cUnpackFlagUncook | cUnpackFlagUnflip);
const mip_level* pDst = get_level(face_index, 0);
image_u8* pDst_image = pDst->get_image();
CRNLIB_ASSERT(pDst_image);
const bool flipped = (face_index == 5);
const uint x_ofs = g_vertical_cross_image_offsets[face_index][0] * face_width;
const uint y_ofs = g_vertical_cross_image_offsets[face_index][1] * face_width;
for (uint y = 0; y < face_width; y++)
{
for (uint x = 0; x < face_width; x++)
{
const color_quad_u8& c = (*pSrc_image)(x_ofs + x, y_ofs + y);
if (!flipped)
(*pDst_image)(x, y) = c;
else
(*pDst_image)(face_width - 1 - x, face_width - 1 - y) = c;
}
}
}
swap(cubemap);
CRNLIB_ASSERT(check());
return true;
}
bool mipmapped_texture::qdxt_pack_init(qdxt_state& state, mipmapped_texture& dst_tex, const qdxt1_params& dxt1_params, const qdxt5_params& dxt5_params, pixel_format fmt, bool cook)
{
if (!is_valid())
return false;
state.m_qdxt1_params = dxt1_params;
state.m_qdxt5_params[0] = dxt5_params;
state.m_qdxt5_params[1] = dxt5_params;
utils::zero_object(state.m_has_blocks);
switch (fmt)
{
case PIXEL_FMT_DXT1:
{
state.m_has_blocks[0] = true;
break;
}
case PIXEL_FMT_DXT1A:
{
state.m_has_blocks[0] = true;
state.m_qdxt1_params.m_use_alpha_blocks = true;
break;
}
case PIXEL_FMT_DXT4:
case PIXEL_FMT_DXT5:
{
state.m_has_blocks[0] = true;
state.m_has_blocks[1] = true;
state.m_qdxt1_params.m_use_alpha_blocks = false;
state.m_qdxt5_params[0].m_comp_index = 3;
break;
}
case PIXEL_FMT_DXT5_CCxY:
case PIXEL_FMT_DXT5_xGxR:
case PIXEL_FMT_DXT5_xGBR:
case PIXEL_FMT_DXT5_AGBR:
{
state.m_has_blocks[0] = true;
state.m_has_blocks[1] = true;
state.m_qdxt1_params.m_use_alpha_blocks = false;
state.m_qdxt1_params.m_perceptual = false;
state.m_qdxt5_params[0].m_comp_index = 3;
break;
}
case PIXEL_FMT_3DC:
{
state.m_has_blocks[1] = true;
state.m_has_blocks[2] = true;
state.m_qdxt5_params[0].m_comp_index = 1;
state.m_qdxt5_params[1].m_comp_index = 0;
break;
}
case PIXEL_FMT_DXN:
{
state.m_has_blocks[1] = true;
state.m_has_blocks[2] = true;
state.m_qdxt5_params[0].m_comp_index = 0;
state.m_qdxt5_params[1].m_comp_index = 1;
break;
}
case PIXEL_FMT_DXT5A:
{
state.m_has_blocks[1] = true;
state.m_qdxt5_params[0].m_comp_index = 3;
break;
}
case PIXEL_FMT_ETC1:
{
console::warning("mipmapped_texture::qdxt_pack_init: This method does not support ETC1");
return false;
}
default:
{
CRNLIB_ASSERT(0);
return false;
}
}
const uint num_elements = state.m_has_blocks[0] + state.m_has_blocks[1] + state.m_has_blocks[2];
uint cur_progress_start = dxt1_params.m_progress_start;
if (state.m_has_blocks[0])
{
state.m_qdxt1_params.m_progress_start = cur_progress_start;
state.m_qdxt1_params.m_progress_range = dxt1_params.m_progress_range / num_elements;
cur_progress_start += state.m_qdxt1_params.m_progress_range;
}
if (state.m_has_blocks[1])
{
state.m_qdxt5_params[0].m_progress_start = cur_progress_start;
state.m_qdxt5_params[0].m_progress_range = dxt1_params.m_progress_range / num_elements;
cur_progress_start += state.m_qdxt5_params[0].m_progress_range;
}
if (state.m_has_blocks[2])
{
state.m_qdxt5_params[1].m_progress_start = cur_progress_start;
state.m_qdxt5_params[1].m_progress_range = dxt1_params.m_progress_range - cur_progress_start;
}
state.m_fmt = fmt;
dst_tex.init(get_width(), get_height(), get_num_levels(), get_num_faces(), fmt, get_name().get_ptr(), cDefaultOrientationFlags);
state.m_pixel_blocks.resize(0);
image_utils::conversion_type cook_conv_type = image_utils::cConversion_Invalid;
if (cook)
{
cook_conv_type = image_utils::get_conversion_type(true, fmt);
if (pixel_format_helpers::is_alpha_only(fmt) && !pixel_format_helpers::has_alpha(m_format))
cook_conv_type = image_utils::cConversion_Y_To_A;
}
state.m_qdxt1_params.m_num_mips = 0;
state.m_qdxt5_params[0].m_num_mips = 0;
state.m_qdxt5_params[1].m_num_mips = 0;
for (uint f = 0; f < get_num_faces(); f++)
{
for (uint l = 0; l < get_num_levels(); l++)
{
mip_level* pLevel = get_level(f, l);
dst_tex.get_level(f, l)->set_orientation_flags(pLevel->get_orientation_flags());
image_u8 tmp_img;
image_u8 img(*pLevel->get_unpacked_image(tmp_img, cUnpackFlagUncook));
if (cook_conv_type != image_utils::cConversion_Invalid)
image_utils::convert_image(img, cook_conv_type);
const uint num_blocks_x = (img.get_width() + 3) / 4;
const uint num_blocks_y = (img.get_height() + 3) / 4;
const uint total_blocks = num_blocks_x * num_blocks_y;
const uint cur_size = state.m_pixel_blocks.size();
state.m_pixel_blocks.resize(cur_size + total_blocks);
dxt_pixel_block* pDst_blocks = &state.m_pixel_blocks[cur_size];
{
CRNLIB_ASSERT(state.m_qdxt1_params.m_num_mips < qdxt1_params::cMaxMips);
qdxt1_params::mip_desc& mip_desc = state.m_qdxt1_params.m_mip_desc[state.m_qdxt1_params.m_num_mips];
mip_desc.m_first_block = cur_size;
mip_desc.m_block_width = num_blocks_x;
mip_desc.m_block_height = num_blocks_y;
state.m_qdxt1_params.m_num_mips++;
}
for (uint i = 0; i < 2; i++)
{
CRNLIB_ASSERT(state.m_qdxt5_params[i].m_num_mips < qdxt5_params::cMaxMips);
qdxt5_params::mip_desc& mip_desc = state.m_qdxt5_params[i].m_mip_desc[state.m_qdxt5_params[i].m_num_mips];
mip_desc.m_first_block = cur_size;
mip_desc.m_block_width = num_blocks_x;
mip_desc.m_block_height = num_blocks_y;
state.m_qdxt5_params[i].m_num_mips++;
}
for (uint block_y = 0; block_y < num_blocks_y; block_y++)
{
const uint img_y = block_y << 2;
for (uint block_x = 0; block_x < num_blocks_x; block_x++)
{
const uint img_x = block_x << 2;
color_quad_u8* pDst_pixel = &pDst_blocks->m_pixels[0][0];
pDst_blocks++;
for (uint by = 0; by < 4; by++)
for (uint bx = 0; bx < 4; bx++)
*pDst_pixel++ = img.get_clamped(img_x + bx, img_y + by);
} // block_x
} // block_y
} // l
} // f
if (state.m_has_blocks[0])
{
if (!state.m_qdxt1.init(state.m_pixel_blocks.size(), &state.m_pixel_blocks[0], state.m_qdxt1_params))
return false;
}
if (state.m_has_blocks[1])
{
if (!state.m_qdxt5a.init(state.m_pixel_blocks.size(), &state.m_pixel_blocks[0], state.m_qdxt5_params[0]))
return false;
}
if (state.m_has_blocks[2])
{
if (!state.m_qdxt5b.init(state.m_pixel_blocks.size(), &state.m_pixel_blocks[0], state.m_qdxt5_params[1]))
return false;
}
return true;
}
bool mipmapped_texture::qdxt_pack(qdxt_state& state, mipmapped_texture& dst_tex, const qdxt1_params& dxt1_params, const qdxt5_params& dxt5_params)
{
if (!is_valid())
return false;
CRNLIB_ASSERT(dxt1_params.m_quality_level <= qdxt1_params::cMaxQuality);
CRNLIB_ASSERT(dxt5_params.m_quality_level <= qdxt5_params::cMaxQuality);
state.m_qdxt1_params.m_quality_level = dxt1_params.m_quality_level;
state.m_qdxt1_params.m_pProgress_func = dxt1_params.m_pProgress_func;
state.m_qdxt1_params.m_pProgress_data = dxt1_params.m_pProgress_data;
state.m_qdxt5_params[0].m_quality_level = dxt5_params.m_quality_level;
state.m_qdxt5_params[0].m_pProgress_func = dxt5_params.m_pProgress_func;
state.m_qdxt5_params[0].m_pProgress_data = dxt5_params.m_pProgress_data;
state.m_qdxt5_params[1].m_quality_level = dxt5_params.m_quality_level;
state.m_qdxt5_params[1].m_pProgress_func = dxt5_params.m_pProgress_func;
state.m_qdxt5_params[1].m_pProgress_data = dxt5_params.m_pProgress_data;
const uint num_elements = state.m_has_blocks[0] + state.m_has_blocks[1] + state.m_has_blocks[2];
uint cur_progress_start = dxt1_params.m_progress_start;
if (state.m_has_blocks[0])
{
state.m_qdxt1_params.m_progress_start = cur_progress_start;
state.m_qdxt1_params.m_progress_range = dxt1_params.m_progress_range / num_elements;
cur_progress_start += state.m_qdxt1_params.m_progress_range;
}
if (state.m_has_blocks[1])
{
state.m_qdxt5_params[0].m_progress_start = cur_progress_start;
state.m_qdxt5_params[0].m_progress_range = dxt1_params.m_progress_range / num_elements;
cur_progress_start += state.m_qdxt5_params[0].m_progress_range;
}
if (state.m_has_blocks[2])
{
state.m_qdxt5_params[1].m_progress_start = cur_progress_start;
state.m_qdxt5_params[1].m_progress_range = dxt1_params.m_progress_range - cur_progress_start;
}
crnlib::vector<dxt1_block> dxt1_blocks;
if (state.m_has_blocks[0])
{
dxt1_blocks.resize(state.m_pixel_blocks.size());
float pow_mul = 1.0f;
if (state.m_fmt == PIXEL_FMT_DXT5_CCxY)
{
// use a "deeper" codebook size curves when compressing chroma into DXT1, because it's not as important
pow_mul = 1.5f;
}
else if (state.m_fmt == PIXEL_FMT_DXT5)
{
// favor color more than alpha
pow_mul = .75f;
}
if (!state.m_qdxt1.pack(&dxt1_blocks[0], 1, state.m_qdxt1_params, pow_mul))
return false;
}
crnlib::vector<dxt5_block> dxt5_blocks[2];
for (uint i = 0; i < 2; i++)
{
if (state.m_has_blocks[i + 1])
{
dxt5_blocks[i].resize(state.m_pixel_blocks.size());
if (!(i ? state.m_qdxt5b : state.m_qdxt5a).pack(&dxt5_blocks[i][0], 1, state.m_qdxt5_params[i]))
return false;
}
}
uint cur_block_ofs = 0;
for (uint f = 0; f < dst_tex.get_num_faces(); f++)
{
for (uint l = 0; l < dst_tex.get_num_levels(); l++)
{
mip_level* pDst_level = dst_tex.get_level(f, l);
const uint num_blocks_x = (pDst_level->get_width() + 3) / 4;
const uint num_blocks_y = (pDst_level->get_height() + 3) / 4;
const uint total_blocks = num_blocks_x * num_blocks_y;
dxt_image* pDst_dxt_image = pDst_level->get_dxt_image();
dxt_image::element* pDst = pDst_dxt_image->get_element_ptr();
for (uint block_index = 0; block_index < total_blocks; block_index++)
{
if (state.m_has_blocks[1])
memcpy(pDst, &dxt5_blocks[0][cur_block_ofs + block_index], 8);
if (state.m_has_blocks[2])
memcpy(pDst + 1, &dxt5_blocks[1][cur_block_ofs + block_index], 8);
if (state.m_has_blocks[0])
memcpy(pDst + state.m_has_blocks[1], &dxt1_blocks[cur_block_ofs + block_index], 8);
pDst += pDst_dxt_image->get_elements_per_block();
}
cur_block_ofs += total_blocks;
}
}
if (dxt1_params.m_pProgress_func)
{
if (!dxt1_params.m_pProgress_func(dxt1_params.m_progress_start + dxt1_params.m_progress_range, dxt1_params.m_pProgress_data))
return false;
}
CRNLIB_ASSERT(dst_tex.check());
return true;
}
bool mipmapped_texture::read_from_file(const char* pFilename, texture_file_types::format file_format)
{
clear();
set_last_error("Can't open file");
bool success = false;
cfile_stream in_stream;
if (in_stream.open(pFilename))
{
data_stream_serializer serializer(in_stream);
success = read_from_stream(serializer, file_format);
}
return success;
}
bool mipmapped_texture::read_from_stream(data_stream_serializer& serializer, texture_file_types::format file_format)
{
clear();
if (!serializer.get_stream())
{
set_last_error("Invalid stream");
return false;
}
if (file_format == texture_file_types::cFormatInvalid)
file_format = texture_file_types::determine_file_format(serializer.get_name().get_ptr());
if (file_format == texture_file_types::cFormatInvalid)
{
set_last_error("Unsupported file format");
return false;
}
set_last_error("Image file load failed");
bool success = false;
if (!texture_file_types::supports_mipmaps(file_format))
{
success = read_regular_image(serializer, file_format);
}
else
{
switch (file_format)
{
case texture_file_types::cFormatDDS:
{
success = read_dds(serializer);
break;
}
case texture_file_types::cFormatCRN:
{
success = read_crn(serializer);
break;
}
case texture_file_types::cFormatKTX:
{
success = read_ktx(serializer);
break;
}
default:
{
CRNLIB_ASSERT(0);
break;
}
}
}
if (success)
{
CRNLIB_ASSERT(check());
m_source_file_type = file_format;
set_name(serializer.get_name());
clear_last_error();
}
return success;
}
bool mipmapped_texture::read_regular_image(data_stream_serializer &serializer, texture_file_types::format file_format)
{
file_format;
image_u8* pImg = crnlib_new<image_u8>();
bool status = image_utils::read_from_stream(*pImg, serializer, 0);
if (!status)
{
crnlib_delete(pImg);
set_last_error("Failed loading image file");
return false;
}
mip_level* pLevel = crnlib_new<mip_level>();
pLevel->assign(pImg);
assign(pLevel);
set_name(serializer.get_name());
return true;
}
bool mipmapped_texture::read_crn_from_memory(const void *pData, uint data_size, const char* pFilename)
{
clear();
set_last_error("Image file load failed");
if ((!pData) || (data_size < 1)) return false;
crnd::crn_texture_info tex_info;
tex_info.m_struct_size = sizeof(crnd::crn_texture_info);
if (!crnd_get_texture_info(pData, data_size, &tex_info))
{
set_last_error("crnd_get_texture_info() failed");
return false;
}
const pixel_format dds_fmt = (pixel_format)crnd::crnd_crn_format_to_fourcc(tex_info.m_format);
if (dds_fmt == PIXEL_FMT_INVALID)
{
set_last_error("Unsupported DXT format");
return false;
}
const dxt_format dxt_fmt = pixel_format_helpers::get_dxt_format(dds_fmt);
face_vec faces(tex_info.m_faces);
for (uint f = 0; f < tex_info.m_faces; f++)
{
faces[f].resize(tex_info.m_levels);
for (uint l = 0; l < tex_info.m_levels; l++)
faces[f][l] = crnlib_new<mip_level>();
}
const uint tex_num_blocks_x = (tex_info.m_width + 3) >> 2;
const uint tex_num_blocks_y = (tex_info.m_height + 3) >> 2;
vector<uint8> dxt_data;
// Create temp buffer big enough to hold the largest mip level, and all faces if it's a cubemap.
dxt_data.resize(tex_info.m_bytes_per_block * tex_num_blocks_x * tex_num_blocks_y * tex_info.m_faces);
set_last_error("CRN unpack failed");
#if 0
timer t;
double total_time = 0.0f;
t.start();
#endif
crnd::crnd_unpack_context pContext = crnd::crnd_unpack_begin(pData, data_size);
#if 0
total_time += t.get_elapsed_secs();
#endif
if (!pContext)
{
for (uint f = 0; f < faces.size(); f++)
for (uint l = 0; l < faces[f].size(); l++)
crnlib_delete(faces[f][l]);
return false;
}
uint total_pixels = 0;
void* pFaces[cCRNMaxFaces];
for (uint f = tex_info.m_faces; f < cCRNMaxFaces; f++)
pFaces[f] = NULL;
for (uint l = 0; l < tex_info.m_levels; l++)
{
const uint level_width = math::maximum<uint>(1U, tex_info.m_width >> l);
const uint level_height = math::maximum<uint>(1U, tex_info.m_height >> l);
const uint num_blocks_x = (level_width + 3U) >> 2U;
const uint num_blocks_y = (level_height + 3U) >> 2U;
const uint row_pitch = num_blocks_x * tex_info.m_bytes_per_block;
const uint size_of_face = num_blocks_y * row_pitch;
total_pixels += num_blocks_x * num_blocks_y * 4 * 4 * tex_info.m_faces;
#if 0
t.start();
#endif
for (uint f = 0; f < tex_info.m_faces; f++)
pFaces[f] = &dxt_data[f * size_of_face];
if (!crnd::crnd_unpack_level(pContext, pFaces, dxt_data.size(), row_pitch, l))
{
crnd::crnd_unpack_end(pContext);
for (uint f = 0; f < faces.size(); f++)
for (uint l = 0; l < faces[f].size(); l++)
crnlib_delete(faces[f][l]);
return false;
}
#if 0
total_time += t.get_elapsed_secs();
#endif
for (uint f = 0; f < tex_info.m_faces; f++)
{
dxt_image* pDXT_image = crnlib_new<dxt_image>();
if (!pDXT_image->init(
dxt_fmt, level_width, level_height,
num_blocks_x * num_blocks_y * (tex_info.m_bytes_per_block / sizeof(dxt_image::element)),
reinterpret_cast<dxt_image::element*>(pFaces[f]), true))
{
crnlib_delete(pDXT_image);
crnd::crnd_unpack_end(pContext);
for (uint f = 0; f < faces.size(); f++)
for (uint l = 0; l < faces[f].size(); l++)
crnlib_delete(faces[f][l]);
return false;
}
faces[f][l]->assign(pDXT_image, dds_fmt);
}
}
#if 0
if (total_pixels)
{
console::info("read_crn_from_memory: Total pixels: %u, ms: %3.3fms, megapixels/sec: %3.3f",
total_pixels, total_time * 1000.0f, total_pixels / total_time);
}
#endif
crnd::crnd_unpack_end(pContext);
assign(faces);
set_name(pFilename);
m_source_file_type = texture_file_types::cFormatCRN;
clear_last_error();
return true;
}
bool mipmapped_texture::read_crn(data_stream_serializer& serializer)
{
crnlib::vector<uint8> crn_data;
if (!serializer.read_entire_file(crn_data))
{
set_last_error("Failed reading CRN file");
return false;
}
return read_crn_from_memory(crn_data.get_ptr(), crn_data.size(), serializer.get_name().get_ptr());
}
bool mipmapped_texture::write_to_file(
const char* pFilename,
texture_file_types::format file_format,
crn_comp_params* pComp_params,
uint32 *pActual_quality_level, float *pActual_bitrate,
uint32 image_write_flags)
{
if (pActual_quality_level) *pActual_quality_level = 0;
if (pActual_bitrate) *pActual_bitrate = 0.0f;
if (!is_valid())
{
set_last_error("Unable to write empty texture");
return false;
}
if (file_format == texture_file_types::cFormatInvalid)
file_format = texture_file_types::determine_file_format(pFilename);
if (file_format == texture_file_types::cFormatInvalid)
{
set_last_error("Unknown file format");
return false;
}
bool success = false;
if ( ((pComp_params) && (file_format == texture_file_types::cFormatDDS)) ||
(file_format == texture_file_types::cFormatCRN) )
{
if (!pComp_params)
return false;
success = write_comp_texture(pFilename, *pComp_params, pActual_quality_level, pActual_bitrate);
}
else if (!texture_file_types::supports_mipmaps(file_format))
{
success = write_regular_image(pFilename, image_write_flags);
}
else
{
if (pComp_params)
{
console::warning("mipmapped_texture::write_to_file: Ignoring CRN compression parameters (currently unsupported for this file type).");
}
cfile_stream write_stream;
if (!write_stream.open(pFilename, cDataStreamWritable | cDataStreamSeekable))
{
set_last_error(dynamic_string(cVarArg, "Failed creating output file \"%s\"", pFilename).get_ptr());
return false;
}
data_stream_serializer serializer(write_stream);
switch (file_format)
{
case texture_file_types::cFormatDDS:
{
success = write_dds(serializer);
break;
}
case texture_file_types::cFormatKTX:
{
success = write_ktx(serializer);
break;
}
default:
{
break;
}
}
}
return success;
}
bool mipmapped_texture::write_regular_image(const char* pFilename, uint32 image_write_flags)
{
image_u8 tmp;
image_u8* pLevel_image = get_level_image(0, 0, tmp);
if (!image_utils::write_to_file(pFilename, *pLevel_image, image_write_flags))
{
set_last_error("File write failed");
return false;
}
return true;
}
void mipmapped_texture::print_crn_comp_params(const crn_comp_params& p)
{
console::debug("CRN compression params:");
console::debug(" File Type: %s", crn_get_file_type_ext(p.m_file_type));
console::debug(" Quality level: %u", p.m_quality_level);
console::debug(" Target Bitrate: %f", p.m_target_bitrate);
console::debug(" Faces: %u", p.m_faces);
console::debug(" Width: %u", p.m_width);
console::debug(" Height: %u", p.m_height);
console::debug(" Levels: %u", p.m_levels);
console::debug(" Pixel Format: %s", crn_get_format_string(p.m_format));
console::debug("Use manual CRN palette sizes: %u", p.get_flag(cCRNCompFlagManualPaletteSizes));
console::debug("Color endpoints: %u", p.m_crn_color_endpoint_palette_size);
console::debug("Color selectors: %u", p.m_crn_color_selector_palette_size);
console::debug("Alpha endpoints: %u", p.m_crn_alpha_endpoint_palette_size);
console::debug("Alpha selectors: %u", p.m_crn_alpha_selector_palette_size);
console::debug("Flags:");
console::debug(" Perceptual: %u", p.get_flag(cCRNCompFlagPerceptual));
console::debug(" Hierarchical: %u", p.get_flag(cCRNCompFlagHierarchical));
console::debug(" UseBothBlockTypes: %u", p.get_flag(cCRNCompFlagUseBothBlockTypes));
console::debug(" UseTransparentIndicesForBlack: %u", p.get_flag(cCRNCompFlagUseTransparentIndicesForBlack));
console::debug(" DisableEndpointCaching: %u", p.get_flag(cCRNCompFlagDisableEndpointCaching));
console::debug("GrayscaleSampling: %u", p.get_flag(cCRNCompFlagGrayscaleSampling));
console::debug(" UseDXT1ATransparency: %u", p.get_flag(cCRNCompFlagDXT1AForTransparency));
console::debug("AdaptiveTileColorPSNRDerating: %2.2fdB", p.m_crn_adaptive_tile_color_psnr_derating);
console::debug("AdaptiveTileAlphaPSNRDerating: %2.2fdB", p.m_crn_adaptive_tile_alpha_psnr_derating);
console::debug("NumHelperThreads: %u", p.m_num_helper_threads);
}
bool mipmapped_texture::write_comp_texture(const char* pFilename, const crn_comp_params &orig_comp_params, uint32 *pActual_quality_level, float *pActual_bitrate)
{
crn_comp_params comp_params(orig_comp_params);
if (pActual_quality_level) *pActual_quality_level = 0;
if (pActual_bitrate) *pActual_bitrate = 0.0f;
if (math::maximum(get_height(), get_width()) > cCRNMaxLevelResolution)
{
set_last_error("Texture resolution is too big!");
return false;
}
comp_params.m_faces = get_num_faces();
comp_params.m_levels = get_num_levels();
comp_params.m_width = get_width();
comp_params.m_height = get_height();
image_u8 temp_images[cCRNMaxFaces][cCRNMaxLevels];
for (uint f = 0; f < get_num_faces(); f++)
{
for (uint l = 0; l < get_num_levels(); l++)
{
image_u8* p = get_level_image(f, l, temp_images[f][l]);
comp_params.m_pImages[f][l] = (crn_uint32*)p->get_ptr();
}
}
if (comp_params.get_flag(cCRNCompFlagDebugging))
print_crn_comp_params(comp_params);
timer t;
t.start();
crnlib::vector<uint8> comp_data;
if (!create_compressed_texture(comp_params, comp_data, pActual_quality_level, pActual_bitrate))
{
set_last_error("CRN compression failed");
return false;
}
double total_time = t.get_elapsed_secs();
if (comp_params.get_flag(cCRNCompFlagDebugging))
{
console::debug("\nTotal compression time: %3.3fs", total_time);
}
cfile_stream out_stream;
if (!out_stream.open(pFilename, cDataStreamWritable | cDataStreamSeekable))
{
set_last_error("Failed opening file");
return false;
}
if (out_stream.write(comp_data.get_ptr(), comp_data.size()) != comp_data.size())
{
set_last_error("Failed writing to file");
return false;
}
if (!out_stream.close())
{
set_last_error("Failed writing to file");
return false;
}
return true;
}
uint mipmapped_texture::get_total_pixels_in_all_faces_and_mips() const
{
uint total_pixels = 0;
for (uint l = 0; l < m_faces.size(); l++)
for (uint m = 0; m < m_faces[l].size(); m++)
total_pixels += m_faces[l][m]->get_total_pixels();
return total_pixels;
}
void mipmapped_texture::set_orientation_flags(orientation_flags_t flags)
{
for (uint l = 0; l < m_faces.size(); l++)
for (uint m = 0; m < m_faces[l].size(); m++)
m_faces[l][m]->set_orientation_flags(flags);
}
bool mipmapped_texture::is_flipped() const
{
for (uint l = 0; l < m_faces.size(); l++)
for (uint m = 0; m < m_faces[l].size(); m++)
if (m_faces[l][m]->is_flipped())
return true;
return false;
}
bool mipmapped_texture::is_x_flipped() const
{
for (uint l = 0; l < m_faces.size(); l++)
for (uint m = 0; m < m_faces[l].size(); m++)
if (m_faces[l][m]->is_x_flipped())
return true;
return false;
}
bool mipmapped_texture::is_y_flipped() const
{
for (uint l = 0; l < m_faces.size(); l++)
for (uint m = 0; m < m_faces[l].size(); m++)
if (m_faces[l][m]->is_y_flipped())
return true;
return false;
}
bool mipmapped_texture::can_unflip_without_unpacking() const
{
if (!is_valid())
return false;
if (!is_packed())
return true;
for (uint l = 0; l < m_faces.size(); l++)
for (uint m = 0; m < m_faces[l].size(); m++)
if (!m_faces[l][m]->can_unflip_without_unpacking())
return false;
return true;
}
bool mipmapped_texture::unflip(bool allow_unpacking_to_flip, bool uncook_if_necessary_to_unpack)
{
if (!is_valid())
return false;
if (is_packed())
{
// The texture is packed - make sure all faces/miplevels can be consistently unflipped.
bool can_do_packed_unflip = can_unflip_without_unpacking();
if ((!can_do_packed_unflip) && (!allow_unpacking_to_flip))
return false;
// If any face/miplevel can't unflip the packed bits, then just unpack the whole texture.
if (!can_do_packed_unflip)
unpack_from_dxt(uncook_if_necessary_to_unpack);
}
for (uint l = 0; l < m_faces.size(); l++)
for (uint m = 0; m < m_faces[l].size(); m++)
if (!m_faces[l][m]->unflip(true, false))
return false;
CRNLIB_VERIFY(check());
return true;
}
#if 0
bool mipmapped_texture::flip_x()
{
for (uint l = 0; l < m_faces.size(); l++)
for (uint m = 0; m < m_faces[l].size(); m++)
if (!m_faces[l][m]->flip_x())
return false;
return true;
}
#endif
bool mipmapped_texture::flip_y_helper()
{
for (uint l = 0; l < m_faces.size(); l++)
for (uint m = 0; m < m_faces[l].size(); m++)
if (!m_faces[l][m]->flip_y())
return false;
return true;
}
bool mipmapped_texture::flip_y(bool update_orientation_flags)
{
mipmapped_texture temp_tex(*this);
if (!temp_tex.flip_y_helper())
{
temp_tex = *this;
temp_tex.unpack_from_dxt(true);
if (!temp_tex.flip_y_helper())
return false;
}
swap(temp_tex);
if (update_orientation_flags)
{
for (uint f = 0; f < get_num_faces(); f++)
{
for (uint m = 0; m < get_face(f).size(); m++)
{
uint orient_flags = get_face(f)[m]->get_orientation_flags();
orient_flags ^= cOrientationFlagYFlipped;
get_face(f)[m]->set_orientation_flags(static_cast<orientation_flags_t>(orient_flags));
}
}
}
CRNLIB_ASSERT(check());
return true;
}
} // namespace crnlib