View Issue Details

IDProjectCategoryView StatusLast Update
0036565PatchesWidgetsetpublic2020-01-13 11:19
ReporterYuriy Sydorov Assigned ToDmitry Boyarintsev  
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Product Version2.1 (SVN) 
Summary0036565: Cocoa: Fixes for pattern brush to be Windows widgetset and Delphi compatible.
DescriptionAttached patch resolves the following:
- Properly draw custom monochrome brush patterns. The current brush color is used to paint the pattern's background, the current text color is used to paint the pattern's foreground.
- Fixed pattern alignment.
Steps To ReproduceAttached the patch, a demo project, screenshots of the demo project to demonstrate the same brush drawing after the patch on Mac and Windows.
TagsNo tags attached.
Fixed in Revision62539
LazTarget-
WidgetsetCocoa
Attached Files

Activities

Yuriy Sydorov

2020-01-12 18:39

developer  

pattern_brush.diff (6,059 bytes)   
Index: lcl/interfaces/cocoa/cocoagdiobjects.pas
===================================================================
--- lcl/interfaces/cocoa/cocoagdiobjects.pas	(revision 62502)
+++ lcl/interfaces/cocoa/cocoagdiobjects.pas	(working copy)
@@ -119,16 +119,20 @@
     property ColorRef: TColorRef read GetColorRef;
   end;
 
+  TCocoaPatternColorMode = (cpmBitmap, cpmBrushColor, cpmContextColor);
+
   { TCocoaBrush }
 
   TCocoaBrush = class(TCocoaColorObject)
   strict private
     FCGPattern: CGPatternRef;
-    FColored: Boolean;
+    FPatternColorMode: TCocoaPatternColorMode;
     FBitmap: TCocoaBitmap;
     FColor: NSColor;
+    FFgColor: TColorRef;
   private
     FImage: CGImageRef;
+    procedure DrawPattern(c: CGContextRef);
   strict protected
     procedure Clear;
 
@@ -3090,11 +3094,8 @@
 procedure DrawBitmapPattern(info: UnivPtr; c: CGContextRef); MWPascal;
 var
   ABrush: TCocoaBrush absolute info;
-  AImage: CGImageRef;
 begin
-  AImage := ABrush.FImage;
-  CGContextDrawImage(c, GetCGRect(0, 0, CGImageGetWidth(AImage), CGImageGetHeight(AImage)),
-    AImage);
+  ABrush.DrawPattern(c);
 end;
 
 procedure TCocoaBrush.SetHatchStyle(AHatch: PtrInt);
@@ -3122,11 +3123,11 @@
     CGDataProvider := CGDataProviderCreateWithData(nil, @HATCH_DATA[AHatch], 8, nil);
     FImage := CGImageMaskCreate(8, 8, 1, 1, 1, CGDataProvider, nil, 0);
     CGDataProviderRelease(CGDataProvider);
-    FColored := False;
+    FPatternColorMode := cpmBrushColor;
     if FCGPattern <> nil then CGPatternRelease(FCGPattern);
     FCGPattern := CGPatternCreate(Self, GetCGRect(0, 0, 8, 8),
       CGAffineTransformIdentity, 8.0, 8.0, kCGPatternTilingConstantSpacing,
-      Ord(FColored), ACallBacks);
+      0, ACallBacks);
   end;
 end;
 
@@ -3134,6 +3135,7 @@
 var
   AWidth, AHeight: Integer;
   ACallBacks: CGPatternCallbacks;
+  CGDataProvider: CGDataProviderRef;
 begin
   AWidth := ABitmap.Width;
   AHeight := ABitmap.Height;
@@ -3142,12 +3144,25 @@
   if (FBitmap <> nil) then FBitmap.Release;
   FBitmap := TCocoaBitmap.Create(ABitmap);
   if FImage <> nil then CGImageRelease(FImage);
