Picture.ApplyMask results in the picture being a black rectangle

Hi,

I’m drawing some small text into two pictures and applying one as the other’s mask, like this:

MainPict=DrawMyText
Mask=DrawMyText

MainPict.ApplyMask Mask

DrawMyText just draws a line of text in the picture, in greyscale (black font).

Putting a breakpoint at “MainPict.ApplyMask Mask”, I can see MainPict is as expected before stepping thru, then becomes solid black after ApplyMask is executed. I’m not sure what it means.

I don’t know what changed, but the same piece of code used to work. Perhaps it’s Sequoia that broke something :man_shrugging:
But even if I found the reason for this change, I don’t have any better idea to draw a text with transparency.

I also checked whether both pictures are of the same depth etc., and they are similar:
Properties of MainPict:
image
Properties of Mask:
image

Both pictures are as expected before ApplyMask; MainPict is this:
image

And Mask is:
image

Anyone noticed similar issues or can point a bug in my code, please?

I believe masks need to be 8 bits in depth, not 32. Try:

Mask=New Picture(width, height, 8)

Thanks for your suggestion.
Actually, I found the problem and the cause (I’m involved in the change…). In fact, I’m in the process of porting my app to iOS, using external items to keep both desktop and iOS, and that’s a rather huge task for an app such as this one.

Originally, in the desktop version, I declared all pictures with 3 parameters (width, height and depth), where ApplyMask worked. But iOS doesn’t support creating pictures with the depth parameter (and the documentation no longer mentions this constructor, so I believe it has been deprecated), so I converted all pictures creation to use the 2 parameters constructor (I was forced to in iOS). And it doesn’t work with ApplyMask, either in iOS or in desktop (Mac).

It looks like I can’t use ApplyMask on iOS at all, then, if it requires a picture built with an explicit depth :thinking:.

Edit: steps to reproduce:

Var p1 As new Picture(100,16) 'Add a third parameter (32) to make it working.
DrawTextPicture p1.Graphics

Var p2 As new Picture(100,16) 'Add a third parameter (32) to make it working.
DrawTextPicture p2.Graphics

p1.ApplyMask p2
Break
Public Sub DrawTextPicture(ToGraphics As Graphics)
  if ToGraphics<>nil then
    ToGraphics.DrawingColor=Color.Black
    ToGraphics.FontName="System"
    ToGraphics.FontSize=12
    
    ToGraphics.DrawText "Bonjour!",2,15
  end if
End Sub

(BTW: I fairly recognise the difference between constructing a picture with 2 or 3 parameters, about the alpha channel vs mask, but my point is the version with 2 parameters (alpha channel) doesn’t work with ApplyMask and it’s exactly the situation where one would need it (since with the 3 parameters version, it’s possible to directly draw into the mask property)).

But without the third parameter, you no longer need a mask. That was the whole point.

1 Like

I’ve always used masks and I’m way more comfortable with them than with the alpha version. If there’s an “alpha” way, I’d be interested in learning it.

Based on my post above yours, I have a class holding properties (text, underline, bold, font, size, colour) and I need to get a picture where the text is rendered transparently (so drawing the picture further doesn’t render a white rectangle).
Using masks, I can start with a white picture and render the pixels where the text is drawn to be non-transparent. Using alpha… I don’t know enough, I guess…

Thanks for your answer.

rem mask API2

var DrawMyText as string
var MainPict as new picture(100,30) // alphakanal
var maske as picture
MainPict.Graphics.DrawingColor=Color.Black
MainPict.Graphics.FontName="System"
MainPict.Graphics.FontSize=22
MainPict.Graphics.DrawText("Bonjour!",2,25)
rem Mask
maske = MainPict.CopyMask
maske.Graphics.DrawingColor = color.rgb(200,200,200) // color.white = clear / color.black = view
maske.Graphics.FontName="System"
maske.Graphics.FontSize=22
maske.Graphics.DrawText("Bonjour!",1,24) // outline
'maske.Graphics.DrawingColor = color.White
'maske.Graphics.fillrectangle(25,0,30,30)
MainPict.ApplyMask(maske)

g.drawpicture(mainpict,100,100)
1 Like

Thanks! Your example works, but I can’t yet find how it’s different than my non-working example. It proves ApplyMask can work in certain circumstances, but I’ve yet to find why/when.

Actually, I’ve used masks at different places in the app. It would help me a lot if someone could tell me why it doesn’t work with alpha channels the way I used to do it.

Here’s another example which doesn’t work. This function takes an array of points which should be opaque in the picture (a clone is returned); all other points must be transparent:

Public Function MixPictureAndPoints(Image As Picture, Points() As Point) As Picture
  if Image=nil then Return nil
  
  Var Mask As Picture=new Picture(Image.Width,Image.Height)
  Var rs As RGBSurface=Mask.RGBSurface
  
  if rs=nil then Return nil
  
  for each Pnt as Point in Points()
    if Pnt<>nil then rs.Pixel(Pnt.X,Pnt.Y)=Color.Black
  next Pnt
  
  Var ComposedPicture As Picture=new Picture(Image.Width,Image.Height)
  ComposedPicture.Graphics.DrawPicture Image,0,0
  ComposedPicture.ApplyMask Mask
  
  Return ComposedPicture
End Function

With a picture which has a mask, it works as it should. With an alpha channel version, again, this produces a solid black rectangle. I can’t get why.

FWIW this is what I wrote to resize/stretch/centre a Picture, but it can also be used to remove the black or white backgrounds:

Protected Function getResizePicture(myPicture As Picture, areaWidth As Integer, areaHeight As Integer, isProportional As Boolean = True, isCentrePicture As Boolean = True) As Picture
  Var tempPicture As Picture
  Var Ratio As Double
  
  If myPicture = Nil Then
    Return Nil
  End If
  
  If areaWidth < 1 Or areaHeight < 1 Then
    Return Nil
  End If
  
  Ratio = myPicture.width / myPicture.height
  
  If isProportional And Ratio <> 1 Then
    If myPicture.Width > myPicture.Height Then
      tempPicture = New Picture(areaWidth, areaHeight)
      If isCentrePicture Then
        Var NewHeight As Double = areaHeight * (myPicture.height / myPicture.width)
        Var NewTop As Double = (areaHeight - (NewHeight)) / 2
        
        tempPicture.Graphics.DrawPicture(myPicture, 0, NewTop, areaWidth, NewHeight, 0, 0, myPicture.Width, myPicture.Height)
      Else
        tempPicture.Graphics.DrawPicture(myPicture, 0, 0, areaWidth, areaWidth / Ratio, 0, 0, myPicture.Width, myPicture.Height)
      End If
    Else
      tempPicture = New Picture(areaWidth, areaHeight)
      If isCentrePicture Then
        Var NewWidth As Double = areaWidth * (myPicture.Width / myPicture.Height)
        Var NewLeft As Double = (areaWidth - (NewWidth)) / 2
        
        tempPicture.Graphics.DrawPicture(myPicture, NewLeft, 0, NewWidth, areaHeight, 0, 0, myPicture.Width, myPicture.Height)
        
      Else
        tempPicture.Graphics.DrawPicture(myPicture, 0, 0, areaHeight * Ratio, areaHeight, 0, 0, myPicture.Width, myPicture.Height)
      End If
    End If
    
  Else
    tempPicture = New Picture(areaWidth, areaHeight)
    tempPicture.Graphics.DrawPicture(myPicture, 0, 0, areaWidth, areaHeight, 0, 0, myPicture.Width, myPicture.Height)
  End If
  
  Return tempPicture
  
'  Exception err
'    CommonOS.doHandleExceptionWAD(err, CurrentMethodName)
    
End Function

Forum-mask-test.xojo_binary_project.zip (4.3 KB)

Thanks for your replies. They are appreciated.
I just now have a problem to convert from my mask-based code to an alpha channel one, because I see the example you provided are working, but my existing code is more complex than that.

Could you please try the following example on your side and tell me why the result is whole black, please? (so I can understand the move from mask-based to alpha-based)

Var Image As new Picture(500,500)
Image.Graphics.DrawLine 0,0,500,500 //Just draw something to it

Var Mask As Picture=new Picture(Image.Width,Image.Height)
Var rs As RGBSurface=Mask.RGBSurface

for x as Integer=100 to 300
  for y as Integer=100 to 150
    rs.Pixel(x,y)=Color.RGB(0,0,0,0) //Also tried variation such as RGB(0,0,0)
  next
next

Var CompositedPicture As Picture=new Picture(Image.Width,Image.Height)
CompositedPicture.Graphics.DrawPicture Image,0,0
CompositedPicture.ApplyMask Mask
Break

I’d expect the drawn line to be opaque only where the pixels are drawn, instead of a black picture.

Ok, I’ve found the problem. A picture with an alpha channel starts with everything transparent, while the mask-based version starts with solid white.
Adding:

Image.DrawingColor=Color.White
Image.FillRectangle 0,0,Image.Width,Image.Height

just after creating the pictures made the rest working.

Thanks for your help.

an alpha channel picture has no background
by setting color.Black you draw a black background

rem color.black = on / color.white = off
rs.Pixel(x,y)=Color.White