From 44739a121eec5710309c84de162145b4e99af523 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Fri, 18 Nov 2022 22:12:22 +0200 Subject: [PATCH] [3.x] Windows icon export improvements. Regenerate Windows icon on export to ensure correct icon size order. Add support for using PNG/WebP/SVG files as an icon for Windows exports. Allow using WebP/SVG files as icon for macOS exports. Add option to select generated icons interpolation, and set default interpolation to Lanczos. --- platform/iphone/export/export.cpp | 19 ++-- platform/osx/export/export.cpp | 11 ++- platform/windows/export/export.cpp | 145 ++++++++++++++++++++++++++++- 3 files changed, 160 insertions(+), 15 deletions(-) diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp index 79cdbc9dd419..c47893741843 100644 --- a/platform/iphone/export/export.cpp +++ b/platform/iphone/export/export.cpp @@ -373,6 +373,9 @@ void EditorExportPlatformIOS::get_export_options(List *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version"), "1.0")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/launch_screens_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); + Vector found_plugins = get_plugins(); for (int i = 0; i < found_plugins.size(); i++) { r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, vformat("%s/%s", PNAME("plugins"), found_plugins[i].name)), false)); @@ -872,7 +875,7 @@ Error EditorExportPlatformIOS::_export_icons(const Ref &p_pr add_message(EXPORT_MESSAGE_ERROR, TTR("Export Icons"), vformat("Icon (%s) must be opaque.", info.preset_key)); return ERR_UNCONFIGURED; } - img->resize(side_size, side_size); + img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int())); err = img->save_png(p_iconset_dir + info.export_name); if (err) { memdelete(da); @@ -895,7 +898,7 @@ Error EditorExportPlatformIOS::_export_icons(const Ref &p_pr } if (img->get_width() != side_size || img->get_height() != side_size) { add_message(EXPORT_MESSAGE_WARNING, TTR("Export Icons"), vformat("Icon (%s): '%s' has incorrect size %s and was automatically resized to %s.", info.preset_key, icon_path, img->get_size(), Vector2(side_size, side_size))); - img->resize(side_size, side_size); + img->resize(side_size, side_size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int())); err = img->save_png(p_iconset_dir + info.export_name); } else { err = da->copy(icon_path, p_iconset_dir + info.export_name); @@ -1029,9 +1032,9 @@ Error EditorExportPlatformIOS::_export_loading_screen_images(const Refget_width() / (float)img->get_height(); if (boot_logo_scale) { if (info.height * aspect_ratio <= info.width) { - img->resize(info.height * aspect_ratio, info.height); + img->resize(info.height * aspect_ratio, info.height, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int())); } else { - img->resize(info.width, info.width / aspect_ratio); + img->resize(info.width, info.width / aspect_ratio, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int())); } } Ref new_img = memnew(Image); @@ -1068,17 +1071,17 @@ Error EditorExportPlatformIOS::_export_loading_screen_images(const Refresize(info.width * aspect_ratio, info.width); + img_bs->resize(info.width * aspect_ratio, info.width, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int())); } else { - img_bs->resize(info.height, info.height / aspect_ratio); + img_bs->resize(info.height, info.height / aspect_ratio, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int())); } } } else { if (boot_logo_scale) { if (info.height * aspect_ratio <= info.width) { - img_bs->resize(info.height * aspect_ratio, info.height); + img_bs->resize(info.height * aspect_ratio, info.height, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int())); } else { - img_bs->resize(info.width, info.width / aspect_ratio); + img_bs->resize(info.width, info.width / aspect_ratio, (Image::Interpolation)(p_preset->get("application/launch_screens_interpolation").operator int())); } } } diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index b55726ffb09c..efbfe2447bcf 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -55,7 +55,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform { Ref logo; void _fix_plist(const Ref &p_preset, Vector &plist, const String &p_binary); - void _make_icon(const Ref &p_icon, Vector &p_data); + void _make_icon(const Ref &p_preset, const Ref &p_icon, Vector &p_data); Error _notarize(const Ref &p_preset, const String &p_path); Error _code_sign(const Ref &p_preset, const String &p_path, const String &p_ent_path); @@ -213,7 +213,8 @@ void EditorExportPlatformOSX::get_export_options(List *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.png,*.icns"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.icns,*.png,*.webp,*.svg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_category", PROPERTY_HINT_ENUM, "Business,Developer-tools,Education,Entertainment,Finance,Games,Action-games,Adventure-games,Arcade-games,Board-games,Card-games,Casino-games,Dice-games,Educational-games,Family-games,Kids-games,Music-games,Puzzle-games,Racing-games,Role-playing-games,Simulation-games,Sports-games,Strategy-games,Trivia-games,Word-games,Graphics-design,Healthcare-fitness,Lifestyle,Medical,Music,News,Photography,Productivity,Reference,Social-networking,Sports,Travel,Utilities,Video,Weather"), "Games")); @@ -352,7 +353,7 @@ void _rgba8_to_packbits_encode(int p_ch, int p_size, PoolVector &p_sour memcpy(&p_dest.write[ofs], result.ptr(), res_size); } -void EditorExportPlatformOSX::_make_icon(const Ref &p_icon, Vector &p_data) { +void EditorExportPlatformOSX::_make_icon(const Ref &p_preset, const Ref &p_icon, Vector &p_data) { Ref it = memnew(ImageTexture); Vector data; @@ -386,7 +387,7 @@ void EditorExportPlatformOSX::_make_icon(const Ref &p_icon, Vector copy = p_icon; // does this make sense? doesn't this just increase the reference count instead of making a copy? Do we even need a copy? copy->convert(Image::FORMAT_RGBA8); - copy->resize(icon_infos[i].size, icon_infos[i].size); + copy->resize(icon_infos[i].size, icon_infos[i].size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int())); if (icon_infos[i].is_png) { // Encode PNG icon. @@ -1181,7 +1182,7 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p icon.instance(); icon->load(iconpath); if (!icon->empty()) { - _make_icon(icon, data); + _make_icon(p_preset, icon, data); } } } diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index e6f366f27a46..ef628e225531 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -30,6 +30,7 @@ #include "export.h" +#include "core/io/image_loader.h" #include "core/os/file_access.h" #include "core/os/os.h" #include "editor/editor_export.h" @@ -38,6 +39,7 @@ #include "platform/windows/logo.gen.h" class EditorExportPlatformWindows : public EditorExportPlatformPC { + Error _process_icon(const Ref &p_preset, const String &p_src_path, const String &p_dst_path); Error _rcedit_add_data(const Ref &p_preset, const String &p_path); Error _code_sign(const Ref &p_preset, const String &p_path); @@ -52,6 +54,131 @@ class EditorExportPlatformWindows : public EditorExportPlatformPC { virtual bool has_valid_project_configuration(const Ref &p_preset, String &r_error) const; }; +Error EditorExportPlatformWindows::_process_icon(const Ref &p_preset, const String &p_src_path, const String &p_dst_path) { + static const uint8_t icon_size[] = { 16, 32, 48, 64, 128, 0 /*256*/ }; + + struct IconData { + PoolVector data; + uint8_t pal_colors = 0; + uint16_t planes = 0; + uint16_t bpp = 32; + }; + + HashMap images; + Error err; + + if (p_src_path.get_extension() == "ico") { + FileAccess *f = FileAccess::open(p_src_path, FileAccess::READ, &err); + if (err != OK) { + return err; + } + + // Read ICONDIR. + f->get_16(); // Reserved. + uint16_t icon_type = f->get_16(); // Image type: 1 - ICO. + uint16_t icon_count = f->get_16(); // Number of images. + if (icon_type != 1) { + f->close(); + memdelete(f); + + ERR_FAIL_V(ERR_CANT_OPEN); + } + + for (uint16_t i = 0; i < icon_count; i++) { + // Read ICONDIRENTRY. + uint16_t w = f->get_8(); // Width in pixels. + uint16_t h = f->get_8(); // Height in pixels. + uint8_t pal_colors = f->get_8(); // Number of colors in the palette (0 - no palette). + f->get_8(); // Reserved. + uint16_t planes = f->get_16(); // Number of color planes. + uint16_t bpp = f->get_16(); // Bits per pixel. + uint32_t img_size = f->get_32(); // Image data size in bytes. + uint32_t img_offset = f->get_32(); // Image data offset. + if (w != h) { + continue; + } + + // Read image data. + uint64_t prev_offset = f->get_position(); + images[w].pal_colors = pal_colors; + images[w].planes = planes; + images[w].bpp = bpp; + images[w].data.resize(img_size); + PoolVector::Write wr = images[w].data.write(); + f->seek(img_offset); + f->get_buffer(wr.ptr(), img_size); + f->seek(prev_offset); + } + f->close(); + memdelete(f); + } else { + Ref src_image; + src_image.instance(); + err = ImageLoader::load_image(p_src_path, src_image.ptr()); + ERR_FAIL_COND_V(err != OK || src_image->empty(), ERR_CANT_OPEN); + for (size_t i = 0; i < sizeof(icon_size) / sizeof(icon_size[0]); ++i) { + int size = (icon_size[i] == 0) ? 256 : icon_size[i]; + + Ref res_image = src_image->duplicate(); + ERR_FAIL_COND_V(res_image.is_null() || res_image->empty(), ERR_CANT_OPEN); + res_image->resize(size, size, (Image::Interpolation)(p_preset->get("application/icon_interpolation").operator int())); + images[icon_size[i]].data = res_image->save_png_to_buffer(); + } + } + + uint16_t valid_icon_count = 0; + for (size_t i = 0; i < sizeof(icon_size) / sizeof(icon_size[0]); ++i) { + if (images.has(icon_size[i])) { + valid_icon_count++; + } else { + int size = (icon_size[i] == 0) ? 256 : icon_size[i]; + add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), vformat(TTR("Icon size \"%d\" is missing."), size)); + } + } + ERR_FAIL_COND_V(valid_icon_count == 0, ERR_CANT_OPEN); + + FileAccess *fw = FileAccess::open(p_dst_path, FileAccess::WRITE, &err); + if (err != OK) { + return err; + } + + // Write ICONDIR. + fw->store_16(0); // Reserved. + fw->store_16(1); // Image type: 1 - ICO. + fw->store_16(valid_icon_count); // Number of images. + + // Write ICONDIRENTRY. + uint32_t img_offset = 6 + 16 * valid_icon_count; + for (size_t i = 0; i < sizeof(icon_size) / sizeof(icon_size[0]); ++i) { + if (images.has(icon_size[i])) { + const IconData &di = images[icon_size[i]]; + fw->store_8(icon_size[i]); // Width in pixels. + fw->store_8(icon_size[i]); // Height in pixels. + fw->store_8(di.pal_colors); // Number of colors in the palette (0 - no palette). + fw->store_8(0); // Reserved. + fw->store_16(di.planes); // Number of color planes. + fw->store_16(di.bpp); // Bits per pixel. + fw->store_32(di.data.size()); // Image data size in bytes. + fw->store_32(img_offset); // Image data offset. + + img_offset += di.data.size(); + } + } + + // Write image data. + for (size_t i = 0; i < sizeof(icon_size) / sizeof(icon_size[0]); ++i) { + if (images.has(icon_size[i])) { + const IconData &di = images[icon_size[i]]; + PoolVector::Read r = di.data.read(); + fw->store_buffer(r.ptr(), di.data.size()); + } + } + fw->close(); + memdelete(fw); + + return OK; +} + Error EditorExportPlatformWindows::sign_shared_object(const Ref &p_preset, bool p_debug, const String &p_path) { if (p_preset->get("codesign/enable")) { return _code_sign(p_preset, p_path); @@ -111,7 +238,8 @@ void EditorExportPlatformWindows::get_export_options(List *r_optio r_options->push_back(ExportOption(PropertyInfo(Variant::POOL_STRING_ARRAY, "codesign/custom_options"), PoolStringArray())); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/modify_resources"), true)); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico,*.png,*.webp,*.svg"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), "")); @@ -148,6 +276,14 @@ Error EditorExportPlatformWindows::_rcedit_add_data(const Refglobalize_path(p_preset->get("application/icon")); + String tmp_icon_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("_rcedit.ico"); + if (!icon_path.empty()) { + if (_process_icon(p_preset, icon_path, tmp_icon_path) != OK) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), vformat(TTR("Invalid icon file \"%s\"."), icon_path)); + icon_path = String(); + } + } + String file_verion = p_preset->get("application/file_version"); String product_version = p_preset->get("application/product_version"); String company_name = p_preset->get("application/company_name"); @@ -161,7 +297,7 @@ Error EditorExportPlatformWindows::_rcedit_add_data(const Refexecute(rcedit_path, args, true, nullptr, &str, nullptr, true); + + if (FileAccess::exists(tmp_icon_path)) { + DirAccess::remove_file_or_error(tmp_icon_path); + } + if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), TTR("Could not start rcedit executable. Configure rcedit path in the Editor Settings (Export > Windows > rcedit), or disable \"Application > Modify Resources\" in the export preset.")); return err;