From f7405e836b48dd276ed889521086c6a8a1b7a1af Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 14 Nov 2025 10:12:31 +0100 Subject: [PATCH 1/6] Add TVirtualPS::CellArrayPng It allows to write whole image as PNG into output --- core/base/inc/TVirtualPS.h | 1 + 1 file changed, 1 insertion(+) diff --git a/core/base/inc/TVirtualPS.h b/core/base/inc/TVirtualPS.h index e722376e924a4..5cdc41ec0b6be 100644 --- a/core/base/inc/TVirtualPS.h +++ b/core/base/inc/TVirtualPS.h @@ -48,6 +48,7 @@ class TVirtualPS : public TNamed, public TAttLine, public TAttFill, public TAttM virtual ~TVirtualPS(); virtual void CellArrayBegin(Int_t W, Int_t H, Double_t x1, Double_t x2, Double_t y1, Double_t y2) = 0; 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 Close(Option_t *opt="") = 0; virtual void DrawBox(Double_t x1, Double_t y1,Double_t x2, Double_t y2) = 0; From e409362979f8b28aeb03b6ff1c1910fb53738f44 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 14 Nov 2025 10:14:08 +0100 Subject: [PATCH 2/6] Implement TSVG::CellArrayPng method So one can embed PNG image as base64 coding --- graf2d/postscript/inc/TSVG.h | 1 + graf2d/postscript/src/TSVG.cxx | 72 ++++++++++++++++++++++------------ 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/graf2d/postscript/inc/TSVG.h b/graf2d/postscript/inc/TSVG.h index d991aff64a904..bdc462977afff 100644 --- a/graf2d/postscript/inc/TSVG.h +++ b/graf2d/postscript/inc/TSVG.h @@ -42,6 +42,7 @@ class TSVG : 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 CellArrayPng(char *buffer, int size) override; void CellArrayEnd() override; void Close(Option_t *opt="") override; Double_t CMtoSVG(Double_t u) { return 0.5 + 72*u/2.54; } diff --git a/graf2d/postscript/src/TSVG.cxx b/graf2d/postscript/src/TSVG.cxx index 8c3ab46b73349..be8c985662211 100644 --- a/graf2d/postscript/src/TSVG.cxx +++ b/graf2d/postscript/src/TSVG.cxx @@ -21,6 +21,7 @@ #include "TROOT.h" #include "TDatime.h" +#include "TBase64.h" #include "TColor.h" #include "TVirtualPad.h" #include "TPoints.h" @@ -944,6 +945,52 @@ void TSVG::DrawPS(Int_t nn, Double_t *xw, Double_t *yw) PrintFast(2,"/>"); } +//////////////////////////////////////////////////////////////////////////////// +/// Begin the Cell Array painting + +void TSVG::CellArrayBegin(Int_t width, Int_t height, Double_t x1, Double_t x2, Double_t y1, Double_t y2) +{ + Double_t svgx1 = XtoSVG(x1); + Double_t svgx2 = XtoSVG(x1 + (x2 - x1) * width); + Double_t svgy1 = YtoSVG(y1); + Double_t svgy2 = YtoSVG(y1 - (y2 - y1) * height); + + PrintStr("@@"); + PrintStr(TString::Format("(buffer), size); + PrintFast(base64.Length(), base64.Data()); +} + +//////////////////////////////////////////////////////////////////////////////// +/// End the Cell Array painting + +void TSVG::CellArrayEnd() +{ + PrintStr("\">@"); + 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 @@ -1508,31 +1555,6 @@ Double_t TSVG::YtoSVG(Double_t y) return fYsizeSVG-VtoSVG(v); } -//////////////////////////////////////////////////////////////////////////////// -/// Begin the Cell Array painting - -void TSVG::CellArrayBegin(Int_t, Int_t, Double_t, Double_t, Double_t, - Double_t) -{ - Warning("TSVG::CellArrayBegin", "not yet implemented"); -} - -//////////////////////////////////////////////////////////////////////////////// -/// Paint the Cell Array - -void TSVG::CellArrayFill(Int_t, Int_t, Int_t) -{ - Warning("TSVG::CellArrayFill", "not yet implemented"); -} - -//////////////////////////////////////////////////////////////////////////////// -/// End the Cell Array painting - -void TSVG::CellArrayEnd() -{ - Warning("TSVG::CellArrayEnd", "not yet implemented"); -} - //////////////////////////////////////////////////////////////////////////////// /// Not needed in SVG case From a004c574b10874c7056aa4793f4ca79bf39834b2 Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Fri, 14 Nov 2025 10:18:55 +0100 Subject: [PATCH 3/6] Use CellArrayPng for SVG output in TASImage One converts image to PNG and then paint it as base64 --- graf2d/asimage/src/TASImage.cxx | 99 ++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 33 deletions(-) diff --git a/graf2d/asimage/src/TASImage.cxx b/graf2d/asimage/src/TASImage.cxx index efaaea8eeb6e5..44b18e7af57e0 100644 --- a/graf2d/asimage/src/TASImage.cxx +++ b/graf2d/asimage/src/TASImage.cxx @@ -1337,6 +1337,7 @@ void TASImage::Image2Drawable(ASImage *im, Drawable_t wid, Int_t x, Int_t y, if (gc) gVirtualX->ChangeGC(gc, &gv); } + //////////////////////////////////////////////////////////////////////////////// /// Draw image on the drawable wid (pixmap, window) at x,y position. /// @@ -1562,6 +1563,9 @@ void TASImage::Paint(Option_t *option) // loop over pixmap and draw image to PostScript if (gVirtualPS) { + + Bool_t paint_as_png = kFALSE; + if (gVirtualPS->InheritsFrom("TImageDump")) { // PostScript is asimage TImage *dump = (TImage *)gVirtualPS->GetStream(); if (!dump) return; @@ -1589,23 +1593,12 @@ void TASImage::Paint(Option_t *option) Warning("Paint", "PDF not implemented yet"); return; } else if (gVirtualPS->InheritsFrom("TSVG")) { - Warning("Paint", "SVG not implemented yet"); - return; + paint_as_png = kTRUE; } - // get special color cell to be reused during image printing - TObjArray *colors = (TObjArray*) gROOT->GetListOfColors(); - TColor *color = nullptr; - // Look for color by name - if ((color = (TColor*)colors->FindObject("Image_PS")) == nullptr) - color = new TColor(colors->GetEntries(), 1., 1., 1., "Image_PS"); - - gVirtualPS->SetFillColor(color->GetNumber()); - gVirtualPS->SetFillStyle(1001); - Double_t dx = gPad->GetX2()-gPad->GetX1(); Double_t dy = gPad->GetY2()-gPad->GetY1(); - Double_t x1,x2,y1,y2; + Double_t x1, x2, y1, y2; if (expand) { x1 = gPad->GetX1(); @@ -1619,19 +1612,44 @@ void TASImage::Paint(Option_t *option) y2 = y1+(dy*(1-gPad->GetTopMargin()-gPad->GetBottomMargin()))/image->height; } + // get special color cell to be reused during image printing + TObjArray *colors = (TObjArray*) gROOT->GetListOfColors(); + TColor *color = nullptr; + // Look for color by name + if ((color = (TColor*)colors->FindObject("Image_PS")) == nullptr) + color = new TColor(colors->GetEntries(), 1., 1., 1., "Image_PS"); + + gVirtualPS->SetFillColor(color->GetNumber()); + gVirtualPS->SetFillStyle(1001); + gVirtualPS->CellArrayBegin(image->width, image->height, x1, x2, y1, y2); - ASImageDecoder *imdec = start_image_decoding(fgVisual, image, SCL_DO_ALL, - 0, 0, image->width, image->height, nullptr); - if (!imdec) return; - 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++) - gVirtualPS->CellArrayFill(imdec->buffer.red[xt], - imdec->buffer.green[xt], - imdec->buffer.blue[xt]); + 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)) { + gVirtualPS->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++) + gVirtualPS->CellArrayFill(imdec->buffer.red[xt], + imdec->buffer.green[xt], + imdec->buffer.blue[xt]); + } + stop_image_decoding(&imdec); } - stop_image_decoding(&imdec); gVirtualPS->CellArrayEnd(); // print the color bar @@ -1645,18 +1663,33 @@ void TASImage::Paint(Option_t *option) gVirtualPS->CellArrayBegin(grad_im->width, grad_im->height, x1, x2, y1, y2); - 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++) - gVirtualPS->CellArrayFill(imdec->buffer.red[xt], - imdec->buffer.green[xt], - imdec->buffer.blue[xt]); + 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)) { + gVirtualPS->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++) + gVirtualPS->CellArrayFill(imdec->buffer.red[xt], + imdec->buffer.green[xt], + imdec->buffer.blue[xt]); + } + stop_image_decoding(&imdec); } - stop_image_decoding(&imdec); gVirtualPS->CellArrayEnd(); // values of palette From a8d19c8b71c7f39120524646a6586c76f329831e Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Fri, 14 Nov 2025 10:24:04 +0100 Subject: [PATCH 4/6] Fix problem with color redeclaration when store TASImage Use method to get white color --- graf2d/asimage/src/TASImage.cxx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/graf2d/asimage/src/TASImage.cxx b/graf2d/asimage/src/TASImage.cxx index 44b18e7af57e0..6ea60a788019f 100644 --- a/graf2d/asimage/src/TASImage.cxx +++ b/graf2d/asimage/src/TASImage.cxx @@ -1613,13 +1613,7 @@ void TASImage::Paint(Option_t *option) } // get special color cell to be reused during image printing - TObjArray *colors = (TObjArray*) gROOT->GetListOfColors(); - TColor *color = nullptr; - // Look for color by name - if ((color = (TColor*)colors->FindObject("Image_PS")) == nullptr) - color = new TColor(colors->GetEntries(), 1., 1., 1., "Image_PS"); - - gVirtualPS->SetFillColor(color->GetNumber()); + gVirtualPS->SetFillColor(TColor::GetColor((Float_t) 1., (Float_t) 1., (Float_t) 1.)); gVirtualPS->SetFillStyle(1001); gVirtualPS->CellArrayBegin(image->width, image->height, x1, x2, y1, y2); From c0c3ce84d2c89809b080fcf6978be5e3747b9f03 Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Fri, 14 Nov 2025 13:51:04 +0100 Subject: [PATCH 5/6] Do not save image in SVG in compact mode This is special mode for testing so skip images completely --- graf2d/postscript/src/TSVG.cxx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/graf2d/postscript/src/TSVG.cxx b/graf2d/postscript/src/TSVG.cxx index be8c985662211..1875a4c28aff6 100644 --- a/graf2d/postscript/src/TSVG.cxx +++ b/graf2d/postscript/src/TSVG.cxx @@ -975,11 +975,14 @@ void TSVG::CellArrayFill(Int_t, Int_t, Int_t) //////////////////////////////////////////////////////////////////////////////// /// Paint the Cell Array as png image +/// Disabled in compact mode to avoid creation of large SVG files void TSVG::CellArrayPng(char *buffer, int size) { - TString base64 = TBase64::Encode(reinterpret_cast<char *>(buffer), size); - PrintFast(base64.Length(), base64.Data()); + if (!fCompact) { + TString base64 = TBase64::Encode(reinterpret_cast<char *>(buffer), size); + PrintFast(base64.Length(), base64.Data()); + } } //////////////////////////////////////////////////////////////////////////////// From 06ec30eee79f00e1825f08cde669a78e34251755 Mon Sep 17 00:00:00 2001 From: Sergey Linev <S.Linev@gsi.de> Date: Fri, 14 Nov 2025 13:51:29 +0100 Subject: [PATCH 6/6] Update timage.svg file It has now placeholder for images --- test/svg_ref/timage.svg | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/svg_ref/timage.svg b/test/svg_ref/timage.svg index 10143cf6fd93b..f28853fec03c6 100644 --- a/test/svg_ref/timage.svg +++ b/test/svg_ref/timage.svg @@ -11,18 +11,31 @@ 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)"> +<image width="191" height="247" href="data:image/png;base64,"></image> +</g> <rect x="312" y="82" width="227" height="165" fill="#f2f2f2"/> <path d="M312,247l 2.0, -2.0v -161h 223l 2.0, -2.0h -227v 165z" fill="white"/> <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)"> +<image width="254" height="186" href="data:image/png;base64,"></image> +</g> <rect x="312" y="302" width="170" height="220" fill="#f2f2f2"/> <path d="M312,522l 2.0, -2.0v -216h 166l 2.0, -2.0h -170v 220z" fill="white"/> <path d="M312,522l 2.0, -2.0h 166v -216l 2.0, -2.0v 220h -170z" fill="#a9a9a9"/> <rect x="329" y="324" width="136" height="176" fill="#f2f2f2"/> <rect x="329" y="324" width="136" height="176" fill="none" stroke="#f2f2f2"/> +<g transform="translate(329 324) scale(0.712 0.712)"> +<image width="191" height="247" href="data:image/png;base64,"></image> +</g> <rect x="29" y="357" width="227" height="165" fill="#f2f2f2"/> <path d="M29,522l 2.0, -2.0v -161h 223l 2.0, -2.0h -227v 165z" fill="white"/> <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"/></svg> +<rect x="52" y="374" width="181" height="132" fill="none" stroke="#f2f2f2"/> +<g transform="translate(52 374) scale(0.714 0.709)"> +<image width="254" height="186" href="data:image/png;base64,"></image> +</g> +</svg>