;+
; Create an image compatible with a WIDGET_BUTTON bitmap format,
; constructed from another image and/or a text value, allowing the
; use of color in the text foreground and background. <br>
; <br>
; Its designed purpose is to create thumbnail image-plus-text
; images suitable for WIDGET_BUTTON menu items, but it is also
; used by the CW_COLORLABEL compound widget to create colored
; WIDGET_LABEL equivalents.
;
; @Returns
;   This function returns a BYTARR dimensioned [m, n, 3] (or
;   [3, m, n] if the No_Transpose keyword is set) containing
;   the specified Text and/or Image converted to an image, as
;   if the text were created in a WIDGET_LABEL.
;
; @Param
;   Text {in}{required}{type=string scalar}
;       Set this keyword to the string value to be converted to
;       an image format as if it were displayed in a WIDGET_LABEL.
; @Param
;   Image {in}{optional} {type=array or filename}{default=none}
;       Set this parameter to specify an optional image to be
;       placed at the left edge of the output image.  This is most
;       useful in creating image-plus-text bitmaps for WIDGET_BUTTON
;       menu items.  If specified, Image is always placed at the
;       left-most edge of the return image array, and is centered in
;       the return image in Y.  If Image is a string value, it is
;       intepreted as a file name, which this routine will attempt
;       to read via READ_IMAGE.  If Image is a 2-dimensional array
;       it is interpreted as a grayscale image whose look-up table
;       is given by the Red, Green, and Blue keywords, if present.
;       Otherwise, Image is trusted to be an RGB image suitable for
;       input to an IDLgrImage object.
;
; @Keyword
;   Font {in}{optional}{type=string}{default=widget default font}
;       Set this keyword to the name of the font to be used for
;       rendering the Text value in the image.
; @Keyword
;   Background_Color {in}{optional}{type=BYTARR(3)}{default=system default}
;       Set this keyword to a 3-element vector of red, green and
;       blue byte values to be used as the image's background color.
; @Keyword
;   Foreground_Color {in}{optional}{type=BYTARR(3)}{default=system default}
;       Set this keyword to a 3-element vector of red, green and
;       blue byte values to be used as the image's foreground (i.e.,
;       Text) color.
; @Keyword
;   Red {in}{optional}{type=BYTARR(256)}{default=BINDGEN(256)}
;       If the input Image parameter is two-dimensional, specify its
;       red color look-up table with this keyword.  The default in this
;       case is a linear color ramp.  This keyword is otherwise
;       ignored.
; @Keyword
;   Green {in}{optional}{type=BYTARR(256)}{default=BINDGEN(256)}
;       If the input Image parameter is two-dimensional, specify its
;       green color look-up table with this keyword.  The default in this
;       case is a linear color ramp.  This keyword is otherwise
;       ignored.
; @Keyword
;   Blue {in}{optional}{type=BYTARR(256)}{default=BINDGEN(256)}
;       If the input Image parameter is two-dimensional, specify its
;       blue color look-up table with this keyword.  The default in this
;       case is a linear color ramp.  This keyword is otherwise
;       ignored.
; @Keyword
;   Text_X_Offset {in}{optional}{type=integer}{default=0}
;       Set this keyword to the offset from the left-most edge of
;       the image at which the left-most pixel of the text
;       should be written.  If Image is also specified, this
;       keyword is ignored if it is less than the width of the
;       image.  Setting this keyword is useful in situations
;       such as WIDGET_BUTTON menu item creation in which associated
;       images are of different widths but the text should all
;       align within the menu.  The left-hand X_Pad is ignored if
;       this keyword is set and is larger than the image width.
; @Keyword
;   X_Pad {in}{optional}{type=integer}{default=3}
;       Set this keyword to the number of pixels to pad the text
;       on the left and right to separate the left- and right-most
;       pixels of the text from the input Image (if specified)
;       and/or the right and left edges of the return image.
; @Keyword
;   Y_Pad {in}{optional}{type=integer}{default=3}
;       Set this keyword to the number of pixels to pad the input
;       Image and/or text on the top and bottom to separate the
;       top- and bottom-most pixels of the text and/or Image from
;       the top and bottom edges of the return image.
; @Keyword
;   No_Transpose {in}{optional}{type=boolean}{default=0}
;       By default, the output image is a BYTARR dimensioned
;       [m, n, 3], suitable for passing to WIDGET_BUTTON as
;       a bitmap image value.  Set this keyword to a non-zero
;       value to indicate that the returned image should instead
;       be a BYTARR dimensioned [3, m, n].
; @Keyword
;   Menu_Bug {in}{optional}{type=boolean}{default=0}
;       Set this keyword to a non-zero value to indicate that
;       the image should be padded, if necessary, in the X
;       dimension so the width is an integral multiple of 4.
;       Set this keyword if the image is intended to serve as
;       a WIDGET_BUTTON image used specifically as a menu item.
;       It works around a bug in the development version of
;       IDL 6.2.
;
; @Examples
;   Create an image from a red string in a blue background in the
;   Courier font </pre>
;   IDL> im = TextImage('A test label', FOREGROUND_COLOR=[255,0,0], $
;   IDL>    FONT='Courier', BACKGROUND_COLOR = [0, 0, 255])
;   IDL> imsize = SIZE(im, /DIMENSIONS)
;   IDL> WINDOW, /FREE, XSIZE = imsize[0], YSIZE = imsize[1]
;   IDL> TV, im, TRUE = 3 </pre>
;
;   Create an image from another image plus a string, as you might
;   when creating a menu item WIDGET_BUTTON image<pre>
;   IDL> im = TextImage('Demo', $
;   IDL>    FILEPATH('demo.bmp', SUBDIR = ['resource', 'bitmaps']), $
;   IDL>    /NO_TRANSPOSE, TEXT_X_OFFSET = 18)
;   IDL> imsize = SIZE(im, /DIMENSIONS)
;   IDL> WINDOW, /FREE, XSIZE = imsize[1], YSIZE = imsize[2]
;   IDL> TV, im, TRUE = 1 </pre>
;
; @Author
;   JLP, RSI Global Services
;
; @History
;   March 31, 2005 - Initial version
;
; @File_Comments
;   Create an image compatible with a WIDGET_BUTTON bitmap format,
;   constructed from another image and/or a text value, allowing the
;   use of color in the text foreground and background.<br>
;
;   Learn more about the IDLdoc documentation style at
;       http://www.rsinc.com/codebank/search.asp?FID=100
;-
Function TextImage, Text, Image, Font = Font_Local, $
    Background_Color = Background_Color, $
    Foreground_Color = Foreground_Color, $
    Red = Red_Local, $
    Green = Green_Local, $
    Blue = Blue_Local, $
    Text_X_Offset = Text_X_Offset, $
    X_Pad = X_Pad, $
    Y_Pad = Y_Pad, $
    No_Transpose = No_Transpose, $
    Menu_Bug = Menu_Bug
