No announcement yet.

Source code for Loading JPEg,GIF,PNG using GDIplus (for DDT and SDK)

  • Filter
  • Time
  • Show
Clear All
new posts

  • Source code for Loading JPEg,GIF,PNG using GDIplus (for DDT and SDK)

    I spent hours working on this code, but I finally have it working somewhat (DDT version).

    The code works perfectly for loading JPeg,GIF and PNG images and returning an API Bitmap handle. SDK'ers get perfect working code.

    I finally got it working where it can return a DDT Bitmap handle (not the same an API bitmap), but the routine has go through hoops to do it. The image has to be loaded into a 24 bit DIB and then run through the image using pointers inverting all the pixel bytes and then swapping the red and blue pixels. I am not sure, but I think DDT stores images in CMYK format rather than RGB.

    The code works, but for some unknown reason when I BitBlt the image to a DDT bitmap, there is a white line at the top.

    Maybe someone can figure it out, so even the DDT version works perfectly. But I did the hard stuff.

    You can see the code and download it here:

    The beauty of this include file is that it does not require any GDI+ API includes to use it. All the GDI+ declarations necessary are in it.

    At least for SDK coders you can now load JPeg,GIF and PNG effortlessly. Just add the include to your app and then a simple call loads the image, like this:


    hBmp = GDIP_LoadImage(0, F$, -1,-1,1)

    Loads image and returns an API Bitmap.

    There are two functions in the include, which are:

    GDIP_GetImageSize F$, W&, H&

    Reads an image file and returns the size (width and height) in pixels, without loading.

    F$ is the filename (full path) of any Image file windows supports (ie. JPeg, PNG, GIF)
    W& is a long variable to return the width in pixels
    H& is a long variable to return the height in pixels

    hBmp& = GDIP_LoadImage(DMode&, F$, W&, H&, QFlag&)

    Loads an image file (JPeg,GIF or PNG)

    DMode& if non-zero (1) makes function return a DDT Bitmap, rather than an API Bitmap
    F$ is the filename (full path) of any Image file windows supports (ie. JPeg, PNG, GIF)
    W& is the requested width of the image (in pixels)
    H& is the requested height of the image (in pixels)
    QFlag& if set to non-zero (1) requests best quality possible image

    If W& and H& are set to -1 (or even zero) then the routine will use the actual size of the image on file.

    The return value is a Bitmap handle.
    If DMode& is non-zero (1) then the handle is a DDT Bitmap handle which can only be used with DDT Graphic commands.
    If DMode& is zero, then the handle is an API Bitmap handle, which can only be used with the API and not DDT Graphic commands.

    The code is based on code by Dan Ginzel, but totally reworked to make it simple and easy to use.

    Chris Boss
    Computer Workshop
    Developer of "EZGUI"

  • #2
    As a DDT user, thank you for your hard work on this, Chris!


    • #3
      Forgive my ignorance about bitmaps.
      The code compiles ok.
      But how can I use the handle hBmp to display the image in a DDT window?
      /Fim W.
      Fim Wästberg


      • #4
        Don't know about Chris' code, but here's a demo of gettiing an image file and displaying it in DDT.

        It uses a cut down GDI+ include file

        #COMPILE EXE
        #DIM ALL
        %UNICODE = 1
        #INCLUDE ""
        #INCLUDE ONCE ""
        ENUM Controls SINGULAR
         IDC_btnGetImage = 1001
        END ENUM
            LOCAL GdipToken, hDlg AS DWORD
            LOCAL lResult AS LONG
            LOCAL StartupInput AS GdiplusStartupInput
            StartupInput.GdiplusVersion = 1
            IF GdiplusStartup(GdipToken,StartupInput, BYVAL %NULL) THEN
                MSGBOX "Error initialising GDI+ ... (Also, program needs XP or later)"
                EXIT FUNCTION
            END IF
            DIALOG NEW 0, "Image Demo", 203, 90, 368, 217, %WS_OVERLAPPED OR _
            CONTROL ADD GRAPHIC, hDlg, %IDC_Graphic, "", 35, 15, 180, 140, %SS_SUNKEN _
                OR %WS_CHILD OR %WS_VISIBLE
            CONTROL ADD TEXTBOX, hDlg, %IDC_txtPhotoName, "", 40, 160, 300, 10
            CONTROL ADD BUTTON,  hDlg, %IDC_btnGetImage, "Get Image", 260, 125, 70, _
            CONTROL ADD BUTTON,  hDlg, %IDC_btnClose, "Close", 260, 175, 70, 25
            DIALOG SHOW MODAL hDlg, CALL DlgProc TO lResult
         GdiplusShutdown GdipToken       'Shutdown GDI plus
        LOCAL lResult AS LONG
        LOCAL wstrImg AS WSTRING
                CASE %WM_COMMAND
                    SELECT CASE AS LONG CB.CTL
                        CASE %IDC_txtPhotoName
                        CASE %IDC_btnGetImage
                            IF CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1 THEN
                               lResult =  BrowseForImage(CB.HNDL,wstrImg )
                               lResult = ShowImage( CB.HNDL,wstrImg)
                            END IF
                        CASE %IDC_btnClose
                            IF CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1 THEN
                                DIALOG END CB.HNDL
                            END IF
                    END SELECT
            END SELECT
        '   ** Image  Functions**
        FUNCTION BrowseForImage(hDlg AS DWORD, _            'hDlg is handle of Parent
                    BYREF InputImage AS WSTRING) AS LONG     'Browse for file InputImage
            DISPLAY OPENFILE hDlg, , , _                    'Browse to file
                "Browse for Main photo / image", _
                PATHNAME$(PATH,  InputImage ), _
                CHR$("Images",0,"*.jpg;*.png;*.gif;*.bmp",0, "All Files", 0, "*.*"), _
                PATHNAME$(NAMEX, InputImage ), "jpg", _
           LOCAL hGraphic AS DWORD
           LOCAL wsTemp AS STRING
           LOCAL x,y AS LONG
           DIALOG UNITS hDlg, 180, 140 TO PIXELS y,x  'PB 10.04 reversed! 10.03  use x,y
           wsTemp = Photo
           IF DIR$(wsTemp) = "" THEN
               MSGBOX  wsTemp & " not found!"
               EXIT FUNCTION
           END IF
             hGraphic = GraphicLoadImageFileToBitmap(wsTemp,x,y,0)
             GRAPHIC ATTACH hGraphic,0
             GRAPHIC ATTACH hDlg,  %IDC_Graphic
             GRAPHIC CLEAR
             GRAPHIC COPY hGraphic,%IDC_Graphic
             GRAPHIC ATTACH hGraphic,0
             CONTROL SET TEXT hdlg,%IDC_txtPhotoName, Photo
        Include File:

        ' GDI Plus includes for Loading images from file (eg .jpg) to/from PB Bitmaps
        ' Code below based on "" by Chris Holbrook PB Forums 3 Jan 2010, based on Jose Roca's work
        ' When dealing with API's such as GDI plus the proper course of action is to grab the type library
        ' from Jose Roca's site (or others...) as an include, and all the functions will be available.
        ' The downside is that your compile time may well go through the roof, and you have no chance of
        ' browsing or understanding just HOW MUCH of that library is being used. And because it basically
        ' supersedes the powerbasic libraries, things may get complicated when you upgrade. Following the
        ' approach used by Chris Holbrook (and others...) a second method is to include this file, with
        ' just the stripped down declarations necessary to get the job done - about 2% of the original.
        ' You need need to be able to find out about GDI plus, reference Jose Rocas site (link @ 2/2/2010)
        ' You also should have on hand a link to full type library from Jose Roca (link valid @ 2/2/2010)
        %UnitPixel = 2 ' Sets each unit to be one device pixel
        TYPE GdiplusStartupInput
        GdiplusVersion AS DWORD ' Must be 1
        DebugEventCallback AS DWORD ' Ignored on free builds
        SuppressBackgroundThread AS LONG ' FALSE unless you're prepared to call
        ' the hook/unhook functions properly
        SuppressExternalCodecs AS LONG ' FALSE unless you want GDI+ only to use
        ' its internal image codecs.
        END TYPE
        TYPE GdiplusStartupOutput
        NotificationHook AS DWORD
        NotificationUnhook AS DWORD
        END TYPE
        TYPE ImageCodecInfo
        ClassID AS GUID ' CLSID. Codec identifier
        FormatID AS GUID ' GUID. File format identifier
        CodecName AS DWORD ' WCHAR*. Pointer to a null-terminated string
        ' that contains the codec name
        DllName AS DWORD ' WCHAR*. Pointer to a null-terminated string
        ' that contains the path name of the DLL in
        ' which the codec resides. If the codec is not
        ' a DLL, this pointer is NULL
        FormatDescription AS DWORD ' WCHAR*. Pointer to a null-terminated string
        ' that contains the name of the format used by the codec
        FilenameExtension AS DWORD ' WCHAR*. Pointer to a null-terminated string
        ' that contains all file-name extensions associated
        ' with the codec. The extensions are separated with semicolons.
        MimeType AS DWORD ' WCHAR*. Pointer to a null-terminated string
        ' that contains the mime type of the codec
        Flags AS DWORD ' Combination of flags from the ImageCodecFlags enumeration
        Version AS DWORD ' Integer that indicates the version of the codec
        SigCount AS DWORD ' Integer that indicates the number of signatures
        ' used by the file format associated with the codec
        SigSize AS DWORD ' Integer that indicates the number of bytes of each signature
        SigPattern AS DWORD ' BYTE*. Pointer to an array of bytes that contains
        ' the pattern for each signature
        SigMask AS DWORD ' BYTE*. Pointer to an array of bytes that contains
        ' the mask for each signature
        END TYPE
        DECLARE FUNCTION GdiplusStartup LIB "GDIPLUS.DLL" ALIAS "GdiplusStartup" _
        (token AS DWORD, inputbuf AS GdiplusStartupInput, outputbuf AS GdiplusStartupOutput) AS LONG
        DECLARE SUB GdiplusShutdown LIB "GDIPLUS.DLL" ALIAS "GdiplusShutdown" _
        (BYVAL token AS DWORD)
        DECLARE FUNCTION GdipGetImageEncodersSize LIB "GDIPLUS.DLL" ALIAS "GdipGetImageEncodersSize" _
        (numEncoders AS DWORD, nSize AS DWORD) AS LONG
        DECLARE FUNCTION GdipGetImageEncoders LIB "GDIPLUS.DLL" ALIAS "GdipGetImageEncoders" _
        (BYVAL numEncoders AS DWORD, BYVAL nSize AS DWORD, BYVAL lpEncoders AS DWORD) AS LONG
        DECLARE FUNCTION GdipSaveImageToFile LIB "GDIPLUS.DLL" ALIAS "GdipSaveImageToFile" _
        (BYVAL lpImage AS DWORD, BYVAL flname AS DWORD, BYREF clsidEncoder AS GUID, _
        BYREF EncoderParams AS DWORD) AS LONG
        DECLARE FUNCTION GdipLoadImageFromFile LIB "GDIPLUS.DLL" ALIAS "GdipLoadImageFromFile" _
        (BYVAL flnameptr AS DWORD, lpImage AS DWORD) AS LONG
        '1st parm of GdipLoadImageFromFile is byval string in Patrice's translation and byval dword in
        ' Jose Roca's translation "for consistency with 1000s of functions..." per PBforums 19/11/09
        DECLARE FUNCTION GdipGetImageDimension LIB "GDIPLUS.DLL" ALIAS "GdipGetImageDimension" _
        (BYVAL nImage AS LONG, nWidth AS SINGLE, Height AS SINGLE) AS LONG
        (BYVAL hdc AS LONG, graphics AS LONG) AS LONG
        DECLARE FUNCTION GdipDrawImageRectRect LIB "GDIPLUS.DLL" ALIAS "GdipDrawImageRectRect" _
        (BYVAL graphics AS DWORD, BYVAL pImage AS DWORD, _
        BYVAL dstx AS SINGLE, BYVAL dsty AS SINGLE, BYVAL dstwidth AS SINGLE, BYVAL dstheight AS SINGLE, _
        BYVAL srcx AS SINGLE, BYVAL srcy AS SINGLE, BYVAL srcwidth AS SINGLE, BYVAL srcheight AS SINGLE, _
        BYVAL srcUnit AS LONG, BYVAL imageAttributes AS DWORD, _
        BYVAL pcallback AS DWORD, BYVAL callbackData AS DWORD) AS LONG
        DECLARE FUNCTION GdipCreateBitmapFromGdiDib LIB "GDIPLUS.DLL" ALIAS "GdipCreateBitmapFromGdiDib" _
        DECLARE FUNCTION GdipDisposeImage LIB "GDIPLUS.DLL" ALIAS "GdipDisposeImage" _
        (BYVAL lpImage AS DWORD) AS LONG
        DECLARE FUNCTION GdipDeleteGraphics LIB "GDIPLUS.DLL" ALIAS "GdipDeleteGraphics" _
        (BYVAL Graphics AS LONG) AS LONG
        ' I need to leave 3 more (unused) definitions here for compatibility with another module I use:
        'declare function GdipGetImagePixelFormat lib "GDIPLUS.DLL" alias "GdipGetImagePixelFormat" _
        ' (byval nImage as long, PixelFormat as long) as long
        'declare function GdipCreateBitmapFromScan0 lib "GDIPLUS.DLL" alias "GdipCreateBitmapFromScan0" _
        ' (byval long, byval long, byval long, byval long, byref any, byref dword) as long
        'declare function GdipGetImageGraphicsContext lib "GDIPLUS.DLL" alias "GdipGetImageGraphicsContext" _
        ' (byval nImage as long, graphics as long) as long
        ' ==========================================================================
        ' GetEncoderClsid
        ' The function GetEncoderClsid in the following example receives the MIME
        ' type of an encoder and returns the class identifier (CLSID) of that encoder.
        ' The MIME types of the encoders built into GDI+ are as follows:
        ' image/bmp image/jpeg image/gif image/tiff image/png
        ' ==========================================================================
        FUNCTION GdiPlusGetEncoderClsid (BYVAL strMimeType AS STRING) AS STRING
        'Retrieve encoder's clsid, where strMimeType is ansi string e.g. "image/jpeg"
        'Routine courtesy Jose Roca, freeware, posted PB Forums 23/12/09
        LOCAL hr AS LONG
        LOCAL pImageCodecInfo AS ImageCodecInfo PTR
        LOCAL numEncoders AS DWORD
        LOCAL nSize AS DWORD
        LOCAL i AS LONG
        LOCAL wstrlen AS LONG
        LOCAL sMimeType AS STRING
        hr = GdipGetImageEncodersSize(numEncoders, nSize)
        REDIM buffer(nSize - 1) AS BYTE
        pImageCodecInfo = VARPTR(buffer(0))
        hr = GdipGetImageEncoders(numEncoders, nSize, BYVAL pImageCodecInfo)
        IF hr = 0 THEN
        FOR i = 1 TO numEncoders
        wstrlen = lstrlenW(BYVAL @pImageCodecInfo.MimeType)
        IF wstrlen THEN sMimeType = ACODE$(PEEK$(@pImageCodecInfo.MimeType, wstrlen * 2))
        IF INSTR(UCASE$(sMimeType), UCASE$(strMimeType) ) THEN
        FUNCTION = GUIDTXT$(@pImageCodecInfo.ClassID)
        EXIT FOR
        END IF
        INCR pImageCodecInfo '// Increments pointer
        END IF
        ' Loading images from file .png .jpg .gif .tif ( and .bmp) to/from PB Bitmaps
        FUNCTION GraphicLoadImageFileToBitmap( _
        BYREF sImageFileName AS STRING, _ 'Image to load, extension = type
        OPTIONAL BYVAL MaxWidth AS DWORD, _ 'Constrain Width to value, 0 = None
        OPTIONAL BYVAL MaxHeight AS DWORD, _ 'Constrain Height value, 0 = None
        OPTIONAL BYVAL StretchImg AS DWORD _ '0 = normal, 1 = Stretch image to xy
        ) AS DWORD 'Returns PB bitmap handle of image
        'Supply the function with an ImageFileName - for instance photo.jpg - and it
        ' will attempt to make a new PB Graphic memory bitmap containing the image.
        'You can optionally specify a maximum width and/or height for the image, and
        ' have the routine scale it as it is read in. While rarely needed, you can
        ' also optionally stretch the image, in which case the image will be made to
        ' equal MaxWidth and MaxHeight even though that may stretch or squash it.
        'sImageFileName can be name or path+name, extn determines file type, eg jpg, png
        'The function needs GDIPlus - so your program needs XP or better, basically.
        'You need to call GdiplusStartup and GdiplusShutdown in your apps init/close
        ' If you do not Gdiplus calls will access memory they are not allowed - a GPF
        'When you are finished using the PB memory bitmap, you must delete it with
        ' GRAPHIC BITMAP END. (Steven Murray 3 February 2010
        LOCAL ucImageFileName AS WSTRING 'sImageFileName converted to unicode
        LOCAL pImage AS DWORD 'GDI+ image pointer, after reading in
        LOCAL ImgWidth AS SINGLE 'Image width, pixels
        LOCAL ImgHeight AS SINGLE 'Image height, pixels
        LOCAL hBmp AS DWORD 'PB Graphic bitmap handle for image
        LOCAL hDC AS DWORD 'Device context for PB Graphic bitmap
        LOCAL pGraphics AS DWORD 'GDI+ Graphics object used to draw image
        LOCAL hStatus AS LONG 'Status return value
        IF StretchImg<>0 AND (MaxWidth=0 OR MaxHeight=0) THEN EXIT FUNCTION 'If bogus xy
        IF ISFALSE ISFILE(sImageFileName) THEN
        MSGBOX "[GraphicLoadImageFileToBitmap] called on <" & sImageFileName & _
        "> Missing file"
        EXIT FUNCTION 'Exit if image not available
        END IF
        ucImageFileName = sImageFileName 'Gdip wants filename as unicode
        'Next ask GDI+ to read in the picture (ucImageFileName) to GDI+ image (pImage)
        hStatus = GdipLoadImagefromFile(STRPTR(ucImageFileName),pImage)
        IF hStatus THEN
        MSGBOX "[GraphicLoadImageFileToBitmap] [GdipLoadImagefromFile] read error on <" & _
        sImageFileName & ">"
        EXIT FUNCTION 'Exit if GDI plus error on read
        END IF
        GdipGetImageDimension(pImage, ImgWidth, ImgHeight) 'Quiz GDI to get image size
        IF ImgWidth = 0 OR ImgHeight = 0 THEN EXIT FUNCTION 'Exit if bogus image size
        IF MaxWidth = 0 THEN MaxWidth = ImgWidth 'If not specified, width = actual
        IF MaxHeight = 0 THEN MaxHeight = ImgHeight 'If not specified, height = actual
        IF StretchImg= 0 THEN 'Only find maximums if no stretch
        IF (ImgWidth / MaxWidth) > (ImgHeight / MaxHeight) THEN 'If width constrained
        IF ImgWidth > MaxWidth THEN 'If a shrink is needed
        MaxHeight = ImgHeight / (ImgWidth / MaxWidth) 'MaxHeight is now new y
        END IF
        IF ImgHeight > MaxHeight THEN 'If a shrink is needed
        MaxWidth = ImgWidth / (ImgHeight / MaxHeight) 'MaxWidth is now new x
        END IF
        END IF
        END IF
        GRAPHIC BITMAP NEW MaxWidth, MaxHeight TO hBmp 'Make a correctly sized PB Bitmap
        IF hBmp = 0 THEN 'Test if PB could make the Bitmap
        IF pImage THEN GdipDisposeImage(pImage) 'Cleanup
        EXIT FUNCTION 'Exit if PB could not make Bitmap
        END IF
        'Else, we have proper sized Bitmap
        GRAPHIC ATTACH hBmp, 0 'Select Bitmap as graphic target
        GRAPHIC GET DC TO hDC 'Get a device context for Bitmap
        hStatus = GdipCreateFromHDC(hDC, pGraphics) 'Create Graphic object
        hStatus = GdipDrawImageRectRect(pGraphics, pImage, 0, 0, MaxWidth, MaxHeight, _
        0, 0, ImgWidth, ImgHeight, %UnitPixel, %NULL, %NULL, %NULL)
        IF pImage THEN GdipDisposeImage(pImage) 'Cleanup
        IF pGraphics THEN GdipDeleteGraphics(pGraphics) ' more Cleanup
        IF hStatus = 0 THEN
        FUNCTION = hBmp 'If draw worked, return Bitmap
        GRAPHIC BITMAP END 'If draw failed, release Bitmap
        END IF
        GRAPHIC DETACH 'Finished with Bitmap, detach
        FUNCTION GraphicSaveBitmapToImageFile(BYVAL hBmp AS DWORD, _
        sImageFileName AS STRING) AS LONG
        'Supply the function with a PB Graphic memory bitmap containing an image,
        ' while specifying an ImageFileName - For instance photo.jpg - to write
        'The extension of the filename selects image type: .png/.jpg/.gif/.bmp/.tif
        'This function is intended as the complement of GraphicLoadImageFileToBitmap
        'The function needs GDIPlus - so your program needs XP or better, basically.
        'When you are finished using the PB memory bitmap, you must delete it with
        ' GRAPHIC BITMAP END. (Steven Murray 3 February 2010
        ' hBmp PB bitmap handle of image to be saved
        ' sImageFileName Name image to be saved as, extn = type
        LOCAL ucImageFileName AS STRING 'sImageFileName converted to unicode
        LOCAL Exten AS STRING 'sImageFileName extension eg ".jpg"
        LOCAL FType AS STRING 'sImageFileName mime-type eg "image-jpeg"
        LOCAL EncoderClsid AS GUID '16 byte GUID string of image encoder
        LOCAL bmpDat AS STRING 'PB graphic bitmap saved to this string
        LOCAL ImgWidth AS LONG 'Width of the bitmap in pixels
        LOCAL ImgHeight AS LONG 'Height of the bitmap in pixels
        LOCAL PixelPtr AS DWORD 'Pointer the start of the pixel data
        LOCAL sbmi AS Bitmapinfo 'Bitmapinfo Structure - dib header
        LOCAL pImage AS DWORD 'GDI+ image pointer, after conversion
        LOCAL hStatus AS LONG 'Status return value
        FUNCTION = %TRUE '
        GRAPHIC ATTACH hBmp, 0 'Select Bitmap as "graphic target" to read out of
        GRAPHIC GET BITS TO bmpDat 'And get the image data into the string bmpDat
        ImgWidth = CVL(bmpDat,1) : ImgHeight = CVL(bmpDat,5) 'Get PB bitmap dimensions
        PixelPtr = STRPTR(bmpDat) + 8 'First 8 bytes are width/height, then pixels
        sbmi.bmiHeader.biSize = SIZEOF(sbmi.bmiHeader) 'Length of the structure
        sbmi.bmiHeader.biWidth = ImgWidth 'Width of the bitmap
        sbmi.bmiHeader.biHeight = 0-ImgHeight 'Height + sign is drawing direction
        sbmi.bmiHeader.biPlanes = 1
        sbmi.bmiHeader.biBitCount = 32 'Each pixel in bitmap is 32 bits
        sbmi.bmiHeader.biCompression = %BI_RGB 'Actually, PB says it is BGR, but this works
        'Next create a GDI+ bitmap (called pImage) from the bmi header we just made
        ' and the Pixel data supplied by "Graphic Get Bits" from the original bitmap
        hStatus = GdipCreateBitmapFromGdiDib(BYREF sbmi, BYVAL PixelPtr, pImage)
        IF hStatus THEN FUNCTION = %FALSE : EXIT FUNCTION 'Indicate failure and exit
        Exten = LCASE$(PATHNAME$(EXTN,sImageFileName)) : FType = "" 'Get extension
        'Now translate the file extension (eg ".png") to mimetype (eg "image/png")
        FType = SWITCH$(Exten=".png", "image/png", Exten=".jpg", "image/jpeg", _
        Exten=".gif", "image/gif", Exten=".tif", "image/tiff", _
        Exten=".bmp", "image/bmp") 'Default seems to be bmp
        EncoderClsid = GUID$(GdiPlusGetEncoderClsid(FType)) 'Get the CLSID of selected encoder
        ucImageFileName = UCODE$(sImageFileName & $NUL) 'Translate the filename to unicode
        'Next save the GDI+ bitmap (pImage) as an image of type specified by EncoderClsid
        'to the filename specified when this function was called with no Encoder parameters
        hStatus = GdipSaveImageToFile(pImage, STRPTR(ucImageFileName), EncoderClsid, BYVAL %Null)
        IF hStatus THEN FUNCTION = %FALSE
        IF pImage THEN GdipDisposeImage(pImage) 'Cleanup


        • #5
          Works as I intended, but it will probably take a while before I understand what I am doing.
          Fim Wästberg