diff --git a/etc/plugins/TVirtualImageUtils/P010_TASImageUtils.C b/etc/plugins/TVirtualImageUtils/P010_TASImageUtils.C new file mode 100644 index 0000000000000..f64a1b5b19169 --- /dev/null +++ b/etc/plugins/TVirtualImageUtils/P010_TASImageUtils.C @@ -0,0 +1,4 @@ +void P010_TASImageUtils() +{ + gPluginMgr->AddHandler("TVirtualImageUtils", "*", "TASImageUtils", "ASImageUtils", "TASImageUtils()"); +} diff --git a/graf2d/asimage/CMakeLists.txt b/graf2d/asimage/CMakeLists.txt index a0e3ea33e25ca..17bc097096fae 100644 --- a/graf2d/asimage/CMakeLists.txt +++ b/graf2d/asimage/CMakeLists.txt @@ -42,6 +42,7 @@ endif() ROOT_STANDARD_LIBRARY_PACKAGE(ASImageUtils NO_GLOB_HEADERS HEADERS + TASImageUtils.h TASUtils.h SOURCES TASUtils.cxx diff --git a/graf2d/asimage/inc/LinkDefUtils.h b/graf2d/asimage/inc/LinkDefUtils.h index 0abd290f2d1e3..4371a94453a28 100644 --- a/graf2d/asimage/inc/LinkDefUtils.h +++ b/graf2d/asimage/inc/LinkDefUtils.h @@ -14,6 +14,7 @@ #pragma link off all classes; #pragma link off all functions; +#pragma link C++ class TASImageUtils; #pragma link C++ class TASUtils; #endif diff --git a/graf2d/asimage/inc/TASImageUtils.h b/graf2d/asimage/inc/TASImageUtils.h new file mode 100644 index 0000000000000..300e11f82c676 --- /dev/null +++ b/graf2d/asimage/inc/TASImageUtils.h @@ -0,0 +1,28 @@ +// For the licensing terms see $ROOTSYS/LICENSE. +// For the list of contributors see $ROOTSYS/README/CREDITS. + +#ifndef ROOT_TASImageUtils +#define ROOT_TASImageUtils + +#include "TASUtils.h" +#include "TVirtualImageUtils.h" + +/// Image utility plugin backed by TASImage. +class TASImageUtils : public TVirtualImageUtils { +public: + TASImageUtils() = default; + + Bool_t PaintImage(TImage *image, Drawable_t window, Int_t x, Int_t y, Bool_t opaque) override + { + return TASUtils::PaintImage(image, window, x, y, opaque); + } + + Bool_t FillImageFromWindow(TImage *image, Window_t window, Int_t width, Int_t height) override + { + return TASUtils::FillImageFromWindow(image, window, width, height); + } + + ClassDefOverride(TASImageUtils, 0) // TASImage implementation of native image operations +}; + +#endif diff --git a/graf2d/gpad/test/CMakeLists.txt b/graf2d/gpad/test/CMakeLists.txt index 8cff9a1bc79a7..9bf1fca87a098 100644 --- a/graf2d/gpad/test/CMakeLists.txt +++ b/graf2d/gpad/test/CMakeLists.txt @@ -7,11 +7,17 @@ ROOT_ADD_GTEST(TRatioPlot ratioplot.cxx LIBRARIES Gpad) ROOT_ADD_GTEST(TPad pdftitle.cxx LIBRARIES Gpad) ROOT_ADD_GTEST(TPDF pdfurl.cxx LIBRARIES Gpad) +ROOT_ADD_GTEST(TCanvasPainter canvaspainter.cxx LIBRARIES Gpad Hist) +add_dependencies(TCanvasPainter ROOTPadPainter HistPainter) +ROOT_ADD_GTEST(TPadPainterBackend padpainter_backend.cxx LIBRARIES Gpad) +add_dependencies(TPadPainterBackend ROOTPadPainter) if(asimage) ROOT_ADD_GTEST(TASImagePDF tasimage_pdf.cxx LIBRARIES Gpad ASImage ZLIB::ZLIB) + ROOT_ADD_GTEST(TPadPainterImage padpainter_image.cxx LIBRARIES Gpad) + add_dependencies(TPadPainterImage ROOTPadPainter ASImageUtils) endif() if(webgui) ROOT_ADD_GTEST(SaveHTML savehtml.cxx LIBRARIES Gpad) -endif() \ No newline at end of file +endif() diff --git a/graf2d/gpad/test/canvaspainter.cxx b/graf2d/gpad/test/canvaspainter.cxx new file mode 100644 index 0000000000000..df3521113cba8 --- /dev/null +++ b/graf2d/gpad/test/canvaspainter.cxx @@ -0,0 +1,23 @@ +#include "gtest/gtest.h" + +#include "TCanvas.h" +#include "TH1D.h" +#include "TROOT.h" +#include "TSystem.h" + +#include + +TEST(TCanvasPainter, BatchHistogramDoesNotLoadImages) +{ + gROOT->SetBatch(kTRUE); + ASSERT_EQ(std::string(gSystem->GetLibraries()).find("libASImage"), std::string::npos); + TCanvas canvas("batch_painter", "batch painter"); + ASSERT_NE(canvas.GetCanvasPainter(), nullptr); + + TH1D histogram("histogram", "Batch histogram;X;Entries", 10, 0., 10.); + histogram.Fill(5.); + histogram.Draw(); + canvas.Update(); + EXPECT_FALSE(canvas.IsModified()); + EXPECT_EQ(std::string(gSystem->GetLibraries()).find("libASImage"), std::string::npos); +} diff --git a/graf2d/gpad/test/padpainter_backend.cxx b/graf2d/gpad/test/padpainter_backend.cxx new file mode 100644 index 0000000000000..36b91d37db2ee --- /dev/null +++ b/graf2d/gpad/test/padpainter_backend.cxx @@ -0,0 +1,23 @@ +#include "gtest/gtest.h" + +#include "TROOT.h" +#include "TSystem.h" +#include "TVirtualPadPainter.h" + +#include +#include + +TEST(TPadPainterBackend, BatchQueriesDoNotLoadLibraries) +{ + gROOT->SetBatch(kTRUE); + std::unique_ptr painter(TVirtualPadPainter::PadPainter("native")); + ASSERT_NE(painter, nullptr); + + const std::string libraries = gSystem->GetLibraries(); + EXPECT_FALSE(painter->IsX11()); + EXPECT_EQ(std::string(gSystem->GetLibraries()), libraries); + EXPECT_FALSE(painter->IsCocoa()); + EXPECT_EQ(std::string(gSystem->GetLibraries()), libraries); + EXPECT_FALSE(painter->IsSupportAlpha()); + EXPECT_EQ(std::string(gSystem->GetLibraries()), libraries); +} diff --git a/graf2d/gpad/test/padpainter_image.cxx b/graf2d/gpad/test/padpainter_image.cxx new file mode 100644 index 0000000000000..c56f63d2fd89b --- /dev/null +++ b/graf2d/gpad/test/padpainter_image.cxx @@ -0,0 +1,91 @@ +#include "gtest/gtest.h" + +#include "TCanvas.h" +#include "TImage.h" +#include "TPluginManager.h" +#include "TROOT.h" +#include "TSystem.h" +#include "TVirtualPadPainter.h" +#include "TVirtualX.h" + +#include +#include +#include + +namespace { + +// Supply pixels and record image painting without requiring a window server. +class ImageBackend : public TVirtualX { +public: + Int_t fCopies = 0; + Int_t fX = 0; + Int_t fY = 0; + UInt_t fWidth = 0; + UInt_t fHeight = 0; + + Window_t GetWindowID(Int_t) override { return 1; } + Window_t GetCurrentWindow() const override { return 1; } + GContext_t CreateGC(Drawable_t, GCValues_t *) override { return 1; } + Pixmap_t CreatePixmapFromData(unsigned char *, UInt_t, UInt_t) override { return 1; } + + unsigned char *GetColorBits(Drawable_t, Int_t, Int_t, UInt_t width, UInt_t height) override + { + auto pixels = new unsigned char[4 * width * height]; + std::fill_n(pixels, 4 * width * height, 255); + return pixels; + } + + void CopyArea(Drawable_t, Drawable_t, GContext_t, Int_t, Int_t, UInt_t w, UInt_t h, Int_t x, Int_t y) override + { + ++fCopies; + fX = x; + fY = y; + fWidth = w; + fHeight = h; + } +}; + +class BackendGuard { + TVirtualX *fPrevious = gVirtualX; + +public: + explicit BackendGuard(TVirtualX &backend) { gVirtualX = &backend; } + ~BackendGuard() { gVirtualX = fPrevious; } +}; + +} // namespace + +TEST(TPadPainterImage, LoadHelperPluginOnDemand) +{ + gROOT->SetBatch(kTRUE); + ASSERT_EQ(std::string(gSystem->GetLibraries()).find("libASImage"), std::string::npos); + TCanvas canvas("image_painter", "image painter", 64, 64); + auto painter = canvas.GetPainter(); + ASSERT_NE(painter, nullptr); + painter->OnPad(&canvas); + + ASSERT_NE(gPluginMgr->FindHandler("TVirtualImageUtils"), nullptr); + EXPECT_EQ(std::string(gSystem->GetLibraries()).find("libASImage"), std::string::npos); + + std::unique_ptr image(TImage::Create()); + ASSERT_NE(image, nullptr); + EXPECT_EQ(std::string(gSystem->GetLibraries()).find("libASImageUtils"), std::string::npos); + + ImageBackend backend; + BackendGuard guard(backend); + ASSERT_TRUE(painter->FillImageFromPad(image.get(), &canvas)); + EXPECT_TRUE(image->IsValid()); + EXPECT_EQ(image->GetWidth(), canvas.GetPadWidth()); + EXPECT_EQ(image->GetHeight(), canvas.GetPadHeight()); + EXPECT_NE(std::string(gSystem->GetLibraries()).find("libASImageUtils"), std::string::npos); + + painter->DrawImage(image.get(), 7, 9); + EXPECT_EQ(backend.fCopies, 1); + EXPECT_EQ(backend.fX, 7); + EXPECT_EQ(backend.fY, 9); + EXPECT_EQ(backend.fWidth, image->GetWidth()); + EXPECT_EQ(backend.fHeight, image->GetHeight()); + + TImage unsupportedImage(1, 1); + EXPECT_FALSE(painter->FillImageFromPad(&unsupportedImage, &canvas)); +} diff --git a/graf2d/graf/CMakeLists.txt b/graf2d/graf/CMakeLists.txt index 6812c0e909553..3d5b00285e6dd 100644 --- a/graf2d/graf/CMakeLists.txt +++ b/graf2d/graf/CMakeLists.txt @@ -48,6 +48,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(Graf TTF.h TTFhandle.h TText.h + TVirtualImageUtils.h TWbox.h SOURCES src/TArc.cxx @@ -87,6 +88,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(Graf src/TTF.cxx src/TTFhandle.cxx src/TText.cxx + src/TVirtualImageUtils.cxx src/TWbox.cxx DICTIONARY_OPTIONS -writeEmptyRootPCM diff --git a/graf2d/graf/inc/LinkDef.h b/graf2d/graf/inc/LinkDef.h index ccb0862fc2b64..1932c69ca62d2 100644 --- a/graf2d/graf/inc/LinkDef.h +++ b/graf2d/graf/inc/LinkDef.h @@ -58,6 +58,7 @@ #pragma link C++ class TText-; #pragma link C++ class TTF; #pragma link C++ class TTFhandle; +#pragma link C++ class TVirtualImageUtils; #pragma link C++ class TWbox+; #pragma link C++ enum TImage::EImageFileTypes; diff --git a/graf2d/graf/inc/TVirtualImageUtils.h b/graf2d/graf/inc/TVirtualImageUtils.h new file mode 100644 index 0000000000000..4409bb1797798 --- /dev/null +++ b/graf2d/graf/inc/TVirtualImageUtils.h @@ -0,0 +1,25 @@ +// For the licensing terms see $ROOTSYS/LICENSE. +// For the list of contributors see $ROOTSYS/README/CREDITS. + +#ifndef ROOT_TVirtualImageUtils +#define ROOT_TVirtualImageUtils + +#include "Rtypes.h" +#include "GuiTypes.h" + +class TImage; + +/// Interface for image operations on native windows. +class TVirtualImageUtils { +public: + virtual ~TVirtualImageUtils() = default; + + virtual Bool_t PaintImage(TImage *image, Drawable_t window, Int_t x, Int_t y, Bool_t opaque) = 0; + virtual Bool_t FillImageFromWindow(TImage *image, Window_t window, Int_t width, Int_t height) = 0; + + static TVirtualImageUtils *Create(); + + ClassDef(TVirtualImageUtils, 0) // Image operations for native windows +}; + +#endif diff --git a/graf2d/graf/src/TVirtualImageUtils.cxx b/graf2d/graf/src/TVirtualImageUtils.cxx new file mode 100644 index 0000000000000..1e5dbef6f19de --- /dev/null +++ b/graf2d/graf/src/TVirtualImageUtils.cxx @@ -0,0 +1,18 @@ +// For the licensing terms see $ROOTSYS/LICENSE. +// For the list of contributors see $ROOTSYS/README/CREDITS. + +#include "TVirtualImageUtils.h" +#include "TPluginManager.h" + +//////////////////////////////////////////////////////////////////////////////// +/// Create an image utility plugin. The caller owns the returned object. +/// Returns nullptr if no implementation can be loaded. + +TVirtualImageUtils *TVirtualImageUtils::Create() +{ + auto handler = gPluginMgr->FindHandler("TVirtualImageUtils"); + if (!handler || handler->LoadPlugin() == -1) + return nullptr; + + return reinterpret_cast(handler->ExecPlugin(0)); +} diff --git a/graf2d/padpainter/CMakeLists.txt b/graf2d/padpainter/CMakeLists.txt index 5b6603d8a256e..e59f122de338f 100644 --- a/graf2d/padpainter/CMakeLists.txt +++ b/graf2d/padpainter/CMakeLists.txt @@ -19,7 +19,6 @@ ROOT_STANDARD_LIBRARY_PACKAGE(ROOTPadPainter -writeEmptyRootPCM DEPENDENCIES Gpad - ASImageUtils ) diff --git a/graf2d/padpainter/src/TPadPainter.cxx b/graf2d/padpainter/src/TPadPainter.cxx index 874f3b06b31f9..ecf58ec2882ef 100644 --- a/graf2d/padpainter/src/TPadPainter.cxx +++ b/graf2d/padpainter/src/TPadPainter.cxx @@ -17,17 +17,33 @@ #include "TPadPainter.h" #include "TVirtualX.h" #include "TSystem.h" -#include "TASUtils.h" #include "TCanvas.h" +#include "TClass.h" #include "TPoint.h" #include "TError.h" #include "TImage.h" #include "TROOT.h" #include "TMath.h" #include "TPad.h" +#include "TVirtualImageUtils.h" namespace { +Bool_t InheritsFromLoadedClass(const char *className) +{ + // The string overload of InheritsFrom can autoload an unused graphics backend. + // Its dictionary is already registered if the backend is in use. + auto dict = TClass::GetDict(className); + return dict && gVirtualX->InheritsFrom(dict()); +} + +// Image helpers must not become a load-time dependency of ordinary pad painting. +TVirtualImageUtils *GetImageUtils() +{ + static const std::unique_ptr imageUtils(TVirtualImageUtils::Create()); + return imageUtils.get(); +} + //Typedef is fine, but let's pretend we look cool and modern: using size_type = std::vector::size_type; @@ -121,7 +137,7 @@ Int_t TPadPainter::ResizeDrawable(Int_t device, UInt_t w, UInt_t h) Bool_t TPadPainter::IsX11() const { - return gVirtualX->InheritsFrom("TGX11"); + return InheritsFromLoadedClass("TGX11"); } //////////////////////////////////////////////////////////////////////////////// @@ -129,7 +145,7 @@ Bool_t TPadPainter::IsX11() const Bool_t TPadPainter::IsCocoa() const { - return gVirtualX->InheritsFrom("TGCocoa"); + return InheritsFromLoadedClass("TGCocoa"); } //////////////////////////////////////////////////////////////////////////////// @@ -137,7 +153,7 @@ Bool_t TPadPainter::IsCocoa() const Bool_t TPadPainter::IsSupportAlpha() const { - return gVirtualX->InheritsFrom("TGQuartz"); + return InheritsFromLoadedClass("TGQuartz"); } //////////////////////////////////////////////////////////////////////////////// @@ -229,11 +245,13 @@ void TPadPainter::DrawImage(TImage *img, Int_t x, Int_t y, Int_t flags) { Window_t wid = gVirtualX->GetWindowID(fPad->GetPixmapID()); - // use old API to draw image on gVirtualX window - // img->PaintImage(wid, x, y, 0, 0, 0, 0, flags ? "" : "opaque"); + auto imageUtils = GetImageUtils(); + if (!imageUtils) { + ::Error("TPadPainter::DrawImage", "Cannot load image utility plugin"); + return; + } - // new API invokes - TASUtils::PaintImage(img, wid, x, y, !flags); + imageUtils->PaintImage(img, wid, x, y, !flags); } //////////////////////////////////////////////////////////////////////////////// @@ -578,21 +596,28 @@ TVirtualPadPainter::ESaveImageResult TPadPainter::SaveAsImage(TVirtualPad *pad, //////////////////////////////////////////////////////////////////////////////// /// Fill image object with the pixel data from the pad -/// Use TASUtils::FillImageFromWindow method which provides native X11 handling +/// Use the image utility plugin, which provides native X11 handling. Bool_t TPadPainter::FillImageFromPad(TImage *image, TVirtualPad *pad) const { if (!image || !pad) return kFALSE; - auto width = pad->GetPadWidth(); - auto height = pad->GetPadHeight(); + auto imageUtils = GetImageUtils(); + if (!imageUtils) { + ::Error("TPadPainter::FillImageFromPad", "Cannot load image utility plugin"); + return kFALSE; + } + + Int_t width = pad->GetPadWidth(); + Int_t height = pad->GetPadHeight(); Int_t wid = (pad == pad->GetCanvas()) ? pad->GetCanvasID() : pad->GetPixmapID(); gVirtualX->SelectWindow(wid); Window_t wd = gVirtualX->GetCurrentWindow(); - return TASUtils::FillImageFromWindow(image, wd, width, height); + + return imageUtils->FillImageFromWindow(image, wd, width, height); } //Aux. private functions.