Compile_Opt StrictArr
On_Error, 2
;
; We need to have an image and/or a text string.
;
HasText = StrLen(Text) ne 0
HasImage = N_elements(Image) ne 0
If (~HasText && ~HasImage) then Begin
    Message, 'Must specify text and/or an image.', /Traceback
EndIf
;
; Establish a friendly error handler
;
Catch, ErrorNumber
If (ErrorNumber ne 0) then Begin
    Catch, /Cancel
    If (Widget_Info(Base, /Valid_ID)) then Begin
        Widget_Control, Base, /Destroy
    EndIf
    If (N_elements(Pixmap) ne 0) then Begin
        If (Pixmap ne -1) then Begin
            Device, Window_State = WState
            If (WState[Pixmap] ne 0) then Begin
                WDelete, Pixmap
            EndIf
        EndIf
    EndIf
    If (N_elements(CurrentFont) ne 0) then Begin
        Device, Set_Font = CurrentFont
    EndIf
    If (N_elements(WasDecomposed) ne 0) then Begin
        Device, Decomposed = WasDecomposed
    EndIf
    If (N_elements(FontSave) ne 0) then Begin
        !p.font = FontSave
    EndIf
    If ((N_elements(WSave) eq 1) && (WSave ne -1)) then Begin
        Device, Window_State = WState
        If (WState[WSave] ne 0) then Begin
            WSet, WSave
        EndIf
    EndIf
    If (N_elements(oBuffer) && Obj_Valid(oBuffer)) then Begin
        Obj_Destroy, oBuffer
    EndIf
    Message, /Reissue_Last
    Return, -1
EndIf
;
; Create a hidden base.  This is used to determine the current
; widget system font, the default system colors, and the
; initial measure of the text dimensions within a widget.
;
Base = Widget_Base(/Row)
Widget_Control, Base, Map = 0
Colors = Widget_Info(Base, /System_Colors)
;
; If the caller didn't specify foreground or background colors,
; use the current system defaults.
;
ForegroundColor = N_elements(Foreground_Color) ne 3 ? $
    Colors.Button_Text : $
    Foreground_Color
BackgroundColor = N_elements(Background_Color) ne 3 ? $
    Colors.Face_3D : $
    Background_Color
;
; If the user hasn't specified padding explicitly, set
; the values to 3 pixels.
;
YPad = N_elements(Y_Pad) eq 1 ? $
    Y_Pad : $
    3
XPad = N_elements(X_Pad) eq 1 ? $
    X_Pad : $
    3
