jens-maus/yam

View on GitHub
src/mui/ImageArea.c

Summary

Maintainability
Test Coverage
/***************************************************************************

 YAM - Yet Another Mailer
 Copyright (C) 1995-2000 Marcel Beck
 Copyright (C) 2000-2022 YAM Open Source Team

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 YAM Official Support Site : http://www.yam.ch
 YAM OpenSource project     : http://sourceforge.net/projects/yamos/

 $Id$

 Superclass:  MUIC_Area
 Description: Class for loading/maintaining datatype based images

***************************************************************************/

#include "ImageArea_cl.h"

#include <stdlib.h>
#include <string.h>

#if !defined(__amigaos4__)
#include <cybergraphx/cybergraphics.h>
#include <proto/cybergraphics.h>
#endif

#include <proto/datatypes.h>
#include <proto/icon.h>
#include <proto/graphics.h>

#if defined(__amigaos4__)
#include <graphics/blitattr.h>
#ifndef MINTERM_ABC
  #define MINTERM_ABC  0x80
#endif
#ifndef MINTERM_ABNC
  #define MINTERM_ABNC 0x40
#endif
#ifndef MINTERM_ANBC
  #define MINTERM_ANBC 0x20
#endif
#ifndef MINTERM_SRCMASK
  #define MINTERM_SRCMASK (MINTERM_ABC | MINTERM_ABNC | MINTERM_ANBC)
#endif
#endif

#include "YAM.h"

#include "ImageCache.h"

#include "Debug.h"

/* CLASSDATA
struct Data
{
  char *id;
  char *filename;
  char *altText;
  char *label;

  struct ImageCacheNode imageNode;

  struct BitMap *scaledBitMap;
  struct BitMap *scaledBitMask;

  APTR scaledPixelArray;
  ULONG scaledBytesPerRow;

  ULONG scaledWidth;
  ULONG scaledHeight;
  ULONG maxWidth;
  ULONG maxHeight;

  BOOL noMinHeight;
  BOOL free_horiz;
  BOOL free_vert;
  BOOL show_label;
  BOOL imageLoaded;
  BOOL setup;

  int label_height;
};
*/

/* EXPORT
#define MakeImageObject(id, file) ImageAreaObject,\
                                    MUIA_ImageArea_ID, (id),\
                                    MUIA_ImageArea_Filename, (file),\
                                  End
#define MakeImageAltObject(id, file, alt) ImageAreaObject,\
                                            MUIA_ImageArea_ID, (id),\
                                            MUIA_ImageArea_Filename, (file),\
                                            MUIA_ImageArea_AltText, (alt),\
                                          End
*/

/* INCLUDE
#include "ImageCache.h"
*/

/* Defines */
/// WritePixelArrayAlpha
#if defined(__MORPHOS__)
  #ifndef WritePixelArrayAlpha
    #define WritePixelArrayAlpha(__p0, __p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9) \
      LP10(216, ULONG , WritePixelArrayAlpha, \
        APTR , __p0, a0, \
        UWORD , __p1, d0, \
        UWORD , __p2, d1, \
        UWORD , __p3, d2, \
        struct RastPort *, __p4, a1, \
        UWORD , __p5, d3, \
        UWORD , __p6, d4, \
        UWORD , __p7, d5, \
        UWORD , __p8, d6, \
        ULONG , __p9, d7, \
        , CYBERGRAPHICS_BASE_NAME, 0, 0, 0, 0, 0, 0)
  #endif
#elif !defined(__amigaos4__)
  #ifndef WritePixelArrayAlpha
    #if defined(__SASC)
      ULONG WritePixelArrayAlpha(APTR, UWORD, UWORD, UWORD, struct RastPort *, UWORD, UWORD, UWORD, UWORD, ULONG);
      #pragma libcall CyberGfxBase WritePixelArrayAlpha d8 76543921080A
    #else
      #define WritePixelArrayAlpha(__p0, __p1, __p2, __p3, __p4, __p5, __p6, __p7, __p8, __p9) \
        LP10(216, ULONG , WritePixelArrayAlpha, \
          APTR , __p0, a0, \
          UWORD , __p1, d0, \
          UWORD , __p2, d1, \
          UWORD , __p3, d2, \
          struct RastPort *, __p4, a1, \
          UWORD , __p5, d3, \
          UWORD , __p6, d4, \
          UWORD , __p7, d5, \
          UWORD , __p8, d6, \
          ULONG , __p9, d7, \
          , CYBERGRAPHICS_BASE_NAME)
    #endif
  #endif
#endif
///