-  FImage := CGImageCreateCopy(MacOSAll.CGImageRef( FBitmap.imageRep.CGImageForProposedRect_context_hints(nil, nil, nil)));
-  FColored := True;
+  if FBitmap.BitmapType = cbtMono then
+  begin
+    with FBitmap do
+    begin
+      CGDataProvider := CGDataProviderCreateWithData(nil, Data, DataSize, nil);
+      FImage := CGImageMaskCreate(Width, Height, BitsPerSample, BitsPerPixel, BytesPerRow, CGDataProvider, nil, 0);
+      CGDataProviderRelease(CGDataProvider);
+    end;
+    FPatternColorMode := cpmContextColor;
+  end
+  else
+  begin
+    FImage := CGImageCreateCopy(MacOSAll.CGImageRef( FBitmap.imageRep.CGImageForProposedRect_context_hints(nil, nil, nil)));
+    FPatternColorMode := cpmBitmap;
+  end;
   if FCGPattern <> nil then CGPatternRelease(FCGPattern);
   FCGPattern := CGPatternCreate(Self, GetCGRect(0, 0, AWidth, AHeight),
     CGAffineTransformIdentity, CGFloat(AWidth), CGFloat(AHeight), kCGPatternTilingConstantSpacing,
-    Ord(FColored), ACallBacks);
+    Ord(FPatternColorMode = cpmBitmap), ACallBacks);
 end;
 
 procedure TCocoaBrush.SetImage(AImage: NSImage);
@@ -3159,7 +3174,7 @@
   ACallBacks.drawPattern := @DrawBitmapPattern;
   if FImage <> nil then CGImageRelease(FImage);
   FImage := CGImageCreateCopy(MacOSAll.CGImageRef( AImage.CGImageForProposedRect_context_hints(nil, nil, nil)));
-  FColored := True;
+  FPatternColorMode := cpmBitmap;
   Rect.origin.x := 0;
   Rect.origin.y := 0;
   Rect.size := CGSize(AImage.size);
@@ -3166,7 +3181,7 @@
   if FCGPattern <> nil then CGPatternRelease(FCGPattern);
   FCGPattern := CGPatternCreate(Self, Rect,
     CGAffineTransformIdentity, Rect.size.width, Rect.size.height, kCGPatternTilingConstantSpacing,
-    Ord(FColored), ACallBacks);
+    1, ACallBacks);
 end;
 
 procedure TCocoaBrush.SetColor(AColor: NSColor);
@@ -3274,6 +3289,22 @@
   end;
 end;
 
+procedure TCocoaBrush.DrawPattern(c: CGContextRef);
+var
+  R: CGRect;
+  sR, sG, sB: single;
+begin
+  R:=CGRectMake(0, 0, CGImageGetWidth(FImage), CGImageGetHeight(FImage));
+  if FPatternColorMode = cpmContextColor then
+  begin
+    CGContextSetRGBFillColor(c, Red/255, Green/255, Blue/255, 1);
+    CGContextFillRect(c, R);
+    ColorToRGBFloat(FFgColor, sR, sG, sB);
+    CGContextSetRGBFillColor(c, sR, sG, sB, 1);
+  end;
+  CGContextDrawImage(c, R, FImage);
+end;
+
 procedure TCocoaBrush.Clear;
 begin
   if FColor <> nil then
@@ -3313,6 +3344,9 @@
   AROP2: Integer;
   APatternSpace: CGColorSpaceRef;
   BaseSpace: CGColorSpaceRef;
+  sR, sG, sB: single;
+  sz: CGSize;
+  offset: TPoint;
 begin
   if ADC = nil then Exit;
 
@@ -3333,13 +3367,39 @@
 
   if Assigned(FCGPattern) then
   begin
-    if not FColored then
-      BaseSpace := CGColorSpaceCreateDeviceRGB
-    else
+    // Set proper pattern alignment
+    offset:=ADC.GetLogicalOffset;
+    with CGPointApplyAffineTransform(CGPointMake(0,0), CGContextGetCTM(ADC.CGContext)) do
     begin