;
; The user may have specified a blank string as input.
; In this case our output image may be identical to the
; input image, returned as a 24-bit image.
;
If (HasText) then Begin
;
; How big does WIDGET_INFO claim our text is going to be
; in the specified font?
;
    Label = Widget_Label(Base, Value = ' ', Font = Font_Local)
    Widget_Control, Base, /Realize
    Font = (N_elements(Font_Local) ne 1) ? $
        Widget_Info(Label, /FontName) : $
        Font_Local
;
; The text dimensions returned by Widget_Info are not strictly
; correct since they apply to the maximum extents of the "largest"
; characters in the font, and not necessarily the size of the
; text we've provided.  So we use these dimensions as a starting
; point and use the remainder of this block to calculate the
; *actual* text dimensions.
;
    TextDimensions = Widget_Info(Label, String_Size = [Text, Font])

;print,'TextDimensions ',TextDimensions
;
; Save the current Direct Graphics device state.
;

    Device, Get_Decomposed = WasDecomposed
    WSave = !d.window
    FontSave = !p.font

;
; Create a new pixmap into which to write the text.  We believe
; this will always be *at least* the actual size of the text.
;

;
    Device, Decomposed = 1

    Window, /Free, XSize = TextDimensions[0], $
        YSize = TextDimensions[1],/Pixmap
    Pixmap = !d.window
    Device, Get_Current_Font = CurrentFont
    !p.font = 0
    Device, Set_Font = Font

;
; Erase to black then write the text in white.
;
    Erase, 0L
 
    XYOuts, 0, 0, Text, Color = 'ffffff'x,/device
 
;
; Read the buffer back in and determine how high
; the text pixels were written in the buffer.  We need
; to worry about whether ascender and/or descenders
; are cut off in the buffer due to the offset of the
; font baseline.
;
    Buffer = TVRd()
    
;    Buffer = TVRead()
;    print,'Array_Equal(Buffer, 0B) ',Array_Equal(Buffer, 0B)
 ;   print,'total(buffer) ',buffer
;    print,''
    If (Array_Equal(Buffer, 0B)) then Begin
        Print, 'Nutty font "' + Font + '" with string "' + Text + '".'
    EndIf
    MaxY = Max((Where(Buffer) > 0)/TextDimensions[0])
;
; If the top-most text pixel is not at the very top of
; the image buffer, redraw the text so it is, offsetting
; up in Y.
;
    YOffset = (TextDimensions[1] - 1) - MaxY
    If (YOffset ne 0) then Begin
        Erase, 0
        XYOuts, 0, YOffset, Text, Color = 'ffffff'x, /Device
        Buffer = TVRd()
    EndIf
;
; Find the bottom-most text pixel in our buffer.
;
    MinY = Min((Where(Buffer) > 0)/TextDimensions[0])
;
; Find the left- and right-most text pixels in the buffer.
;
    MinX = Min((Where(Buffer) > 0) mod TextDimensions[0], Max = MaxX) > 0
    If (StrMid(Text, 0, 1) eq ' ') then Begin
;
; If the string started with one or more spaces, assume the left edge
; of the image represents the left edge of the space and
; don't truncate on the left side.
;
        MinX = 0
    EndIf
    If (StrMid(Text, StrLen(Text) - 1, 1) eq ' ') then Begin
;
; If the string ended with one or more spaces, assume the right
; edge of the image correctly represents the right edge of the
; spaces and don't truncate.
;
        MaxX = TextDimensions[0] - 1
    EndIf
;
; Erase the buffer to the specified background color then
; display the text in the specified foreground color.
;
    Erase, Long(Total(BackgroundColor*[1L, 2L^8, 2L^16]))
    XYOuts, 0, YOffset, Text, $
        Color = Long(Total(ForegroundColor*[1L, 2L^8, 2L^16])), $
        /Device
;
; Read the text image from the buffer.
;
    TextBuffer = TVRd(/True)
;
; Restore the Direct Graphics environment.
;
    Device, Set_Font = CurrentFont
    WDelete, !d.window
    Device, Decomposed = WasDecomposed
    !p.font = FontSave
    If (WSave ne -1) then Begin
        Device, Window_State = WState
        If (WState[WSave] ne 0) then Begin
            WSet, WSave
        EndIf
    EndIf
;
; Truncate the text buffer where we know the edges are.
;
    TextBuffer = TextBuffer[*, MinX:MaxX, (MinY > 0):*]
;
; Get the *actual* text dimensions.
;
    TextDimensions = (Size(TextBuffer, /Dimensions))[1:2]
EndIf Else Begin
;
; There's no text, so the text has no extents.
;
    TextDimensions = Replicate(0, 2)
EndElse
If (HasImage) then Begin
;
; The caller also passed in an image.  It'll be placed
; to the left of the text, if specified.  It will align
; to the left edge of the image and will be centered in Y.
;
    ImageType = Size(Image, /TName)
    Case ImageType of
        'BYTE' : Begin
