Skip to content
Merged
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions inc/cvc/image/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ void write_image(const image &img, const std::string &path, int quality = 90);
// lower-cased file extension without the dot ("" if none).
std::string image_file_extension(const std::string &path);

// Initialize ImageMagick, once, after pointing it at its coder modules and config. On Windows a
// cvcpkg ImageMagick is extracted rather than installed, so until then it has no coders at all,
// not even xc:. read_image/write_image call this themselves; code that uses Magick++ directly
// calls it first. A no-op in a build without ImageMagick.
void init_imagemagick();

} // namespace cvc

#endif // __CVC_IMAGE_H__
50 changes: 46 additions & 4 deletions src/cvc-cli/cvc_cli_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@

#if defined(_WIN32)
#include <process.h>
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#define CVC_GETPID() _getpid()
#define CVC_POPEN(cmd, mode) _popen(cmd, mode)
#define CVC_PCLOSE(fp) _pclose(fp)
Expand All @@ -50,9 +57,41 @@
#define CVC_POPEN(cmd, mode) popen(cmd, mode)
#define CVC_PCLOSE(fp) pclose(fp)
#endif
#if defined(__APPLE__)
#include <cstdint>
#include <mach-o/dyld.h>
#endif

namespace fs = std::filesystem;

namespace {
// This test binary's own path (cvc-cli is built beside it), or empty when it cannot be found.
// /proc/self/exe exists only on Linux; elsewhere fs::canonical threw from SetUp().
fs::path self_exe() {
#if defined(_WIN32)
std::wstring buf(32768, L'\0');
const DWORD n = GetModuleFileNameW(nullptr, buf.data(), static_cast<DWORD>(buf.size()));
if (n == 0 || n >= buf.size())
return {};
buf.resize(n);
return fs::path(buf);
#elif defined(__APPLE__)
std::uint32_t size = 0;
_NSGetExecutablePath(nullptr, &size);
std::string buf(size, '\0');
if (_NSGetExecutablePath(buf.data(), &size) != 0)
return {};
std::error_code ec;
fs::path p = fs::canonical(buf.c_str(), ec);
return ec ? fs::path() : p;
#else
std::error_code ec;
fs::path p = fs::canonical("/proc/self/exe", ec);
return ec ? fs::path() : p;
#endif
}
} // namespace

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -208,10 +247,13 @@ class CvcCliTest : public ::testing::Test {
if (env && fs::exists(env)) {
cvc_bin = env;
} else {
fs::path self = fs::canonical("/proc/self/exe");
fs::path dir = self.parent_path();
if (fs::exists(dir / "cvc"))
cvc_bin = (dir / "cvc").string();
#if defined(_WIN32)
const fs::path bin = self_exe().parent_path() / "cvc.exe";
#else
const fs::path bin = self_exe().parent_path() / "cvc";
#endif
if (!bin.parent_path().empty() && fs::exists(bin))
cvc_bin = bin.string();
else
GTEST_SKIP() << "cvc binary not found; set CVC_CLI_BINARY env var";
}
Expand Down
56 changes: 42 additions & 14 deletions src/cvc/image/magick_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,27 +51,41 @@ namespace {
// modules FLAT — a coder resolves its own CORE_RL_* deps in its directory, so the
// modules must sit beside them — and <prefix>\share\imagemagick holds the .xml
// config. Only set what the environment has not already overridden (a user wins).
void set_magick_paths_win() {
HMODULE h = nullptr;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCSTR>(&set_magick_paths_win), &h);
// The directory a loaded module's file is in; empty if it cannot be found.
std::string module_dir(HMODULE h) {
if (!h)
return;
return std::string();
char buf[MAX_PATH];
const DWORD n = GetModuleFileNameA(h, buf, MAX_PATH);
if (n == 0 || n >= MAX_PATH)
return;
return std::string();
std::string p(buf, n);
const auto slash = p.find_last_of("\\/");
if (slash == std::string::npos)
return slash == std::string::npos ? std::string() : p.substr(0, slash);
}

// The module holding `addr`.
HMODULE module_at(const void *addr) {
HMODULE h = nullptr;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCSTR>(addr), &h);
return h;
}

void set_magick_paths_win() {
const std::string dllDir =
module_dir(module_at(reinterpret_cast<const void *>(&set_magick_paths_win)));
if (dllDir.empty())
return;
// Search order: DLL's own directory (normal case: <prefix>\bin), then
// <prefix>\bin from the DLL's parent (in case cvc.dll got copied to
// build\bin\Release\ for CTest while the real coders live in deps\bin), then
// any CVC_DEPS_PREFIX\bin the environment names. Pick the first that contains
// at least one IM_MOD_RL_*.dll — an empty dir here becomes a MIFF-only Magick.
const std::string dllDir = p.substr(0, slash);
// Search order: DLL's own directory (normal case: <prefix>\bin); then the
// directory the ImageMagick DLL itself was loaded from, since the coders sit
// beside it (a dev build's cvc.dll is in build\bin while ImageMagick comes
// from the deps prefix on PATH); then <prefix>\bin from the DLL's parent (in
// case cvc.dll got copied to build\bin\Release\ for CTest while the real
// coders live in deps\bin); then any CVC_DEPS_PREFIX\bin the environment
// names. Pick the first that contains at least one IM_MOD_RL_*.dll — an empty
// dir here becomes a Magick with no coders, not even xc: or MIFF.
auto has_coder = [](const std::string &dir) -> bool {
WIN32_FIND_DATAA d;
HANDLE h = FindFirstFileA((dir + "\\IM_MOD_RL_*.dll").c_str(), &d);
Expand All @@ -81,6 +95,14 @@ void set_magick_paths_win() {
return true;
};
std::vector<std::string> cand{dllDir};
// The ImageMagick DLL by its name in the cvcpkg bundle (the official Windows
// build's), then by the address of one of its functions, for other builds.
// When that address is an import thunk in cvc.dll it names dllDir again.
for (const std::string &d :
{module_dir(GetModuleHandleA("CORE_RL_MagickCore_.dll")),
module_dir(module_at(reinterpret_cast<const void *>(&MagickCore::GetMagickVersion)))})
if (!d.empty())
cand.push_back(d);
// If dllDir is <something>\build\bin\Release, walk up to try <something>\bin.
cand.push_back(dllDir + "\\..\\..\\..\\..\\deps\\bin");
for (const char *var : {"CVC_DEPS_PREFIX", "LIBCVC_DEPS_PREFIX", "CVCPKG_PREFIX"})
Expand Down Expand Up @@ -262,6 +284,12 @@ class magick_image_io : public image_file_io {
#endif // CVC_ENABLE_IMAGEMAGICK

namespace cvc {
void init_imagemagick() {
#ifdef CVC_ENABLE_IMAGEMAGICK
init_magick_once();
#endif
}

void register_stb_image_handler(); // stb_io.cpp — no dependencies at all.

// Register the built-in image handlers. Called once (lazily) from image.cpp.
Expand Down
4 changes: 3 additions & 1 deletion src/cvc/tests/image_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,9 @@ TEST(ImageTest, ReadMissingFileThrows) { EXPECT_ANY_THROW(image::load("/no/such/
// normalization the raw C,M,Y,K quantums land in R,G,B,A — a pure-cyan CMYK pixel
// came back bright RED. Synthesize a genuine CMYK file and assert read() -> cyan.
TEST(ImageTest, ReadsCmykInputAsRgb) {
Magick::InitializeMagick(nullptr); // idempotent; the test uses Magick++ directly
// The test uses Magick++ directly, so it initializes Magick as libcvc does: on Windows,
// InitializeMagick alone leaves a cvcpkg ImageMagick without coders (not even xc:).
cvc::init_imagemagick();
// A 4x4 pure-cyan image transformed into the CMYK colorspace, so the file on
// disk is genuinely CMYK (not an RGB image merely tagged CMYK).
Magick::Image cyan(Magick::Geometry(4, 4), Magick::ColorRGB(0.0, 1.0, 1.0));
Expand Down
3 changes: 3 additions & 0 deletions src/cvc/volume/volume_ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include <algorithm>
#include <cmath>
#include <cvc/image/image.h>
#include <cvc/volume/volume_ops.h>
#include <limits>
#include <numeric>
Expand Down Expand Up @@ -609,6 +610,7 @@ volume vol_back_project(const volume &projections, const std::vector<double> &an

void vol_to_slices(const volume &vol, const std::string &directory, const std::string &format) {
#ifdef CVC_ENABLE_IMAGEMAGICK
init_imagemagick();
// Normalise to [0,255] UChar for image output
volume norm = vol_normalize(vol, 0.0, 255.0);

Expand Down Expand Up @@ -641,6 +643,7 @@ volume slices_to_volume(app &ctx, const std::vector<std::string> &paths, const b
#ifdef CVC_ENABLE_IMAGEMAGICK
if (paths.empty())
throw std::invalid_argument("slices_to_volume: empty path list");
init_imagemagick();

// Read first image for dimensions
Magick::Image first(paths[0]);
Expand Down
Loading