/* Private Functions */
/// Image_Load
// loads an image via our datatype methods
static BOOL Image_Load(struct Data *data, Object *obj)
{
  ENTER();

  if(data->imageLoaded == FALSE &&
     IsStrEmpty(data->id) == FALSE &&
     IsStrEmpty(data->filename) == FALSE)
  {
    struct ImageCacheNode *node;

    if((node = ObtainImage(data->id, data->filename, _screen(obj))) != NULL)
    {
      memcpy(&data->imageNode, node, sizeof(data->imageNode));

      if(node->dt_obj != NULL)
        data->imageLoaded = TRUE;
    }
  }

  RETURN(data->imageLoaded);
  return data->imageLoaded;
}

///
/// Image_Unload
// unloads a certain image from our datatypes cache methods
static void Image_Unload(struct Data *data)
{
  ENTER();

  if(data->imageLoaded == TRUE)
  {
    // releasing an image requires a valid ID
    if(IsStrEmpty(data->id) == FALSE)
    {
      D(DBF_IMAGE, "unloaded old image '%s' (%s)", data->id, data->filename);
      ReleaseImage(data->id, FALSE);
    }

    data->imageLoaded = FALSE;
  }

  LEAVE();
}

///
/// Image_Scale
// scale the image to the given size
static void Image_Scale(struct Data *data)
{
  ENTER();

  if(data->scaledBitMap != NULL)
  {
    FreeBitMap(data->scaledBitMap);
    data->scaledBitMap = NULL;
  }

  if(data->scaledBitMask != NULL)
  {
    FreeBitMap(data->scaledBitMask);
    data->scaledBitMask = NULL;
  }

  if(data->scaledPixelArray != NULL)
  {
    FreeVecPooled(G->SharedMemPool, data->scaledPixelArray);
    data->scaledPixelArray = NULL;
  }

  data->scaledWidth = 0;
  data->scaledHeight = 0;

  // check if correctly obtained the header and if it is valid
  if(data->imageNode.bitmap != NULL && data->imageNode.depth > 0)
  {
    // make sure to scale down the image if maxHeight/maxWidth is specified
    ULONG oldWidth = data->imageNode.width;
    ULONG oldHeight = data->imageNode.height;
    LONG scaleHeightDiff = oldHeight - data->maxHeight;
    LONG scaleWidthDiff  = oldWidth - data->maxWidth;

    if((scaleHeightDiff > 0 && data->maxHeight > 0) ||
       (scaleWidthDiff > 0 && data->maxWidth > 0))
    {
      ULONG newWidth;
      ULONG newHeight;
      BOOL wasScaled = FALSE;
      double scaleFactor;

      // make sure we are scaling proportional
      if(scaleHeightDiff > scaleWidthDiff)
      {
        scaleFactor = (double)oldWidth / (double)oldHeight;
        newWidth = scaleFactor * data->maxHeight;
        // force a width of a least one pixel
        if(newWidth < 1)
          newWidth = 1;
        newHeight = data->maxHeight;
      }
      else
      {
        scaleFactor = (double)oldHeight / (double)oldWidth;
        newWidth = data->maxWidth;
        newHeight = scaleFactor * data->maxWidth;
        // force a height of a least one pixel
        if(newHeight < 1)
          newHeight = 1;
      }

      // in case we have image data with an alpha channel embedded we
      // have to use the PDTM_SCALE method of picture.datatype v45
      if(data->imageNode.pixelArray != NULL)
      {
        BOOL result;

        result = DoMethod(data->imageNode.dt_obj, PDTM_SCALE, newWidth, newHeight, 0);
        if(result == TRUE)
        {
          data->scaledBytesPerRow = newWidth * data->imageNode.bytesPerPixel;
          SHOWVALUE(DBF_IMAGE, data->scaledBytesPerRow);
          SHOWVALUE(DBF_IMAGE, data->imageNode.pixelFormat);

          if((data->scaledPixelArray = AllocVecPooled(G->SharedMemPool, data->scaledBytesPerRow * newHeight)) != NULL)
          {
            // perform a PDTM_READPIXELARRAY operation
            // for writing the image data of the image in our pixelArray
            result = DoMethod(data->imageNode.dt_obj, PDTM_READPIXELARRAY, data->scaledPixelArray, data->imageNode.pixelFormat, data->scaledBytesPerRow,
                                                                           0, 0, newWidth, newHeight);

            if(result == FALSE)
            {
              W(DBF_IMAGE, "PDTM_READPIXELARRAY on image '%s' with depth %ld failed after PDTM_SCALE!", data->imageNode.id, data->imageNode.depth);

              FreeVecPooled(G->SharedMemPool, data->scaledPixelArray);
              data->scaledPixelArray = NULL;
            }
            else
            {
              data->scaledWidth  = newWidth;
              data->scaledHeight = newHeight;

              D(DBF_IMAGE, "Scaled pixelarray in ImageArea (w/h) from %ld/%ld to %ld/%ld", data->imageNode.width,
                                                                                           data->imageNode.height,
                                                                                           data->scaledWidth,
                                                                                           data->scaledHeight);

            }
          }

          // rescale the image to its original size
          DoMethod(data->imageNode.dt_obj, PDTM_SCALE, data->imageNode.width, data->imageNode.height, 0);

          wasScaled = TRUE;
        }
        else
          W(DBF_IMAGE, "PDTM_SCALE method on image '%s' returned an error! picture.datatype >= v45?", data->imageNode.id);
      }

      // check if we generated a scaled interpretation or not
      if(wasScaled == FALSE)
      {
        // now we can allocate the new bitmap and scale it
        // if required. But we use BitMapScale() for all operations
        if((data->scaledBitMap = AllocBitMap(newWidth, newHeight, data->imageNode.depth, BMF_CLEAR|BMF_MINPLANES, data->imageNode.bitmap)) != NULL)
        {
          struct BitScaleArgs args;

          args.bsa_SrcBitMap = data->imageNode.bitmap;
          args.bsa_DestBitMap = data->scaledBitMap;
          args.bsa_Flags = 0;

          args.bsa_SrcY = 0;
          args.bsa_DestY = 0;

          args.bsa_SrcWidth = oldWidth;
          args.bsa_SrcHeight = oldHeight;

          args.bsa_XSrcFactor = oldWidth;
          args.bsa_XDestFactor = newWidth;

          args.bsa_YSrcFactor = oldHeight;
          args.bsa_YDestFactor = newHeight;

          args.bsa_SrcX = 0;
          args.bsa_DestX = 0;

          // scale the image now with the arguments set
          BitMapScale(&args);

          // read out the scaled values
          data->scaledWidth  = args.bsa_DestWidth;
          data->scaledHeight = args.bsa_DestHeight;

          D(DBF_IMAGE, "Scaled image in ImageArea (w/h) from %ld/%ld to %ld/%ld", data->imageNode.width,
                                                                                  data->imageNode.height,
                                                                                  data->scaledWidth,
                                                                                  data->scaledHeight);
        }

        // now we can allocate the new bitmap for our scale mask
        if(data->imageNode.mask != NULL &&
           (data->scaledBitMask = AllocBitMap(newWidth, newHeight, 1L, BMF_CLEAR|BMF_MINPLANES, NULL)) != NULL)
        {
          struct BitScaleArgs args;
          struct BitMap bm;

          InitBitMap(&bm, 1L, oldWidth, oldHeight);
          bm.Planes[0] = data->imageNode.mask;

          args.bsa_SrcBitMap = &bm;
          args.bsa_DestBitMap = data->scaledBitMask;
          args.bsa_Flags = 0;

          args.bsa_SrcY = 0;
          args.bsa_DestY = 0;

          args.bsa_SrcWidth = oldWidth;
          args.bsa_SrcHeight = oldHeight;

          args.bsa_XSrcFactor = oldWidth;
          args.bsa_XDestFactor = newWidth;

          args.bsa_YSrcFactor = oldHeight;
          args.bsa_YDestFactor = newHeight;

          args.bsa_SrcX = 0;
          args.bsa_DestX = 0;

          // scale the image now with the arguments set
          BitMapScale(&args);

          D(DBF_IMAGE, "Scaled transparency mask of image");
        }
        else
        {
          D(DBF_IMAGE, "no bitmask for image available, skipped scaling of bitmask");
          data->scaledBitMask = NULL;
        }
      }
    }
    else
    {
      D(DBF_IMAGE, "no image scaling required for image '%s'", data->imageNode.id);

      data->scaledBitMap = NULL;
      data->scaledBitMask = NULL;
      data->scaledPixelArray = NULL;
    }
  }

  LEAVE();
}

