From 946a8f2fb5596344276b3f2d7898991ce77d649f Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 3 Sep 2026 15:50:57 +0200 Subject: [PATCH 01/13] Introduce DrawImage in TVirtualPS and TVirtualPadPainter These classes should handle image drawing directly without complex logic in TASImage::Paint method. Now it is just entry points --- core/base/inc/TVirtualPS.h | 5 +++++ core/base/inc/TVirtualPadPainter.h | 3 +++ core/base/src/TVirtualPadPainter.cxx | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/core/base/inc/TVirtualPS.h b/core/base/inc/TVirtualPS.h index 604caedfa7dea..17fb37b3a6c39 100644 --- a/core/base/inc/TVirtualPS.h +++ b/core/base/inc/TVirtualPS.h @@ -27,6 +27,8 @@ #include "TAttText.h" #include "TAttMarker.h" +class TImage; + class TVirtualPS : public TNamed, public TAttLine, public TAttFill, public TAttMarker, public TAttText { private: @@ -54,6 +56,9 @@ class TVirtualPS : public TNamed, public TAttLine, public TAttFill, public TAttM virtual void CellArrayFill(Int_t r, Int_t g, Int_t b) = 0; virtual void CellArrayPng(char * /* buffer */, int /* size */) {} virtual void CellArrayEnd() = 0; + + virtual void DrawImage(TImage * /* img */, Int_t /* x */, Int_t /* y */ , Int_t /* flags */ = 0) {} + virtual void Close(Option_t *opt="") = 0; virtual void DrawBox(Double_t x1, Double_t y1,Double_t x2, Double_t y2) = 0; virtual void DrawFrame(Double_t xl, Double_t yl, Double_t xt, Double_t yt, diff --git a/core/base/inc/TVirtualPadPainter.h b/core/base/inc/TVirtualPadPainter.h index 79d1b22cb6ee6..9ad912fc32831 100644 --- a/core/base/inc/TVirtualPadPainter.h +++ b/core/base/inc/TVirtualPadPainter.h @@ -17,6 +17,7 @@ class TVirtualPad; class TVirtualPS; +class TImage; class TAttFill; class TAttLine; class TAttMarker; @@ -151,6 +152,8 @@ class TVirtualPadPainter { virtual void DrawTextUrl(Double_t x, Double_t y, const char *text, const char *url); + virtual void DrawImage(TImage *img, Int_t x, Int_t y, Int_t flags = 0); + //gif, jpg, png, bmp output. virtual void SaveImage(TVirtualPad *pad, const char *fileName, Int_t type) const = 0; diff --git a/core/base/src/TVirtualPadPainter.cxx b/core/base/src/TVirtualPadPainter.cxx index e3c6795cdd82e..cec650fa652a5 100644 --- a/core/base/src/TVirtualPadPainter.cxx +++ b/core/base/src/TVirtualPadPainter.cxx @@ -212,3 +212,10 @@ void TVirtualPadPainter::SetCursor(Int_t device, ECursor cursor) gVirtualX->SetCursor(device, cursor); } + +//////////////////////////////////////////////////////////////////////////////// +/// Draw image, need to be implemented in correspondent + +void TVirtualPadPainter::DrawImage(TImage *, Int_t, Int_t, Int_t) +{ +} From 0d6696013d97bf3f28542f78d3088bd312e2cc22 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 3 Sep 2026 10:46:19 +0200 Subject: [PATCH 02/13] [imagedump] implement DrawImage method Just call Merge for target image --- graf2d/postscript/inc/TImageDump.h | 2 ++ graf2d/postscript/src/TImageDump.cxx | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/graf2d/postscript/inc/TImageDump.h b/graf2d/postscript/inc/TImageDump.h index 49df2d8f0561e..9ae6f6ac7b241 100644 --- a/graf2d/postscript/inc/TImageDump.h +++ b/graf2d/postscript/inc/TImageDump.h @@ -50,6 +50,8 @@ class TImageDump : public TVirtualPS { void CellArrayBegin(Int_t W, Int_t H, Double_t x1, Double_t x2, Double_t y1, Double_t y2) override; void CellArrayFill(Int_t r, Int_t g, Int_t b) override; void CellArrayEnd() override; + void DrawImage(TImage *img, Int_t x, Int_t y , Int_t flags = 0) override; + void Close(Option_t *opt = "") override; void DrawBox(Double_t x1, Double_t y1,Double_t x2, Double_t y2) override; void DrawFrame(Double_t xl, Double_t yl, Double_t xt, Double_t yt, diff --git a/graf2d/postscript/src/TImageDump.cxx b/graf2d/postscript/src/TImageDump.cxx index a3ffca6fe8a24..2c3e4011713db 100644 --- a/graf2d/postscript/src/TImageDump.cxx +++ b/graf2d/postscript/src/TImageDump.cxx @@ -624,6 +624,16 @@ void TImageDump::CellArrayEnd() fCellArrayY2 = 0; } +//////////////////////////////////////////////////////////////////////////////// +/// Draw image + +void TImageDump::DrawImage(TImage *img, Int_t x, Int_t y, Int_t) +{ + if (fImage) + fImage->Merge(img, "alphablend", x, y); +} + + //////////////////////////////////////////////////////////////////////////////// /// Set color with its R G B components /// From ac5e9d84ff75d5b36894134c88c45660df066e2b Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 3 Sep 2026 10:48:49 +0200 Subject: [PATCH 03/13] [svg] implement DrawImage Instead calling 4 different methods, just create PNG buffer and embed it into SVG file. Make implementation straight-forward --- graf2d/postscript/inc/TSVG.h | 3 +++ graf2d/postscript/src/TSVG.cxx | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/graf2d/postscript/inc/TSVG.h b/graf2d/postscript/inc/TSVG.h index cee6b15b1f265..5b6a2999f2143 100644 --- a/graf2d/postscript/inc/TSVG.h +++ b/graf2d/postscript/inc/TSVG.h @@ -46,6 +46,9 @@ class TSVG : public TVirtualPS { void CellArrayFill(Int_t r, Int_t g, Int_t b) override; void CellArrayPng(char *buffer, int size) override; void CellArrayEnd() override; + + void DrawImage(TImage *img, Int_t x, Int_t y , Int_t flags = 0) override; + void Close(Option_t *opt="") override; Double_t CMtoSVG(Double_t u) { return 0.5 + 72*u/2.54; } void DrawBox(Double_t x1, Double_t y1,Double_t x2, Double_t y2) override; diff --git a/graf2d/postscript/src/TSVG.cxx b/graf2d/postscript/src/TSVG.cxx index 01759e4cda013..435371d594955 100644 --- a/graf2d/postscript/src/TSVG.cxx +++ b/graf2d/postscript/src/TSVG.cxx @@ -26,6 +26,7 @@ #include "TVirtualPad.h" #include "TPoint.h" #include "TPoints.h" +#include "TImage.h" #include "TSVG.h" #include "TStyle.h" #include "TMath.h" @@ -752,6 +753,53 @@ void TSVG::CellArrayEnd() PrintStr("@"); } +//////////////////////////////////////////////////////////////////////////////// +/// Draw image in the SVG +/// Replaces CellArray methods calling + +void TSVG::DrawImage(TImage *img, Int_t x, Int_t y, Int_t) +{ + Int_t width = img->GetWidth(); + Int_t height = img->GetHeight(); + + auto x1 = gPad->AbsPixeltoX(x); + auto x2 = gPad->AbsPixeltoX(x + width); + auto y1 = gPad->AbsPixeltoY(y); + auto y2 = gPad->AbsPixeltoY(y + height); + + Double_t svgx1 = XtoSVG(x1); + Double_t svgx2 = XtoSVG(x2); + Double_t svgy1 = YtoSVG(y1); + Double_t svgy2 = YtoSVG(y2); + + char *buffer = nullptr; + int size = 0; + + img->GetImageBuffer(&buffer, &size, TImage::kPng); + if (!buffer) { + Error("DrawImage", "Fail to get PNG format of the image"); + return; + } + + TString base64 = TBase64::Encode(reinterpret_cast(buffer), size); + free(buffer); + + PrintStr("@@"); + PrintStr(TString::Format("@"); + PrintStr("@"); +} + //////////////////////////////////////////////////////////////////////////////// /// Initialize the SVG file. The main task of the function is to output the /// SVG header file which consist in ``, `<desc>` and `<defs>`. The From 61abd66a60ab9d2b34ad72df58fd2fc80ae3031f Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Thu, 3 Sep 2026 13:34:21 +0200 Subject: [PATCH 04/13] [pdf] implement draw image Simplify logic how image placed in the PDF file Old API was not clear enough for that --- graf2d/postscript/inc/TPDF.h | 3 +++ graf2d/postscript/src/TPDF.cxx | 46 ++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/graf2d/postscript/inc/TPDF.h b/graf2d/postscript/inc/TPDF.h index 81a68b2d82edd..c00145a560982 100644 --- a/graf2d/postscript/inc/TPDF.h +++ b/graf2d/postscript/inc/TPDF.h @@ -103,6 +103,9 @@ class TPDF : public TVirtualPS { void CellArrayBegin(Int_t W, Int_t H, Double_t x1, Double_t x2, Double_t y1, Double_t y2) override; void CellArrayFill(Int_t r, Int_t g, Int_t b) override; void CellArrayEnd() override; + + void DrawImage(TImage *img, Int_t x, Int_t y, Int_t flags = 0) override; + void Close(Option_t *opt = "") override; Double_t CMtoPDF(Double_t u) { return Int_t(0.5 + 72 * u / 2.54); } void ComputeRect(const char* chars, Double_t fontsize, Double_t a, Double_t b, Double_t c, Double_t d, Double_t e, Double_t f); diff --git a/graf2d/postscript/src/TPDF.cxx b/graf2d/postscript/src/TPDF.cxx index e1cd9b10d10ac..3d67b69be8d97 100644 --- a/graf2d/postscript/src/TPDF.cxx +++ b/graf2d/postscript/src/TPDF.cxx @@ -64,6 +64,7 @@ the table of contents. #include "TVirtualPad.h" #include "TPoint.h" #include "TPoints.h" +#include "TImage.h" #include "TPDF.h" #include "TStyle.h" #include "TMath.h" @@ -259,6 +260,51 @@ void TPDF::CellArrayEnd() fCellArrayRGB.shrink_to_fit(); } +//////////////////////////////////////////////////////////////////////////////// +/// Draw image in the PDF + +void TPDF::DrawImage(TImage *img, Int_t x, Int_t y, Int_t) +{ + Int_t width = img->GetWidth(); + Int_t height = img->GetHeight(); + + auto x1 = gPad->AbsPixeltoX(x); + auto x2 = gPad->AbsPixeltoX(x + width); + auto y1 = gPad->AbsPixeltoY(y); + auto y2 = gPad->AbsPixeltoY(y + height); + + fCellArrayW = width; + fCellArrayH = height; + + Double_t xLeft = XtoPDF(x1); + Double_t xRight = XtoPDF(x2); + Double_t yTop = YtoPDF(y1); + Double_t yBot = YtoPDF(y2); + + fCellArrayXpdf = xLeft; + fCellArrayYpdfBot = yBot; + fCellArrayWpdf = xRight - xLeft; + fCellArrayHpdf = yTop - yBot; + + auto argb = img->GetArgbArray(); + if (!argb) { + Error("DrawImage", "Fail to access to ARGB values"); + return; + } + + fCellArrayRGB.resize(3 * width * height); + for (Int_t i = 0; i < width * height; ++i) { + UInt_t p = argb[i]; + fCellArrayRGB[i*3] = static_cast<unsigned char>((p >> 16) & 0xFF); + fCellArrayRGB[i*3 + 1] = static_cast<unsigned char>((p >> 8) & 0xFF); + fCellArrayRGB[i*3 + 2] = static_cast<unsigned char>(p & 0xFF); + } + + // use old method, move here once old API is deprecated + CellArrayEnd(); +} + + //////////////////////////////////////////////////////////////////////////////// /// Close a PDF file From 05a3b4866d2cda3ee65930926492a91bcc09e343 Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Thu, 3 Sep 2026 14:22:05 +0200 Subject: [PATCH 05/13] [ps] implement DrawImage Use correct coordinates calculations to properly scale image from pixel to PS coordinates Partially use old API - until old will be deprecated --- graf2d/postscript/inc/TPostScript.h | 1 + graf2d/postscript/src/TPostScript.cxx | 97 +++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/graf2d/postscript/inc/TPostScript.h b/graf2d/postscript/inc/TPostScript.h index d84e4d6d1bb9a..d36860dbf9fd0 100644 --- a/graf2d/postscript/inc/TPostScript.h +++ b/graf2d/postscript/inc/TPostScript.h @@ -98,6 +98,7 @@ class TPostScript : public TVirtualPS { Double_t y1, Double_t y2) override; void CellArrayFill(Int_t r, Int_t g, Int_t b) override; void CellArrayEnd() override; + void DrawImage(TImage *img, Int_t x, Int_t y, Int_t flags = 0) override; void Close(Option_t *opt="") override; Int_t CMtoPS(Double_t u) {return Int_t(0.5 + 72*u/2.54);} void DrawBox(Double_t x1, Double_t y1, Double_t x2, Double_t y2) override; diff --git a/graf2d/postscript/src/TPostScript.cxx b/graf2d/postscript/src/TPostScript.cxx index d65ca29240fe3..08ea96937c50b 100644 --- a/graf2d/postscript/src/TPostScript.cxx +++ b/graf2d/postscript/src/TPostScript.cxx @@ -244,6 +244,7 @@ To change the color model use `gStyle->SetColorModelPS(c)`. #include "TVirtualPad.h" #include "TPoints.h" #include "TPoint.h" +#include "TImage.h" #include "TPostScript.h" #include "TStyle.h" #include "TMath.h" @@ -621,6 +622,102 @@ void TPostScript::CellArrayEnd() PrintStr(" def DrawCT "); } +//////////////////////////////////////////////////////////////////////////////// +/// Draw image in postscript + +void TPostScript::DrawImage(TImage *img, Int_t x, Int_t y, Int_t) +{ + auto argb = img->GetArgbArray(); + if (!argb) { + Error("DrawImage", "Fail to access ARGB values"); + return; + } + + Int_t width = img->GetWidth(); + Int_t height = img->GetHeight(); + + auto x1 = gPad->AbsPixeltoX(x); + auto x2 = gPad->AbsPixeltoX(x + width); + auto y1 = gPad->AbsPixeltoY(y); + auto y2 = gPad->AbsPixeltoY(y - height); + + Int_t ix1 = XtoPS(x1); + Int_t iy1 = YtoPS(y1); + Int_t ix2 = XtoPS(x2); + Int_t iy2 = YtoPS(y2); + + Float_t wt = (0. + ix2 - ix1) / width; + Float_t ht = (0. + iy2 - iy1) / height; + + fLastCellRed = 300; + fLastCellGreen = 300; + fLastCellBlue = 300; + fNBSameColorCell = 0; + + fNbinCT = 0; + fNbCellW = width; + fNbCellLine = 0; + fMaxLines = 40000/(3*fNbCellW); + + // Define some parameters + PrintStr("@/WT"); WriteReal(wt) ; PrintStr(" def"); // Cells width + PrintStr(" /HT"); WriteReal(ht) ; PrintStr(" def"); // Cells height + PrintStr(" /XS"); WriteInteger(ix1) ; PrintStr(" def"); // X start + PrintStr(" /YY"); WriteInteger(iy1) ; PrintStr(" def"); // Y start + PrintStr(" /NX"); WriteInteger(width) ; PrintStr(" def"); // Number of columns + PrintStr(" /NY"); WriteInteger(fMaxLines); PrintStr(" def"); // Number of lines + + // This PS procedure draws one cell. + PrintStr(" /DrawCell "); + PrintStr( "{WT HT XX YY bf"); + PrintStr( " /NBBD NBBD 1 add def"); + PrintStr( " NBBD NBB eq {exit} if"); + PrintStr( " /XX WT XX add def"); + PrintStr( " IX NX eq "); + PrintStr( "{/YY YY HT sub def"); + PrintStr( " /XX XS def"); + PrintStr( " /IX 0 def} if"); + PrintStr( " /IX IX 1 add def} def"); + + // This PS procedure draws fMaxLines line. It takes care of duplicated + // colors. Values "n" greater than 300 mean than the previous color + // should be duplicated n-300 times. + PrintStr(" /DrawCT "); + PrintStr( "{/NBB NX NY mul def"); + PrintStr( " /XX XS def"); + PrintStr( " /IX 1 def"); + PrintStr( " /NBBD 0 def"); + PrintStr( " /RC 0 def /GC 1 def /BC 2 def"); + PrintStr( " 1 1 NBB "); + PrintStr( "{/NB CT RC get def"); + PrintStr( " NB 301 ge "); + PrintStr( "{/NBL NB 300 sub def"); + PrintStr( " 1 1 NBL "); + PrintStr( "{DrawCell}"); + PrintStr( " for"); + PrintStr( " /RC RC 1 add def"); + PrintStr( " /GC RC 1 add def"); + PrintStr( " /BC RC 2 add def}"); + PrintStr( "{CT RC get 255 div CT GC get 255 div CT BC get 255 div setrgbcolor"); + PrintStr( " DrawCell"); + PrintStr( " /RC RC 3 add def"); + PrintStr( " /GC GC 3 add def"); + PrintStr( " /BC BC 3 add def} ifelse NBBD NBB eq {exit} if} for"); + PrintStr( " /YY YY HT sub def clear} def"); + + PrintStr(" /CT ["); + + // use old API, move here once old is deprecated + for (Int_t i = 0; i < width * height; ++i) { + UInt_t p = argb[i]; + auto r = static_cast<unsigned char>((p >> 16) & 0xFF); + auto g = static_cast<unsigned char>((p >> 8) & 0xFF); + auto b = static_cast<unsigned char>(p & 0xFF); + CellArrayFill(r, g, b); + } + + CellArrayEnd(); +} //////////////////////////////////////////////////////////////////////////////// /// Draw a Box From 24e06d2d6e3278dc9eb63bb0e8d08d52094a125c Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Thu, 3 Sep 2026 15:53:37 +0200 Subject: [PATCH 06/13] [padpainterps] Implement DrawImage Redirect to fPS instance, recalculate to global coordinates while all image formats operate with global coordinates --- graf2d/gpad/inc/TPadPainterPS.h | 2 ++ graf2d/gpad/src/TPadPainterPS.cxx | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/graf2d/gpad/inc/TPadPainterPS.h b/graf2d/gpad/inc/TPadPainterPS.h index 64b7a33c6f1da..aaa2a83316f7e 100644 --- a/graf2d/gpad/inc/TPadPainterPS.h +++ b/graf2d/gpad/inc/TPadPainterPS.h @@ -85,6 +85,8 @@ class TPadPainterPS : public TPadPainterBase { void DrawTextUrl(Double_t x, Double_t y, const char *text, const char *url) override; + void DrawImage(TImage *img, Int_t x, Int_t y, Int_t flags = 0) override; + //jpg, png, bmp, gif output. void SaveImage(TVirtualPad *pad, const char *fileName, Int_t type) const override; diff --git a/graf2d/gpad/src/TPadPainterPS.cxx b/graf2d/gpad/src/TPadPainterPS.cxx index ce9165e07b5ff..c36328a912dc7 100644 --- a/graf2d/gpad/src/TPadPainterPS.cxx +++ b/graf2d/gpad/src/TPadPainterPS.cxx @@ -421,6 +421,19 @@ void TPadPainterPS::DrawTextNDC(Double_t u, Double_t v, const char *text, ETextM } +//////////////////////////////////////////////////////////////////////////////// +/// Draw image on the PS output + +void TPadPainterPS::DrawImage(TImage *img, Int_t x, Int_t y, Int_t flags) +{ + // transfer to absolute pixel coordiantes for PS + x += fPad->UtoAbsPixel(0); + y += fPad->VtoAbsPixel(1); + + fPS->DrawImage(img, x, y, flags); +} + + //////////////////////////////////////////////////////////////////////////////// /// Save the image displayed in the canvas pointed by "pad" into a binary file. From 22bddcbc1d19199920dfb6ebf2b4e4ce3b1c8e9d Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Thu, 3 Sep 2026 16:12:39 +0200 Subject: [PATCH 07/13] [glpadpainter] implement DrawImage Simple redirection to existing DrawPixels method, can be eliminated in the future while only used from ASImage --- graf3d/gl/inc/TGLPadPainter.h | 2 ++ graf3d/gl/src/TGLPadPainter.cxx | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/graf3d/gl/inc/TGLPadPainter.h b/graf3d/gl/inc/TGLPadPainter.h index a0baf240ee660..1087afe223947 100644 --- a/graf3d/gl/inc/TGLPadPainter.h +++ b/graf3d/gl/inc/TGLPadPainter.h @@ -116,6 +116,8 @@ class TGLPadPainter : public TPadPainterBase { void DrawTextNDC(Double_t x, Double_t y, const char *text, ETextMode mode) override; void DrawTextNDC(Double_t, Double_t, const wchar_t *, ETextMode) override; + void DrawImage(TImage *img, Int_t x, Int_t y, Int_t flags = 0) override; + //jpg, png, gif and bmp output. void SaveImage(TVirtualPad *pad, const char *fileName, Int_t type) const override; diff --git a/graf3d/gl/src/TGLPadPainter.cxx b/graf3d/gl/src/TGLPadPainter.cxx index 85ba074511f23..2b3efe96e3aba 100644 --- a/graf3d/gl/src/TGLPadPainter.cxx +++ b/graf3d/gl/src/TGLPadPainter.cxx @@ -22,6 +22,7 @@ #include "TROOT.h" #include "TPad.h" #include "TCanvas.h" +#include "TImage.h" #include "TColorGradient.h" #include "TGLPadPainter.h" @@ -858,6 +859,19 @@ void TGLPadPainter::RestoreViewport() glViewport(fVp[0], fVp[1], fVp[2], fVp[3]); } + +//////////////////////////////////////////////////////////////////////////////// +/// Draw image on the GL window + +void TGLPadPainter::DrawImage(TImage *img, Int_t x, Int_t y, Int_t flags) +{ + Int_t width = img->GetWidth(); + Int_t height = img->GetHeight(); + auto bits = img->GetArgbArray(); + if (bits) + DrawPixels((unsigned char *)bits, width, height, x, y, flags); +} + //////////////////////////////////////////////////////////////////////////////// /// Using TImage save frame-buffer contents as a picture. From 869cea2cf828b9112dca12245bc19a3d01b199c5 Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Thu, 3 Sep 2026 15:55:38 +0200 Subject: [PATCH 08/13] [padpainter] implement DrawImage Use gVirtualX-based code like in original Image2Drawable method of TASImage. There are now several shortcuts, therefore methods looks much simpler. Also GL handling will be implemented in correspondent painter --- graf2d/gpad/inc/TPadPainter.h | 2 ++ graf2d/gpad/src/TPadPainter.cxx | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/graf2d/gpad/inc/TPadPainter.h b/graf2d/gpad/inc/TPadPainter.h index 17275ae1d7c39..08aa50a76f0e3 100644 --- a/graf2d/gpad/inc/TPadPainter.h +++ b/graf2d/gpad/inc/TPadPainter.h @@ -79,6 +79,8 @@ class TPadPainter : public TPadPainterBase { void DrawTTFglyphs(Int_t x, Int_t y, TTFhandle &ttf, ETextMode mode) override; + void DrawImage(TImage *img, Int_t x, Int_t y, Int_t flags = 0) override; + //jpg, png, bmp, gif output. void SaveImage(TVirtualPad *pad, const char *fileName, Int_t type) const override; diff --git a/graf2d/gpad/src/TPadPainter.cxx b/graf2d/gpad/src/TPadPainter.cxx index f2ebd73efceb0..2c887fcd735df 100644 --- a/graf2d/gpad/src/TPadPainter.cxx +++ b/graf2d/gpad/src/TPadPainter.cxx @@ -213,6 +213,15 @@ Bool_t TPadPainter::HasTTFonts() const return gVirtualX->HasTTFonts(); } +//////////////////////////////////////////////////////////////////////////////// +/// Draw image on the gVirtualX window + +void TPadPainter::DrawImage(TImage *img, Int_t x, Int_t y, Int_t flags) +{ + Window_t wid = (Window_t)gVirtualX->GetWindowID(fPad->GetPixmapID()); + // use old API to draw image on gVirtualX window + img->PaintImage(wid, x, y, 0, 0, 0, 0, flags ? "" : "opaque"); +} //////////////////////////////////////////////////////////////////////////////// ///Noop, for non-gl pad TASImage calls gVirtualX->CopyArea. From 36a8c74eaa58094451d554ded6aa6f2834f86faa Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Thu, 3 Sep 2026 16:25:24 +0200 Subject: [PATCH 09/13] [asimage] change Paint() implementation Radical paradigm change. Now image painting delegated to pad painter instance, which knows better details of platform. Thus lot of magic around virtualX, virtualPS, GL completely vanishes. Extra palette painting made very simple and works exactly the same as image painting itself plut TGaxis. Special implementation provided for: - gVirtualX and TGX11 in TPadPainter - GL support in TGLPadPainter - SVG, PS, PDF, ImageDump via TPadPainterPS New approach opens possibility to provide image painting for any external platforms like Qt6 or Gtk4 or any other --- graf2d/asimage/src/TASImage.cxx | 178 +++++--------------------------- 1 file changed, 25 insertions(+), 153 deletions(-) diff --git a/graf2d/asimage/src/TASImage.cxx b/graf2d/asimage/src/TASImage.cxx index f3589b32740b5..f6b89a9e98645 100644 --- a/graf2d/asimage/src/TASImage.cxx +++ b/graf2d/asimage/src/TASImage.cxx @@ -1411,6 +1411,9 @@ void TASImage::Paint(Option_t *option) } } + // opaque flag used in some X11 + Int_t flags = opt.Contains("opaque") ? 0 : 1; + ASImage *image = fImage; // Get geometry of pad @@ -1536,173 +1539,42 @@ void TASImage::Paint(Option_t *option) int tox = expand ? 0 : int(gPad->UtoPixel(1.) * gPad->GetLeftMargin()); int toy = expand ? 0 : int(gPad->VtoPixel(0.) * gPad->GetTopMargin()); - auto ps = gPad->GetPainter()->GetPS(); + auto pp = gPad->GetPainter(); + + pp->DrawImage(fScaledImage ? fScaledImage : this, tox, toy, flags); - if (!ps) { - Window_t wid = (Window_t)gVirtualX->GetWindowID(gPad->GetPixmapID()); - Image2Drawable(fScaledImage ? fScaledImage->fImage : fImage, wid, tox, toy); + if (grad_im && fPaletteEnabled) { + TASImage pimg; + pimg.fImage = grad_im; + grad_im = nullptr; // will be delete with pimg destructor - if (grad_im && fPaletteEnabled) { - // draw color bar - Image2Drawable(grad_im, wid, pal_x, pal_y); + // draw color bar + pp->DrawImage(&pimg, pal_x, pal_y, flags); - // values of palette - TGaxis axis; - Int_t ndiv = 510; - double min = fMinValue; - double max = fMaxValue; + // values of palette + TGaxis axis; + Int_t ndiv = 510; + Double_t min = fMinValue; + Double_t max = fMaxValue; + Double_t pal_Xpos = gPad->AbsPixeltoX(pal_Ax + pal_w); + if (!pp->GetPS()) { + // TODO: check why here special drawing for none-PS axis.SetLineColor(0); // draw white ticks - Double_t pal_Xpos = gPad->AbsPixeltoX(pal_Ax + pal_w); axis.PaintAxis(pal_Xpos, gPad->PixeltoY(pal_Ay + pal_h - 1), pal_Xpos, gPad->PixeltoY(pal_Ay), min, max, ndiv, "+LU"); min = fMinValue; max = fMaxValue; - axis.SetLineColor(1); // draw black ticks - axis.PaintAxis(pal_Xpos, gPad->AbsPixeltoY(pal_Ay + pal_h), - pal_Xpos, gPad->AbsPixeltoY(pal_Ay + 1), - min, max, ndiv, "+L"); - } - } else { - // loop over pixmap and draw image to PostScript - - Bool_t paint_as_png = kFALSE; - - if (ps->InheritsFrom("TImageDump")) { // PostScript is asimage - TImage *dump = (TImage *)ps->GetStream(); - if (!dump) return; - dump->Merge(fScaledImage ? fScaledImage : this, "alphablend", - gPad->XtoAbsPixel(0), gPad->YtoAbsPixel(1)); - - if (grad_im) { - TASImage tgrad; - tgrad.fImage = grad_im; - dump->Merge(&tgrad, "alphablend", pal_Ax, pal_Ay); - - // values of palette - TGaxis axis; - Int_t ndiv = 510; - double min = fMinValue; - double max = fMaxValue; - axis.SetLineColor(1); // draw black ticks - Double_t pal_Xpos = gPad->AbsPixeltoX(pal_Ax + pal_w); - axis.PaintAxis(pal_Xpos, gPad->AbsPixeltoY(pal_Ay + pal_h), - pal_Xpos, gPad->AbsPixeltoY(pal_Ay + 1), - min, max, ndiv, "+L"); - } - return; - } else if (ps->InheritsFrom("TSVG")) { - paint_as_png = kTRUE; - } - - Double_t dx = gPad->GetX2() - gPad->GetX1(); - Double_t dy = gPad->GetY2() - gPad->GetY1(); - Double_t x1, x2, y1, y2; - - if (expand) { - x1 = gPad->GetX1(); - x2 = x1+dx/image->width; - y1 = gPad->GetY2(); - y2 = y1+dy/image->height; - } else { - x1 = gPad->GetX1()+dx*gPad->GetLeftMargin(); - x2 = x1+(dx*(1-gPad->GetRightMargin()-gPad->GetLeftMargin()))/image->width; - y1 = gPad->GetY2()-dy*gPad->GetTopMargin(); - y2 = y1+(dy*(1-gPad->GetTopMargin()-gPad->GetBottomMargin()))/image->height; - } - - // get special color cell to be reused during image printing - ps->SetFillColor(TColor::GetColor((Float_t) 1., (Float_t) 1., (Float_t) 1.)); - ps->SetFillStyle(1001); - - ps->CellArrayBegin(image->width, image->height, x1, x2, y1, y2); - - if (paint_as_png) { - char *buffer = nullptr; - int size = 0; - ASImageExportParams params; - params.png.type = ASIT_Png; - params.png.flags = EXPORT_ALPHA; - params.png.compression = GetImageCompression(); - if (!params.png.compression) - params.png.compression = -1; - if (ASImage2PNGBuff(image, (CARD8 **)&buffer, &size, ¶ms)) { - ps->CellArrayPng(buffer, size); - free(buffer); - } - } else { - auto imdec = start_image_decoding(fgVisual, image, SCL_DO_ALL, - 0, 0, image->width, image->height, nullptr); - if (imdec) - for (Int_t yt = 0; yt < (Int_t)image->height; yt++) { - imdec->decode_image_scanline(imdec); - for (Int_t xt = 0; xt < (Int_t)image->width; xt++) - ps->CellArrayFill(imdec->buffer.red[xt], - imdec->buffer.green[xt], - imdec->buffer.blue[xt]); - } - stop_image_decoding(&imdec); } - ps->CellArrayEnd(); - - // print the color bar - if (grad_im) { - Double_t xconv = (gPad->AbsPixeltoX(pal_Ax + pal_w) - gPad->AbsPixeltoX(pal_Ax)) / grad_im->width; - Double_t yconv = (gPad->AbsPixeltoY(pal_Ay - pal_h) - gPad->AbsPixeltoY(pal_Ay)) / grad_im->height; - x1 = gPad->AbsPixeltoX(pal_Ax); - x2 = x1 + xconv; - y2 = gPad->AbsPixeltoY(pal_Ay); - y1 = y2 - yconv; - ps->CellArrayBegin(grad_im->width, grad_im->height, - x1, x2, y1, y2); - - if (paint_as_png) { - char *buffer = nullptr; - int size = 0; - ASImageExportParams params; - params.png.type = ASIT_Png; - params.png.flags = EXPORT_ALPHA; - params.png.compression = GetImageCompression(); - if (!params.png.compression) - params.png.compression = -1; - - if (ASImage2PNGBuff(grad_im, (CARD8 **)&buffer, &size, ¶ms)) { - ps->CellArrayPng(buffer, size); - free(buffer); - } - } else { - auto imdec = start_image_decoding(fgVisual, grad_im, SCL_DO_ALL, - 0, 0, grad_im->width, grad_im->height, nullptr); - if (imdec) - for (Int_t yt = 0; yt < (Int_t)grad_im->height; yt++) { - imdec->decode_image_scanline(imdec); - for (Int_t xt = 0; xt < (Int_t)grad_im->width; xt++) - ps->CellArrayFill(imdec->buffer.red[xt], - imdec->buffer.green[xt], - imdec->buffer.blue[xt]); - } - stop_image_decoding(&imdec); - } - ps->CellArrayEnd(); - - // values of palette - TGaxis axis; - Int_t ndiv = 510; - double min = fMinValue; - double max = fMaxValue; - axis.SetLineColor(1); // draw black ticks - Double_t pal_Xpos = gPad->AbsPixeltoX(pal_Ax + pal_w); - // TODO: provide PaintAxisOn method - axis.PaintAxis(pal_Xpos, gPad->AbsPixeltoY(pal_Ay + pal_h), - pal_Xpos, gPad->AbsPixeltoY(pal_Ay + 1), - min, max, ndiv, "+L"); - } + axis.SetLineColor(1); // draw black ticks + axis.PaintAxis(pal_Xpos, gPad->AbsPixeltoY(pal_Ay + pal_h), + pal_Xpos, gPad->AbsPixeltoY(pal_Ay + 1), + min, max, ndiv, "+L"); } - if (grad_im) { + if (grad_im) destroy_asimage(&grad_im); - } } //////////////////////////////////////////////////////////////////////////////// From fec34546f117cf16b63ef06a92364d1b5ad0cd62 Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Fri, 4 Sep 2026 07:18:48 +0200 Subject: [PATCH 10/13] [asimage] change Image2Drawable Remove support of the GL - while it now handles in TGLPadPainter Simplify several places --- graf2d/asimage/src/TASImage.cxx | 51 ++++++++++++++------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/graf2d/asimage/src/TASImage.cxx b/graf2d/asimage/src/TASImage.cxx index f6b89a9e98645..c7653dcef278e 100644 --- a/graf2d/asimage/src/TASImage.cxx +++ b/graf2d/asimage/src/TASImage.cxx @@ -86,6 +86,7 @@ ROOT tutorials: `$ROOTSYS/tutorials/visualisation/image/` #include "TVirtualX.h" #include <iostream> +#include <vector> #include <memory> #include "snprintf.h" @@ -1230,13 +1231,13 @@ void TASImage::Image2Drawable(ASImage *im, Drawable_t wid, Int_t x, Int_t y, Int_t xsrc, Int_t ysrc, UInt_t wsrc, UInt_t hsrc, Option_t *opt) { - if (!im) return; + if (!im) + return; wsrc = wsrc ? wsrc : im->width; hsrc = hsrc ? hsrc : im->height; - static int x11 = -1; - if (x11 < 0) x11 = gVirtualX->InheritsFrom("TGX11"); + static int x11 = gVirtualX->InheritsFrom("TGX11"); Pixmap_t mask = kNone; @@ -1245,21 +1246,18 @@ void TASImage::Image2Drawable(ASImage *im, Drawable_t wid, Int_t x, Int_t y, UInt_t ow = wsrc%8; UInt_t ww = wsrc - ow + (ow ? 8 : 0); - UInt_t bit = 0; - int i = 0; - UInt_t yy = 0; - UInt_t xx = 0; + UInt_t bit = 0, i = 0; - char *bits = new char[ww*hh]; //an array of bits + std::vector<char> bits(ww*hh); //an array of bits ASImageDecoder *imdec = start_image_decoding(fgVisual, im, SCL_DO_ALPHA, xsrc, ysrc, ww, 0, nullptr); if (imdec) { - for (yy = 0; yy < hh; yy++) { + for (UInt_t yy = 0; yy < hh; yy++) { imdec->decode_image_scanline(imdec); CARD32 *a = imdec->buffer.alpha; - for (xx = 0; xx < ww; xx++) { + for (UInt_t xx = 0; xx < ww; xx++) { if (a[xx]) { SETBIT(bits[i], bit); } else { @@ -1277,8 +1275,7 @@ void TASImage::Image2Drawable(ASImage *im, Drawable_t wid, Int_t x, Int_t y, stop_image_decoding(&imdec); mask = gVirtualX->CreateBitmap(gVirtualX->GetDefaultRootWindow(), - (const char *)bits, ww, hh); - delete [] bits; + bits.data(), ww, hh); } GCValues_t gv; @@ -1295,7 +1292,7 @@ void TASImage::Image2Drawable(ASImage *im, Drawable_t wid, Int_t x, Int_t y, gVirtualX->ChangeGC(gc, &gv); } - if (x11 && (!gPad || gPad->GetGLDevice() == -1)) { //use built-in optimized version + if (x11) { //use built-in optimized version asimage2drawable(fgVisual, wid, im, (GC)gc, xsrc, ysrc, x, y, wsrc, hsrc, 1); } else { ASImage *img = nullptr; @@ -1311,33 +1308,29 @@ void TASImage::Image2Drawable(ASImage *im, Drawable_t wid, Int_t x, Int_t y, TString option(opt); option.ToLower(); - if (gPad && gPad->GetGLDevice() != -1) { - if (TVirtualPadPainter *painter = gPad->GetPainter()) - painter->DrawPixels(bits, wsrc, hsrc, x, y, !option.Contains("opaque")); - } else { - Pixmap_t pic = gVirtualX->CreatePixmapFromData(bits, wsrc, hsrc); - if (pic) { - if (!option.Contains("opaque")) { - SETBIT(wsrc,31); - SETBIT(hsrc,31); - } - gVirtualX->CopyArea(pic, wid, gc, 0, 0, wsrc, hsrc, x, y); - gVirtualX->DeletePixmap(pic); + Pixmap_t pic = gVirtualX->CreatePixmapFromData(bits, wsrc, hsrc); + if (pic) { + if (!option.Contains("opaque")) { + SETBIT(wsrc,31); + SETBIT(hsrc,31); } + gVirtualX->CopyArea(pic, wid, gc, 0, 0, wsrc, hsrc, x, y); + gVirtualX->DeletePixmap(pic); } } - if (img) { + if (img) destroy_asimage(&img); - } } // free mask pixmap - if (gv.fClipMask != kNone) gVirtualX->DeletePixmap(gv.fClipMask); + if (mask != kNone) + gVirtualX->DeletePixmap(mask); gv.fMask = kGCClipMask; gv.fClipMask = kNone; - if (gc) gVirtualX->ChangeGC(gc, &gv); + if (gc) + gVirtualX->ChangeGC(gc, &gv); } From 226134cf30da8fdba21efc1cf1fd39d401aa59e7 Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Thu, 3 Sep 2026 16:55:51 +0200 Subject: [PATCH 11/13] Adjust stressGraphics ref file After change in TASImage::Paint there are minimal changes in scaling/offset because of different rounding Therefore in SVG file image offset changed sometime by one pixel or scaling factor by 0.1%. --- test/stressGraphics.ref | 2 +- test/stressGraphics_zlibng.ref | 2 +- test/svg_ref/timage.svg | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/stressGraphics.ref b/test/stressGraphics.ref index a53d01ec1b7b2..501c31bb4d088 100644 --- a/test/stressGraphics.ref +++ b/test/stressGraphics.ref @@ -73,7 +73,7 @@ tgraph2d3 15025 3000 29874 500 42525 11300 33336 3900 14787 4000 ntuple1 293000 5000 383731 7000 55666 30400 46534 6500 298000 7000 quarks 4846 150 14289 200 33239 600 25303 500 4877 150 - timage 1435879 150000 456978 10000 26170 100 365000 15000 1085792 250000 + timage 1435879 150000 456978 10000 26470 100 365000 15000 1085792 250000 zoomtf1 5884 500 16856 200 40706 14000 30026 4000 5884 500 zoomfit 5723 700 15959 200 32236 11000 16916 3200 5670 800 parallelcoord 1027338 160000 532630 50000 29519 3000 71404 7000 1027338 160000 diff --git a/test/stressGraphics_zlibng.ref b/test/stressGraphics_zlibng.ref index 5b43fcffe9490..740a55244e261 100644 --- a/test/stressGraphics_zlibng.ref +++ b/test/stressGraphics_zlibng.ref @@ -73,7 +73,7 @@ tgraph2d3 16675 3000 30066 500 40536 11300 33519 3900 16452 4000 ntuple1 295000 5000 382457 7000 54446 30400 45002 6500 300000 7000 quarks 4759 180 14288 100 32913 600 25421 1000 4790 180 - timage 1442666 150000 463176 10000 26100 100 374206 15000 1102279 250000 + timage 1442666 150000 463176 10000 26400 100 374206 15000 1102279 250000 zoomtf1 6130 500 16747 300 49750 14000 29288 4000 5429 500 zoomfit 6200 700 15885 200 31219 11000 18280 3200 6113 800 parallelcoord 1021126 160000 531255 50000 29893 3000 73438 7000 1027338 160000 diff --git a/test/svg_ref/timage.svg b/test/svg_ref/timage.svg index f28853fec03c6..a34cbeceaa356 100644 --- a/test/svg_ref/timage.svg +++ b/test/svg_ref/timage.svg @@ -11,7 +11,7 @@ timage.svg <path d="M29,247l 2.0, -2.0h 166v -216l 2.0, -2.0v 220h -170z" fill="#a9a9a9"/> <rect x="46" y="49" width="136" height="176" fill="#f2f2f2"/> <rect x="46" y="49" width="136" height="176" fill="none" stroke="#f2f2f2"/> -<g transform="translate(46 49) scale(0.712 0.712)"> +<g transform="translate(45 49) scale(0.712 0.712)"> <image width="191" height="247" href="data:image/png;base64,"></image> </g> <rect x="312" y="82" width="227" height="165" fill="#f2f2f2"/> @@ -19,7 +19,7 @@ timage.svg <path d="M312,247l 2.0, -2.0h 223v -161l 2.0, -2.0v 165h -227z" fill="#a9a9a9"/> <rect x="335" y="99" width="181" height="132" fill="#f2f2f2"/> <rect x="335" y="99" width="181" height="132" fill="none" stroke="#f2f2f2"/> -<g transform="translate(335 99) scale(0.714 0.709)"> +<g transform="translate(335 99) scale(0.712 0.712)"> <image width="254" height="186" href="data:image/png;base64,"></image> </g> <rect x="312" y="302" width="170" height="220" fill="#f2f2f2"/> @@ -35,7 +35,7 @@ timage.svg <path d="M29,522l 2.0, -2.0h 223v -161l 2.0, -2.0v 165h -227z" fill="#a9a9a9"/> <rect x="52" y="374" width="181" height="132" fill="#f2f2f2"/> <rect x="52" y="374" width="181" height="132" fill="none" stroke="#f2f2f2"/> -<g transform="translate(52 374) scale(0.714 0.709)"> +<g transform="translate(51 374) scale(0.712 0.712)"> <image width="254" height="186" href="data:image/png;base64,"></image> </g> </svg> From d97b61da3e5523a577ee18200e3403adea735cdc Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Thu, 3 Sep 2026 17:33:21 +0200 Subject: [PATCH 12/13] [asimage] fix text align in DrawTextTTF After change code to TTFhandle wrong align was applied. One should use 13 instead 11 --- graf2d/asimage/src/TASImage.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graf2d/asimage/src/TASImage.cxx b/graf2d/asimage/src/TASImage.cxx index c7653dcef278e..45761ecc22bc8 100644 --- a/graf2d/asimage/src/TASImage.cxx +++ b/graf2d/asimage/src/TASImage.cxx @@ -5638,7 +5638,7 @@ void TASImage::DrawTextTTF(Int_t x, Int_t y, const char *text, Int_t size, ttf.PrepareString(text); ttf.LayoutGlyphs(); - if (ttf.ApplyAlignRotate(x, y, 11, GetWidth(), GetHeight())) + if (ttf.ApplyAlignRotate(x, y, 13, GetWidth(), GetHeight())) DrawFTGlyphs(ttf, color, x, y); } From d8ab7ae90b0c6f7fbcfaacfcbc918fc15bae1a39 Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Thu, 3 Sep 2026 17:44:00 +0200 Subject: [PATCH 13/13] [histpainter] use pad painter for image drawing When image object drawn on the pad - just use pp->DrawImage(). It is natural way now. --- hist/histpainter/src/THistPainter.cxx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/hist/histpainter/src/THistPainter.cxx b/hist/histpainter/src/THistPainter.cxx index a3758eda1384e..b76ffd7884c7f 100644 --- a/hist/histpainter/src/THistPainter.cxx +++ b/hist/histpainter/src/THistPainter.cxx @@ -58,6 +58,7 @@ #include "TPaletteAxis.h" #include "TCrown.h" #include "TArrow.h" +#include "TVirtualPadPainter.h" #include "TVirtualPadEditor.h" #include "TVirtualX.h" #include "TEnv.h" @@ -5772,11 +5773,14 @@ void THistPainter::PaintColorLevelsFast(Option_t*) pImage->SetImage(buffer.data(), nXPixels, nYPixels, pPalette); delete pPalette; - Window_t wid = static_cast<Window_t>(gVirtualX->GetWindowID(gPad->GetPixmapID())); - pImage->PaintImage(wid, px0, py1, 0, 0, nXPixels, nYPixels); + auto pp = gPad->GetPainter(); + if (pp) + pp->DrawImage(pImage, px0, py1); + delete pImage; - if (Hoption.Zscale) PaintPalette(); + if (Hoption.Zscale) + PaintPalette(); // Reset the maximum and minimum values to their original values // when this function was called. If we don't do this, an initial