;
; The image should be either a 2-dimensional or 3-dimensional
; BYTARR.
;
            ImageDims = Size(Image, /N_Dimensions)
            If (ImageDims ne 2 && ImageDims ne 3) then Begin
                Message, 'Illegal image type.', /Traceback
            EndIf
;
; Copy the input image to a local image buffer.
;
            ImageBuffer = Image
            If (ImageDims eq 2) then Begin
;
; If we have a grayscale image, use the user-specified
; look-up tables, or create color ramps.
;
                Red = N_elements(Red_Local) eq 0 ? $
                    Bindgen(256) : $
                    Red_Local
                Green = N_elements(Green_Local) eq 0 ? $
                    Bindgen(256) : $
                    Green_Local
                Blue = N_elements(Blue_Local) eq 0 ? $
                    Bindgen(256) : $
                    Blue_local
            EndIf
            End
        'STRING' : Begin
;
; If the "image" came is a string value, assume it's an image file
; of some sort.
;
            ImageBuffer = Read_Image(Image, Red, Green, Blue)
            End
        Else : Message, 'Illegal image type.', /Traceback
    EndCase
    If (N_elements(Red) ne 0) then Begin
;
; If we have a look-up table and a 2-dimensional input image,
; convert the 8-bit image to a 24-bit image.
;
        Dims = Size(ImageBuffer, /Dimensions)
        Elements = Product(Dims[0:1])
        ImageBuffer24 = BytArr(3, Elements)
        ImageBuffer24[0, 0] = Reform(Red[ImageBuffer], 1, Elements)
        ImageBuffer24[1, 0] = Reform(Green[ImageBuffer], 1, Elements)
        ImageBuffer24[2, 0] = Reform(Blue[ImageBuffer], 1, Elements)
        ImageBuffer = Reform(ImageBuffer24, 3, Dims[0], Dims[1], $
            /Overwrite)
    EndIf
    ImageSize = Size(ImageBuffer, /Dimensions)
EndIf Else Begin
    ImageSize = Replicate(0, 3)
EndElse
;
; Calculate the dimensions of the buffer into which to place
; the image and/or text.  If Text_X_Offset is supplied, this
; overrides the X_PAD on the left-hand side of the text.  The
; Y dimension of our buffer can be no less than the greater
; of the text height or the image height.
;
Dimensions = [Imagesize[1] + TextDimensions[0] + $
    (N_elements(Text_X_Offset) ? (Text_X_Offset - Imagesize[1] > 0) : $
    (2*XPad)*(HasImage || HasText)), $
    (ImageSize[2] > TextDimensions[1]) + (2*YPad)*HasText]
;
; We're going to construct our return image in an IDLgrBuffer object.
;
oBuffer = Obj_New('IDLgrBuffer', Dimensions = Dimensions)
oView = Obj_New('IDLgrView', Viewplane_Rect = [0, 0, Dimensions], $
    Color = BackgroundColor)
oBuffer->SetProperty, Graphics_Tree = oView
oModel = Obj_New('IDLgrModel')
oView->Add, oModel
If (HasImage) then Begin
;
; If we have an image, drop it into the left side of the buffer,
; centered in Y.
;
    oImageModel = Obj_New('IDLgrModel')
    oModel->Add, oImageModel
    oImage = Obj_New('IDLgrImage', ImageBuffer)
    oImageModel->Add, oImage
    oImageModel->Translate, 0, (Dimensions[1] - ImageSize[2])/2., 0.
EndIf
If (HasText) then Begin
;
; If we have text, position it to the right of the image,
; if any, at the appropriate offset, and center it in Y.
;
    oImageModel2 = Obj_New('IDLgrModel')
    oModel->Add, oImageModel2
    oImage2 = Obj_New('IDLgrImage', TextBuffer)
    oImageModel2->Add, oImage2
    XOffset = N_elements(Text_X_Offset) ? $
        (Text_X_Offset > ImageSize[1]) : $
        ImageSize[1] + XPad*(HasImage || HasText)
    oImageModel2->Translate, $
        XOffset, (Dimensions[1] - TextDimensions[1])/2, 0.
EndIf
;
; Draw the images into the buffer object and retrieve the
; image data.
;
oBuffer->Draw
oBuffer->GetProperty, Image_Data = ImageData
If (~Keyword_Set(No_Transpose)) then Begin
;
; To make the image suitable for a WIDGET_BUTTON bitmap,
; we transpose from dimensions [3, m, n] to [m, n, 3].
;
    ImageData = Transpose(Temporary(ImageData), [1, 2, 0])
EndIf
;
; All done with the buffer and the base.
;
Obj_Destroy, oBuffer
Widget_Control, Base, /Destroy
Return, ImageData
End