///

/* Overloaded Methods */
/// OVERLOAD(OM_NEW)
OVERLOAD(OM_NEW)
{
  ENTER();

  if((obj = DoSuperNew(cl, obj,
    TAG_MORE, inittags(msg))) != NULL)
  {
    GETDATA;
    struct TagItem *tags = inittags(msg);
    struct TagItem *tag;

    // set default values
    data->id = NULL;
    data->filename = NULL;
    data->altText = NULL;
    data->label = NULL;
    data->free_vert = FALSE;
    data->free_horiz = FALSE;
    data->show_label = TRUE;
    data->noMinHeight = FALSE;
    data->maxHeight = 0;
    data->maxWidth = 0;

    while((tag = NextTagItem((APTR)&tags)) != NULL)
    {
      switch(tag->ti_Tag)
      {
        case ATTR(ID):          if((char *)tag->ti_Data != NULL) { free(data->id); data->id = strdup((char *)tag->ti_Data); } break;
        case ATTR(Filename):    if((char *)tag->ti_Data != NULL) { free(data->filename); data->filename = strdup((char *)tag->ti_Data); } break;
        case ATTR(AltText):     if((char *)tag->ti_Data != NULL) { free(data->altText); data->altText = strdup((char *)tag->ti_Data); } break;
        case ATTR(Label):       if((char *)tag->ti_Data != NULL) { free(data->label); data->label = strdup((char *)tag->ti_Data); } break;
        case ATTR(FreeVert):    data->free_vert   = (BOOL)tag->ti_Data; break;
        case ATTR(FreeHoriz):   data->free_horiz  = (BOOL)tag->ti_Data; break;
        case ATTR(ShowLabel):   data->show_label  = (BOOL)tag->ti_Data; break;
        case ATTR(MaxHeight):   data->maxHeight   = (ULONG)tag->ti_Data; break;
        case ATTR(MaxWidth):    data->maxWidth    = (ULONG)tag->ti_Data; break;
        case ATTR(NoMinHeight): data->noMinHeight = (BOOL)tag->ti_Data; break;
      }
    }
  }

  RETURN((IPTR)obj);
  return (IPTR)obj;
}

