-
Notifications
You must be signed in to change notification settings - Fork 315
refactor Image::convert() #1324
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,6 +41,8 @@ | |
| #include "core/config/project_settings.h" | ||
| #include "core/error/error_macros.h" | ||
| #include "core/io/image_loader.h" | ||
| #include "core/io/image_rw.h" | ||
| #include "core/io/io.h" | ||
| #include "core/io/resource_loader.h" | ||
| #include "core/math/math_funcs.h" | ||
| #include "core/templates/hash_map.h" | ||
|
|
@@ -475,69 +477,6 @@ int Image::get_mipmap_count() const { | |
| } | ||
| } | ||
|
|
||
| /// Using template generates perfectly optimized code due to constant expression reduction and unused variable removal present in all compilers. | ||
| template <uint32_t read_bytes, bool read_alpha, uint32_t write_bytes, bool write_alpha, bool read_gray, bool write_gray> | ||
| static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p_dst) { | ||
| constexpr uint32_t max_bytes = MAX(read_bytes, write_bytes); | ||
|
|
||
| for (int y = 0; y < p_height; y++) { | ||
| for (int x = 0; x < p_width; x++) { | ||
| const uint8_t *rofs = &p_src[((y * p_width) + x) * (read_bytes + (read_alpha ? 1 : 0))]; | ||
| uint8_t *wofs = &p_dst[((y * p_width) + x) * (write_bytes + (write_alpha ? 1 : 0))]; | ||
|
|
||
| uint8_t rgba[4] = { 0, 0, 0, 255 }; | ||
|
|
||
| if constexpr (read_gray) { | ||
| rgba[0] = rofs[0]; | ||
| rgba[1] = rofs[0]; | ||
| rgba[2] = rofs[0]; | ||
| } else { | ||
| for (uint32_t i = 0; i < max_bytes; i++) { | ||
| rgba[i] = (i < read_bytes) ? rofs[i] : 0; | ||
| } | ||
| } | ||
|
|
||
| if constexpr (read_alpha || write_alpha) { | ||
| rgba[3] = read_alpha ? rofs[read_bytes] : 255; | ||
| } | ||
|
|
||
| if constexpr (write_gray) { | ||
| // REC.709 | ||
| const uint8_t luminance = (13938U * rgba[0] + 46869U * rgba[1] + 4729U * rgba[2] + 32768U) >> 16U; | ||
| wofs[0] = luminance; | ||
| } else { | ||
| for (uint32_t i = 0; i < write_bytes; i++) { | ||
| wofs[i] = rgba[i]; | ||
| } | ||
| } | ||
|
|
||
| if constexpr (write_alpha) { | ||
| wofs[write_bytes] = rgba[3]; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| template <typename T, uint32_t read_channels, uint32_t write_channels, T def_zero, T def_one> | ||
| static void _convert_fast(int p_width, int p_height, const T *p_src, T *p_dst) { | ||
| uint32_t dst_count = 0; | ||
| uint32_t src_count = 0; | ||
|
|
||
| const int resolution = p_width * p_height; | ||
|
|
||
| for (int i = 0; i < resolution; i++) { | ||
| memcpy(p_dst + dst_count, p_src + src_count, MIN(read_channels, write_channels) * sizeof(T)); | ||
|
|
||
| if constexpr (write_channels > read_channels) { | ||
| const T def_value[4] = { def_zero, def_zero, def_zero, def_one }; | ||
| memcpy(p_dst + dst_count + read_channels, &def_value[read_channels], (write_channels - read_channels) * sizeof(T)); | ||
| } | ||
|
|
||
| dst_count += write_channels; | ||
| src_count += read_channels; | ||
| } | ||
| } | ||
|
|
||
| static bool _are_formats_compatible(Image::Format p_format0, Image::Format p_format1) { | ||
| if (p_format0 <= Image::FORMAT_RGBA8 && p_format1 <= Image::FORMAT_RGBA8) { | ||
| return true; | ||
|
|
@@ -551,6 +490,7 @@ static bool _are_formats_compatible(Image::Format p_format0, Image::Format p_for | |
| } | ||
|
|
||
| void Image::convert(Format p_new_format) { | ||
| IO::Error err = IO::Error::Okay; | ||
| ERR_FAIL_INDEX_MSG(p_new_format, FORMAT_MAX, vformat("The Image format specified (%d) is out of range. See Image's Format enum.", p_new_format)); | ||
|
|
||
| if (data.is_empty() || p_new_format == format) { | ||
|
|
@@ -592,185 +532,75 @@ void Image::convert(Format p_new_format) { | |
| // Convert the formats in an optimized way by removing/adding color channels if necessary. | ||
| Image new_img(width, height, mipmaps, p_new_format); | ||
|
|
||
| const int conversion_type = format | p_new_format << 8; | ||
|
|
||
| for (int mip = 0; mip < mipmap_count; mip++) { | ||
| int64_t mip_offset = 0; | ||
| int64_t mip_size = 0; | ||
| int mip_width = 0; | ||
| int mip_height = 0; | ||
| IO::Reader reader = {}; | ||
| IO::Writer writer = {}; | ||
| IO::Image::Reader imReader = {}; | ||
| IO::Image::Writer imWriter = {}; | ||
| ColorRGBAF32x16 block; | ||
| get_mipmap_offset_size_and_dimensions(mip, mip_offset, mip_size, mip_width, mip_height); | ||
|
|
||
| const uint8_t *rptr = data.ptr() + mip_offset; | ||
| uint8_t *wptr = new_img.data.ptrw() + new_img.get_mipmap_offset(mip); | ||
|
|
||
| switch (conversion_type) { | ||
| case FORMAT_L8 | (FORMAT_LA8 << 8): | ||
| _convert<1, false, 1, true, true, true>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_L8 | (FORMAT_R8 << 8): | ||
| _convert<1, false, 1, false, true, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_L8 | (FORMAT_RG8 << 8): | ||
| _convert<1, false, 2, false, true, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_L8 | (FORMAT_RGB8 << 8): | ||
| _convert<1, false, 3, false, true, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_L8 | (FORMAT_RGBA8 << 8): | ||
| _convert<1, false, 3, true, true, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_LA8 | (FORMAT_L8 << 8): | ||
| _convert<1, true, 1, false, true, true>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_LA8 | (FORMAT_R8 << 8): | ||
| _convert<1, true, 1, false, true, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_LA8 | (FORMAT_RG8 << 8): | ||
| _convert<1, true, 2, false, true, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_LA8 | (FORMAT_RGB8 << 8): | ||
| _convert<1, true, 3, false, true, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_LA8 | (FORMAT_RGBA8 << 8): | ||
| _convert<1, true, 3, true, true, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_R8 | (FORMAT_L8 << 8): | ||
| _convert<1, false, 1, false, false, true>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_R8 | (FORMAT_LA8 << 8): | ||
| _convert<1, false, 1, true, false, true>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_R8 | (FORMAT_RG8 << 8): | ||
| _convert<1, false, 2, false, false, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_R8 | (FORMAT_RGB8 << 8): | ||
| _convert<1, false, 3, false, false, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_R8 | (FORMAT_RGBA8 << 8): | ||
| _convert<1, false, 3, true, false, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RG8 | (FORMAT_L8 << 8): | ||
| _convert<2, false, 1, false, false, true>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RG8 | (FORMAT_LA8 << 8): | ||
| _convert<2, false, 1, true, false, true>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RG8 | (FORMAT_R8 << 8): | ||
| _convert<2, false, 1, false, false, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RG8 | (FORMAT_RGB8 << 8): | ||
| _convert<2, false, 3, false, false, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RG8 | (FORMAT_RGBA8 << 8): | ||
| _convert<2, false, 3, true, false, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RGB8 | (FORMAT_L8 << 8): | ||
| _convert<3, false, 1, false, false, true>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RGB8 | (FORMAT_LA8 << 8): | ||
| _convert<3, false, 1, true, false, true>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RGB8 | (FORMAT_R8 << 8): | ||
| _convert<3, false, 1, false, false, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RGB8 | (FORMAT_RG8 << 8): | ||
| _convert<3, false, 2, false, false, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RGB8 | (FORMAT_RGBA8 << 8): | ||
| _convert<3, false, 3, true, false, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RGBA8 | (FORMAT_L8 << 8): | ||
| _convert<3, true, 1, false, false, true>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RGBA8 | (FORMAT_LA8 << 8): | ||
| _convert<3, true, 1, true, false, true>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RGBA8 | (FORMAT_R8 << 8): | ||
| _convert<3, true, 1, false, false, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RGBA8 | (FORMAT_RG8 << 8): | ||
| _convert<3, true, 2, false, false, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RGBA8 | (FORMAT_RGB8 << 8): | ||
| _convert<3, true, 3, false, false, false>(mip_width, mip_height, rptr, wptr); | ||
| break; | ||
| case FORMAT_RH | (FORMAT_RGH << 8): | ||
| _convert_fast<uint16_t, 1, 2, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); | ||
| break; | ||
| case FORMAT_RH | (FORMAT_RGBH << 8): | ||
| _convert_fast<uint16_t, 1, 3, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); | ||
| break; | ||
| case FORMAT_RH | (FORMAT_RGBAH << 8): | ||
| _convert_fast<uint16_t, 1, 4, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); | ||
| break; | ||
| case FORMAT_RGH | (FORMAT_RH << 8): | ||
| _convert_fast<uint16_t, 2, 1, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); | ||
| break; | ||
| case FORMAT_RGH | (FORMAT_RGBH << 8): | ||
| _convert_fast<uint16_t, 2, 3, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); | ||
| break; | ||
| case FORMAT_RGH | (FORMAT_RGBAH << 8): | ||
| _convert_fast<uint16_t, 2, 4, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); | ||
| break; | ||
| case FORMAT_RGBH | (FORMAT_RH << 8): | ||
| _convert_fast<uint16_t, 3, 1, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); | ||
| break; | ||
| case FORMAT_RGBH | (FORMAT_RGH << 8): | ||
| _convert_fast<uint16_t, 3, 2, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); | ||
| break; | ||
| case FORMAT_RGBH | (FORMAT_RGBAH << 8): | ||
| _convert_fast<uint16_t, 3, 4, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); | ||
| break; | ||
| case FORMAT_RGBAH | (FORMAT_RH << 8): | ||
| _convert_fast<uint16_t, 4, 1, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); | ||
| break; | ||
| case FORMAT_RGBAH | (FORMAT_RGH << 8): | ||
| _convert_fast<uint16_t, 4, 2, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); | ||
| break; | ||
| case FORMAT_RGBAH | (FORMAT_RGBH << 8): | ||
| _convert_fast<uint16_t, 4, 3, 0x0000, 0x3C00>(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); | ||
| break; | ||
| case FORMAT_RF | (FORMAT_RGF << 8): | ||
| _convert_fast<uint32_t, 1, 2, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); | ||
| break; | ||
| case FORMAT_RF | (FORMAT_RGBF << 8): | ||
| _convert_fast<uint32_t, 1, 3, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); | ||
| break; | ||
| case FORMAT_RF | (FORMAT_RGBAF << 8): | ||
| _convert_fast<uint32_t, 1, 4, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); | ||
| break; | ||
| case FORMAT_RGF | (FORMAT_RF << 8): | ||
| _convert_fast<uint32_t, 2, 1, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); | ||
| break; | ||
| case FORMAT_RGF | (FORMAT_RGBF << 8): | ||
| _convert_fast<uint32_t, 2, 3, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); | ||
| break; | ||
| case FORMAT_RGF | (FORMAT_RGBAF << 8): | ||
| _convert_fast<uint32_t, 2, 4, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); | ||
| break; | ||
| case FORMAT_RGBF | (FORMAT_RF << 8): | ||
| _convert_fast<uint32_t, 3, 1, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); | ||
| break; | ||
| case FORMAT_RGBF | (FORMAT_RGF << 8): | ||
| _convert_fast<uint32_t, 3, 2, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); | ||
| break; | ||
| case FORMAT_RGBF | (FORMAT_RGBAF << 8): | ||
| _convert_fast<uint32_t, 3, 4, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); | ||
| break; | ||
| case FORMAT_RGBAF | (FORMAT_RF << 8): | ||
| _convert_fast<uint32_t, 4, 1, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); | ||
| break; | ||
| case FORMAT_RGBAF | (FORMAT_RGF << 8): | ||
| _convert_fast<uint32_t, 4, 2, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); | ||
| break; | ||
| case FORMAT_RGBAF | (FORMAT_RGBF << 8): | ||
| _convert_fast<uint32_t, 4, 3, 0x00000000, 0x3F800000>(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); | ||
| break; | ||
| IO::Reader::make( | ||
| &reader, | ||
| { | ||
| .data = (void*)rptr, | ||
| .length = (size_t)mip_size, | ||
| } | ||
| ); | ||
| err = IO::Image::Reader::make( | ||
| &imReader, | ||
| reader, | ||
| format, | ||
| mip_width, | ||
| mip_height | ||
| ); | ||
| if (err != IO::Error::Okay) | ||
| { | ||
| break; | ||
| } | ||
| new_img.get_mipmap_offset_and_size(mip, mip_offset, mip_size); | ||
| err = IO::Writer::make( | ||
| &writer, | ||
| { | ||
| .data = (void*)wptr, | ||
| .length = (size_t)(mip_size), | ||
| } | ||
| ); | ||
| if (err != IO::Error::Okay) | ||
| { | ||
| break; | ||
| } | ||
| IO::Image::Writer::make( | ||
| &imWriter, | ||
| writer, | ||
| new_img.format, | ||
| mip_width, | ||
| mip_height | ||
| ); | ||
| do | ||
| { | ||
| err = IO::Image::Reader::read(imReader, &block); | ||
| if (err == IO::Error::Okay) | ||
| { | ||
| err = IO::Image::Writer::write(imWriter, &block); | ||
| } | ||
| } while (err == IO::Error::Okay); | ||
| err = IO::Image::Writer::flush(imWriter); | ||
| IO::Image::Reader::destroy(&imReader); | ||
| IO::Image::Writer::destroy(&imWriter); | ||
| IO::Reader::destroy(&reader); | ||
| IO::Writer::destroy(&writer); | ||
| if (err != IO::Error::Okay) { return; } | ||
| } | ||
|
|
||
| _copy_internals_from(new_img); | ||
| return; | ||
| } | ||
|
Comment on lines
+556
to
604
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win On Both failure paths (563-566, 575-578) 🐛 Proposed fix: return (with cleanup) instead of break if (err != IO::Error::Okay)
{
- break;
+ IO::Reader::destroy(&reader);
+ return;
}(apply analogously to the second failure branch, additionally destroying 🤖 Prompt for AI Agents |
||
|
|
||
| Image::Format Image::get_format() const { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Redot-Engine/redot-engine
Length of output: 540
🏁 Script executed:
Repository: Redot-Engine/redot-engine
Length of output: 48493
🏁 Script executed:
Repository: Redot-Engine/redot-engine
Length of output: 41789
🏁 Script executed:
Repository: Redot-Engine/redot-engine
Length of output: 11119
Propagate
IO::Image::Writer::make()failure before the conversion loop.IO::Image::Writer::make(&imWriter, ...)returnsIO::Error::NotImplementedwhenwriteisnullptrin the destination format’s vtbl;FORMAT_RGB565,FORMAT_RGBA4444, andFORMAT_RGBE9995have null writes, and they are covered by_are_formats_compatible. When this happens in the optimized path withformat = FORMAT_RGB8going top_new_format = FORMAT_RGB565, the code skips error handling and later callsIO::Image::Writer::write(imWriter, &block), dereferencing a null function pointer. Store and check the return value, destroying any initialized handles before returning.🤖 Prompt for AI Agents