Difference between revisions of "User:Ismael"

From Apache OpenOffice Wiki
Jump to: navigation, search
m (updated intro)
m (updated links: added CGContext and CGImage links)
Line 19: Line 19:
 
== Apple Documentation and References ==
 
== Apple Documentation and References ==
  
Apple carbon reference about menus:  [http://developer.apple.com/documentation/Carbon/Reference/Menu_Manager/index.html]
+
Apple Carbon/Quartz reference  
 +
*about menus:  [http://developer.apple.com/documentation/Carbon/Reference/Menu_Manager/index.html]
 +
*about images (CGImage): [http://developer.apple.com/documentation/GraphicsImaging/Reference/CGImage/index.html]
 +
*about graphic contexts (CGContexts): [http://developer.apple.com/documentation/GraphicsImaging/Reference/CGContext/index.html]
  
 
== Image masks ==
 
== Image masks ==

Revision as of 21:02, 4 January 2007

Developper infos

Name: Ismael MERZAQ

IRC Nickname: Ismael_

blog: http://lebasket.free.fr/blog

Current work

I have implemented key accelerators in native menus (like Cmd+N for new document instead of ctrl+N in the X11 version). This was my first contribution to native port and also to OOo. I'm now working on graphics for the native port: i have corrected a bug on image mask to have beautiful and correct images in the menu, and i will implement functions in AquaSalGraphic.

Introduction

The purpose of this page is to be a place for documentations on my work to help people who would continue my work or use it, or simply want to understand what i do. And, moreover, writing documentation is an important part of coding.

Apple Documentation and References

Apple Carbon/Quartz reference

  • about menus: [1]
  • about images (CGImage): [2]
  • about graphic contexts (CGContexts): [3]

Image masks

Short intro

As i explained it on my blog, i have continued the work Pavel Janick did on image masks. At the beginning, my work was to correct a bug concerning images in menus. I firstly thought i would have to concentrate on AquaSalMenu::SetItemImage but the problem wasn't on this function. So my work mainly concerned AquaSalBitmap::CreateMask and AquaSalBitmap::CreateWithMask functions (both located in vcl/aqua/source/gdi/salbmp.cxx). The problem came from the CreateMask function which didn't work as expected. Indeed, it created a mask with random values and so images in the menus had some pixels transparent and some others not, making the image not looking as it should. AquaImageMenuBug.png


In the CreateMask function, the image mask is created from a RawMemorySharedArray (defined in the basebmp namespace, see in bitmapdevice.hxx. The RawMemorySharedArray uses the boost library (typedef boost::shared_array< sal_uInt8 > RawMemorySharedArray;)). Depending on the number of bits per component (colors+alpha), pixels have different values which are added to a RawMemorySharedArray. Then a CGDataprovider is created with the data of the RawMemorySharedArray (CGDataProviderRef xDataProvider( CGDataProviderCreateWithData(NULL, aMaskBuffer.get(), nHeight * nWidth, NULL) ); where aMaskBuffer is defined as basebmp::RawMemorySharedArray aMaskBuffer( new sal_uInt8[ nWidth * nHeight ] );). And finally, the image mask is created from this CGDataProvider (xMask = CGImageMaskCreate(nWidth, nHeight, 8, 8, nWidth, xDataProvider, NULL, true );).

ImageAndMasks.png

Image and its mask (seen in Icon Composer)

The possible causes of the bug

So the problem may come from the RawMemorySharedArray, it may also come from the creation of the CGDataProvider from it, or from the creation of the imagemask from the cgdataprovider. What is sure is that the problem comes from the CreateMask function in salbmp.cxx, but i haven't been able to fix it. So i decided to do it by another way.

So, how did i do?

Instead of creating an image mask from the alpha of the original image, i created a CGImage from the alpha mask (passed as argument of the function as an AquaSalBitmap&), and i masked the original image with it, using context.

Here's the part of code which masks the image:

CGContextSaveGState(mxGraphicContext);
            
CGContextBeginTransparencyLayer(mxGraphicContext, NULL);
CGContextClipToMask(mxGraphicContext, CGRectMake( nX, nY, nDX, nDY), xMask);
CGContextDrawImage(mxGraphicContext, CGRectMake( nX, nY, nDX, nDY), xImage );
xMaskedImage=CGBitmapContextCreateImage(mxGraphicContext);
CGContextEndTransparencyLayer(mxGraphicContext);
            
CGContextRestoreGState(mxGraphicContext);

We firstly save the context. Then we call CGContextBeginTransparencyLayer(mxGraphicContext, NULL) to have transparency else the transparent regions are black. Then we use the function CGContextClipToMask(mxGraphicContext, CGRectMake( nX, nY, nDX, nDY), xMask). Here's what the AppleDoc says about it:

CGContextClipToMask
Maps a mask into the specified rectangle and intersects it with the current clipping
area of the graphics context.

void CGContextClipToMask(
CGContextRef c, 
CGRect rect, 
CGImageRef mask
);
[...]
mask
An image or an image mask.
[...]
Discussion
If the mask parameter is an image mask, then Quartz clips in a manner identical to the 
behavior seen with the function CGContextDrawImage—the mask indicates an area to be left
unchanged when drawing. The source samples of the image mask determine which points of 
the clipping area are changed, acting as an "inverse alpha" value. If the value of a source
sample in the image mask is S, then the corresponding point in the current clipping area is
multiplied by an alpha value of (1–S). For example, if S is 1 then the point in the clipping
area becomes transparent. If S is 0, the point in the clipping area is unchanged.

Then we draw the image on the context (which has been masked) with CGContextDrawImage(mxGraphicContext, CGRectMake( nX, nY, nDX, nDY), xImage ). And we create the CGImage from this context with xMaskedImage=CGBitmapContextCreateImage(mxGraphicContext). Finally we restore the context (CGContextRestoreGState(mxGraphicContext);)

But this is not enough. Indeed, in the function CGContextClipToMask, the mask is a CGImage not the AquaSalBitmap of the arguments (CGImageRef AquaSalBitmap::CreateWithMask( const AquaSalBitmap& rMask, sal_uInt32 nX, sal_uInt32 nY, sal_uInt32 nDX, sal_uInt32 nDY )). So we have to transform the AquaSalBitmap rMask in CGImage. This is done thanks to the CreateCroppedImage function (CGImageRef AquaSalBitmap::CreateCroppedImage( int nX, int nY, int nWidth, int nHeight);) But the AquaSalBitmap rMask can't be a const, so we have 2 possibilities: remove the const in the declaration of the function, or copy the aquasalbitmap. The first solution can't be used because salgdi.cxx and other files need to have it defined with the const mask. So i used the second solution: copying the AquaSalBitmap (rMaskNew.Create( rMask ) creates a copy of rMask).

But there was still a problem. The image mask should be inverted, i.e. the black pixels should become white and the white pixels should become black.

AquaMenuImagesWithNotInvertedMask.jpg

Without inverting the mask, the parts which should be painted are transparent and those which should be transparent are painted.

So i looked for a function to do that in the OOo API. The AquaSalGraphics class provide one but it is not yet implemented. And as its implementation is, in my opinion, difficult and need some time, i didn't implement it yet and so I inverted the image mask using contexts in the function which need it. Instead of inverting the AquaSalBitmap with the OOo API, i inverted the CGImage with the Quartz API. But this solution should stay a temporary solution and, in my opinion, should be replaced as soon as possible by the one using OOo API, mainly for the clarity of code. Here's the code which invert the image thanks to context:

rMaskNew.CreateContext();
CGContextTranslateCTM (rMaskNew.mxGraphicContext, 0, mnHeight);
CGContextScaleCTM (rMaskNew.mxGraphicContext, 1.0, -1.0);
CGContextSetGrayFillColor(rMaskNew.mxGraphicContext, 1.0, 1.0);
CGContextFillRect(rMaskNew.mxGraphicContext, CGRectMake( nX, nY, nDX, nDY));
CGContextSetBlendMode(rMaskNew.mxGraphicContext,  kCGBlendModeDifference);
CGContextDrawImage(rMaskNew.mxGraphicContext, CGRectMake( nX, nY, nDX, nDY), xMask);
xMask=CGBitmapContextCreateImage(rMaskNew.mxGraphicContext);

Key accelerators

Short introduction: presenting the basis

The function which interests us is
void AquaSalMenu::SetAccelerator( unsigned nPos, SalMenuItem* pSalMenuItem, const KeyCode& rKeyCode, const XubString& rKeyName ) 
  • nPos is the menu item index, but nPos is 0 to n based whereas the carbon API we use for ooo macport is from 1 to n+1 e.g. for the first element nPos=0, and his carbon index will be 1
  • SalMenuItem* pSalMenuItem is a pointer to the menu item we want to add key accelerator
  • const KeyCode& rKeyCode is the keycode of the accelerator. We can get the keycode with rKeyCode.GetCode() and the key modifier(s) (like cmd, shift, ... for Mac OS) with rKeyCode.GetAllModifier()
  • const XubString& rKeyName is not used for the MacOs port

Implementation

We will use the SetMenuItemModifiers function to chose the modifier(s) of the key accelerator like cmd, shift, alt... and we'll set the key accelerator with two different function depending of the situation: the SetMenuItemCommandKey function for visible key accelerator (like for the N of Cmd+N) and the SetMenuItemKeyGlyph function for non visible characters (like for F1, F2,... or space).

Setting the key accelerator

The difficulty is that the OOo keycodes are different of the ASCII keycodes used in Carbon. For example: A is 65 in ASCII but in OOo it's 512 (defined in Key.hdl) So we have to translate from ooo codes to ascii, but the order of the characters are not exactly the same as ASCII so we translate only by intervals. We obtain this code:

if ((ncode>=KEY_A) && (ncode<=KEY_Z)) {
    nresultcode=ncode-KEY_A+'A';
}
else if ((ncode>=KEY_0) && (ncode<=KEY_9)) {
    nresultcode=ncode-KEY_0+'0';
}

We do the same thing for non visible characters but instead of using the ascii code of the given character we use its carbon value.

else if((ncode>=KEY_F1) && (ncode<=KEY_F12)) {
    nresultcode=kMenuF1Glyph+ncode-KEY_F1; // kMenuFnGlyph=kMenuF1Glyph+n only for n<13 then it should be processed for each case
}
else if((ncode>=KEY_F13) && (ncode<=KEY_F15)) {
    nresultcode=kMenuF13Glyph+ncode-KEY_F13; //kMenuF13Glyph=0x87 and kMenuF12Glyph=0x7A
}
else if(ncode==KEY_SPACE) {
    nresultcode=kMenuSpaceGlyph; 
}

Then we only have to add the accelator to the menu:

  • For the non visible characters: The syntax of SetMenuItemKeyGlyph is:
OSErr SetMenuItemKeyGlyph (
   MenuRef inMenu,
   SInt16 inItem,
   SInt16 inGlyph
);

so we use it like this:

SetMenuItemKeyGlyph (
    mrMenuRef, //is the reference to the menu
    nPos+1, // for the reason of using nPos+1 instead of nPos see the short intro
    nresultcode
    );
  • For visible characters: The syntax of SetMenuItemCommandKey is:
OSStatus SetMenuItemCommandKey (
   MenuRef inMenu,
   MenuItemIndex inItem,
   Boolean inSetVirtualKey,
   UInt16 inKey
);

so we use it like this:

SetMenuItemCommandKey (
    mrMenuRef,
    nPos+1,
    0,
    nresultcode
    );

Problems and known bugs

Key accelerators used both by OOo and MacOS X

The first problem is that some key accelerators are already used by mac os, and so macos catch them and do the associated action, and ooo don't see them. For example, when we press F9 in openoffice (native version or X11 version), the fields aren't refreshed because the macos function exposé is launched and so catch the key accelerator (to refresh the fields go to tools>refresh>fields in the menu (or something like that, as I have a french version of ooo, i translate the menu texts in english and so it's not always the good translation)). What we have to do is to change the conflictual key accelerators. Here are these conflictual key accelerators:

  • OOCalc:
    • F9 ==> shows exposé instead of recalculing the cell content (tools>cell content>recalc)
    • F12 ==> shows dashboard instead of doing Data>Plan>Group

Following Apple Human Interface Guidelines in menus

The second problem which is not really a problem is that we have to change some key accelerators if we want to respect the Apple Human Interface Guidelines (AHIG) (Apple Human Interface Guidelines and mostly AHIG for key accelerators and menus and for menu exemples ) Here's a list of key accelerators which doesn't correspond to Apple Human Interface Guidelines:


  • The key accelerator used in OOo refers to another function in AHIG:
    • cmd '-' (hyphen):
      • in OOo: adds a "conditional hyphen" ("tiret conditionnel" in french)
      • AHIG: Decrease the size of the selected item (equivalent to the Smaller command).
  • The function uses in OOo a key accelerator which is different than the one specified in AHIG
    • SpellCheck:
      • in OOo: F7
      • AHIG: cmd+';' (semicolon) and should be in the edit menu
    • Preference window:
      • in OOo: in the menu Tools>options without key accelerator
      • AHIG: key accelerator cmd+',' (comma), and located in “The Application Menu”
    • Opening Help:
      • in OOo: F1
      • AHIG: cmd+? (question mark)
    • Redo command:
      • in OOo: cmd+Y
      • AHIG: shift+cmd+Z
    • non breaking space
    • Close window
      • in OOo: in the window menu with the key accelerator and the text "close the window" and in the file menu without key accelerator and with the text "close"
      • AHIG: in the file menu with the text "close" and with the key accelerator cmd+W
  • The function hasn't a key accelerator in OOo but should have one to follow AHIG
    • Save as
      • shift+cmd+S
    • Left-align a selection
      • cmd+{ (left bracket)
    • Right-align a selection
      • cmd+} (right bracket)
    • Center-align a selection
      • cmd+| (pipe)


Last change --Ismael 17:36, 11 November 2006 (CET)

Personal tools