I am trying to use TChart to generate this chart, and then use VclTee.TeePNG.TPNGExportFormat to generate the PNG stream.
The TChart is created in runtime from within a separate TThread (not the main thread).
The problem is that there are tons of problems related to this.
Even creating a TChart in a separate thread throws exception like "GDI+ error 3 out of memory" or "List index out of bounds" etc.
I imagine that lots of other developers before me must have tried using a TChart component to return a PNG stream in a response from TIdHTTPServer, where each request will run its own context thread?
How did they get TChart to work using GDI+ in a separate thread, or did they not?
I know a lot of people advise NOT to use GDI+ in non-main thread.
However, I am not trying to share the TChart object itself between threads, not even render it on the screen. It will only life for the purpose of generating a PNG stream, and then die. All this will be done in the same thread.
I have tried calling calling VclTee.TeeGDIPOBJ.TeeGDIPlusStartup and VclTee.TeeGDIPOBJ.TeeGDIPlusShutdown (inside the thread) before and after I create the TChart. But that won’t help. Sometimes I don’t get an exception, but instead the image is partially rendered or corrupted.
I have also tried to use the conditional define "TEECANVASLOCKS".
If it’s impossible to use GDI+ in a non-main thread, what’s the best way to fall back on simple GDI?
I have tried putting TeeRenderClasses.Clear; in the Initialization section to get rid of TGDIPlusCanvas, and then add TeeRenderClasses.Add(TCanvas3D);
That seems to work for falling back on GDI, but it’s that the preferred way?
Anyway, this is part of my code, where I create the chart, prepare it, and then generate a PNG stream.
Code: Select all
TMailReportBuilder = class(TThread) // GenerateHourChart called inside the thread, and its job is to fill the aStream with a PNG stream of the chart. procedure TMailReportBuilder.GenerateHourChart(aStream : TMemoryStream; aForDarkTheme : boolean); var aChart: TCustomChart; aExport : TPNGExportFormat; aCoins : TBarSeries; aCredits : TBarSeries; aFreeRuns : TBarSeries; lp0 : integer; aBackColor : TColor; begin VclTee.TeeGDIPOBJ.TeeGDIPlusStartup; // <- Testing if this helps, it did not aChart := TCustomChart.Create(nil); // GDI+ exceptions comes already here!!! Switching to GDI makes this code work fine, but its ugly. aExport := TPNGExportFormat.Create; try aStream.Clear; aChart.AutoRepaint := false; // We dont want updates while we setup and fill the chart aChart.Width := 1000; aChart.Height := 600; aCoins := aChart.AddSeries(TBarSeries) as TBarSeries; aCredits := aChart.AddSeries(TBarSeries) as TBarSeries; aFreeRuns := aChart.AddSeries(TBarSeries) as TBarSeries; if aForDarkTheme then begin aBackColor := $000000; end else begin aBackColor := $FFFFFF; end; InitChart(aChart,aBackColor,aForDarkTheme); InitBarSeries(aCoins,'Coins',$4040A0); InitBarSeries(aCredits,'Credits',$A04040); InitBarSeries(aFreeRuns,'Free Runs',$A0A0A0); for lp0 := 0 to FReport.PeriodHourData.Count-1 do begin aCoins.AddXY(FReport.PeriodHourData[lp0].StartTime,FReport.PeriodHourData[lp0].Coins); aCredits.AddXY(FReport.PeriodHourData[lp0].StartTime,FReport.PeriodHourData[lp0].Credits); aFreeRuns.AddXY(FReport.PeriodHourData[lp0].StartTime,-FReport.PeriodHourData[lp0].FreeRuns); end; // TEECANVASLOCKS aChart.Canvas.ReferenceCanvas.Lock; try aExport.PixelFormat := TPixelFormat.pf24bit; aExport.Panel := aChart; aExport.Width := 1000; aExport.Height := 600; aExport.SaveToStream(aStream); finally aChart.Canvas.ReferenceCanvas.Unlock; end; finally aExport.Free; aChart.Free; VclTee.TeeGDIPOBJ.TeeGDIPlusShutdown; // <- Testing if this helps, it did not end; end;