From b5186416019221c1311f1f8c810bb90a04d698fe Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 22 Oct 2026 10:18:56 +0200 Subject: [PATCH 1/6] [asimage] change copy constructor and assign operator Provide CloneImageData method which clone all memebers of the TASImage Include argb32 which can be already created because of painting. --- graf2d/asimage/inc/TASImage.h | 2 + graf2d/asimage/src/TASImage.cxx | 76 +++++++++++++++++---------------- 2 files changed, 41 insertions(+), 37 deletions(-) diff --git a/graf2d/asimage/inc/TASImage.h b/graf2d/asimage/inc/TASImage.h index 3fad702ba1f0f..1cbcebddb8289 100644 --- a/graf2d/asimage/inc/TASImage.h +++ b/graf2d/asimage/inc/TASImage.h @@ -55,6 +55,8 @@ class TASImage : public TImage { void DestroyScaledImage(); const char *TypeFromMagicNumber(const char *file); + void CloneImageData(const TASImage &src); + protected: ASImage *fImage; ///(img.fScaledImage->Clone()) : nullptr; - fGrayImage = img.fGrayImage ? clone_asimage(img.fGrayImage, SCL_DO_ALL) : nullptr; - - if (img.fImage->alt.vector) { - Int_t size = img.fImage->width * img.fImage->height * sizeof(double); - fImage->alt.vector = (double*)malloc(size); - memcpy(fImage->alt.vector, img.fImage->alt.vector, size); - } - - fZoomUpdate = kNoZoom; - fZoomOffX = img.fZoomOffX; - fZoomOffY = img.fZoomOffY; - fZoomWidth = img.fZoomWidth; - fZoomHeight = img.fZoomHeight; - fEditable = img.fEditable; - fIsGray = img.fIsGray; - } + if (img.IsValid()) + CloneImageData(img); } //////////////////////////////////////////////////////////////////////////////// @@ -334,24 +317,7 @@ TASImage &TASImage::operator=(const TASImage &img) DestroyImage(); DestroyScaledImage(); - fImage = clone_asimage(img.fImage, SCL_DO_ALL); - fScaledImage = img.fScaledImage ? static_cast(img.fScaledImage->Clone()) : nullptr; - fGrayImage = img.fGrayImage ? clone_asimage(img.fGrayImage, SCL_DO_ALL) : nullptr; - - if (img.fImage->alt.vector) { - Int_t size = img.fImage->width * img.fImage->height * sizeof(double); - fImage->alt.vector = (double*)malloc(size); - memcpy(fImage->alt.vector, img.fImage->alt.vector, size); - } - - fZoomUpdate = kNoZoom; - fZoomOffX = img.fZoomOffX; - fZoomOffY = img.fZoomOffY; - fZoomWidth = img.fZoomWidth; - fZoomHeight = img.fZoomHeight; - fEditable = img.fEditable; - fIsGray = img.fIsGray; - fPaintMode = 1; + CloneImageData(img); } return *this; @@ -366,6 +332,42 @@ TASImage::~TASImage() DestroyScaledImage(); } + +//////////////////////////////////////////////////////////////////////////////// +/// Clone image data +/// Used for clones and assign operator + +void TASImage::CloneImageData(const TASImage &img) +{ + fImage = img.fImage ? clone_asimage(img.fImage, SCL_DO_ALL) : nullptr; + fScaledImage = img.fScaledImage ? static_cast(img.fScaledImage->Clone()) : nullptr; + fGrayImage = img.fGrayImage ? clone_asimage(img.fGrayImage, SCL_DO_ALL) : nullptr; + + if (img.fImage && img.fImage->alt.vector) { + Int_t size = img.fImage->width * img.fImage->height * sizeof(double); + fImage->alt.vector = (double*)malloc(size); + memcpy(fImage->alt.vector, img.fImage->alt.vector, size); + } + + if (img.fImage && img.fImage->alt.argb32) { + Int_t size = img.fImage->width * img.fImage->height * sizeof(ARGB32); + fImage->alt.argb32 = (ARGB32*)safemalloc(size); + memcpy(fImage->alt.argb32, img.fImage->alt.argb32, size); + } + + fPaintMode = img.fPaintMode; + fZoomOffX = img.fZoomOffX; + fZoomOffY = img.fZoomOffY; + fZoomWidth = img.fZoomWidth; + fZoomHeight = img.fZoomHeight; + fZoomUpdate = img.fZoomUpdate; + fEditable = img.fEditable; + fIsGray = img.fIsGray; + fMaxValue = img.fMaxValue; + fMinValue = img.fMinValue; +} + + //////////////////////////////////////////////////////////////////////////////// /// Set icons paths. From c76368978e79a1aacf90f68be529cd075fa72b64 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 22 Oct 2026 10:21:18 +0200 Subject: [PATCH 2/6] [asimage] Simplify Clone method Now CloneImageData() does the job - one only need to create new instance and set new name --- graf2d/asimage/src/TASImage.cxx | 36 +++++---------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/graf2d/asimage/src/TASImage.cxx b/graf2d/asimage/src/TASImage.cxx index c6211f728381e..4799d988aa9e4 100644 --- a/graf2d/asimage/src/TASImage.cxx +++ b/graf2d/asimage/src/TASImage.cxx @@ -2694,37 +2694,11 @@ void TASImage::Blur(Double_t hr, Double_t vr) TObject *TASImage::Clone(const char *newname) const { - if (!InitVisual() || !fImage) { - Warning("Clone", "Image not initiated"); - return nullptr; - } - - TASImage *im = static_cast(TImage::Create()); - - if (!im) { - Warning("Clone", "Failed to create image"); - return nullptr; - } - - im->SetName(newname); - - im->fImage = clone_asimage(fImage, SCL_DO_ALL); - im->fMaxValue = fMaxValue; - im->fMinValue = fMinValue; - im->fZoomOffX = fZoomOffX; - im->fZoomOffY = fZoomOffY; - im->fZoomWidth = fZoomWidth; - im->fZoomHeight = fZoomHeight; - im->fZoomUpdate = fZoomUpdate; - im->fScaledImage = fScaledImage ? static_cast(fScaledImage->Clone()) : nullptr; - - if (fImage->alt.argb32) { - UInt_t sz = fImage->width * fImage->height; - im->fImage->alt.argb32 = (ARGB32*)safemalloc(sz*sizeof(ARGB32)); - memcpy(im->fImage->alt.argb32, fImage->alt.argb32, sz * sizeof(ARGB32)); - } - - return im; + auto newimg = new TASImage(); + if (newname) + newimg->SetName(newname); + newimg->CloneImageData(*this); + return newimg; } //////////////////////////////////////////////////////////////////////////////// From 607cade1ca85810e7eedc0df980067734b595d4d Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 22 Oct 2026 10:24:18 +0200 Subject: [PATCH 3/6] [asimage] fix Append method When image does not have data - just clone data from source image. Fixing ownership problem when image pointer was copied and causing segfault when TASImage was deleted. It was a reason for memory leak in TRootSniffer when processing root.png request --- graf2d/asimage/src/TASImage.cxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/graf2d/asimage/src/TASImage.cxx b/graf2d/asimage/src/TASImage.cxx index 4799d988aa9e4..258544b229914 100644 --- a/graf2d/asimage/src/TASImage.cxx +++ b/graf2d/asimage/src/TASImage.cxx @@ -3381,7 +3381,9 @@ void TASImage::Append(const TImage *im, const char *option, const char *color ) } if (!fImage) { - fImage = ((TASImage*)im)->fImage; + // if image not exists - just clone data from source image + if (auto asimg = dynamic_cast(im)) + CloneImageData(*asimg); return; } From eccf0fe338450d51d4c3e4234ba53c920cc81106 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Wed, 21 Oct 2026 16:49:41 +0200 Subject: [PATCH 4/6] [http] fix image generation TImage::Append() was used with "intentional" memory leak while second image took over ownership. Use of Append not necessary at all - one can use original image. --- net/httpsniff/src/TRootSnifferFull.cxx | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/net/httpsniff/src/TRootSnifferFull.cxx b/net/httpsniff/src/TRootSnifferFull.cxx index 0323937d4dbd7..d4d451e6159fb 100644 --- a/net/httpsniff/src/TRootSnifferFull.cxx +++ b/net/httpsniff/src/TRootSnifferFull.cxx @@ -403,7 +403,7 @@ Bool_t TRootSnifferFull::ProduceImage(Int_t kind, const std::string &path, const TObject *obj = (TObject *)obj_ptr; - TImage *img = TImage::Create(); + std::unique_ptr img(TImage::Create()); if (!img) return kFALSE; @@ -412,6 +412,7 @@ Bool_t TRootSnifferFull::ProduceImage(Int_t kind, const std::string &path, const if (gDebug > 1) Info("TRootSniffer", "Crate IMAGE directly from pad"); img->FromPad((TPad *)obj); + } else if (CanDrawClass(obj->IsA())) { if (gDebug > 1) @@ -439,26 +440,21 @@ Bool_t TRootSnifferFull::ProduceImage(Int_t kind, const std::string &path, const if (!isbatch) gROOT->SetBatch(kTRUE); - TCanvas *c1 = new TCanvas("__online_draw_canvas__", "title", width, height); - obj->Draw(drawopt.Data()); - img->FromPad(c1); - delete c1; + TCanvas c1("__online_draw_canvas__", "title", width, height); + c1.Add(obj, drawopt.Data()); + img->FromPad(&c1); if (!isbatch) gROOT->SetBatch(kFALSE); } else { - delete img; return kFALSE; } - TImage *im = TImage::Create(); - im->Append(img); - char *png_buffer = nullptr; - int size(0); + int size = 0; - im->GetImageBuffer(&png_buffer, &size, (TImage::EImageFileTypes)kind); + img->GetImageBuffer(&png_buffer, &size, (TImage::EImageFileTypes)kind); if (png_buffer && (size > 0)) { res.resize(size); @@ -466,7 +462,6 @@ Bool_t TRootSnifferFull::ProduceImage(Int_t kind, const std::string &path, const } free(png_buffer); - delete im; return !res.empty(); } From f337d60c9414f88108246eed4f177d03b2246220 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Wed, 21 Oct 2026 16:55:26 +0200 Subject: [PATCH 5/6] [httpsniff] add test of root.png request Verify that code does not crash and returns meaningful image size --- net/httpsniff/test/test_sniffer.cxx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/net/httpsniff/test/test_sniffer.cxx b/net/httpsniff/test/test_sniffer.cxx index e4782439cd7b3..63205f28b0509 100644 --- a/net/httpsniff/test/test_sniffer.cxx +++ b/net/httpsniff/test/test_sniffer.cxx @@ -353,3 +353,21 @@ TEST(TRootSniffer, multi_json) " \"fTitle\" : \"title2\"\n" "}]") << "return value of multi.json"; } + +// Produce image object +TEST(TRootSniffer, root_png) +{ + TH1F h1("h1", "title", 100, -10, 10); + + TRootSnifferFull sniffer("sniffer"); + // disable readonly to get method executed + sniffer.SetReadOnly(kFALSE); + + sniffer.RegisterObject("/", &h1); + + std::string res; + + sniffer.Produce("/h1", "root.png", "w=500&h=500", res); + EXPECT_NE(res.length(), 0) << "image size should not be 0"; + EXPECT_NEAR(res.length(), 5558, 2000) << "image size should be around 5K"; +} From 77203587382b8a8b66bb7ac186168d0689dccee8 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Wed, 21 Oct 2026 16:59:48 +0200 Subject: [PATCH 6/6] [http] add root.png request test --- net/http/test/test_suite.cxx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/net/http/test/test_suite.cxx b/net/http/test/test_suite.cxx index 76e8b39ce3ad2..0f8407afe0978 100644 --- a/net/http/test/test_suite.cxx +++ b/net/http/test/test_suite.cxx @@ -1,3 +1,5 @@ +#include "TH1F.h" + Int_t server_hash = 0; TString server_url; TString unix_socket; @@ -54,9 +56,12 @@ void test_suite(THttpServer &serv) TNamed obj1("obj1", "title1"); TNamed obj2("obj2", "title2"); + TH1F h1("h1", "title", 100, -10, 10); + serv.Register("/", &obj1); serv.Register("/", &obj2); + serv.Register("/", &h1); // check JSON representation for the object std::string res = execute_request("/obj1/root.json"); @@ -142,6 +147,12 @@ void test_suite(THttpServer &serv) EXPECT_EQ(res, "") << "exe.json should be empty in readonly"; + res = execute_request("/h1/root.png?w=500&h=500"); + EXPECT_NE(res.length(), 0) << "root.png should not be empty"; + EXPECT_NEAR(res.length(), 5558, 2000) << "root.png should be around 5K"; + + + // disable readonly to get extra functionality serv.SetReadOnly(kFALSE);