-      BaseSpace := nil;
-      RGBA[0] := 1.0;
+      sz.width:=x - offset.X;
+      sz.height:=y + offset.Y;
+      sz.width:=Round(sz.width) mod CGImageGetWidth(FImage);
+      sz.height:=Round(sz.height) mod CGImageGetHeight(FImage);
     end;
+    CGContextSetPatternPhase(ADC.CGContext, sz);
+
+    case FPatternColorMode of
+      cpmBitmap:
+        begin
+          BaseSpace := nil;
+          RGBA[0] := 1.0;
+        end;
+      cpmBrushColor:
+        begin
+          BaseSpace := CGColorSpaceCreateDeviceRGB;
+        end;
+      cpmContextColor:
+        begin
+          BaseSpace := CGColorSpaceCreateDeviceRGB;
+          SetColor(ADC.BkColor, True);
+          FFgColor:=ColorToRGB(ADC.TextColor);
+          ColorToRGBFloat(FFgColor, sR, sG, sB);
+          RGBA[0]:=sR;
+          RGBA[1]:=sG;
+          RGBA[2]:=sB;
+          RGBA[3]:=1.0;
+        end;
+    end;
     APatternSpace := CGColorSpaceCreatePattern(BaseSpace);
     CGContextSetFillColorSpace(ADC.CGContext, APatternSpace);
     CGColorSpaceRelease(APatternSpace);
pattern_brush.diff (6,059 bytes)   
screen-mac.png (7,906 bytes)   
screen-mac.png (7,906 bytes)   
screen-win.png (17,108 bytes)   
screen-win.png (17,108 bytes)   
demo.zip (101,147 bytes)

Dmitry Boyarintsev

2020-01-12 21:21

developer   ~0120375

thanks for the patch, applied.
please test and close if ok

jamie philbrook

2020-01-13 02:58

reporter   ~0120390

As a side note, I don't do Cocoa Widget but Windows has a function "SetBrushOrgEx" which offsets the Brush pattern so if you want to keep with that note in mind a little research if the widget set supports this feature and if it does then maybe the code should also test the Offset ?

 Its possible all of this al already taken care of in the under layer but I don't see that here.

Dmitry Boyarintsev

2020-01-13 04:30

developer   ~0120392

please update the sample to test the offset.
I don't recall any code that would support it.

Yuriy Sydorov

2020-01-13 11:18

developer   ~0120396

Thanks for applying the patch.

There is no cross-platform support for SetBrushOrgEx in LCL. Also there is no TCanvas API to change brush alignment.
By default brush patterns must be aligned to the top-left corner of the device context. Now the Cocoa widgetset handles brush patterns properly.

Issue History

Date Modified Username Field Change
2020-01-12 18:39 Yuriy Sydorov New Issue
2020-01-12 18:39 Yuriy Sydorov File Added: pattern_brush.diff
2020-01-12 18:39 Yuriy Sydorov File Added: screen-mac.png
2020-01-12 18:39 Yuriy Sydorov File Added: screen-win.png
2020-01-12 18:39 Yuriy Sydorov File Added: demo.zip
2020-01-12 21:21 Dmitry Boyarintsev Assigned To => Dmitry Boyarintsev
2020-01-12 21:21 Dmitry Boyarintsev Status new => resolved
2020-01-12 21:21 Dmitry Boyarintsev Resolution open => fixed
2020-01-12 21:21 Dmitry Boyarintsev Fixed in Revision => 62539
2020-01-12 21:21 Dmitry Boyarintsev LazTarget => -
2020-01-12 21:21 Dmitry Boyarintsev Widgetset Cocoa => Cocoa
2020-01-12 21:21 Dmitry Boyarintsev Note Added: 0120375
2020-01-13 02:58 jamie philbrook Note Added: 0120390
2020-01-13 04:30 Dmitry Boyarintsev Note Added: 0120392
2020-01-13 11:18 Yuriy Sydorov Note Added: 0120396
2020-01-13 11:19 Yuriy Sydorov Status resolved => closed