///
/// OVERLOAD(OM_DISPOSE)
OVERLOAD(OM_DISPOSE)
{
  GETDATA;
  ULONG result;

  ENTER();

  free(data->id);
  free(data->filename);
  free(data->altText);
  free(data->label);

  // everything else has been freed during MUIM_Cleanup already
  result = DoSuperMethodA(cl, obj, msg);

  RETURN(result);
  return result;
}

///
/// OVERLOAD(OM_GET)
// get info about the instance data
OVERLOAD(OM_GET)
{
  GETDATA;
  IPTR *store = ((struct opGet *)msg)->opg_Storage;

  switch(((struct opGet *)msg)->opg_AttrID)
  {
    case ATTR(ID): *store = (IPTR)data->id; return TRUE;
    case ATTR(Filename): *store = (IPTR)data->filename; return TRUE;

    // return the raw image width
    case ATTR(RawWidth):
    {
      BOOL result = FALSE;

      if(data->setup == TRUE && data->imageLoaded == TRUE)
      {
        *store = data->imageNode.width;
        result = TRUE;
      }
      else
      {
        struct ImageCacheNode *icnode;

        if((icnode = ObtainImage(data->id, data->filename, NULL)) != NULL)
        {
          *store = icnode->width;

          // release the image again
          ReleaseImage(data->id, FALSE);
          result = TRUE;
        }
      }

      return result;
    }
    break;

    // return the raw image height
    case ATTR(RawHeight):
    {
      BOOL result = FALSE;

      if(data->setup == TRUE && data->imageLoaded == TRUE)
      {
        *store = data->imageNode.height;
        result = TRUE;
      }
      else
      {
        struct ImageCacheNode *icnode;

        if((icnode = ObtainImage(data->id, data->filename, NULL)) != NULL)
        {
          *store = icnode->height;

          // release the image again
          ReleaseImage(data->id, FALSE);
          result = TRUE;
        }
      }

      return result;
    }
    break;
  }

  return DoSuperMethodA(cl, obj, msg);
}

///
/// OVERLOAD(OM_SET)
OVERLOAD(OM_SET)
{
  GETDATA;

  ULONG result;
  BOOL relayout = FALSE;
  struct TagItem *tags = inittags(msg), *tag;

  ENTER();

  while((tag = NextTagItem((APTR)&tags)) != NULL)
  {
    switch(tag->ti_Tag)
    {
      case ATTR(ShowLabel):
      {
        if(data->show_label != (BOOL)tag->ti_Data)
        {
          data->show_label = tag->ti_Data;
          relayout = TRUE;
        }

        // make the superMethod call ignore those tags
        tag->ti_Tag = TAG_IGNORE;
      }
      break;

      case ATTR(ID):
      {
        char *newId = (char *)tag->ti_Data;

        Image_Unload(data);

        if(data->id != NULL)
        {
          if(IsStrEmpty(newId) == TRUE)
            ReleaseImage(data->id, TRUE);

          free(data->id);
          data->id = NULL;
        }

        if(IsStrEmpty(newId) == FALSE)
        {
          data->id = strdup(newId);

          // remember to relayout the image
          relayout = TRUE;
        }

        // make the superMethod call ignore those tags
        tag->ti_Tag = TAG_IGNORE;
      }
      break;

      case ATTR(Filename):
      {
        char *newFilename = (char *)tag->ti_Data;

        Image_Unload(data);

        free(data->filename);
        data->filename = NULL;

        if(newFilename != NULL)
        {
          data->filename = strdup(newFilename);
          // remember to relayout the image
          relayout = TRUE;
        }
        else
        {
          if(data->id != NULL)
            ReleaseImage(data->id, TRUE);
        }

        // make the superMethod call ignore those tags
        tag->ti_Tag = TAG_IGNORE;
      }
      break;
    }
  }

  if(relayout == TRUE &&
     data->setup == TRUE &&
     Image_Load(data, obj) == TRUE)
  {
    Image_Scale(data);
  }

  result = DoSuperMethodA(cl, obj, msg);

  RETURN(result);
  return result;
}

