Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BasisUniversal: Fix artifacts on images with resolutions not divisible by 4 #89426

Merged
merged 1 commit into from
May 2, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 63 additions & 7 deletions modules/basis_universal/image_compress_basisu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,74 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
} break;
}

// Copy the source image data with mipmaps into BasisU.
{
// Encode the image with mipmaps.
const int orig_width = image->get_width();
const int orig_height = image->get_height();

bool is_res_div_4 = (orig_width % 4 == 0) && (orig_height % 4 == 0);

// Image's resolution rounded up to the nearest values divisible by 4.
int next_width = orig_width <= 2 ? orig_width : (orig_width + 3) & ~3;
int next_height = orig_height <= 2 ? orig_height : (orig_height + 3) & ~3;

Vector<uint8_t> image_data = image->get_data();
basisu::vector<basisu::image> basisu_mipmaps;

// Buffer for storing padded mipmap data.
Vector<uint32_t> mip_data_padded;

for (int32_t i = 0; i <= image->get_mipmap_count(); i++) {
int ofs, size, width, height;
image->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height);

const uint8_t *image_mip_data = image_data.ptr() + ofs;

// Pad the mipmap's data if its resolution isn't divisible by 4.
if (image->has_mipmaps() && !is_res_div_4 && (width > 2 && height > 2) && (width != next_width || height != next_height)) {
// Source mip's data interpreted as 32-bit RGBA blocks to help with copying pixel data.
const uint32_t *mip_src_data = reinterpret_cast<const uint32_t *>(image_mip_data);

// Reserve space in the padded buffer.
mip_data_padded.resize(next_width * next_height);
uint32_t *data_padded_ptr = mip_data_padded.ptrw();

// Pad mipmap to the nearest block by smearing.
int x = 0, y = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width; x++) {
data_padded_ptr[next_width * y + x] = mip_src_data[width * y + x];
}

// First, smear in x.
for (; x < next_width; x++) {
data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - 1];
}
}

// Then, smear in y.
for (; y < next_height; y++) {
for (x = 0; x < next_width; x++) {
data_padded_ptr[next_width * y + x] = data_padded_ptr[next_width * y + x - next_width];
}
}

// Override the image_mip_data pointer with our temporary Vector.
image_mip_data = reinterpret_cast<const uint8_t *>(mip_data_padded.ptr());

// Override the mipmap's properties.
width = next_width;
height = next_height;
size = mip_data_padded.size() * 4;
}

// Get the next mipmap's resolution.
next_width /= 2;
next_height /= 2;

// Copy the source mipmap's data to a BasisU image.
basisu::image basisu_image(width, height);
memcpy(basisu_image.get_ptr(), image_data.ptr() + ofs, size);
memcpy(basisu_image.get_ptr(), image_mip_data, size);

if (i == 0) {
params.m_source_images.push_back(basisu_image);
Expand All @@ -132,10 +189,10 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha

// Copy the encoded data to the buffer.
{
uint8_t *w = basisu_data.ptrw();
*(uint32_t *)w = decompress_format;
uint8_t *wb = basisu_data.ptrw();
*(uint32_t *)wb = decompress_format;

memcpy(w + 4, basisu_out.get_ptr(), basisu_out.size());
memcpy(wb + 4, basisu_out.get_ptr(), basisu_out.size());
}

return basisu_data;
Expand Down Expand Up @@ -238,8 +295,7 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
uint8_t *dst = out_data.ptrw();
memset(dst, 0, out_data.size());

uint32_t mip_count = Image::get_image_required_mipmaps(basisu_info.m_orig_width, basisu_info.m_orig_height, image_format);
for (uint32_t i = 0; i <= mip_count; i++) {
for (uint32_t i = 0; i < basisu_info.m_total_levels; i++) {
basist::basisu_image_level_info basisu_level;
transcoder.get_image_level_info(src_ptr, src_size, basisu_level, 0, i);

Expand Down
Loading