View Issue Details

IDProjectCategoryView StatusLast Update
0025491LazarusWidgetsetpublic2016-08-27 00:04
ReporterKostas Michalopoulos Assigned ToJuha Manninen  
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionfixed 
PlatformGTK2OSLinux 
Product Version1.3 (SVN) 
Summary0025491: GTK2 missing transparency (with patch for common cases)
DescriptionThe GTK2 widgetset doesn't support drawing bitmaps with full transparency (alpha channel). While the bitmaps themselves can be stored as 32bit, it is impossible to use them on the GUI - everywhere a bitmap with an alpha channel is used (toolbars, icons, images, etc), the bitmap is drawn with jaggies because the widgetset only support 1bit transparency.

The reason for that is that the graphics contexts are implemented using GDK pixmaps which themselves do not support alpha channels, but it is possible to use a bitmask (hence the ability for 1bit transparency). When a graphics context is created for a bitmap with an alpha channel (stored as a GDK pixbuf, which however isn't a drawable and so it cant be used directly), the GTK2 backend generates a 24bit pixmap from the RGB data and a pixmap using an alpha threshold. Then the 32bit pixbuf is destroyed.

The only way to avoid the above is to rewrite the GTK2 graphics context functionality to use Cairo (which supports full alpha blending operations). However with the ongoing work on GTK3 and the impact that would have on the stability of existing applications (bugs from the new interface) this might not be a good idea at the moment.

As an intermediate solution, i have attached a patch that fixes the most common cases where a bitmap is drawn to widgets or other bitmaps (which are used, among others, for toolbars, images, icon views, lists, etc). This patch keeps the original pixbuf in the graphics context alongside with the generated pixmap and uses it instead of the pixmap when a SRCCOPY draw is requested (the patch works with or without scaling and with or without flipping). The pixbuf is destroyed when the graphics context is destroyed or when any drawing operation
on the graphics context is performed (f.e. drawing a line, arc or anything else on the bitmap). The latter is necessary since GDK does not provide functionality for those functions on pixbufs (which would allow a full 32bit aware implementation of graphics contexts under GTK2).

Attached i have the patch, a test program and an image that shows the problem with before and after the patch has been applied. The program tests toolbars, an image with an icon and a generated bitmap via TLazIntfImage drawn on a paintbox normally, flipped, scaled and both scaled and flipped.
Steps To ReproduceDownload, compile and run the test program under GTK2.
TagsNo tags attached.
Fixed in Revisionr43644
LazTarget-
WidgetsetGTK 2
Attached Files

Relationships

related to 0025488 closedZeljan Rikalo Transparency issues with 32bpp toolbar icons in Ubuntu (gtk 2) 
related to 0011721 closedJuha Manninen Linux TSpeedButtons shows binary glyphs 
related to 0023850 new Bug converting tbitmap 32 in tlazintfimage and reversing to a tbitmap again 
related to 0023823 new Bug painting 32bits tbitmaps on linux lazarus. 
related to 0023007 resolvedVincent Snijders TBitBtn does not display Glyphs (PNG and TIFF) with transparent color set 
related to 0022901 feedbackZeljan Rikalo TSpeedButton color issue 
related to 0022787 new TWinControl.SetShape works in LCL-GTK2 only after expose 
related to 0020911 confirmed GTK2 Toolbar does not support transparency 
related to 0017891 assignedFelipe Monteiro de Carvalho TTrayIcon - no transparency on GTK 
related to 0018668 resolvedJuha Manninen png pictures import without transparency 
related to 0010589 resolvedJuha Manninen PNG Transparency gets lost on GTK 
related to 0009581 resolvedMarc Weustink GTK2 Listview: Images transparency 
has duplicate 0016554 resolvedZeljan Rikalo Error in transparency 

Activities

Kostas Michalopoulos

2014-01-04 19:16

reporter  

lazarus-gtk2-transparency-fix-r43643.patch (12,043 bytes)   
Index: lcl/interfaces/gtk2/gtk2def.pp
===================================================================
--- lcl/interfaces/gtk2/gtk2def.pp	(revision 43643)
+++ lcl/interfaces/gtk2/gtk2def.pp	(working copy)
@@ -270,6 +270,8 @@
                              // or the gdk_bitmap/pixmap of the selected image
                              // or the double buffer (OriginalDrawable will hold the original)
 
+    FPixbuf: PGdkPixbuf;     // pixbuf reference for when the drawable comes from a pixbuf
+
     FOriginalDrawable: PGDKDrawable; // only set if dcfDoubleBuffer in DCFlags
 
     FWidget: PGtkWidget;     // the owner (in case of a windowDC)
@@ -344,6 +346,7 @@
     Antialiasing: Boolean;
 
     constructor Create; virtual;
+    destructor Destroy; override;
     procedure CreateGDIObject(AGDIType: TGDIType);
     procedure SelectBrushProps; virtual;
     procedure SelectTextProps; virtual;
@@ -364,6 +367,7 @@
     function IsNullPen: boolean;
     function SelectObject(AGdiObject: PGdiObject): PGdiObject;
     procedure SetTextMetricsValid(AValid: Boolean); // temp helper, to allow flag manipulation
+    procedure RemovePixbuf; // called to remove the stored pixbuf (because, f.e., the pixmap was modified)
 
     // viewport/affine transformations
     procedure InvTransfPoint(var X1, Y1: Integer);
@@ -410,6 +414,7 @@
     property Flags: TDeviceContextsFlags read FFlags write FFlags;
     property OwnedGDIObjects[ID: TGDIType]: PGdiObject read GetOwnedGDIObjects write SetOwnedGDIObjects;
     property Drawable: PGDKDrawable read FDrawable;
+    property Pixbuf: PGdkPixbuf read FPixbuf;
     property Widget: PGtkWidget read FWidget; // the owner
     property GC: pgdkGC read GetGC write FGC;
     property WithChildWindows: Boolean read FWithChildWindows;
Index: lcl/interfaces/gtk2/gtk2devicecontext.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2devicecontext.inc	(revision 43643)
+++ lcl/interfaces/gtk2/gtk2devicecontext.inc	(working copy)
@@ -319,6 +319,15 @@
     Exclude(FFlags, dcfTextMetricsValid);
 end;
 
+procedure TGtkDeviceContext.RemovePixbuf;
+begin
+  if Assigned(FPixbuf) then
+  begin
+    gdk_pixbuf_unref(FPixbuf);
+    FPixbuf := nil;
+  end;
+end;
+
 procedure TGtkDeviceContext.InvTransfPoint(var X1, Y1: Integer);
 begin
   X1 := MulDiv(X1 + FWindowOrg.x - FViewPortOrg.x, FWindowExt.x, FViewPortExt.x);
@@ -516,6 +525,7 @@
 
   FWithChildWindows := AWithChildWindows;
   FWidget := AWidget;
+  FPixbuf := nil;
 
   if AWidget = nil then
   begin
@@ -583,8 +593,10 @@
   end;
 
 begin
+  if Assigned(FPixbuf) then gdk_pixbuf_unref(FPixbuf);
   FWidget := nil;
   FDrawable := nil;
+  FPixbuf := nil;
   FGC := nil;
   FillChar(FGCValues, SizeOf(FGCValues), 0);
 
@@ -662,6 +674,7 @@
 
   FWithChildWindows := ASource.FWithChildWindows;
   FDrawable := ASource.FDrawable;
+  FPixbuf := ASource.Pixbuf;
   FOriginalDrawable := ASource.FOriginalDrawable;
 
   if Assigned(FGC) then
@@ -777,6 +790,7 @@
 
     DCOrigin := Offset;
     ClipArea := ClipRect;
+    RemovePixbuf;
     if (CurrentBrush^.GDIBrushFill = GDK_SOLID) and
        (IsBackgroundColor(CurrentBrush^.GDIBrushColor.ColorRef)) then
       StyleFillRectangle(Drawable, GC,
@@ -987,6 +1001,7 @@
 
 function TGtkDeviceContext.SelectBitmap(AGdiObject: PGdiObject): PGdiObject;
 var
+  NewPixbuf: PGdkPixbuf = nil;
   NewDrawable: PGdkPixmap;
   Mask: PGdkBitmap;
 begin
@@ -1003,9 +1018,9 @@
         begin
           NewDrawable := nil;
           Mask := nil;
+          NewPixbuf := GDIPixbufObject;
           gdk_pixbuf_render_pixmap_and_mask(GDIPixbufObject, NewDrawable, Mask, $80);
           GDIBitmapType := gbPixmap;
-          gdk_pixbuf_unref(GDIPixbufObject);
           GDIPixmapObject.Image := NewDrawable;
           GDIPixmapObject.Mask := Mask;
           if Visual <> nil then
@@ -1024,6 +1039,7 @@
   if FGC <> nil then
     gdk_gc_unref(FGC);
   FDrawable := NewDrawable;
+  FPixbuf := NewPixbuf;
   FGC := gdk_gc_new(FDrawable);
   gdk_gc_set_function(FGC, GDK_COPY);
   SelectedColors := dcscCustom;
@@ -1086,6 +1102,12 @@
   BkMode := OPAQUE;
 end;
 
+destructor TGtkDeviceContext.Destroy;
+begin
+  if Assigned(FPixbuf) then gdk_pixbuf_unref(FPixbuf);
+  inherited Destroy;
+end;
+
 {------------------------------------------------------------------------------
   Procedure: TGtkDeviceContext.SelectPenProps
   Params:  DC: a (LCL)devicecontext
@@ -1290,6 +1312,7 @@
       renderer := gdk_pango_renderer_get_default(gtk_widget_get_screen(Widget))
     else
       renderer := gdk_pango_renderer_get_default(gdk_screen_get_default);
+    RemovePixbuf;
     gdk_pango_renderer_set_drawable(renderer, drawable);
     gdk_pango_renderer_set_gc(renderer, GC);
     SetColors(FGColor, BGColor);
Index: lcl/interfaces/gtk2/gtk2widgetset.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2widgetset.inc	(revision 43643)
+++ lcl/interfaces/gtk2/gtk2widgetset.inc	(working copy)
@@ -3817,8 +3817,51 @@
     ClipMask: PGdkBitmap;
     SrcGDIBitmap: PGdiObject;
     B: Boolean;
+    TmpPixbuf, TmpPixbuf2: PGdkPixbuf;
   begin
     Result:=true;
+
+    // special case for copying from bitmaps with alpha channel
+    if (ROP=SRCCOPY) and Assigned(SrcDevContext.Pixbuf) then
+    begin
+      if SizeChange then
+      begin
+        // there isn't a "stretch draw" function for pixbufs so we need to make
+        // a temporary scaled copy if we have a different size
+        if (Width <> SrcWidth) or (Height <> SrcHeight) then begin
+          TmpPixbuf:=gdk_pixbuf_scale_simple(SrcDevContext.Pixbuf, Width, Height, GDK_INTERP_HYPER);
+          if not Assigned(TmpPixbuf) then
+          begin
+            DebugLn('SrcDevBitmapToDrawable: failed to create temporary pixbuf for scaled draw');
+            exit;
+          end;
+        end else begin
+          // same size but we have flips, just increase the refcount of the
+          // original pixbuf
+          TmpPixbuf:=SrcDevContext.Pixbuf;
+          gdk_pixbuf_ref(TmpPixbuf);
+        end;
+        // flip the pixmap, if necessary
+        if FlipHorz then begin
+          TmpPixbuf2:=gdk_pixbuf_flip(TmpPixbuf, True);
+          gdk_pixbuf_unref(TmpPixbuf);
+          TmpPixbuf:=TmpPixbuf2;
+        end;
+        if FlipVert then begin
+          TmpPixbuf2:=gdk_pixbuf_flip(TmpPixbuf, False);
+          gdk_pixbuf_unref(TmpPixbuf);
+          TmpPixbuf:=TmpPixbuf2;
+        end;
+        // draw and release the final pixbuf
+        gdk_draw_pixbuf(DstDevContext.Drawable, DstDevContext.GC, TmpPixbuf, XSrc, YSrc, X, Y, Width, Height, GDK_RGB_DITHER_MAX, 0, 0);
+        gdk_pixbuf_unref(TmpPixbuf);
+      end else
+      begin
+        gdk_draw_pixbuf(DstDevContext.Drawable, DstDevContext.GC, SrcDevContext.Pixbuf, XSrc, YSrc, X, Y, Width, Height, GDK_RGB_DITHER_MAX, 0, 0);
+      end;
+      Exit;
+    end;
+
     {$IFDEF VerboseStretchCopyArea}
     DebugLn('SrcDevBitmapToDrawable Start');
     {$ENDIF}
Index: lcl/interfaces/gtk2/gtk2winapi.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2winapi.inc	(revision 43643)
+++ lcl/interfaces/gtk2/gtk2winapi.inc	(working copy)
@@ -84,6 +84,7 @@
   inc(Bottom, DCOrigin.Y);
 
   {$IFDEF DebugGDKTraps}BeginGDKErrorTrap;{$ENDIF}
+  DevCtx.RemovePixbuf;
   gdk_draw_arc(DevCtx.Drawable, DevCtx.GC, 0, left, top, right - left, bottom - top,
                    Angle1*4, Angle2*4);
   {$IFDEF DebugGDKTraps}EndGDKErrorTrap;{$ENDIF}
@@ -2005,6 +2006,7 @@
 
     if aStyle<>nil then
     begin
+      aDC.RemovePixbuf;
       if (Shadow=GTK_SHADOW_NONE) then
         gtk_paint_flat_box(aStyle,aDC.Drawable,
            State,
@@ -2076,6 +2078,7 @@
     Result := Style <> nil;
     if Result then
     begin
+      aDC.RemovePixbuf;
       if IsRadioButton then
         gtk_paint_option(Style,aDC.Drawable, State,
           Shadow, @ClipArea, Widget, 'radiobutton',
@@ -2170,6 +2173,7 @@
   begin
     inc(X1,Origin.X);
     inc(Y1,Origin.Y);
+    TGtkDeviceContext(DC).RemovePixbuf;
     gdk_draw_point(TGtkDeviceContext(DC).Drawable, TGtkDeviceContext(DC).GC, X1, Y1);
   end;
   
@@ -2333,11 +2337,17 @@
 
       // Draw outer rect
       if BOuter then
+      begin
+        RemovePixbuf;
         DrawEdges(R, GC,Drawable,OuterTL,OuterBR);
+      end;
 
       // Draw inner rect
       if BInner then
+      begin
+        RemovePixbuf;
         DrawEdges(R,GC,Drawable,InnerTL,InnerBR);
+      end;
 
   //      gdk_colormap_free_colors(gdk_colormap_get_system, @OuterTL, 1);
   //      gdk_colormap_free_colors(gdk_colormap_get_system, @OuterBR, 1);
@@ -2347,6 +2357,7 @@
       //Draw interiour
       if ((grfFlags and BF_MIDDLE) = BF_MIDDLE) then
       begin
+        RemovePixbuf;
         MiddleColor := AllocGDKColor(GetSysColor(COLOR_BTNFACE));
         gdk_gc_set_foreground(GC, @MiddleColor);
         gdk_draw_rectangle(Drawable, GC, 1, R.Left, R.Top,
@@ -3572,6 +3583,7 @@
   if not DevCtx.IsNullBrush then
   begin
     DevCtx.SelectBrushProps;
+    DevCtx.RemovePixbuf;
     gdk_draw_arc(DevCtx.Drawable, DevCtx.GC, 1,
                  Left+DCOrigin.X, Top+DCOrigin.Y, Width, Height, 0, 360 shl 6);
   end;
@@ -3584,6 +3596,7 @@
     Result := True;
     if not DevCtx.IsNullPen then
     begin
+      DevCtx.RemovePixbuf;
       gdk_draw_arc(DevCtx.Drawable, DevCtx.GC, 0,
                    Left+DCOrigin.X, Top+DCOrigin.Y, Width, Height, 0, 360 shl 6);
     end;
@@ -3833,6 +3846,7 @@
     Width := Rect^.Right - Rect^.Left;
     Height := Rect^.Bottom - Rect^.Top;
     EnsureGCColor(DC, dccCurrentBackColor, True, False);
+    DevCtx.RemovePixbuf;
     gdk_draw_rectangle(DevCtx.Drawable, DevCtx.GC, 1,
                        Rect^.Left+DCOrigin.X, Rect^.Top+DCOrigin.Y,
                        Width, Height);
@@ -4019,6 +4033,7 @@
       gdk_gc_set_subwindow(gc2, GDK_INCLUDE_INFERIORS);
     end;
 
+    DevCtx.RemovePixbuf;
     for i := 1 to AWidth do
     begin
       gdk_draw_line(Drawable, gc1, ARect.Left + Offset.x, ARect.Top + Offset.y,
@@ -4070,6 +4085,7 @@
   LPtoDP(DC, R, 2);
 
   DCOrigin := DevCtx.Offset;
+  DevCtx.RemovePixbuf;
   gdk_draw_rectangle(DevCtx.Drawable, DevCtx.GC, 0,
                      R.Left + DCOrigin.X, R.Top + DCOrigin.Y,
                      R.Right-R.Left-1, R.Bottom-R.Top-1);
@@ -6410,6 +6426,7 @@
   LPToDP(DC, ToPt, 1);
   
   {$IFDEF DebugGDK}BeginGDKErrorTrap;{$ENDIF}
+  DevCtx.RemovePixbuf;
   gdk_draw_line(DevCtx.Drawable, DevCtx.GC, FromPt.X, FromPt.Y, ToPt.X, ToPt.Y);
   {$IFDEF DebugGDK}EndGDKErrorTrap;{$ENDIF}
 
@@ -6758,6 +6775,7 @@
     end else
     begin
       DevCtx.SelectBrushProps;
+      DevCtx.RemovePixbuf;
       gdk_draw_polygon(DevCtx.Drawable, DevCtx.GC, 1, PointArray, NumPts);
     end;
   end;
@@ -6766,6 +6784,7 @@
   if not DevCtx.IsNullPen
   then begin
     DevCtx.SelectPenProps;
+    DevCtx.RemovePixbuf;
     gdk_draw_polygon(DevCtx.Drawable, DevCtx.GC, 0, PointArray, NumPts);
   end;
 
@@ -6806,6 +6825,7 @@
   if Result and not DevCtx.IsNullPen
   then begin
     {$IFDEF DebugGDK}BeginGDKErrorTrap;{$ENDIF}
+    DevCtx.RemovePixbuf;
     gdk_draw_lines(DevCtx.Drawable, DevCtx.GC, PointArray, NumPts);
     {$IFDEF DebugGDKTraps}EndGDKErrorTrap;{$ENDIF}
   end;
@@ -7058,6 +7078,7 @@
   begin
     ClipArea := DevCtx.ClipRect;
     Brush := DevCtx.GetBrush;
+    DevCtx.RemovePixbuf;
     if  (Brush^.GDIBrushFill = GDK_SOLID) and
       (IsBackgroundColor(TColor(Brush^.GDIBrushColor.ColorRef)))
     then
@@ -7072,8 +7093,11 @@
   DevCtx.SelectPenProps;
   Result := dcfPenSelected in DevCtx.Flags;
   if Result and not DevCtx.IsNullPen
-  then gdk_draw_rectangle(DevCtx.Drawable, DevCtx.GC, 0,
-                          Left+DCOrigin.X, Top+DCOrigin.Y, Width, Height);
+  then begin
+    DevCtx.RemovePixbuf;
+    gdk_draw_rectangle(DevCtx.Drawable, DevCtx.GC, 0,
+                       Left+DCOrigin.X, Top+DCOrigin.Y, Width, Height);
+  end;
 
   {$IFDEF DebugGDKTraps}EndGDKErrorTrap;{$ENDIF}
 end;

Kostas Michalopoulos

2014-01-04 19:16

reporter  

patchshot.png (80,622 bytes)   
patchshot.png (80,622 bytes)   

Kostas Michalopoulos

2014-01-04 19:16

reporter  

Juha Manninen

2014-01-04 22:06

developer   ~0072206

I applied the patch. It works well. Thanks!

wp

2014-01-05 00:00

developer   ~0072207

Excellent work. Resolves also my issue 0025488.

Issue History

Date Modified Username Field Change
2014-01-04 19:16 Kostas Michalopoulos New Issue
2014-01-04 19:16 Kostas Michalopoulos File Added: lazarus-gtk2-transparency-fix-r43643.patch
2014-01-04 19:16 Kostas Michalopoulos File Added: patchshot.png
2014-01-04 19:16 Kostas Michalopoulos File Added: laztest-transparency.tar.bz2
2014-01-04 21:20 Juha Manninen Relationship added related to 0025488
2014-01-04 21:20 Juha Manninen Assigned To => Juha Manninen
2014-01-04 21:20 Juha Manninen Status new => assigned
2014-01-04 21:25 Juha Manninen Relationship added related to 0011721
2014-01-04 21:26 Juha Manninen Relationship added related to 0023850
2014-01-04 21:28 Juha Manninen Relationship added related to 0023823
2014-01-04 21:32 Juha Manninen Relationship added related to 0023007
2014-01-04 21:37 Juha Manninen Relationship added related to 0022901
2014-01-04 21:40 Juha Manninen Relationship added related to 0022787
2014-01-04 21:51 Juha Manninen Relationship added related to 0020911
2014-01-04 21:54 Juha Manninen Relationship added related to 0017891
2014-01-04 21:56 Juha Manninen Relationship added related to 0018668
2014-01-04 22:00 Juha Manninen Relationship added related to 0016554
2014-01-04 22:06 Juha Manninen Fixed in Revision => r43644
2014-01-04 22:06 Juha Manninen LazTarget => -
2014-01-04 22:06 Juha Manninen Note Added: 0072206
2014-01-04 22:06 Juha Manninen Status assigned => resolved
2014-01-04 22:06 Juha Manninen Resolution open => fixed
2014-01-04 22:39 Juha Manninen Relationship added related to 0010589
2014-01-04 22:40 Juha Manninen Relationship added related to 0009581
2014-01-05 00:00 wp Note Added: 0072207
2014-01-05 09:44 Zeljan Rikalo Relationship replaced has duplicate 0016554