///
/// OVERLOAD(MUIM_Setup)
OVERLOAD(MUIM_Setup)
{
  GETDATA;
  IPTR result;

  ENTER();

  if((result = DoSuperMethodA(cl, obj, msg)))
  {
    if(Image_Load(data, obj) == TRUE)
      Image_Scale(data);

    data->setup = TRUE;
  }

  RETURN(result);
  return result;
}

///
/// OVERLOAD(MUIM_Cleanup)
OVERLOAD(MUIM_Cleanup)
{
  GETDATA;
  ULONG result;

  ENTER();

  // now it should be fine to free the allocated bitmap
  if(data->scaledBitMap != NULL)
  {
    FreeBitMap(data->scaledBitMap);
    data->scaledBitMap = NULL;
  }

  if(data->scaledBitMask != NULL)
  {
    FreeBitMap(data->scaledBitMask);
    data->scaledBitMask = NULL;
  }

  if(data->scaledPixelArray != NULL)
  {
    FreeVecPooled(G->SharedMemPool, data->scaledPixelArray);
    data->scaledPixelArray = NULL;
  }

  Image_Unload(data);

  data->setup = FALSE;

  result = DoSuperMethodA(cl, obj, msg);

  RETURN(result);
  return result;
}

///
/// OVERLOAD(MUIM_AskMinMax)
OVERLOAD(MUIM_AskMinMax)
{
  GETDATA;
  struct MUI_MinMax *mi;
  ULONG minwidth;
  ULONG minheight;

  ENTER();

  DoSuperMethodA(cl, obj, msg);

  mi = ((struct MUIP_AskMinMax *)msg)->MinMaxInfo;

  if(data->imageLoaded == TRUE)
  {
    if(data->scaledBitMap != NULL ||
       data->scaledPixelArray != NULL)
    {
      minwidth  = data->scaledWidth;
      minheight = data->scaledHeight;
    }
    else
    {
      minwidth  = data->imageNode.width;
      minheight = data->imageNode.height;
    }
  }
  else if(IsStrEmpty(data->altText) == FALSE)
  {
    struct RastPort rp;

    InitRastPort(&rp);
    SetFont(&rp, _font(obj));
    minwidth = TextLength(&rp, data->altText, strlen(data->altText));
    minheight = _font(obj)->tf_YSize;
  }
  else
  {
    minwidth = 0;
    minheight = 0;
  }
  data->label_height = 0;

  if(data->label != NULL && data->show_label == TRUE)
  {
    struct RastPort rp;
    ULONG width;
    char *str = data->label;
    char *uptr;

    data->label_height = _font(obj)->tf_YSize;

    minheight += data->label_height + (!!data->imageLoaded);
    InitRastPort(&rp);
    SetFont(&rp, _font(obj));

    if(!(uptr = strchr(str,'_')))
    {
      width = TextLength(&rp, str, strlen(str));
    }
    else
    {
      width = TextLength(&rp, str, uptr - str);
      width += TextLength(&rp, uptr+1, strlen(uptr+1));
    }

    if(width > minwidth)
      minwidth = width;
  }

  mi->MinHeight += data->noMinHeight ? 0 : minheight;
  mi->DefHeight += minheight;
  if(data->free_vert)
    mi->MaxHeight += MUI_MAXMAX;
  else
    mi->MaxHeight += minheight;

  mi->MinWidth += minwidth;
  mi->DefWidth += minwidth;
  if(data->free_horiz)
    mi->MaxWidth += MUI_MAXMAX;
  else
    mi->MaxWidth += minwidth;

  RETURN(0);
  return 0;
}

