diff --git a/CMakeLists.txt b/CMakeLists.txt index 134719ca4..48fad5201 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,7 @@ endif() # # general -#option(SD_BUILD_TESTS "sd: build tests" ${SD_STANDALONE}) +option(SD_BUILD_TESTS "sd: build tests" OFF) option(SD_BUILD_EXAMPLES "sd: build examples" ${SD_STANDALONE}) option(SD_WEBP "sd: enable WebP image I/O support" ${SD_WEBP_DEFAULT}) option(SD_USE_SYSTEM_WEBP "sd: link against system libwebp" OFF) @@ -352,6 +352,11 @@ if (SD_BUILD_EXAMPLES) add_subdirectory(examples) endif() +if (SD_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + # diff --git a/examples/common/common.cpp b/examples/common/common.cpp index 933c8622e..f8007d736 100644 --- a/examples/common/common.cpp +++ b/examples/common/common.cpp @@ -2217,7 +2217,7 @@ bool SDGenerationParams::from_json_str( LOG_ERROR("invalid end_image"); return false; } - if (!parse_image_array_json_field(j, "ref_images", 3, width, height, ref_images)) { + if (!parse_image_array_json_field(j, "ref_images", 3, 0, 0, ref_images)) { LOG_ERROR("invalid ref_images"); return false; } diff --git a/examples/server/routes_openai.cpp b/examples/server/routes_openai.cpp index 0f122922c..e15247b2a 100644 --- a/examples/server/routes_openai.cpp +++ b/examples/server/routes_openai.cpp @@ -164,7 +164,7 @@ static bool build_openai_edit_request(const httplib::Request& req, reinterpret_cast(bytes.data()), static_cast(bytes.size()), img_w, img_h, - width, height, 3); + 0, 0, 3); if (raw_pixels == nullptr) { continue; } diff --git a/examples/server/routes_sdapi.cpp b/examples/server/routes_sdapi.cpp index 699ba022d..e357a5572 100644 --- a/examples/server/routes_sdapi.cpp +++ b/examples/server/routes_sdapi.cpp @@ -244,8 +244,8 @@ static bool build_sdapi_img_gen_request(const json& j, SDImageOwner image_owner; if (decode_base64_image(extra_image.get(), 3, - request.gen_params.width_and_height_are_set() ? request.gen_params.width : 0, - request.gen_params.width_and_height_are_set() ? request.gen_params.height : 0, + 0, + 0, image_owner)) { const sd_image_t& image = image_owner.get(); request.gen_params.set_width_and_height_if_unset(image.width, image.height); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..67ff8ab29 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,16 @@ +add_executable(sd-test-image-decode + image_decode.cpp + ../examples/common/common.cpp + ../examples/common/log.cpp + ../examples/common/media_io.cpp +) + +target_include_directories(sd-test-image-decode PRIVATE + "${PROJECT_SOURCE_DIR}/examples" + "${PROJECT_SOURCE_DIR}/src" +) + +target_link_libraries(sd-test-image-decode PRIVATE stable-diffusion zip ${CMAKE_THREAD_LIBS_INIT}) +target_compile_features(sd-test-image-decode PUBLIC c_std_11 cxx_std_17) + +add_test(NAME sd-test-image-decode COMMAND sd-test-image-decode) diff --git a/tests/image_decode.cpp b/tests/image_decode.cpp new file mode 100644 index 000000000..7f243d7d4 --- /dev/null +++ b/tests/image_decode.cpp @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include + +#include "common/common.h" +#include "common/media_io.h" + +static std::string encode_base64(const std::vector& bytes) { + static constexpr char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + std::string result; + result.reserve((bytes.size() + 2) / 3 * 4); + + for (size_t i = 0; i < bytes.size(); i += 3) { + const uint32_t value = (static_cast(bytes[i]) << 16) | + (i + 1 < bytes.size() ? static_cast(bytes[i + 1]) << 8 : 0) | + (i + 2 < bytes.size() ? static_cast(bytes[i + 2]) : 0); + result.push_back(alphabet[(value >> 18) & 0x3f]); + result.push_back(alphabet[(value >> 12) & 0x3f]); + result.push_back(i + 1 < bytes.size() ? alphabet[(value >> 6) & 0x3f] : '='); + result.push_back(i + 2 < bytes.size() ? alphabet[value & 0x3f] : '='); + } + + return result; +} + +static bool expect_size(const char* name, const sd_image_t& image, uint32_t width, uint32_t height) { + if (image.width == width && image.height == height) { + return true; + } + + std::cerr << name << " has size " << image.width << "x" << image.height << ", expected " << width << "x" + << height << '\n'; + return false; +} + +int main() { + const uint8_t pixels[] = { + 0, + 32, + 64, + 16, + 48, + 80, + 32, + 64, + 96, + 48, + 80, + 112, + 64, + 96, + 128, + 80, + 112, + 144, + 96, + 128, + 160, + 112, + 144, + 176, + }; + const std::vector encoded = encode_image_to_vector(EncodedImageFormat::PNG, pixels, 4, 2, 3); + if (encoded.empty()) { + std::cerr << "failed to encode test image\n"; + return 1; + } + + const std::string image = "data:image/png;base64," + encode_base64(encoded); + + int decoded_width = 0; + int decoded_height = 0; + uint8_t* decoded = load_image_from_memory(reinterpret_cast(encoded.data()), + static_cast(encoded.size()), + decoded_width, + decoded_height, + 0, + 0, + 3); + if (decoded == nullptr || decoded_width != 4 || decoded_height != 2) { + std::cerr << "decode without expected dimensions did not preserve the source size\n"; + free(decoded); + return 1; + } + free(decoded); + + SDGenerationParams params; + const std::string json = "{\"width\":8,\"height\":8,\"init_image\":\"" + image + + "\",\"mask_image\":\"" + image + "\",\"ref_images\":[\"" + image + + "\"]}"; + if (!params.from_json_str(json)) { + std::cerr << "failed to parse image generation parameters\n"; + return 1; + } + + if (!expect_size("init_image", params.init_image.get(), 8, 8) || + !expect_size("mask_image", params.mask_image.get(), 8, 8) || + params.ref_images.size() != 1 || !expect_size("ref_images[0]", params.ref_images[0].get(), 4, 2)) { + return 1; + } + + return 0; +}