Files
unity/crnlib/crn_mipmapped_texture.cpp
T
Alexander Suvorov 3e12aff909 Fix miscellaneous compiler warnings
DXT Testing:

The modified algorithm has been tested on the Kodak test set using 64-bit build with default settings (running on Windows 10, i7-4790, 3.6GHz). All the decompressed test images are identical to the images being compressed and decompressed using original version of Crunch (revision ea9b8d8).

[Compressing Kodak set without mipmaps using DXT1 encoding]
Original: 1582222 bytes / 28.866 sec
Modified: 1468204 bytes / 11.858 sec
Improvement: 7.21% (compression ratio) / 58.92% (compression time)

[Compressing Kodak set with mipmaps using DXT1 encoding]
Original: 2065243 bytes / 36.878 sec
Modified: 1914805 bytes / 15.625 sec
Improvement: 7.28% (compression ratio) / 57.63% (compression time)

ETC Testing:

The modified algorithm has been tested on the Kodak test set using 64-bit build with default settings (running on Windows 10, i7-4790, 3.6GHz). The ETC1 quantization parameters have been selected in such a way, so that ETC1 compression gives approximately the same average Luma PSNR as the corresponding DXT1 compression (which is equal to 34.044 dB for the Kodak test set compressed without mipmaps using DXT1 encoding and default quality settings).

[Compressing Kodak set without mipmaps using ETC1 encoding]
Total size: 1607858 bytes
Total time: 17.181 sec
Average bitrate: 1.363 bpp
Average Luma PSNR: 34.050 dB
2017-09-11 13:52:21 +02:00

3082 lines
93 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"
#include "../inc/crn_defs.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>();
CRNLIB_ASSERT(get_unpacked_image(*pNew_img, uncook ? cUnpackFlagUncook : 0) == 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;
}
case PIXEL_FMT_ETC2: {
m_format = PIXEL_FMT_ETC2;
dxt_fmt = cETC2;
break;
}
case PIXEL_FMT_ETC2A: {
m_format = PIXEL_FMT_ETC2A;
dxt_fmt = cETC2A;
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_ETC2: {
desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_ETC2;
desc.ddpfPixelFormat.dwRGBBitCount = 0;
break;
}
case PIXEL_FMT_ETC2A: {
desc.ddpfPixelFormat.dwFourCC = (uint32)PIXEL_FMT_ETC2A;
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_COMPRESSED_RGB8_ETC2:
dxt_fmt = cETC2;
break;
case KTX_COMPRESSED_RGBA8_ETC2_EAC:
dxt_fmt = cETC2A;
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;
}
case PIXEL_FMT_ETC2: {
ogl_internal_fmt = KTX_COMPRESSED_RGB8_ETC2;
break;
}
case PIXEL_FMT_ETC2A: {
ogl_internal_fmt = KTX_COMPRESSED_RGBA8_ETC2_EAC;
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) || (fmt == PIXEL_FMT_ETC2) || (fmt == PIXEL_FMT_ETC2A)) {
// QDXT doesn't support DXT3 or ETCn 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:
case PIXEL_FMT_ETC2:
case PIXEL_FMT_ETC2A: {
console::warning("mipmapped_texture::qdxt_pack_init: This method does not support ETCn");
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);
} 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) {
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