///
/// OVERLOAD(MUIM_Draw)
OVERLOAD(MUIM_Draw)
{
  GETDATA;
  struct MUIP_Draw *dmsg = (struct MUIP_Draw *)msg;

  ENTER();

  // call the super method first
  DoSuperMethodA(cl, obj, msg);

  if(isFlagSet(dmsg->flags, MADF_DRAWOBJECT) || isFlagSet(dmsg->flags, MADF_DRAWUPDATE))
  {
    LONG rel_y = 0;
    struct RastPort *rp = _rp(obj);

    // in case we successfully have an imageNode object
    // we blit the image on our rastport
    if(data->imageLoaded == TRUE)
    {
      if(data->scaledBitMap != NULL)
      {
        ULONG width = MIN(data->scaledWidth, (ULONG)_mwidth(obj));
        ULONG height = MIN(data->scaledHeight, (ULONG)_mheight(obj));

        #if defined(__amigaos4__)
        if(data->scaledBitMask != NULL)
        {
          D(DBF_IMAGE, "drawing scaled/masked bitmap image '%s' (%s)", data->id, data->filename);
          BltBitMapTags(BLITA_Source,     data->scaledBitMap,
                        BLITA_Dest,       rp,
                        BLITA_SrcType,    BLITT_BITMAP,
                        BLITA_DestType,   BLITT_RASTPORT,
                        BLITA_DestX,      _mleft(obj) + (_mwidth(obj) - width) / 2,
                        BLITA_DestY,      _mtop(obj) + (_mheight(obj) - data->label_height - height) / 2,
                        BLITA_Width,      width,
                        BLITA_Height,     height,
                        BLITA_Minterm,    MINTERM_SRCMASK,
                        BLITA_MaskPlane,  data->scaledBitMask->Planes[0],
                        TAG_DONE);
        }
        else
        {
          D(DBF_IMAGE, "drawing scaled bitmap image '%s' (%s)", data->id, data->filename);
          BltBitMapTags(BLITA_Source,     data->scaledBitMap,
                        BLITA_Dest,       rp,
                        BLITA_SrcType,    BLITT_BITMAP,
                        BLITA_DestType,   BLITT_RASTPORT,
                        BLITA_DestX,      _mleft(obj) + (_mwidth(obj) - width) / 2,
                        BLITA_DestY,      _mtop(obj) + (_mheight(obj) - data->label_height - height) / 2,
                        BLITA_Width,      width,
                        BLITA_Height,     height,
                        BLITA_Minterm,    MINTERM_ABC | MINTERM_ABNC,
                        TAG_DONE);
        }
        #else
        if(data->scaledBitMask != NULL)
        {
          D(DBF_IMAGE, "drawing scaled/masked bitmap image '%s' (%s)", data->id, data->filename);
          // we use an own BltMaskBitMapRastPort() implemenation to also support
          // interleaved images.
          MyBltMaskBitMapRastPort(data->scaledBitMap,
                                  0,
                                  0,
                                  rp,
                                  _mleft(obj) + (_mwidth(obj) - width) / 2,
                                  _mtop(obj) + (_mheight(obj) - data->label_height - height) / 2,
                                  width,
                                  height,
                                  (ABC|ABNC|ANBC),
                                  data->scaledBitMask->Planes[0]);
        }
        else
        {
          D(DBF_IMAGE, "drawing scaled bitmap image '%s' (%s)", data->id, data->filename);
          BltBitMapRastPort(data->scaledBitMap,
                            0,
                            0,
                            rp,
                            _mleft(obj) + (_mwidth(obj) - width) / 2,
                            _mtop(obj) + (_mheight(obj) - data->label_height - height) / 2,
                            width,
                            height,
                            (ABC|ABNC));
        }
        #endif

        rel_y += height;
      }
      else if(data->scaledPixelArray != NULL)
      {
        ULONG width = MIN(data->scaledWidth, (ULONG)_mwidth(obj));
        ULONG height = MIN(data->scaledHeight, (ULONG)_mheight(obj));

        #if defined(__amigaos4__)
        D(DBF_IMAGE, "drawing scaled (A)RGB image '%s' (%s)", data->id, data->filename);
        BltBitMapTags(BLITA_Source,         data->scaledPixelArray,
                      BLITA_Dest,           rp,
                      BLITA_SrcType,        (data->imageNode.pixelFormat == PBPAFMT_ARGB) ? BLITT_ARGB32 : BLITT_RGB24,
                      BLITA_DestType,       BLITT_RASTPORT,
                      BLITA_DestX,          _mleft(obj) + (_mwidth(obj) - width) / 2,
                      BLITA_DestY,          _mtop(obj) + (_mheight(obj) - data->label_height - height) / 2,
                      BLITA_Width,          width,
                      BLITA_Height,         height,
                      BLITA_SrcBytesPerRow, data->scaledBytesPerRow,
                      BLITA_UseSrcAlpha,    TRUE,
                      TAG_DONE);
        #else
        // this also works for OS3, because we only have valid pixel data if
        // cybergraphics.library and picture.datatype are able to handle the
        // alpha channel correctly.
        if(data->imageNode.pixelFormat == PBPAFMT_ARGB)
        {
          D(DBF_IMAGE, "drawing scaled ARGB image '%s' (%s)", data->id, data->filename);
          WritePixelArrayAlpha(data->scaledPixelArray,
                               0,
                               0,
                               data->scaledBytesPerRow,
                               rp,
                               _mleft(obj) + (_mwidth(obj) - width) / 2,
                               _mtop(obj) + (_mheight(obj) - data->label_height - height) / 2,
                               width,
                               height,
                               0xffffffff);
        }
        else
        {
          D(DBF_IMAGE, "drawing scaled RGB image '%s' (%s)", data->id, data->filename);
          WritePixelArray(data->scaledPixelArray,
                          0,
                          0,
                          data->scaledBytesPerRow,
                          rp,
                          _mleft(obj) + (_mwidth(obj) - width) / 2,
                          _mtop(obj) + (_mheight(obj) - data->label_height - height) / 2,
                          width,
                          height,
                          RECTFMT_RGB);
        }
        #endif

        rel_y += height;
      }
      else
      {
        // blit the (A)RGB data if we retrieved them successfully.
        if(data->imageNode.pixelArray != NULL)
        {
          #if defined(__amigaos4__)
          D(DBF_IMAGE, "drawing (A)RGB image '%s' (%s)", data->id, data->filename);
          BltBitMapTags(BLITA_Source,         data->imageNode.pixelArray,
                        BLITA_Dest,           rp,
                        BLITA_SrcType,        (data->imageNode.pixelFormat == PBPAFMT_ARGB) ? BLITT_ARGB32 : BLITT_RGB24,
                        BLITA_DestType,       BLITT_RASTPORT,
                        BLITA_DestX,          _mleft(obj) + (_mwidth(obj) - data->imageNode.width) / 2,
                        BLITA_DestY,          _mtop(obj) + (_mheight(obj) - data->label_height - data->imageNode.height) / 2,
                        BLITA_Width,          data->imageNode.width,
                        BLITA_Height,         data->imageNode.height,
                        BLITA_SrcBytesPerRow, data->imageNode.bytesPerRow,
                        BLITA_UseSrcAlpha,    TRUE,
                        TAG_DONE);
          #else
          // this also works for OS3, because we only have valid pixel data if
          // cybergraphics.library and picture.datatype are able to handle the
          // alpha channel correctly.
          if(data->imageNode.pixelFormat == PBPAFMT_ARGB)
          {
            D(DBF_IMAGE, "drawing ARGB image '%s' (%s)", data->id, data->filename);
            WritePixelArrayAlpha(data->imageNode.pixelArray,
                                 0,
                                 0,
                                 data->imageNode.bytesPerRow,
                                 rp,
                                 _mleft(obj) + (_mwidth(obj) - data->imageNode.width) / 2,
                                 _mtop(obj) + (_mheight(obj) - data->label_height - data->imageNode.height) / 2,
                                 data->imageNode.width,
                                 data->imageNode.height,
                                 0xffffffff);
          }
          else
          {
            D(DBF_IMAGE, "drawing RGB image '%s' (%s)", data->id, data->filename);
            WritePixelArray(data->imageNode.pixelArray,
                            0,
                            0,
                            data->imageNode.bytesPerRow,
                            rp,
                            _mleft(obj) + (_mwidth(obj) - data->imageNode.width) / 2,
                            _mtop(obj) + (_mheight(obj) - data->label_height - data->imageNode.height) / 2,
                            data->imageNode.width,
                            data->imageNode.height,
                            RECTFMT_RGB);
          }
          #endif
        }
        // blit the bitmap if we retrieved it successfully.
        else if(data->imageNode.bitmap != NULL)
        {
          #if defined(__amigaos4__)
          if(data->imageNode.mask != NULL)
          {
            D(DBF_IMAGE, "drawing masked bitmap image '%s' (%s)", data->id, data->filename);
            BltBitMapTags(BLITA_Source,     data->imageNode.bitmap,
                          BLITA_Dest,       rp,
                          BLITA_SrcType,    BLITT_BITMAP,
                          BLITA_DestType,   BLITT_RASTPORT,
                          BLITA_DestX,      _mleft(obj) + (_mwidth(obj) - data->imageNode.width)/2,
                          BLITA_DestY,      _mtop(obj)  + (_mheight(obj) - data->label_height - data->imageNode.height)/2,
                          BLITA_Width,      data->imageNode.width,
                          BLITA_Height,     data->imageNode.height,
                          BLITA_Minterm,    MINTERM_SRCMASK,
                          BLITA_MaskPlane,  data->imageNode.mask,
                          TAG_DONE);
          }
          else
          {
            D(DBF_IMAGE, "drawing bitmap image '%s' (%s)", data->id, data->filename);
            BltBitMapTags(BLITA_Source,     data->imageNode.bitmap,
                          BLITA_Dest,       rp,
                          BLITA_SrcType,    BLITT_BITMAP,
                          BLITA_DestType,   BLITT_RASTPORT,
                          BLITA_DestX,      _mleft(obj) + (_mwidth(obj) - data->imageNode.width)/2,
                          BLITA_DestY,      _mtop(obj)  + (_mheight(obj) - data->label_height - data->imageNode.height)/2,
                          BLITA_Width,      data->imageNode.width,
                          BLITA_Height,     data->imageNode.height,
                          BLITA_Minterm,    MINTERM_ABC | MINTERM_ABNC,
                          TAG_DONE);
          }
          #else
          if(data->imageNode.mask != NULL)
          {
            D(DBF_IMAGE, "drawing masked bitmap image '%s' (%s)", data->id, data->filename);
            // we use an own BltMaskBitMapRastPort() implemenation to also support
            // interleaved images.
            MyBltMaskBitMapRastPort(data->imageNode.bitmap,
                                    0,
                                    0,
                                    rp,
                                    _mleft(obj) + (_mwidth(obj) - data->imageNode.width)/2,
                                    _mtop(obj) + (_mheight(obj) - data->label_height - data->imageNode.height)/2,
                                    data->imageNode.width,
                                    data->imageNode.height,
                                    (ABC|ABNC|ANBC),
                                    data->imageNode.mask);
          }
          else
          {
            D(DBF_IMAGE, "drawing bitmap image '%s' (%s)", data->id, data->filename);
            BltBitMapRastPort(data->imageNode.bitmap,
                              0,
                              0,
                              rp,
                              _mleft(obj) + (_mwidth(obj) - data->imageNode.width)/2,
                              _mtop(obj) + (_mheight(obj) - data->label_height - data->imageNode.height)/2,
                              data->imageNode.width,
                              data->imageNode.height,
                              (ABC|ABNC));
          }
          #endif
        }

        rel_y += data->imageNode.height;
      }
    }
    else if(IsStrEmpty(data->altText) == FALSE)
    {
      LONG len;
      struct TextExtent te;

      if((len = TextFit(rp, data->altText, strlen(data->altText), &te, NULL, 1, _mwidth(obj), _mheight(obj))) > 0)
      {
        Move(rp, _mleft(obj) + (_mwidth(obj) - te.te_Width)/2, _mtop(obj) + (_mheight(obj) - te.te_Height)/2 + _font(obj)->tf_Baseline);
        SetAPen(rp, _dri(obj)->dri_Pens[TEXTPEN]);
        SetDrMd(rp, JAM1);
        Text(rp, data->altText, len);
      }
    }

    if(data->label != NULL && data->show_label == TRUE)
    {
      STRPTR ufreestr = StripUnderscore(data->label);

      if(ufreestr != NULL)
      {
        LONG len;
        struct TextExtent te;

        SetFont(rp, _font(obj));

        if((len = TextFit(rp, ufreestr, strlen(ufreestr), &te, NULL, 1, _mwidth(obj), _font(obj)->tf_YSize)) > 0)
        {
          char *str = data->label;
          char *uptr;

          Move(rp, _mleft(obj) + (_mwidth(obj) - te.te_Width)/2, _mbottom(obj)+_font(obj)->tf_Baseline - data->label_height);
          SetAPen(rp, _dri(obj)->dri_Pens[TEXTPEN]);
          SetDrMd(rp, JAM1);

          if((uptr = strchr(str, '_')) == NULL)
          {
            Text(rp, str, strlen(str));
          }
          else
          {
            Text(rp, str, uptr - str);
            if(uptr[1] != '\0')
            {
              SetSoftStyle(rp, FSF_UNDERLINED, AskSoftStyle(rp));
              Text(rp, uptr+1, 1);
              SetSoftStyle(rp, FS_NORMAL, 0xffff);
              Text(rp, uptr+2, strlen(uptr+2));
            }
          }
        }
      }
    }
  }

  RETURN(0);
  return 0;
}

///

/* Public Methods */