package Graphics::Framebuffer::Accel;

use base Exporter;

use strict;
no strict 'vars';    # We have to map a variable as the screen.  So strict is going to whine about what we do with it.
no strict 'subs';
no warnings;         # We have to be as quiet as possible

use constant {
    TRUE  => 1,
    FALSE => 0,

    HARDWARE => 2,
    SOFTWARE => 1,
    PERL     => 0,

    NORMAL_MODE => 0,    #   Constants for DRAW_MODE
    XOR_MODE    => 1,
    OR_MODE     => 2,
    AND_MODE    => 3,
    MASK_MODE   => 4,
    UNMASK_MODE => 5,
    ALPHA_MODE  => 6,
    ADD_MODE    => 7,
    SUBTRACT_MODE => 8,
    MULTIPLY_MODE => 9,
    DIVIDE_MODE   => 10,

    ARC      => 0,       #   Constants for "draw_arc" method
    PIE      => 1,       #   Constants for "draw_arc" method
    POLY_ARC => 2,       #   Constants for "draw_arc" method

    RGB => 0,            #   Constants for color mapping
    RBG => 1,            #   Constants for color mapping
    BGR => 2,            #   Constants for color mapping
    BRG => 3,            #   Constants for color mapping
    GBR => 4,            #   Constants for color mapping
    GRB => 5,            #   Constants for color mapping

    CENTER_NONE => 0,    #   Constants for centering
    CENTER_X    => 1,    #   Constants for centering
    CENTER_Y    => 2,    #   Constants for centering
    CENTER_XY   => 3,    #   Constants for centering

    ## Set up the Framebuffer driver "constants" defaults
    # Commands
    FBIOGET_VSCREENINFO => 0x4600,    # These come from "fb.h" in the kernel source
    FBIOPUT_VSCREENINFO => 0x4601,
    FBIOGET_FSCREENINFO => 0x4602,
    FBIOGETCMAP         => 0x4604,
    FBIOPUTCMAP         => 0x4605,
    FBIOPAN_DISPLAY     => 0x4606,
    FBIO_CURSOR         => 0x4608,
    FBIOGET_CON2FBMAP   => 0x460F,
    FBIOPUT_CON2FBMAP   => 0x4610,
    FBIOBLANK           => 0x4611,
    FBIOGET_VBLANK      => 0x4612,
    FBIOGET_GLYPH       => 0x4615,
    FBIOGET_HWCINFO     => 0x4616,
    FBIOPUT_MODEINFO    => 0x4617,
    FBIOGET_DISPINFO    => 0x4618,
    FBIO_WAITFORVSYNC   => 0x4620,
    VT_GETSTATE         => 0x5603,

    # FLAGS
    FBINFO_HWACCEL_NONE      => 0x0000,    # These come from "fb.h" in the kernel source
    FBINFO_HWACCEL_COPYAREA  => 0x0100,
    FBINFO_HWACCEL_FILLRECT  => 0x0200,
    FBINFO_HWACCEL_IMAGEBLIT => 0x0400,
    FBINFO_HWACCEL_ROTATE    => 0x0800,
    FBINFO_HWACCEL_XPAN      => 0x1000,
    FBINFO_HWACCEL_YPAN      => 0x2000,
    FBINFO_HWACCEL_YWRAP     => 0x4000,
};

use POSIX ();
use POSIX qw(modf);
use List::Util qw(min max);
use Math::Gradient qw( gradient array_gradient multi_gradient );
use Imager;
use Imager::Matrix2d;
use Imager::Fill;
use Imager::Fountain;

# use Inline (Config => directory => './.Graphics-Framebuffer-Inline');
our $INLINE = FALSE;
eval {
    require Inline;
    Inline->import(C => <<CC);

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <math.h>

#define NORMAL_MODE   0
#define XOR_MODE      1
#define OR_MODE       2
#define AND_MODE      3
#define MASK_MODE     4
#define UNMASK_MODE   5
#define ALPHA_MODE    6
#define ADD_MODE      7
#define SUBTRACT_MODE 8
#define MULTIPLY_MODE 9
#define DIVIDE_MODE   10
#define RGB           0
#define RBG           1
#define BGR           2
#define BRG           3
#define GBR           4
#define GRB           5

#define ipart_(X) ((int)(X))
#define round_(X) ((int)(((double)(X))+0.5))
#define fpart_(X) (((double)(X))-(double)ipart_(X))
#define rfpart_(X) (1.0-fpart_(X))
#define swap_(a, b) do{ __typeof__(a) tmp;  tmp = a; a = b; b = tmp; }while(0)

unsigned int c_get_screen_info(char *fb_file) {
    int fbfd = 0;
/*  struct fb_var_screeninfo vinfo; */
    struct fb_fix_screeninfo finfo;
    
    fbfd = open(fb_file,O_RDWR);
    ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);
/*  ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo); */
    close(fbfd);
    return(finfo.line_length);
}

void c_filled_circle(
    char *framebuffer,
    short x, short y, short r,
    unsigned char draw_mode,
    unsigned int color,
    unsigned int bcolor,
    unsigned char bytes_per_pixel,
    unsigned int bytes_per_line,
    unsigned short x_clip, unsigned short y_clip, unsigned short xx_clip, unsigned short yy_clip,
    unsigned short xoffset, unsigned short yoffset,
    unsigned char alpha
) {
    int r2 = r * r;
    int area = r2 << 2;
    int rr = r << 1;
    int i;
    
    for (i = 0; i < area; i++)
    {
        int tx = (i % rr) - r;
        int ty = (i / rr) - r;
        
        if (tx * tx + ty * ty <= r2)
          c_plot(framebuffer,x + tx, y + ty, draw_mode, color, bcolor, bytes_per_pixel, bytes_per_line, x_clip, y_clip, xx_clip, yy_clip, xoffset, yoffset, alpha);
    }
}

void c_plot(
    char *framebuffer,
    short x, short y,
    unsigned char draw_mode,
    unsigned int color,
    unsigned int bcolor,
    unsigned char bytes_per_pixel,
    unsigned int bytes_per_line,
    unsigned short x_clip, unsigned short y_clip, unsigned short xx_clip, unsigned short yy_clip,
    unsigned short xoffset, unsigned short yoffset,
    unsigned char alpha
) {
    if (x >= x_clip && x <= xx_clip && y >= y_clip && y <= yy_clip) {
        x += xoffset;
        y += yoffset;
        unsigned int index = (x * bytes_per_pixel) + (y * bytes_per_line);
        switch(draw_mode) {
            case NORMAL_MODE :
              if (bytes_per_pixel == 4) {
                 *((unsigned int*)(framebuffer + index)) = color;
              } else if (bytes_per_pixel == 3) {
                  *(framebuffer + index)     = color         & 255;
                  *(framebuffer + index + 1) = (color >> 8)  & 255;
                  *(framebuffer + index + 2) = (color >> 16) & 255;
              } else {
                  *((unsigned short*)(framebuffer + index)) = (short) color;
              }
              break;
            case XOR_MODE :
              if (bytes_per_pixel == 4) {
                  *((unsigned int*)(framebuffer + index)) ^= color;
              } else if (bytes_per_pixel == 3) {
                  *(framebuffer + index)     ^= color         & 255;
                  *(framebuffer + index + 1) ^= (color >> 8)  & 255;
                  *(framebuffer + index + 2) ^= (color >> 16) & 255;
              } else {
                  *((unsigned short*)(framebuffer + index)) ^= (short) color;
              }
              break;
            case OR_MODE :
              if (bytes_per_pixel == 4) {
                  *((unsigned int*)(framebuffer + index)) |= color;
              } else if (bytes_per_pixel == 3) {
                  *(framebuffer + index)     |= color         & 255;
                  *(framebuffer + index + 1) |= (color >> 8)  & 255;
                  *(framebuffer + index + 2) |= (color >> 16) & 255;
              } else {
                  *((unsigned short*)(framebuffer + index)) |= (short) color;
              }
              break;
            case AND_MODE :
              if (bytes_per_pixel == 4) {
                  *((unsigned int*)(framebuffer + index)) &= color;
              } else if (bytes_per_pixel == 3) {
                  *(framebuffer + index)     &= color         & 255;
                  *(framebuffer + index + 1) &= (color >> 8)  & 255;
                  *(framebuffer + index + 2) &= (color >> 16) & 255;
              } else {
                  *((unsigned short*)(framebuffer + index)) &= (short) color;
              }
              break;
            case MASK_MODE :
              if (bytes_per_pixel == 4) {
                  unsigned int rgb = *((unsigned int*)(framebuffer + index ));
                  if ((rgb & 0xFFFFFF00) != (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
                      *((unsigned int*)(framebuffer + index )) = color;
                  }
              } else if (bytes_per_pixel == 3) {
                  unsigned int rgb = *((unsigned int*)(framebuffer + index )) & 0xFFFFFF00;
                  if (rgb != (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
                      *(framebuffer + index )     = color         & 255;
                      *(framebuffer + index  + 1) = (color >> 8)  & 255;
                      *(framebuffer + index  + 2) = (color >> 16) & 255;
                  }
              } else {
                  unsigned short rgb = *((unsigned short*)(framebuffer + index));
                  if (rgb != (bcolor & 0xFFFF)) {
                      *((unsigned short*)(framebuffer + index )) = color;
                  }
              }
              break;
            case UNMASK_MODE :
              if (bytes_per_pixel == 4) {
                  unsigned int rgb = *((unsigned int*)(framebuffer + index ));
                  if ((rgb & 0xFFFFFF00) == (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
                      *((unsigned int*)(framebuffer + index )) = color;
                  }
              } else if (bytes_per_pixel == 3) {
                  unsigned int rgb = *((unsigned int*)(framebuffer + index )) & 0xFFFFFF00;
                  if (rgb == (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
                      *(framebuffer + index )     = color         & 255;
                      *(framebuffer + index  + 1) = (color >> 8)  & 255;
                      *(framebuffer + index  + 2) = (color >> 16) & 255;
                  }
              } else {
                  unsigned short rgb = *((unsigned short*)(framebuffer + index));
                  if (rgb == (bcolor & 0xFFFF)) {
                      *((unsigned short*)(framebuffer + index )) = color;
                  }
              }
              break;
            case ALPHA_MODE :
              if (bytes_per_pixel >= 3) {
                  unsigned short fb_r = *(framebuffer + index);
                  unsigned short fb_g = *(framebuffer + index + 1);
                  unsigned short fb_b = *(framebuffer + index + 2);
                  unsigned char invA  = (255 - alpha);
                  unsigned char R     = color         & 255;
                  unsigned char G     = (color >> 8)  & 255;
                  unsigned char B     = (color >> 16) & 255;

                  fb_r = ((R * alpha) + (fb_r * invA)) >> 8;
                  fb_g = ((G * alpha) + (fb_g * invA)) >> 8;
                  fb_b = ((B * alpha) + (fb_b * invA)) >> 8;

                  *(framebuffer + index)     = fb_r;
                  *(framebuffer + index + 1) = fb_g;
                  *(framebuffer + index + 2) = fb_b;
              } else {
                  unsigned short rgb565 = *((unsigned short*)(framebuffer + index));
                  unsigned short fb_r   = rgb565;
                  unsigned short fb_g   = rgb565;
                  unsigned short fb_b   = rgb565;
                  fb_b >>= 11;
                  fb_g >>= 5;
                  fb_r  &= 31;
                  fb_g  &= 63;
                  fb_b  &= 31;
                  unsigned short R = color;
                  unsigned short G = color;
                  unsigned short B = color;
                  B >>= 11;
                  G >>= 5;
                  R  &= 31;
                  G  &= 63;
                  B  &= 31;
                  unsigned char invA = (255 - alpha);
                  fb_r = ((R * alpha) + (fb_r * invA)) >> 8;
                  fb_g = ((G * alpha) + (fb_g * invA)) >> 8;
                  fb_b = ((B * alpha) + (fb_b * invA)) >> 8;
                  rgb565 = 0;
                  rgb565 = (fb_b << 11) | (fb_g << 5) | fb_r;
                  *((unsigned short*)(framebuffer + index)) = rgb565;
              }
              break;
            case ADD_MODE :
              if (bytes_per_pixel == 4) {
                  *((unsigned int*)(framebuffer + index)) += color;
              } else if (bytes_per_pixel == 3) {
                  *(framebuffer + index)     += color         & 255;
                  *(framebuffer + index + 1) += (color >> 8)  & 255;
                  *(framebuffer + index + 2) += (color >> 16) & 255;
              } else {
                  *((unsigned short*)(framebuffer + index)) += (short) color;
              }
              break;
            case SUBTRACT_MODE :
              if (bytes_per_pixel == 4) {
                  *((unsigned int*)(framebuffer + index)) -= color;
              } else if (bytes_per_pixel == 3) {
                  *(framebuffer + index)     -= color         & 255;
                  *(framebuffer + index + 1) -= (color >> 8)  & 255;
                  *(framebuffer + index + 2) -= (color >> 16) & 255;
              } else {
                  *((unsigned short*)(framebuffer + index)) -= (short) color;
              }
              break;
            case MULTIPLY_MODE :
              if (bytes_per_pixel == 4) {
                  *((unsigned int*)(framebuffer + index)) *= color;
              } else if (bytes_per_pixel == 3) {
                  *(framebuffer + index)     *= color         & 255;
                  *(framebuffer + index + 1) *= (color >> 8)  & 255;
                  *(framebuffer + index + 2) *= (color >> 16) & 255;
              } else {
                  *((unsigned short*)(framebuffer + index)) *= (short) color;
              }
              break;
            case DIVIDE_MODE :
              if (bytes_per_pixel == 4) {
                  *((unsigned int*)(framebuffer + index)) /= color;
              } else if (bytes_per_pixel == 3) {
                  *(framebuffer + index)     /= color         & 255;
                  *(framebuffer + index + 1) /= (color >> 8)  & 255;
                  *(framebuffer + index + 2) /= (color >> 16) & 255;
              } else {
                  *((unsigned short*)(framebuffer + index)) /= (short) color;
              }
              break;
        }
    }
}

void c_line(
    char *framebuffer,
    short x1, short y1, short x2, short y2,
    unsigned char draw_mode,
    unsigned int color,
    unsigned int bcolor,
    unsigned char bytes_per_pixel,
    unsigned int bytes_per_line,
    unsigned short x_clip, unsigned short y_clip, unsigned short xx_clip, unsigned short yy_clip,
    unsigned short xoffset, unsigned short yoffset,
    unsigned char alpha
) {
        int shortLen = y2 - y1;
        int longLen  = x2 - x1;
        int yLonger  = false;

        if (abs(shortLen) > abs(longLen)) {
            int swap = shortLen;
            shortLen   = longLen;
            longLen    = swap;                
            yLonger    = true;
        }
        int decInc;
        if (longLen == 0) {
            decInc = 0;
        } else {
            decInc = (shortLen << 16) / longLen;
        }
        int count;
        if (yLonger) {
            if (longLen > 0) {
                longLen += y1;
                for (count = 0x8000 + (x1 << 16); y1 <= longLen; ++y1) {
                    c_plot(framebuffer, count >> 16, y1, draw_mode, color, bcolor, bytes_per_pixel, bytes_per_line, x_clip, y_clip, xx_clip, yy_clip, xoffset, yoffset, alpha);
                    count += decInc;
                }
                return;
            }
            longLen += y1;
            for (count = 0x8000 + (x1 << 16); y1 >= longLen; --y1) {
                c_plot(framebuffer, count >> 16, y1, draw_mode, color, bcolor, bytes_per_pixel, bytes_per_line, x_clip, y_clip, xx_clip, yy_clip, xoffset, yoffset, alpha);
                count -= decInc;
            }
            return;    
        }
 
        if (longLen > 0) {
            longLen += x1;
            for (count = 0x8000 + (y1 << 16); x1 <= longLen; ++x1) {
                c_plot(framebuffer, x1, count >> 16, draw_mode, color, bcolor, bytes_per_pixel, bytes_per_line, x_clip, y_clip, xx_clip, yy_clip, xoffset, yoffset, alpha);
                count += decInc;
            }
            return;
        }
        longLen += x1;
        for (count = 0x8000 + (y1 << 16); x1 >= longLen; --x1) {
            c_plot(framebuffer, x1, count >> 16, draw_mode, color, bcolor, bytes_per_pixel, bytes_per_line, x_clip, y_clip, xx_clip, yy_clip, xoffset, yoffset, alpha);
            count -= decInc;
        }
}

void c_blit_read(char *framebuffer, unsigned short screen_width, unsigned short screen_height, unsigned int bytes_per_line, unsigned short xoffset, unsigned short yoffset, char *blit_data, short x, short y, unsigned short w, unsigned short h, unsigned char bytes_per_pixel, unsigned char draw_mode, unsigned char alpha, unsigned int bcolor, unsigned short x_clip, unsigned short y_clip, unsigned short xx_clip, unsigned short yy_clip) {
    short fb_x = xoffset + x;
    short fb_y = yoffset + y;
    short xx   = x + w;
    short yy   = y + h;
    unsigned short horizontal;
    unsigned short vertical;
    unsigned int bline = w * bytes_per_pixel;

    for (vertical = 0; vertical < h; vertical++) {
        unsigned int vbl  = vertical * bline;
        unsigned short yv = fb_y + vertical;
        unsigned int yvbl = yv * bytes_per_line;
        if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
            for (horizontal = 0; horizontal < w; horizontal++) {
                unsigned short xh = fb_x + horizontal;
                unsigned int xhbp = xh * bytes_per_pixel;
                if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
                    unsigned int hzpixel   = horizontal * bytes_per_pixel;
                    unsigned int vhz       = vbl + hzpixel;
                    unsigned int yvhz      = yvbl + hzpixel;
                    unsigned int xhbp_yvbl = xhbp + yvbl;
                    if (bytes_per_pixel == 4) {
                        *((unsigned int*)(blit_data + vhz)) = *((unsigned int*)(framebuffer + xhbp_yvbl)); 
                    } else if (bytes_per_pixel == 3) {
                        *(blit_data + vhz ) = *(framebuffer + xhbp_yvbl );
                        *(blit_data + vhz  + 1) = *(framebuffer + xhbp_yvbl  + 1);
                        *(blit_data + vhz  + 2) = *(framebuffer + xhbp_yvbl  + 2);
                    } else {
                        *((unsigned short*)(blit_data + vhz )) = *((unsigned short*)(framebuffer + xhbp_yvbl ));
                    }
                }
            }
        }
    }
}

void c_blit_write(char *framebuffer, unsigned short screen_width, unsigned short screen_height, unsigned int bytes_per_line, unsigned short xoffset, unsigned short yoffset, char *blit_data, short x, short y, unsigned short w, unsigned short h, unsigned char bytes_per_pixel, unsigned char draw_mode, unsigned char alpha, unsigned int bcolor, unsigned short x_clip, unsigned short y_clip, unsigned short xx_clip, unsigned short yy_clip) {
    short fb_x = xoffset + x;
    short fb_y = yoffset + y;
    short xx   = x + w;
    short yy   = y + h;
    unsigned short horizontal;
    unsigned short vertical;
    unsigned int bline = w * bytes_per_pixel;

    if (draw_mode == NORMAL_MODE && x >= x_clip && xx <= xx_clip && y >= y_clip && yy <= yy_clip) {
        unsigned char *source = blit_data;
        unsigned char *dest   = &framebuffer[(fb_y * bytes_per_line) + (fb_x * bytes_per_pixel)];
        for (vertical = 0; vertical < h; vertical++) {
            memcpy(dest, source, bline);
            source += bline;
            dest += bytes_per_line;
        }
    } else {
        for (vertical = 0; vertical < h; vertical++) {
            unsigned int vbl  = vertical * bline;
            unsigned short yv = fb_y + vertical;
            unsigned int yvbl = yv * bytes_per_line;
            if (yv >= (yoffset + y_clip) && yv <= (yoffset + yy_clip)) {
                for (horizontal = 0; horizontal < w; horizontal++) {
                    unsigned short xh = fb_x + horizontal;
                    unsigned int xhbp = xh * bytes_per_pixel;
                    if (xh >= (xoffset + x_clip) && xh <= (xoffset + xx_clip)) {
                        unsigned int hzpixel   = horizontal * bytes_per_pixel;
                        unsigned int vhz       = vbl + hzpixel;
                        unsigned int yvhz      = yvbl + hzpixel;
                        unsigned int xhbp_yvbl = xhbp + yvbl;
                        switch(draw_mode) {
                            case NORMAL_MODE :
                              if (bytes_per_pixel == 4) {
                                  *((unsigned int*)(framebuffer + xhbp_yvbl)) = *((unsigned int*)(blit_data + vhz));
                              } else if (bytes_per_pixel == 3) {
                                  *(framebuffer + xhbp_yvbl )     = *(blit_data + vhz );
                                  *(framebuffer + xhbp_yvbl  + 1) = *(blit_data + vhz  + 1);
                                  *(framebuffer + xhbp_yvbl  + 2) = *(blit_data + vhz  + 2);
                              } else {
                                  *((unsigned short*)(framebuffer + xhbp_yvbl )) = *((unsigned short*)(blit_data + vhz ));
                              }
                            break;
                            case XOR_MODE :
                              if (bytes_per_pixel == 4) {
                                  *((unsigned int*)(framebuffer + xhbp_yvbl )) ^= *((unsigned int*)(blit_data + vhz ));
                              } else if (bytes_per_pixel == 3) {
                                  *(framebuffer + xhbp_yvbl )     ^= *(blit_data + vhz );
                                  *(framebuffer + xhbp_yvbl  + 1) ^= *(blit_data + vhz  + 1);
                                  *(framebuffer + xhbp_yvbl  + 2) ^= *(blit_data + vhz  + 2);
                              } else {
                                  *((unsigned short*)(framebuffer + xhbp_yvbl )) ^= *((unsigned short*)(blit_data + vhz ));
                              }
                            break;
                            case OR_MODE :
                              if (bytes_per_pixel == 4) {
                                  *((unsigned int*)(framebuffer + xhbp_yvbl )) |= *((unsigned int*)(blit_data + vhz ));
                              } else if (bytes_per_pixel == 3) {
                                  *(framebuffer + xhbp_yvbl )     |= *(blit_data + vhz );
                                  *(framebuffer + xhbp_yvbl  + 1) |= *(blit_data + vhz  + 1);
                                  *(framebuffer + xhbp_yvbl  + 2) |= *(blit_data + vhz  + 2);
                              } else {
                                  *((unsigned short*)(framebuffer + xhbp_yvbl )) |= *((unsigned short*)(blit_data + vhz ));
                              }
                            break;
                            case AND_MODE :
                              if (bytes_per_pixel == 4) {
                                  *((unsigned int*)(framebuffer + xhbp_yvbl )) &= *((unsigned int*)(blit_data + vhz ));
                              } else if (bytes_per_pixel == 3) {
                                  *(framebuffer + xhbp_yvbl )     &= *(blit_data + vhz );
                                  *(framebuffer + xhbp_yvbl  + 1) &= *(blit_data + vhz  + 1);
                                  *(framebuffer + xhbp_yvbl  + 2) &= *(blit_data + vhz  + 2);
                              } else {
                                  *((unsigned short*)(framebuffer + xhbp_yvbl )) &= *((unsigned short*)(blit_data + vhz ));
                              }
                            break;
                            case MASK_MODE :
                              if (bytes_per_pixel == 4) {
                                  unsigned int rgb = *((unsigned int*)(blit_data + vhz ));
                                  if ((rgb & 0xFFFFFF00) != (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
                                      *((unsigned int*)(framebuffer + xhbp_yvbl )) = rgb;
                                  }
                              } else if (bytes_per_pixel == 3) {
                                  unsigned int rgb = *((unsigned int*)(blit_data + vhz )) & 0xFFFFFF00;
                                  if (rgb != (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
                                      *(framebuffer + xhbp_yvbl )     = *(blit_data + vhz );
                                      *(framebuffer + xhbp_yvbl  + 1) = *(blit_data + vhz  + 1);
                                      *(framebuffer + xhbp_yvbl  + 2) = *(blit_data + vhz  + 2);
                                  }
                              } else {
                                  unsigned short rgb = *((unsigned short*)(blit_data + vhz ));
                                  if (rgb != (bcolor & 0xFFFF)) {
                                      *((unsigned short*)(framebuffer + xhbp_yvbl )) = rgb;
                                  }
                              }
                            break;
                            case UNMASK_MODE :
                              if (bytes_per_pixel == 4) {
                                  unsigned int rgb = *((unsigned int*)(framebuffer + xhbp_yvbl ));
                                  if ((rgb & 0xFFFFFF00) == (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
                                      *((unsigned int*)(framebuffer + xhbp_yvbl )) = *((unsigned int*)(blit_data + vhz ));
                                  }
                              } else if (bytes_per_pixel == 3) {
                                  unsigned int rgb = *((unsigned int*)(framebuffer + xhbp + yvhz ));
                                  if (rgb == (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
                                      *(framebuffer + xhbp_yvbl )     = *(blit_data + vhz );
                                      *(framebuffer + xhbp_yvbl  + 1) = *(blit_data + vhz  + 1);
                                      *(framebuffer + xhbp_yvbl  + 2) = *(blit_data + vhz  + 2);
                                  }
                              } else {
                                  unsigned short rgb = *((unsigned short*)(framebuffer + xhbp + yvhz ));
                                  if (rgb == (bcolor & 0xFFFF)) {
                                      *((unsigned short*)(framebuffer + xhbp_yvbl )) = *((unsigned short*)(blit_data + vhz ));
                                  }
                              }
                            break;
                            case ALPHA_MODE :
                              if (bytes_per_pixel == 4) {
                                  unsigned char fb_r = *(framebuffer + xhbp_yvbl );
                                  unsigned char fb_g = *(framebuffer + xhbp_yvbl + 1);
                                  unsigned char fb_b = *(framebuffer + xhbp_yvbl + 2);
                                  unsigned char fb_a = *(framebuffer + xhbp_yvbl + 3);
                                  unsigned char R    = *(blit_data + vhz );
                                  unsigned char G    = *(blit_data + vhz  + 1);
                                  unsigned char B    = *(blit_data + vhz  + 2);
                                  unsigned char A    = *(blit_data + vhz  + 3);
                                  unsigned char invA = (255 - A);

                                  fb_r = ((R * A) + (fb_r * invA)) >> 8;
                                  fb_g = ((G * A) + (fb_g * invA)) >> 8;
                                  fb_b = ((B * A) + (fb_b * invA)) >> 8;
                                  fb_a = (fb_a + A) & 255;

                                  *(framebuffer + xhbp_yvbl )     = fb_r;
                                  *(framebuffer + xhbp_yvbl  + 1) = fb_g;
                                  *(framebuffer + xhbp_yvbl  + 2) = fb_b;
                                  *(framebuffer + xhbp_yvbl  + 3) = fb_a;
                              } else if (bytes_per_pixel == 3) {
                                  unsigned char fb_r = *(framebuffer + xhbp_yvbl );
                                  unsigned char fb_g = *(framebuffer + xhbp_yvbl  + 1);
                                  unsigned char fb_b = *(framebuffer + xhbp_yvbl  + 2);
                                  unsigned char R    = *(blit_data + vhz );
                                  unsigned char G    = *(blit_data + vhz + 1);
                                  unsigned char B    = *(blit_data + vhz + 2);
                                  unsigned char invA = (255 - alpha);

                                  fb_r = ((R * alpha) + (fb_r * invA)) >> 8;
                                  fb_g = ((G * alpha) + (fb_g * invA)) >> 8;
                                  fb_b = ((B * alpha) + (fb_b * invA)) >> 8;

                                  *(framebuffer + xhbp_yvbl )     = fb_r;
                                  *(framebuffer + xhbp_yvbl  + 1) = fb_g;
                                  *(framebuffer + xhbp_yvbl  + 2) = fb_b;
                              } else {
                                  unsigned short rgb565 = *((unsigned short*)(framebuffer + xhbp_yvbl ));

                                  unsigned short fb_r = rgb565;
                                  unsigned short fb_g = rgb565;
                                  unsigned short fb_b = rgb565;
                                  fb_b >>= 11;
                                  fb_g >>= 5;
                                  fb_r &= 31;
                                  fb_g &= 63;
                                  fb_b &= 31;
                                  rgb565 = *((unsigned short*)(blit_data + vhz ));
                                  unsigned short R = rgb565;
                                  unsigned short G = rgb565;
                                  unsigned short B = rgb565;
                                  B >>= 11;
                                  G >>= 5;
                                  R &= 31;
                                  G &= 63;
                                  B &= 31;
                                  unsigned char invA = (255 - alpha);
                                  fb_r = ((R * alpha) + (fb_r * invA)) >> 8;
                                  fb_g = ((G * alpha) + (fb_g * invA)) >> 8;
                                  fb_b = ((B * alpha) + (fb_b * invA)) >> 8;
                                  rgb565 = 0;
                                  rgb565 = (fb_b << 11) | (fb_g << 5) | fb_r;

                                  *((unsigned short*)(framebuffer + xhbp_yvbl )) = rgb565;
                              }
                            break;
                            case ADD_MODE :
                              if (bytes_per_pixel == 4) {
                                  *((unsigned int*)(framebuffer + xhbp_yvbl )) += *((unsigned int*)(blit_data + vhz ));
                              } else if (bytes_per_pixel == 3) {
                                  *(framebuffer + xhbp_yvbl )     += *(blit_data + vhz );
                                  *(framebuffer + xhbp_yvbl  + 1) += *(blit_data + vhz  + 1);
                                  *(framebuffer + xhbp_yvbl  + 2) += *(blit_data + vhz  + 2);
                              } else {
                                  *((unsigned short*)(framebuffer + xhbp_yvbl )) += *((unsigned short*)(blit_data + vhz ));
                              }
                            break;
                            case SUBTRACT_MODE :
                              if (bytes_per_pixel == 4) {
                                  *((unsigned int*)(framebuffer + xhbp_yvbl )) -= *((unsigned int*)(blit_data + vhz ));
                              } else if (bytes_per_pixel == 3) {
                                  *(framebuffer + xhbp_yvbl )     -= *(blit_data + vhz );
                                  *(framebuffer + xhbp_yvbl  + 1) -= *(blit_data + vhz  + 1);
                                  *(framebuffer + xhbp_yvbl  + 2) -= *(blit_data + vhz  + 2);
                              } else {
                                  *((unsigned short*)(framebuffer + xhbp_yvbl )) -= *((unsigned short*)(blit_data + vhz ));
                              }
                            break;
                            case MULTIPLY_MODE :
                              if (bytes_per_pixel == 4) {
                                  *((unsigned int*)(framebuffer + xhbp_yvbl )) *= *((unsigned int*)(blit_data + vhz ));
                              } else if (bytes_per_pixel == 3) {
                                  *(framebuffer + xhbp_yvbl )     *= *(blit_data + vhz );
                                  *(framebuffer + xhbp_yvbl  + 1) *= *(blit_data + vhz  + 1);
                                  *(framebuffer + xhbp_yvbl  + 2) *= *(blit_data + vhz  + 2);
                              } else {
                                  *((unsigned short*)(framebuffer + xhbp_yvbl )) *= *((unsigned short*)(blit_data + vhz ));
                              }
                            break;
                            case DIVIDE_MODE :
                              if (bytes_per_pixel == 4) {
                                  *((unsigned int*)(framebuffer + xhbp_yvbl )) /= *((unsigned int*)(blit_data + vhz ));
                              } else if (bytes_per_pixel == 3) {
                                  *(framebuffer + xhbp_yvbl )     /= *(blit_data + vhz );
                                  *(framebuffer + xhbp_yvbl  + 1) /= *(blit_data + vhz  + 1);
                                  *(framebuffer + xhbp_yvbl  + 2) /= *(blit_data + vhz  + 2);
                              } else {
                                  *((unsigned short*)(framebuffer + xhbp_yvbl )) /= *((unsigned short*)(blit_data + vhz ));
                              }
                            break;
                        }
                    }
                }
            }
        }
    }
}

void c_rotate(char *image, char *new_img, unsigned short width, unsigned short height, unsigned short wh, double degrees, unsigned char bytes_per_pixel) {
    unsigned int hwh    = floor(wh / 2 + 0.5);
    unsigned int bbline = wh * bytes_per_pixel;
    unsigned int bline  = width * bytes_per_pixel;
    unsigned short hwidth        = floor(width / 2 + 0.5);
    unsigned short hheight       = floor(height / 2 + 0.5);
    double sinma        = sin((degrees * M_PI) / 180);
    double cosma        = cos((degrees * M_PI) / 180);
    short x;
    short y;

    for (x = 0; x < wh; x++) {
        short xt = x - hwh;
        for (y = 0; y < wh; y++) {
            short yt = y - hwh;
            short xs = ((cosma * xt - sinma * yt) + hwidth);
            short ys = ((sinma * xt + cosma * yt) + hheight);
            if (xs >= 0 && xs < width && ys >= 0 && ys < height) {
                if (bytes_per_pixel == 4) {
                    *((unsigned int*)(new_img + (x * bytes_per_pixel) + (y * bbline))) = *((unsigned int*)(image + (xs * bytes_per_pixel) + (ys * bline)));
                } else if (bytes_per_pixel == 3) {
                    *(new_img + (x * bytes_per_pixel) + (y * bbline))     = *(image + (xs * bytes_per_pixel) + (ys * bline));
                    *(new_img + (x * bytes_per_pixel) + (y * bbline) + 1) = *(image + (xs * bytes_per_pixel) + (ys * bline) + 1);
                    *(new_img + (x * bytes_per_pixel) + (y * bbline) + 2) = *(image + (xs * bytes_per_pixel) + (ys * bline) + 2);
                } else {
                    *((unsigned short*)(new_img + (x * bytes_per_pixel) + (y * bbline))) = *((unsigned short*)(image + (xs * bytes_per_pixel) + (ys * bline)));
                }
            }
        }
    }
}

void c_flip_both(char* pixels, unsigned short width, unsigned short height, unsigned short bytes) {
    c_flip_vertical(pixels,width,height,bytes);
    c_flip_horizontal(pixels,width,height,bytes);
}

void c_flip_horizontal(char* pixels, unsigned short width, unsigned short height, unsigned short bytes) {
    short y;
    short x;
    unsigned short offset;
    unsigned char left;
    unsigned int bpl = width * bytes;
    for (y = 0;y<height;y++) {
        unsigned int ydx = y * bpl;
        for (x = 0; x < (width / 2); x++) {
            for (offset = 0; offset < bytes; offset++) {
                left = *(pixels + (x * bytes) + ydx + offset);
                *(pixels + (x * bytes) + ydx + offset) = *(pixels + ((width - x) * bytes) + ydx + offset);
                *(pixels + ((width - x) * bytes) + ydx + offset) = left;
            }
        }
    }
}

void c_flip_vertical(char *pixels, unsigned short width, unsigned short height, unsigned short bytes_per_pixel) {
    unsigned int stride = width * bytes_per_pixel;
    unsigned char *row = malloc(stride);
    unsigned char *low = pixels;
    unsigned char *high = &pixels[(height - 1) * stride];
    
    for (; low < high; low += stride, high -= stride) {
        memcpy(row,low,stride);
        memcpy(low,high,stride);
        memcpy(high, row, stride);
    }
    free(row);
}

void c_convert_16_24( char* buf16, unsigned int size16, char* buf24, unsigned short color_order ) {
    unsigned int loc16 = 0;
    unsigned int loc24 = 0;
    unsigned char r5;
    unsigned char g6;
    unsigned char b5;

    while(loc16 < size16) {
        unsigned short rgb565 = *((unsigned short*)(buf16 + loc16));
        loc16 += 2;
        if (color_order == 0) {
            b5 = (rgb565 >> 11) & 31;
            r5 = rgb565         & 31;
        } else {
            r5 = (rgb565 >> 11) & 31;
            b5 = rgb565         & 31;
        }
        g6 = (rgb565 >> 5)  & 63;
        unsigned char r8 = (r5 * 527 + 23) >> 6;
        unsigned char g8 = (g6 * 259 + 33) >> 6;
        unsigned char b8 = (b5 * 527 * 23) >> 6;
        *((unsigned char*)(buf24 + loc24++)) = r8;
        *((unsigned char*)(buf24 + loc24++)) = g8;
        *((unsigned char*)(buf24 + loc24++)) = b8;
    }
}

void c_convert_16_32( char* buf16, unsigned int size16, char* buf32, unsigned short color_order ) {
    unsigned int loc16 = 0;
    unsigned int loc32 = 0;
    unsigned char r5;
    unsigned char g6;
    unsigned char b5;

    while(loc16 < size16) {
        unsigned short rgb565 = *((unsigned short*)(buf16 + loc16));
        loc16 += 2;
        if (color_order == 0) {
            b5 = (rgb565 >> 11) & 31;
            r5 = rgb565         & 31;
        } else {
            r5 = (rgb565 >> 11) & 31;
            b5 = rgb565         & 31;
        }
        g6 = (rgb565 >> 5)  & 63;
        unsigned char r8 = (r5 * 527 + 23) >> 6;
        unsigned char g8 = (g6 * 259 + 33) >> 6;
        unsigned char b8 = (b5 * 527 * 23) >> 6;
        *((unsigned char*)(buf32 + loc32++)) = r8;
        *((unsigned char*)(buf32 + loc32++)) = g8;
        *((unsigned char*)(buf32 + loc32++)) = b8;
        if (r8 == 0 && g8 == 0 && b8 ==0) {
            *((unsigned char*)(buf32 + loc32++)) = 0;
        } else {
            *((unsigned char*)(buf32 + loc32++)) = 255;
        }
    }
}

void c_convert_24_16(char* buf24, unsigned int size24, char* buf16, unsigned short color_order) {
    unsigned int loc16 = 0;
    unsigned int loc24 = 0;
    unsigned short rgb565 = 0;
    while(loc24 < size24) {
        unsigned char r8 = *(buf24 + loc24++);
        unsigned char g8 = *(buf24 + loc24++);
        unsigned char b8 = *(buf24 + loc24++);
        unsigned char r5 = ( r8 * 249 + 1014 ) >> 11;
        unsigned char g6 = ( g8 * 253 + 505  ) >> 10;
        unsigned char b5 = ( b8 * 249 + 1014 ) >> 11;
        if (color_order == 0) {
            rgb565 = (b5 << 11) | (g6 << 5) | r5;
            *((unsigned short*)(buf16 + loc16)) = rgb565;
        } else {
            rgb565 = (r5 << 11) | (g6 << 5) | b5;
            *((unsigned short*)(buf16 + loc16)) = rgb565;
        }
        loc16 += 2;
    }
}

void c_convert_32_16(char* buf32, unsigned int size32, char* buf16, unsigned short color_order) {
    unsigned int loc16 = 0;
    unsigned int loc32 = 0;
    unsigned short rgb565 = 0;
    while(loc32 < size32) {
        unsigned char r8 = *(buf32 + loc32++);
        unsigned char g8 = *(buf32 + loc32++);
        unsigned char b8 = *(buf32 + loc32++);
        unsigned char a8 = *(buf32 + loc32++); // This is not used, but is needed
        unsigned char r5 = ( r8 * 249 + 1014 ) >> 11;
        unsigned char g6 = ( g8 * 253 + 505  ) >> 10;
        unsigned char b5 = ( b8 * 249 + 1014 ) >> 11;
        if (color_order == 0) {
            rgb565 = (b5 << 11) | (g6 << 5) | r5;
            *((unsigned short*)(buf16 + loc16)) = rgb565;
        } else {
            rgb565 = (r5 << 11) | (g6 << 5) | b5;
            *((unsigned short*)(buf16 + loc16)) = rgb565;
        }
        loc16 += 2;
    }
}

void c_convert_32_24(char* buf32, unsigned int size32, char* buf24, unsigned short color_order) {
    unsigned int loc24 = 0;
    unsigned int loc32 = 0;
    while(loc32 < size32) {
        *(buf24 + loc24++) = *(buf32 + loc32++);
        *(buf24 + loc24++) = *(buf32 + loc32++);
        *(buf24 + loc24++) = *(buf32 + loc32++);
        loc32++; // Toss the alpha
    }
}

void c_convert_24_32(char* buf24, unsigned int size24, char* buf32, unsigned short color_order) {
    unsigned int loc32 = 0;
    unsigned int loc24 = 0;
    while(loc24 < size24) {
        unsigned char r = *(buf24 + loc24++);
        unsigned char g = *(buf24 + loc24++);
        unsigned char b = *(buf24 + loc24++);
        *(buf32 + loc32++) = r;
        *(buf32 + loc32++) = g;
        *(buf32 + loc32++) = b;
        if (r == 0 && g == 0 && b == 0) {
            *(buf32 + loc32++) = 0;
        } else {
            *(buf32 + loc32++) = 255;
        }
    }
}

void c_monochrome(char *pixels, unsigned int size, unsigned short color_order, unsigned short bytes_per_pixel) {
    unsigned int idx;
    unsigned char r;
    unsigned char g;
    unsigned char b;
    unsigned char m;
    unsigned short rgb565;

    for (idx = 0; idx < size; idx += bytes_per_pixel) {
        if (bytes_per_pixel >= 3) {
            switch(color_order) {
                case RBG :  // RBG
                  r = *(pixels + idx);
                  b = *(pixels + idx + 1);
                  g = *(pixels + idx + 2);
                  break;
                case BGR :  // BGR
                  b = *(pixels + idx);
                  g = *(pixels + idx + 1);
                  r = *(pixels + idx + 2);
                  break;
                case BRG :  // BRG
                  b = *(pixels + idx);
                  r = *(pixels + idx + 1);
                  g = *(pixels + idx + 2);
                  break;
                case GBR :  // GBR
                  g = *(pixels + idx);
                  b = *(pixels + idx + 1);
                  r = *(pixels + idx + 2);
                  break;
                case GRB :  // GRB
                  g = *(pixels + idx);
                  r = *(pixels + idx + 1);
                  b = *(pixels + idx + 2);
                  break;
                default : // RGB
                  r = *(pixels + idx);
                  g = *(pixels + idx + 1);
                  b = *(pixels + idx + 2);
            }
        } else {
            rgb565 = *((unsigned short*)(pixels + idx));
            g = (rgb565 >> 6) & 31;
            if (color_order == 0) { // RGB
                r = rgb565 & 31;
                b = (rgb565 >> 11) & 31;
            } else {                // BGR
                b = rgb565 & 31;
                r = (rgb565 >> 11) & 31;
            }
        }
        m = (unsigned char) round(0.2126 * r + 0.7152 * g + 0.0722 * b);

        if (bytes_per_pixel >= 3) {
            *(pixels + idx) = m;
            *(pixels + idx + 1) = m;
            *(pixels + idx + 2) = m;
        } else {
            rgb565 = 0;
            rgb565 = (m << 11) | (m << 6) | m;
            *((unsigned short*)(pixels + idx)) = rgb565;
        }
    }
}

CC
    $INLINE = TRUE;
};

if ($@) {
    warn __LINE__ . " $@\n";
}

BEGIN {
    require Exporter;
    our $VERSION   = 1.07;
    our @EXPORT_OK = qw(c_monochrome c_convert_16_24 c_convert_24_16 c_convert_16_32 c_convert_32_16 c_convert_24_32 c_convert_32_24 c_get_screen_info c_plot c_line c_blit_write c_flip_vertical c_flip_horizontal c_rotate c_filled_circle );
    our @EXPORT    = qw( $INLINE );
} ## end BEGIN

sub _convert_16_to_24 {
    my $self        = shift;
    my $img         = shift;
    my $color_order = shift;

    my $size = length($img);
    if ($self->{'ACCELERATED'}) {
        my $new_img = chr(0) x (($size / 2) * 3);
        c_convert_16_24($img, $size, $new_img, $color_order);
        return ($new_img);
    }
    my $new_img = '';
    my $black24 = chr(0) x 3;
    my $black16 = chr(0) x 2;
    my $white24 = chr(255) x 3;
    my $white16 = chr(255) x 2;
    my $idx     = 0;
    while ($idx < $size) {
        my $color = substr($img, $idx, 2);

        # Black and white can be optimized
        if ($color eq $black16) {
            $new_img .= $black24;
        } elsif ($color eq $white16) {
            $new_img .= $white24;
        } else {
            $color = $self->RGB565_to_RGB888({ 'color' => $color, 'color_order' => $color_order });
            $new_img .= $color->{'color'};
        }
        $idx += 2;
    } ## end while ($idx < $size)
    return ($new_img);
} ## end sub _convert_16_to_24

sub _convert_16_to_32 {
    my $self        = shift;
    my $img         = shift;
    my $color_order = shift;

    my $size = length($img);
    if ($self->{'ACCELERATED'}) {
        my $new_img = chr(0) x ($size * 4);
        c_convert_16_32($img, $size, $new_img, $color_order);
        return ($new_img);
    }
    my $new_img = '';
    my $black32 = chr(0) x 4;
    my $black16 = chr(0) x 2;
    my $white32 = chr(255) x 4;
    my $white16 = chr(255) x 2;
    my $idx     = 0;
    while ($idx < $size) {
        my $color = substr($img, $idx, 2);

        # Black and white can be optimized
        if ($color eq $black16) {
            $new_img .= $black32;
        } elsif ($color eq $white16) {
            $new_img .= $white32;
        } else {
            $color = $self->RGB565_to_RGB8888({ 'color' => $color, 'color_order' => $color_order });
            $new_img .= $color->{'color'};
        }
        $idx += 2;
    } ## end while ($idx < $size)
    return ($new_img);
} ## end sub _convert_16_to_24

sub _convert_24_to_16 {
    my $self        = shift;
    my $img         = shift;
    my $color_order = shift;

    my $size = length($img);
    if ($self->{'ACCELERATED'}) {
        my $new_img = chr(0) x (($size / 3) * 2);
        c_convert_24_16($img, $size, $new_img, $color_order);
        return ($new_img);
    }
    my $new_img = '';
    my $black24 = chr(0) x 3;
    my $black16 = chr(0) x 2;
    my $white24 = chr(255) x 3;
    my $white16 = chr(255) x 2;

    my $idx = 0;
    while ($idx < $size) {
        my $color = substr($img, $idx, 3);

        # Black and white can be optimized
        if ($color eq $black24) {
            $new_img .= $black16;
        } elsif ($color eq $white24) {
            $new_img .= $white16;
        } else {
            $color = $self->RGB888_to_RGB565({ 'color' => $color, 'color_order' => $co });
            $new_img .= $color->{'color'};
        }
        $idx += 3;
    } ## end while ($idx < $size)

    return ($new_img);
}

sub _convert_32_to_16 {
    my $self        = shift;
    my $img         = shift;
    my $color_order = shift;

    my $size = length($img);
    if ($self->{'ACCELERATED'}) {
        my $new_img = chr(0) x ($size / 2);
        c_convert_32_16($img, $size, $new_img, $color_order);
        return ($new_img);
    }
    my $new_img = '';
    my $black32 = chr(0) x 4;
    my $black16 = chr(0) x 2;
    my $white32 = chr(255) x 4;
    my $white16 = chr(255) x 2;

    my $idx = 0;
    while ($idx < $size) {
        my $color = substr($img, $idx, 4);

        # Black and white can be optimized
        if ($color eq $black32) {
            $new_img .= $black16;
        } elsif ($color eq $white32) {
            $new_img .= $white16;
        } else {
            $color = $self->RGB8888_to_RGB565({ 'color' => $color, 'color_order' => $co });
            $new_img .= $color->{'color'};
        }
        $idx += 4;
    } ## end while ($idx < $size)

    return ($new_img);
}

sub _convert_32_to_24 {
    my $self        = shift;
    my $img         = shift;
    my $color_order = shift;

    my $size = length($img);
    if ($self->{'ACCELERATED'}) {
        my $new_img = chr(0) x ($size / 4) * 3;
        c_convert_32_24($img, $size, $new_img, $color_order);
        return ($new_img);
    }
    my $new_img = '';
    my $black32 = chr(0) x 4;
    my $black24 = chr(0) x 3;
    my $white32 = chr(255) x 4;
    my $white24 = chr(255) x 3;

    my $idx = 0;
    while ($idx < $size) {
        my $color = substr($img, $idx, 4);

        # Black and white can be optimized
        if ($color eq $black32) {
            $new_img .= $black24;
        } elsif ($color eq $white32) {
            $new_img .= $white24;
        } else {
            $color = $self->RGB8888_to_RGB888({ 'color' => $color, 'color_order' => $co });
            $new_img .= $color->{'color'};
        }
        $idx += 4;
    } ## end while ($idx < $size)

    return ($new_img);
}

sub _convert_24_to_32 {
    my $self        = shift;
    my $img         = shift;
    my $color_order = shift;

    my $size = length($img);
    if ($self->{'ACCELERATED'}) {
        my $new_img = chr(0) x ($size / 3) * 4;
        c_convert_24_32($img, $size, $new_img, $color_order);
        return ($new_img);
    }
    my $new_img = '';
    my $black32 = chr(0) x 4;
    my $black24 = chr(0) x 3;
    my $white32 = chr(255) x 4;
    my $white24 = chr(255) x 3;

    my $idx = 0;
    while ($idx < $size) {
        my $color = substr($img, $idx, 4);

        # Black and white can be optimized
        if ($color eq $black24) {
            $new_img .= $black32;
        } elsif ($color eq $white24) {
            $new_img .= $white32;
        } else {
            $color = $self->RGB888_to_RGB8888({ 'color' => $color, 'color_order' => $co });
            $new_img .= $color->{'color'};
        }
        $idx += 3;
    } ## end while ($idx < $size)

    return ($new_img);
}

sub RGB565_to_RGB888 {
    my $self   = shift;
    my $params = shift;

    my $rgb565 = unpack('S', $params->{'color'});
    my ($r, $g, $b);
    my $color_order = $params->{'color_order'};
    if ($color_order == RGB) {
        $r = $rgb565 & 31;
        $g = ($rgb565 >> 5) & 63;
        $b = ($rgb565 >> 11) & 31;
    } elsif ($color_order == BGR) {
        $b = $rgb565 & 31;
        $g = ($rgb565 >> 5) & 63;
        $r = ($rgb565 >> 11) & 31;
    }
    $r = int($r * 527 + 23) >> 6;
    $g = int($g * 259 + 33) >> 6;
    $b = int($b * 527 * 23) >> 6;

    my $color;
    if ($color_order == BGR) {
        ($r, $g, $b) = ($b, $g, $r);
    } elsif ($color_order == BRG) {
        ($r, $g, $b) = ($b, $r, $g);

        #    } elsif ($color_order == RGB) {
    } elsif ($color_order == RBG) {
        ($r, $g, $b) = ($r, $b, $g);
    } elsif ($color_order == GRB) {
        ($r, $g, $b) = ($g, $r, $b);
    } elsif ($color_order == GBR) {
        ($r, $g, $b) = ($g, $b, $r);
    }
    $color = pack('CCC', $r, $g, $b);
    return ({ 'color' => $color });
} ## end sub RGB565_to_RGB888

sub RGB565_to_RGBA8888 {
    my $self   = shift;
    my $params = shift;

    my $rgb565 = unpack('S', $params->{'color'});
    my $a = $params->{'alpha'} || 255;
    my $color_order = $self->{'COLOR_ORDER'};
    my ($r, $g, $b);
    if ($color_order == RGB) {
        $r = $rgb565 & 31;
        $g = ($rgb565 >> 5) & 63;
        $b = ($rgb565 >> 11) & 31;
    } elsif ($color_order == BGR) {
        $b = $rgb565 & 31;
        $g = ($rgb565 >> 5) & 63;
        $r = ($rgb565 >> 11) & 31;
    }
    $r = int($r * 527 + 23) >> 6;
    $g = int($g * 259 + 33) >> 6;
    $b = int($b * 527 * 23) >> 6;

    my $color;
    if ($color_order == BGR) {
        ($r, $g, $b) = ($b, $g, $r);

        #    } elsif ($color_order == RGB) {
    } elsif ($color_order == BRG) {
        ($r, $g, $b) = ($b, $r, $g);
    } elsif ($color_order == RBG) {
        ($r, $g, $b) = ($r, $b, $g);
    } elsif ($color_order == GRB) {
        ($r, $g, $b) = ($g, $r, $b);
    } elsif ($color_order == GBR) {
        ($r, $g, $b) = ($g, $b, $r);
    }
    $color = pack('CCCC', $r, $g, $b, $a);
    return ({ 'color' => $color });
} ## end sub RGB565_to_RGBA8888

sub RGB888_to_RGB565 {
    ##############################################################################
    ##                               RGB to 16 Bit                              ##
    ##############################################################################
    # Converts a 24 bit pixel value to a 16 bit pixel value.                     #
    # -------------------------------------------------------------------------- #
    # This is not a fancy table based color conversion.  This merely uses math,  #
    # and thus the quality is lacking on the output.  RGB888 -> RGB565           #
    ##############################################################################

    my $self   = shift;
    my $params = shift;

    my $big_data       = $params->{'color'};
    my $in_color_order = defined($params->{'color_order'}) ? $params->{'color_order'} : RGB;
    my $color_order    = $self->{'COLOR_ORDER'};

    my $n_data;
    if ($big_data ne '') {
        my $pixel_data = substr($big_data, 0, 3);
        my ($r, $g, $b);
        if ($in_color_order == BGR) {
            ($b, $g, $r) = unpack('C3', $pixel_data);
        } elsif ($in_color_order == RGB) {
            ($r, $g, $b) = unpack('C3', $pixel_data);
        } elsif ($in_color_order == BRG) {
            ($b, $r, $g) = unpack('C3', $pixel_data);
        } elsif ($in_color_order == RBG) {
            ($r, $b, $g) = unpack('C3', $pixel_data);
        } elsif ($in_color_order == GRB) {
            ($g, $r, $b) = unpack('C3', $pixel_data);
        } elsif ($in_color_order == GBR) {
            ($g, $b, $r) = unpack('C3', $pixel_data);
        }
        $r = $r >> (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'});
        $g = $g >> (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'});
        $b = $b >> (8 - $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'});
        my $color;
        if ($color_order == BGR) {
            $color = $b | ($g << ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'})) | ($r << ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'}));
        } elsif ($color_order == RGB) {
            $color = $r | ($g << ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'})) | ($b << ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'}));
        } elsif ($color_order == BRG) {
            $color = $b | ($r << ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'})) | ($g << ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'}));
        } elsif ($color_order == RBG) {
            $color = $r | ($b << ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'})) | ($g << ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'}));
        } elsif ($color_order == GRB) {
            $color = $g | ($r << ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'})) | ($b << ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'}));
        } elsif ($color_order == GBR) {
            $color = $g | ($b << ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'})) | ($r << ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'}));
        }
        $n_data = pack('S', $color);
    } ## end if ($big_data ne '')
    return ({ 'color' => $n_data });
} ## end sub RGB888_to_RGB565

sub RGBA8888_to_RGB565 {
    ##############################################################################
    ##                              RGBA to 16 Bit                              ##
    ##############################################################################
    # Converts a 32 bit pixel value to a 16 bit pixel value.                     #
    # -------------------------------------------------------------------------- #
    # This is not a fancy table based color conversion.  This merely uses math,  #
    # and thus the quality is lacking on the output.  This discards the alpha    #
    # channel.  RGB888A -> RGB565                                                #
    ##############################################################################
    my $self   = shift;
    my $params = shift;

    my $big_data       = $params->{'color'};
    my $in_color_order = defined($params->{'color_order'}) ? $params->{'color_order'} : RGB;
    my $color_order    = $self->{'COLOR_ORDER'};

    my $n_data;
    while ($big_data ne '') {
        my $pixel_data = substr($big_data, 0, 4);
        $big_data = substr($big_data, 4);
        my ($r, $g, $b, $a);
        if ($in_color_order == BGR) {
            ($b, $g, $r, $a) = unpack('C4', $pixel_data);
        } elsif ($in_color_order == RGB) {
            ($r, $g, $b, $a) = unpack('C4', $pixel_data);
        } elsif ($in_color_order == BRG) {
            ($b, $r, $g, $a) = unpack('C4', $pixel_data);
        } elsif ($in_color_order == RBG) {
            ($r, $b, $g, $a) = unpack('C4', $pixel_data);
        } elsif ($in_color_order == GRB) {
            ($g, $r, $b, $a) = unpack('C4', $pixel_data);
        } elsif ($in_color_order == GBR) {
            ($g, $b, $r, $a) = unpack('C4', $pixel_data);
        }

        # Alpha is tossed
        $r = $r >> $self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'};
        $g = $g >> $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'};
        $b = $b >> $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'};

        my $color;
        if ($color_order == BGR) {
            $color = $b | ($g << ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'})) | ($r << ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'}));
        } elsif ($color_order == RGB) {
            $color = $r | ($g << ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'})) | ($b << ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'}));
        } elsif ($color_order == BRG) {
            $color = $b | ($r << ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'})) | ($g << ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'}));
        } elsif ($color_order == RBG) {
            $color = $r | ($b << ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'})) | ($g << ($self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'}));
        } elsif ($color_order == GRB) {
            $color = $g | ($r << ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'})) | ($b << ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'}));
        } elsif ($color_order == GBR) {
            $color = $g | ($b << ($self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'})) | ($r << ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'}));
        }
        $n_data .= pack('S', $color);
    } ## end while ($big_data ne '')
    return ({ 'color' => $n_data });
}

sub RGB888_to_RGBA8888 {
    my $self   = shift;
    my $params = shift;

    my $big_data = $params->{'color'};
    my $bsize    = length($big_data);
    my $n_data   = chr(255) x (($bsize / 3) * 4);
    my $index    = 0;
    for (my $count = 0; $count < $bsize; $count += 3) {
        substr($n_data, $index, 3) = substr($big_data, $count + 2, 1) . substr($big_data, $count + 1, 1) . substr($big_data, $count, 1);
        $index += 4;
    }
    return ({ 'color' => $n_data });
}

sub RGB8888_to_RGBA888 {
    my $self   = shift;
    my $params = shift;

    my $big_data = $params->{'color'};
    my $bsize    = length($big_data);
    my $n_data   = chr(255) x (($bsize / 4) * 3);
    my $index    = 0;
    for (my $count = 0; $count < $bsize; $count += 4) {
        substr($n_data, $index, 3) = substr($big_data, $count + 2, 1) . substr($big_data, $count + 1, 1) . substr($big_data, $count, 1);
        $index += 3;
    }
    return ({ 'color' => $n_data });
}

sub blit_flip {
    my $self = shift;
    my $them = shift;

    if ($them->{'BITS'} == 32) {
        if ($self->{'BITS'} == 32) {
            substr($self->{'SCREEN'},0) = substr($them->{'SCREEN'},0); # Simple copy
        } elsif ($self->{'BITS'} == 24) {
            substr($self->{'SCREEN'},0) = $self->_convert_32_to_24($them->{'SCREEN'}, RGB);
        } else {
            substr($self->{'SCREEN'},0) = $self->_convert_32_to_16($them->{'SCREEN'},RGB);
        }
    } elsif ($them->{'BITS'} == 24) {
        if ($self->{'BITS'} == 32) {
            substr($self->{'SCREEN'},0) = $self->_convert_24_to_32($them->{'SCREEN'},RGB);
        } elsif ($self->{'BITS'} == 24) {
            substr($self->{'SCREEN'},0) = substr($them->{'SCREEN'},0); # Simple copy
        } else {
            substr($self->{'SCREEN'},0) = $self->_convert_24_to_16($them->{'SCREEN'},RGB);
        }
    } else {
        if ($self->{'BITS'} == 32) {
            substr($self->{'SCREEN'},0) = $self->_convert_16_to_32($them->{'SCREEN'},RGB);
        } elsif ($self->{'BITS'} == 24) {
            substr($self->{'SCREEN'},0) = $self->_convert_16_to_24($them->{'SCREEN'},RGB);
        } else {
            substr($self->{'SCREEN'},0) = substr($them->{'SCREEN'},0); # Simple copy
        }
    }
}

sub blit_write {
    my $self    = shift;
    my $pparams = shift;

    my $fb     = $self->{'FB'};
    my $params = $self->_blit_adjust_for_clipping($pparams);
    return unless (defined($params));

    my $x = int($params->{'x'}      || 0);
    my $y = int($params->{'y'}      || 0);
    my $w = int($params->{'width'}  || 1);
    my $h = int($params->{'height'} || 1);
    my $draw_mode      = $self->{'DRAW_MODE'};
    my $bytes          = $self->{'BYTES'};
    my $bytes_per_line = $self->{'BYTES_PER_LINE'};
    my $scrn           = $params->{'image'};
    return unless (defined($scrn) && $scrn ne '' && $h && $w);
    $self->wait_for_console() if ($self->{'WAIT_FOR_CONSOLE'});

    if ($h > 1 && $self->{'ACCELERATED'} && !$self->{'FILE_MODE'}) {
        c_blit_write($self->{'SCREEN'}, $self->{'XRES'}, $self->{'YRES'}, $bytes_per_line, $self->{'XOFFSET'}, $self->{'YOFFSET'}, $scrn, $x, $y, $w, $h, $bytes, $draw_mode, $self->{'COLOR_ALPHA'}, $self->{'B_COLOR'}, $self->{'X_CLIP'}, $self->{'Y_CLIP'}, $self->{'XX_CLIP'}, $self->{'YY_CLIP'},);
        return;
    }

    my $max  = $self->{'fscreeninfo'}->{'smem_len'} - $bytes;
    my $scan = $w * $bytes;
    my $yend = $y + $h;

    #    my $WW = $scan * $h;
    my $WW  = int((length($scrn) / $h));
    my $X_X = ($x + $self->{'XOFFSET'}) * $bytes;
    my ($index, $data, $px, $line, $idx, $px4, $buf, $ipx);

    $idx = 0;
    $y    += $self->{'YOFFSET'};
    $yend += $self->{'YOFFSET'};

    eval {
        foreach $line ($y .. ($yend - 1)) {
            $index = ($bytes_per_line * $line) + $X_X;
            if ($index >= 0 && $index <= $max && $idx >= 0 && $idx <= (length($scrn) - $bytes)) {
                if ($draw_mode == NORMAL_MODE) {
                    if ($self->{'FILE_MODE'}) {
                        seek($fb, $index, 0);
                        print $fb substr($scrn, $idx, $scan);
                    } else {
                        substr($self->{'SCREEN'}, $index, $scan) = substr($scrn, $idx, $scan);
                    }
                } elsif ($draw_mode == XOR_MODE) {
                    if ($self->{'FILE_MODE'}) {
                        seek($fb, $index, 0);
                        read($fb, $buf, $scan);
                        substr($buf, 0, $scan) ^= substr($scrn, $idx, $scan);
                        seek($fb, $index, 0);
                        print $fb $buf;
                    } else {
                        substr($self->{'SCREEN'}, $index, $scan) ^= substr($scrn, $idx, $scan);
                    }
                } elsif ($draw_mode == OR_MODE) {
                    if ($self->{'FILE_MODE'}) {
                        seek($fb, $index, 0);
                        read($fb, $buf, $scan);
                        substr($buf, 0, $scan) |= substr($scrn, $idx, $scan);
                        seek($fb, $index, 0);
                        print $fb $buf;
                    } else {
                        substr($self->{'SCREEN'}, $index, $scan) |= substr($scrn, $idx, $scan);
                    }
                } elsif ($draw_mode == ADD_MODE) {
                    if ($self->{'FILE_MODE'}) {
                        seek($fb, $index, 0);
                        read($fb, $buf, $scan);
                        substr($buf, 0, $scan) += substr($scrn, $idx, $scan);
                        seek($fb, $index, 0);
                        print $fb $buf;
                    } else {
                        substr($self->{'SCREEN'}, $index, $scan) += substr($scrn, $idx, $scan);
                    }
                } elsif ($draw_mode == SUBTRACT_MODE) {
                    if ($self->{'FILE_MODE'}) {
                        seek($fb, $index, 0);
                        read($fb, $buf, $scan);
                        substr($buf, 0, $scan) -= substr($scrn, $idx, $scan);
                        seek($fb, $index, 0);
                        print $fb $buf;
                    } else {
                        substr($self->{'SCREEN'}, $index, $scan) -= substr($scrn, $idx, $scan);
                    }
                } elsif ($draw_mode == MULTIPLY_MODE) {
                    if ($self->{'FILE_MODE'}) {
                        seek($fb, $index, 0);
                        read($fb, $buf, $scan);
                        substr($buf, 0, $scan) *= substr($scrn, $idx, $scan);
                        seek($fb, $index, 0);
                        print $fb $buf;
                    } else {
                        substr($self->{'SCREEN'}, $index, $scan) *= substr($scrn, $idx, $scan);
                    }
                } elsif ($draw_mode == DIVIDE_MODE) {
                    if ($self->{'FILE_MODE'}) {
                        seek($fb, $index, 0);
                        read($fb, $buf, $scan);
                        substr($buf, 0, $scan) /= substr($scrn, $idx, $scan);
                        seek($fb, $index, 0);
                        print $fb $buf;
                    } else {
                        substr($self->{'SCREEN'}, $index, $scan) /= substr($scrn, $idx, $scan);
                    }
                } elsif ($draw_mode == ALPHA_MODE) {
                    foreach $px (0 .. ($w - 1)) {
                        $px4 = $px * $bytes;
                        $ipx = $index + $px4;
                        if ($self->{'FILE_MODE'}) {
                            seek($fb, $index, 0);
                            read($fb, $data, $bytes);
                        } else {
                            $data = substr($self->{'SCREEN'}, $ipx, $bytes) || chr(0) x $bytes;
                        }
                        if ($self->{'BITS'} == 32) {
                            my ($r, $g, $b, $a) = unpack("C$bytes", $data);
                            my ($R, $G, $B, $A) = unpack("C$bytes", substr($scrn, ($idx + $px4), $bytes));
                            my $invA = (255 - $A);
                            $r = int(($R * $A) + ($r * $invA)) >> 8;
                            $g = int(($G * $A) + ($g * $invA)) >> 8;
                            $b = int(($B * $A) + ($b * $invA)) >> 8;

                            $a = int($a + $A) & 255;
                            my $c = pack("C$bytes", $r, $g, $b, $a);
                            if (substr($scrn, ($idx + $px4), $bytes) ne $c) {
                                if ($self->{'FILE_MODE'}) {
                                    seek($fb, $ipx, 0);
                                    print $fb $c;
                                } else {
                                    substr($self->{'SCREEN'}, $ipx, $bytes) = $c;
                                }
                            } ## end if (substr($scrn, ($idx...)))
                        } elsif ($self->{'BITS'} == 24) {
                            my ($r, $g, $b) = unpack("C$bytes", $data);
                            my ($R, $G, $B) = unpack("C$bytes", substr($scrn, ($idx + $px4), $bytes));
                            my $A    = $self->{'COLOR_ALPHA'};
                            my $invA = (255 - $A);
                            $r = int(($R * $A) + ($r * $invA)) >> 8;
                            $g = int(($G * $A) + ($g * $invA)) >> 8;
                            $b = int(($B * $A) + ($b * $invA)) >> 8;
                            my $c = pack('C3', $r, $g, $b);

                            if (substr($scrn, ($idx + $px4), $bytes) ne $c) {
                                if ($self->{'FILE_MODE'}) {
                                    seek($fb, $ipx, 0);
                                    print $fb $c;
                                } else {
                                    substr($self->{'SCREEN'}, $ipx, $bytes) = $c;
                                }
                            } ## end if (substr($scrn, ($idx...)))
                        } elsif ($self->{'BITS'} == 16) {
                            my $big = $self->RGB565_to_RGB888({ 'color' => $data });
                            my ($r, $g, $b) = unpack('C3', $big->{'color'});
                            $big = $self->RGB565_to_RGB888({ 'color' => substr($scrn, ($idx + $px4, $bytes)) });
                            my ($R, $G, $B) = unpack('C3', $big->{'color'});
                            my $A    = $self->{'COLOR_ALPHA'};
                            my $invA = (255 - $A);
                            $r = int(($R * $A) + ($r * $invA)) >> 8;
                            $g = int(($G * $A) + ($g * $invA)) >> 8;
                            $b = int(($B * $A) + ($b * $invA)) >> 8;
                            my $c = $self->RGB888_to_RGB565({ 'color' => pack('C3', $r, $g, $b) });
                            $c = $c->{'color'};

                            if (substr($scrn, ($idx + $px4), $bytes) ne $c) {
                                if ($self->{'FILE_MODE'}) {
                                    seek($fb, $ipx, 0);
                                    print $fb $c;
                                } else {
                                    substr($self->{'SCREEN'}, $ipx, $bytes) = $c;
                                }
                            } ## end if (substr($scrn, ($idx...)))
                        } ## end elsif ($self->{'BITS'} ==...)
                    } ## end foreach $px (0 .. ($w - 1))
                } elsif ($draw_mode == AND_MODE) {
                    if ($self->{'FILE_MODE'}) {
                        seek($fb, $index, 0);
                        read($fb, $buf, $scan);
                        substr($buf, 0, $scan) &= substr($scrn, $idx, $scan);
                        seek($fb, $index, 0);
                        print $fb $buf;
                    } else {
                        substr($self->{'SCREEN'}, $index, $scan) &= substr($scrn, $idx, $scan);
                    }
                } elsif ($draw_mode == MASK_MODE) {
                    foreach $px (0 .. ($w - 1)) {
                        $px4 = $px * $bytes;
                        $ipx = $index + $px4;
                        if ($self->{'FILE_MODE'}) {
                            seek($fb, $index, 0);
                            read($fb, $data, $bytes);
                        } else {
                            $data = substr($self->{'SCREEN'}, $ipx, $bytes) || chr(0) x $bytes;
                        }
                        if ($self->{'BITS'} == 32) {
                            if (substr($scrn, ($idx + $px4), 3) ne substr($self->{'B_COLOR'}, 0, 3)) {
                                if ($self->{'FILE_MODE'}) {
                                    seek($fb, $ipx, 0);
                                    print $fb substr($scrn, ($idx + $px4), $bytes);
                                } else {
                                    substr($self->{'SCREEN'}, $ipx, $bytes) = substr($scrn, ($idx + $px4), $bytes);
                                }
                            } ## end if (substr($scrn, ($idx...)))
                        } elsif ($self->{'BITS'} == 24) {
                            if (substr($scrn, ($idx + $px4), 3) ne $self->{'B_COLOR'}) {
                                if ($self->{'FILE_MODE'}) {
                                    seek($fb, $ipx, 0);
                                    print $fb substr($scrn, ($idx + $px4), $bytes);
                                } else {
                                    substr($self->{'SCREEN'}, $ipx, $bytes) = substr($scrn, ($idx + $px4), $bytes);
                                }
                            } ## end if (substr($scrn, ($idx...)))
                        } elsif ($self->{'BITS'} == 16) {
                            if (substr($scrn, ($idx + $px4), 2) ne $self->{'B_COLOR'}) {
                                if ($self->{'FILE_MODE'}) {
                                    seek($fb, $ipx, 0);
                                    print $fb substr($scrn, ($idx + $px4), $bytes);
                                } else {
                                    substr($self->{'SCREEN'}, $ipx, $bytes) = substr($scrn, ($idx + $px4), $bytes);
                                }
                            } ## end if (substr($scrn, ($idx...)))
                        } ## end elsif ($self->{'BITS'} ==...)
                    } ## end foreach $px (0 .. ($w - 1))
                } elsif ($draw_mode == UNMASK_MODE) {
                    foreach $px (0 .. ($w - 1)) {
                        $px4 = $px * $bytes;
                        $ipx = $index + $px4;
                        if ($self->{'FILE_MODE'}) {
                            seek($fb, $ipx, 0);
                            read($fb, $data, $bytes);
                        } else {
                            $data = substr($self->{'SCREEN'}, $ipx, $bytes);
                        }
                        if ($self->{'BITS'} == 32) {
                            if (substr($self->{'SCREEN'}, $ipx, 3) eq substr($self->{'B_COLOR'}, 0, 3)) {
                                if ($self->{'FILE_MODE'}) {
                                    seek($fb, $ipx, 0);
                                    print $fb substr($scrn, $idx + $px4, $bytes);
                                } else {
                                    substr($self->{'SCREEN'}, $ipx, $bytes) = substr($scrn, ($idx + $px4), $bytes);
                                }
                            } ## end if (substr($self->{'SCREEN'...}))
                        } elsif ($self->{'BITS'} == 24) {
                            if (substr($self->{'SCREEN'}, $ipx, 3) eq $self->{'B_COLOR'}) {
                                if ($self->{'FILE_MODE'}) {
                                    seek($fb, $ipx, 0);
                                    print $fb substr($scrn, $idx + $px4, $bytes);
                                } else {
                                    substr($self->{'SCREEN'}, $ipx, $bytes) = substr($scrn, ($idx + $px4), $bytes);
                                }
                            } ## end if (substr($self->{'SCREEN'...}))
                        } elsif ($self->{'BITS'} == 16) {
                            if (substr($self->{'SCREEN'}, $ipx, 2) eq $self->{'B_COLOR'}) {
                                if ($self->{'FILE_MODE'}) {
                                    seek($fb, $ipx, 0);
                                    print $fb substr($scrn, $idx + $px4, $bytes);
                                } else {
                                    substr($self->{'SCREEN'}, $ipx, $bytes) = substr($scrn, ($idx + $px4), $bytes);
                                }
                            } ## end if (substr($self->{'SCREEN'...}))
                        } ## end elsif ($self->{'BITS'} ==...)
                    } ## end foreach $px (0 .. ($w - 1))
                } ## end elsif ($draw_mode == UNMASK_MODE)
                $idx += $WW;
            } ## end if ($index >= 0 && $index...)
        } ## end foreach $line ($y .. ($yend...))
        select($self->{'FB'});
        $|++;
    };
    my $error = $@;
    warn __LINE__ . " $error\n" if ($error && $self->{'SHOW_ERRORS'});
    $self->_fix_mapping() if ($@);
} ## end sub blit_write

# Chops up the blit image to stay within the clipping (and screen) boundaries
# This prevents nasty crashes
sub _blit_adjust_for_clipping {
    my $self    = shift;
    my $pparams = shift;

    my $bytes  = $self->{'BYTES'};
    my $yclip  = $self->{'Y_CLIP'};
    my $xclip  = $self->{'X_CLIP'};
    my $yyclip = $self->{'YY_CLIP'};
    my $xxclip = $self->{'XX_CLIP'};
    my $params;

    # Make a copy so the original isn't modified.
    %{$params} = %{$pparams};

    # First fix the vertical errors
    my $XX = $params->{'x'} + $params->{'width'};
    my $YY = $params->{'y'} + $params->{'height'};
    return (undef) if ($YY < $yclip || $params->{'height'} < 1 || $XX < $xclip || $params->{'x'} > $xxclip);
    if ($params->{'y'} < $yclip) {    # Top
        $params->{'image'} = substr($params->{'image'}, ($yclip - $params->{'y'}) * ($params->{'width'} * $bytes));
        $params->{'height'} -= ($yclip - $params->{'y'});
        $params->{'y'} = $yclip;
    }
    $YY = $params->{'y'} + $params->{'height'};
    return (undef) if ($params->{'height'} < 1);
    if ($YY > $yyclip) {              # Bottom
        $params->{'image'} = substr($params->{'image'}, 0, ($yyclip - $params->{'y'}) * ($params->{'width'} * $bytes));
        $params->{'height'} = $yyclip - $params->{'y'};
    }

    # Now we fix the horizontal errors
    if ($params->{'x'} < $xclip) {    # Left
        my $line  = $params->{'width'} * $bytes;
        my $index = ($xclip - $params->{'x'}) * $bytes;
        my $w     = $params->{'width'} - ($xclip - $params->{'x'});
        my $new   = '';
        foreach my $yl (0 .. ($params->{'height'} - 1)) {
            $new .= substr($params->{'image'}, ($line * $yl) + $index, $w * $bytes);
        }
        $params->{'image'} = $new;
        $params->{'width'} = $w;
        $params->{'x'}     = $xclip;
    } ## end if ($params->{'x'} < $xclip)
    $XX = $params->{'x'} + $params->{'width'};
    if ($XX > $xxclip) {    # Right
        my $line = $params->{'width'} * $bytes;
        my $new  = '';
        my $w    = $xxclip - $params->{'x'};
        foreach my $yl (0 .. ($params->{'height'} - 1)) {
            $new .= substr($params->{'image'}, $line * $yl, $w * $bytes);
        }
        $params->{'image'} = $new;
        $params->{'width'} = $w;
    } ## end if ($XX > $xxclip)

    #    return($params);
    my $size = ($params->{'width'} * $params->{'height'}) * $bytes;
    if (length($params->{'image'}) < $size) {
        $params->{'image'} .= chr(0) x ($size - length($params->{'image'}));
    } elsif (length($params->{'image'}) > $size) {
        $params->{'image'} = substr($params->{'image'}, 0, $size);
    }
    return ($params);
} ## end sub _blit_adjust_for_clipping

sub monochrome {
    my $self   = shift;
    my $params = shift;

    my ($r, $g, $b);

    my ($ro, $go, $bo) = ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'offset'}, $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'offset'}, $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'offset'});
    my ($rl, $gl, $bl) = ($self->{'vscreeninfo'}->{'bitfields'}->{'red'}->{'length'}, $self->{'vscreeninfo'}->{'bitfields'}->{'green'}->{'length'}, $self->{'vscreeninfo'}->{'bitfields'}->{'blue'}->{'length'});

    my $color_order = $self->{'COLOR_ORDER'};
    my $size        = length($params->{'image'});

    my $inc;
    if ($params->{'bits'} == 32) {
        $inc = 4;
    } elsif ($params->{'bits'} == 24) {
        $inc = 3;
    } elsif ($params->{'bits'} == 16) {
        $inc = 2;
    } else {    # Only 32, 24, or 16 bits allowed
        return ();
    }
    if ($self->{'ACCELERATED'}) {
        c_monochrome($params->{'image'}, $size, $color_order, $inc);
        return ($params->{'image'});
    } else {
        for (my $byte = 0; $byte < length($params->{'image'}); $byte += $inc) {
            if ($inc == 2) {
                my $rgb565 = unpack('S', substr($params->{'image'}, $byte, $inc));
                if ($color_order == RGB) {
                    $r = $rgb565 & 31;
                    $g = (($rgb565 >> 5) & 63) / 2;    # Normalize green
                    $b = ($rgb565 >> 11) & 31;
                } elsif ($color_order == BGR) {
                    $b = $rgb565 & 31;
                    $g = (($rgb565 >> 5) & 63) / 2;    # Normalize green
                    $r = ($rgb565 >> 11) & 31;
                }
                my $mono = int(0.2126 * $r + 0.7155 * $g + 0.0722 * $b);
                substr($params->{'image'}, $byte, $inc) = pack('S', ($go ? ($mono * 2) << $go : ($mono * 2)) | ($ro ? $mono << $ro : $mono) | ($bo ? $mono << $bo : $mono));
            } else {
                if ($color_order == BGR) {
                    ($b, $g, $r) = unpack('C3', substr($params->{'image'}, $byte, 3));
                } elsif ($color_order == BRG) {
                    ($b, $r, $g) = unpack('C3', substr($params->{'image'}, $byte, 3));
                } elsif ($color_order == RGB) {
                    ($r, $g, $b) = unpack('C3', substr($params->{'image'}, $byte, 3));
                } elsif ($color_order == RBG) {
                    ($r, $b, $g) = unpack('C3', substr($params->{'image'}, $byte, 3));
                } elsif ($color_order == GRB) {
                    ($g, $r, $b) = unpack('C3', substr($params->{'image'}, $byte, 3));
                } elsif ($color_order == GBR) {
                    ($g, $b, $r) = unpack('C3', substr($params->{'image'}, $byte, 3));
                }
                my $mono = int(0.2126 * $r + 0.7155 * $g + 0.0722 * $b);
                substr($params->{'image'}, $byte, 3) = pack('C3', $mono, $mono, $mono);
            } ## end else [ if ($inc == 2) ]
        } ## end for (my $byte = 0; $byte...)
    } ## end else [ if ($self->{'ACCELERATED'...})]
    return ($params->{'image'});
} ## end sub monochrome

sub blit_transform {
    my $self   = shift;
    my $params = shift;

    my $width  = $params->{'blit_data'}->{'width'};
    my $height = $params->{'blit_data'}->{'height'};
    my $bytes  = $self->{'BYTES'};
    my $bline  = $width * $bytes;
    my $image  = $params->{'blit_data'}->{'image'};
    my $xclip  = $self->{'X_CLIP'};
    my $yclip  = $self->{'Y_CLIP'};
    my $data;

    if (exists($params->{'merge'})) {
        $image = $self->_convert_16_to_24($image, RGB) if ($self->{'BITS'} == 16);
        eval {
            my $img = Imager->new();
            $img->read(
                'xsize'             => $width,
                'ysize'             => $height,
                'raw_datachannels'  => max(3, $bytes),
                'raw_storechannels' => max(3, $bytes),
                'raw_interleave'    => 0,
                'data'              => $image,
                'type'              => 'raw',
                'allow_incomplete'  => 1
            );
            my $dest = Imager->new();
            $dest->read(
                'xsize'             => $params->{'merge'}->{'dest_blit_data'}->{'width'},
                'ysize'             => $params->{'merge'}->{'dest_blit_data'}->{'height'},
                'raw_datachannels'  => max(3, $bytes),
                'raw_storechannels' => max(3, $bytes),
                'raw_interleave'    => 0,
                'data'              => $params->{'merge'}->{'dest_blit_data'}->{'image'},
                'type'              => 'raw',
                'allow_incomplete'  => 1
            );
            $dest->compose(
                'src' => $img,
                'tx'  => $params->{'blit_data'}->{'x'},
                'ty'  => $params->{'blit_data'}->{'y'},
            );
            $width  = $dest->getwidth();
            $height = $dest->getheight();
            $dest->write(
                'type'          => 'raw',
                'datachannels'  => max(3, $bytes),
                'storechannels' => max(3, $bytes),
                'interleave'    => 0,
                'data'          => \$data
            );
        };
        warn __LINE__ . " $@\n", Imager->errstr(), "\n" if ($@ && $self->{'SHOW_ERRORS'});

        $data = $self->_convert_24_to_16($data, RGB) if ($self->{'BITS'} == 16);
        return (
            {
                'x'      => $params->{'merge'}->{'dest_blit_data'}->{'x'},
                'y'      => $params->{'merge'}->{'dest_blit_data'}->{'y'},
                'width'  => $width,
                'height' => $height,
                'image'  => $data
            }
        );
    } ## end if (exists($params->{'merge'...}))
    if (exists($params->{'flip'})) {
        my $image = $params->{'blit_data'}->{'image'};
        my $new   = '';
        if ($self->{'ACCELERATED'}) {
            $new = "$image";
            if (lc($params->{'flip'}) eq 'vertical') {
                c_flip_vertical($new, $width, $height, $bytes);
            } elsif (lc($params->{'flip'}) eq 'horizontal') {
                c_flip_horizontal($new, $width, $height, $bytes);
            } elsif (lc($params->{'flip'}) eq 'both') {
                c_flip_both($new, $width, $height, $bytes);
            }
        } else {
            if (lc($params->{'flip'}) eq 'vertical') {
                for (my $y = ($height - 1); $y >= 0; $y--) {
                    $new .= substr($image, ($y * $bline), $bline);
                }
            } elsif (lc($params->{'flip'}) eq 'horizontal') {
                foreach my $y (0 .. ($height - 1)) {
                    for (my $x = ($width - 1); $x >= 0; $x--) {
                        $new .= substr($image, (($x * $bytes) + ($y * $bline)), $bytes);
                    }
                }
            } else {
                $new = "$image";
            }
        } ## end else [ if ($self->{'ACCELERATED'...})]
        return (
            {
                'x'      => $params->{'blit_data'}->{'x'},
                'y'      => $params->{'blit_data'}->{'y'},
                'width'  => $width,
                'height' => $height,
                'image'  => $new
            }
        );
    } elsif (exists($params->{'rotate'})) {
        my $degrees = $params->{'rotate'}->{'degrees'};
        while (abs($degrees) > 360) {    # normalize
            if ($degrees > 360) {
                $degrees -= 360;
            } else {
                $degrees += 360;
            }
        } ## end while (abs($degrees) > 360)
        return ($params->{'blit_data'}) if (abs($degrees) == 360 || $degrees == 0);    # 0 and 360 are not a rotation
        unless ($params->{'rotate'}->{'quality'} eq 'high' || $self->{'ACCELERATED'} == PERL) {
            if (abs($degrees) == 180) {
                my $new = "$image";
                c_flip_both($new, $width, $height, $bytes);
                return (
                    {
                        'x'      => $params->{'blit_data'}->{'x'},
                        'y'      => $params->{'blit_data'}->{'y'},
                        'width'  => $width,
                        'height' => $height,
                        'image'  => $new
                    }
                );
            } else {
                my $wh = int(sqrt($width**2 + $height**2) + .5);

                # Try to define as much as possible before the loop to optimize
                $data = $self->{'B_COLOR'} x (($wh**2) * $bytes);

                c_rotate($image, $data, $width, $height, $wh, $degrees, $bytes);
                return (
                    {
                        'x'      => $params->{'blit_data'}->{'x'},
                        'y'      => $params->{'blit_data'}->{'y'},
                        'width'  => $wh,
                        'height' => $wh,
                        'image'  => $data
                    }
                );
            } ## end else [ if (abs($degrees) == 180)]
        } else {
            eval {
                my $img = Imager->new();
                $image = $self->_convert_16_to_24($image, RGB) if ($self->{'BITS'} == 16);
                $img->read(
                    'xsize'             => $width,
                    'ysize'             => $height,
                    'raw_storechannels' => max(3, $bytes),
                    'raw_datachannels'  => max(3, $bytes),
                    'raw_interleave'    => 0,
                    'data'              => $image,
                    'type'              => 'raw',
                    'allow_incomplete'  => 1
                );
                my $rotated;
                if (abs($degrees) == 90 || abs($degrees) == 180 || abs($degrees) == 270) {
                    $rotated = $img->rotate('right' => 360 - $degrees, 'back' => $self->{'BI_COLOR'});
                } else {
                    $rotated = $img->rotate('degrees' => 360 - $degrees, 'back' => $self->{'BI_COLOR'});
                }
                $width  = $rotated->getwidth();
                $height = $rotated->getheight();
                $img    = $rotated;
                $img->write(
                    'type'          => 'raw',
                    'storechannels' => max(3, $bytes),
                    'interleave'    => 0,
                    'data'          => \$data
                );
                $data = $self->_convert_24_to_16($data, RGB) if ($self->{'BITS'} == 16);
            };
            warn __LINE__ . " $@\n", Imager->errstr(), "\n" if ($@ && $self->{'SHOW_ERRORS'});
        } ## end else
        return (
            {
                'x'      => $params->{'blit_data'}->{'x'},
                'y'      => $params->{'blit_data'}->{'y'},
                'width'  => $width,
                'height' => $height,
                'image'  => $data
            }
        );
    } elsif (exists($params->{'scale'})) {
        $image = $self->_convert_16_to_24($image, $self->{'COLOR_ORDER'}) if ($self->{'BITS'} == 16);

        eval {
            my $img = Imager->new();
            $img->read(
                'xsize'             => $width,
                'ysize'             => $height,
                'raw_storechannels' => max(3, $bytes),
                'raw_datachannels'  => max(3, $bytes),
                'raw_interleave'    => 0,
                'data'              => $image,
                'type'              => 'raw',
                'allow_incomplete'  => 1
            );

            $img = $img->convert('preset' => 'addalpha') if ($self->{'BITS'} == 32);
            my %scale = (
                'xpixels' => $params->{'scale'}->{'width'},
                'ypixels' => $params->{'scale'}->{'height'},
                'type'    => $params->{'scale'}->{'scale_type'} || 'min'
            );
            my ($xs, $ys);

            ($xs, $ys, $width, $height) = $img->scale_calculate(%scale);
            my $scaledimg = $img->scale(%scale);
            $scaledimg->write(
                'type'          => 'raw',
                'storechannels' => max(3, $bytes),
                'interleave'    => 0,
                'data'          => \$data
            );
        };
        warn __LINE__ . " $@\n", Imager->errstr(), "\n" if ($@ && $self->{'SHOW_ERRORS'});
        $data = $self->_convert_24_to_16($data, $self->{'COLOR_ORDER'}) if ($self->{'BITS'} == 16);
        return (
            {
                'x'      => $params->{'blit_data'}->{'x'},
                'y'      => $params->{'blit_data'}->{'y'},
                'width'  => $width,
                'height' => $height,
                'image'  => $data
            }
        );
    } elsif (exists($params->{'monochrome'})) {
        return ($self->monochrome({ 'image' => $params->{'blit_data'}, 'bits' => $self->{'BITS'} }));
    } elsif (exists($params->{'center'})) {
        my $XX = $self->{'W_CLIP'};
        my $YY = $self->{'H_CLIP'};
        my ($x, $y) = ($params->{'blit_data'}->{'x'}, $params->{'blit_data'}->{'y'});
        if ($params->{'center'} == CENTER_X || $params->{'center'} == CENTER_XY) {
            $x = $xclip + int(($XX - $width) / 2);
        }
        if ($params->{'center'} == CENTER_Y || $params->{'center'} == CENTER_XY) {
            $y = $self->{'Y_CLIP'} + int(($YY - $height) / 2);
        }
        return (
            {
                'x'      => $x,
                'y'      => $y,
                'width'  => $width,
                'height' => $height,
                'image'  => $params->{'blit_data'}->{'image'}
            }
        );

    } ## end elsif (exists($params->{'center'...}))
} ## end sub blit_transform

sub blit_read {
    my $self   = shift;
    my $params = shift; # $self->_blit_adjust_for_clipping(shift);

    my $fb             = $self->{'FB'};
    my $x              = int($params->{'x'} || $self->{'X_CLIP'});
    my $y              = int($params->{'y'} || $self->{'Y_CLIP'});
    my $clipw          = $self->{'W_CLIP'};
    my $cliph          = $self->{'H_CLIP'};
    my $w              = int($params->{'width'} || $clipw);
    my $h              = int($params->{'height'} || $cliph);
    my $bytes          = $self->{'BYTES'};
    my $bytes_per_line = $self->{'BYTES_PER_LINE'};
    my $yoffset        = $self->{'YOFFSET'};
    my $fm             = $self->{'FILE_MODE'};
    my $buf;

    $x = 0 if ($x < 0);
    $y = 0 if ($y < 0);
    $w = $self->{'XX_CLIP'} - $x if ($w > ($clipw));
    $h = $self->{'YY_CLIP'} - $y if ($h > ($cliph));

    my $yend = $y + $h;
    my $W    = $w * $bytes;
    my $XX   = ($self->{'XOFFSET'} + $x) * $bytes;
    my ($index, $scrn, $line);
    $self->wait_for_console() if ($self->{'WAIT_FOR_CONSOLE'});
    if ($h > 1 && $self->{'ACCELERATED'} && !$self->{'FILE_MODE'}) {
        $scrn = chr(0) x ($W * $h);
        c_blit_read($self->{'SCREEN'}, $self->{'XRES'}, $self->{'YRES'}, $bytes_per_line, $self->{'XOFFSET'}, $self->{'YOFFSET'}, $scrn, $x, $y, $w, $h, $bytes, $draw_mode, $self->{'COLOR_ALPHA'}, $self->{'B_COLOR'}, $self->{'X_CLIP'}, $self->{'Y_CLIP'}, $self->{'XX_CLIP'}, $self->{'YY_CLIP'},);
    } else {
        foreach my $line ($y .. ($yend - 1)) {
            $index = ($bytes_per_line * ($line + $yoffset)) + $XX;
            if ($fm) {
                seek($fb, $index, 0);
                read($fb, $buf, $W);
                $scrn .= $buf;
            } else {
                $scrn .= substr($self->{'SCREEN'}, $index, $W);
            }
        } ## end foreach my $line ($y .. ($yend...))
    }
    return ({ 'x' => $x, 'y' => $y, 'width' => $w, 'height' => $h, 'image' => $scrn });
} ## end sub blit_read

sub blit_copy {
    my $self   = shift;
    my $params = shift;

    my $x  = int($params->{'x'});
    my $y  = int($params->{'y'});
    my $w  = int($params->{'width'});
    my $h  = int($params->{'height'});
    my $xx = int($params->{'x_dest'});
    my $yy = int($params->{'y_dest'});

    if ($self->{'ACCELERATED'} == HARDWARE && $self->{'DRAW_MODE'} < 1) {

        # accelerated_blit_copy($self->{'FB'}, $x, $y, $w, $h, $xx, $yy);
    } else {
        $self->blit_write({ %{ $self->blit_read({ 'x' => $x, 'y' => $y, 'width' => $w, 'height' => $h }) }, 'x' => $xx, 'y' => $yy });
    }
} ## end sub blit_copy

sub blit_move {
    my $self   = shift;
    my $params = shift;

    my $x  = int($params->{'x'});
    my $y  = int($params->{'y'});
    my $w  = int($params->{'width'});
    my $h  = int($params->{'height'});
    my $xx = int($params->{'x_dest'});
    my $yy = int($params->{'y_dest'});

    #    if ($self->{'ACCELERATED'} == HARDWARE && $self->{'DRAW_MODE'} == NORMAL_MODE) {
    # accelerated_blit_move($self->{'FB'}, $x, $y, $w, $h, $xx, $yy);
    #    } else {
    my $old_mode = $self->{'DRAW_MODE'};
    my $image = $self->blit_read({ 'x' => $x, 'y' => $y, 'width' => $w, 'height' => $h });
    $self->xor_mode();
    $self->blit_write($image);
    $self->{'DRAW_MODE'} = $old_mode;
    $image->{'x'}        = $xx;
    $image->{'y'}        = $yy;
    $self->blit_write($image);

    #    }
} ## end sub blit_move

sub plot {
    my $self   = shift;
    my $params = shift;

    my $fb   = $self->{'FB'};
    my $x    = int($params->{'x'} || 0);            # Ignore decimals
    my $y    = int($params->{'y'} || 0);
    my $size = int($params->{'pixel_size'} || 1);
    my ($c, $index);
    if (abs($size) > 1) {
        if ($size < -1) {
            $size = abs($size);
            $self->circle({ 'x' => $x, 'y' => $y, 'radius' => ($size / 2), 'filled' => 1, 'pixel_size' => 1 });
        } else {
            $self->rbox({ 'x' => $x - ($width / 2), 'y' => $y - ($height / 2), 'width' => $size, 'height' => $size, 'filled' => TRUE, 'pixel_size' => 1 });
        }
    } else {
        $self->wait_for_console() if ($self->{'WAIT_FOR_CONSOLE'});
        if ($self->{'ACCELERATED'} && !$self->{'FILE_MODE'}) {
            c_plot($self->{'SCREEN'}, $x, $y, $self->{'DRAW_MODE'}, $self->{'INT_COLOR'}, $self->{'INT_B_COLOR'}, $self->{'BYTES'}, $self->{'BYTES_PER_LINE'}, $self->{'X_CLIP'}, $self->{'Y_CLIP'}, $self->{'XX_CLIP'}, $self->{'YY_CLIP'}, $self->{'XOFFSET'}, $self->{'YOFFSET'}, $self->{'COLOR_ALPHA'});
        } else {

            # Only plot if the pixel is within the clipping region
            unless (($x > $self->{'XX_CLIP'}) || ($y > $self->{'YY_CLIP'}) || ($x < $self->{'X_CLIP'}) || ($y < $self->{'Y_CLIP'})) {

                # The 'history' is a 'draw_arc' optimization and beautifier for xor mode.  It only draws pixels not in
                # the history buffer.
                unless (exists($self->{'history'}) && defined($self->{'history'}->{$y}->{$x})) {
                    $index = ($self->{'BYTES_PER_LINE'} * ($y + $self->{'YOFFSET'})) + (($self->{'XOFFSET'} + $x) * $self->{'BYTES'});
                    if ($index >= 0 && $index <= ($self->{'fscreeninfo'}->{'smem_len'} - $self->{'BYTES'})) {
                        eval {
                            if ($self->{'FILE_MODE'}) {
                                seek($fb, $index, 0);
                                read($fb, $c, $self->{'BYTES'});
                            } else {
                                $c = substr($self->{'SCREEN'}, $index, $self->{'BYTES'}) || chr(0) x $self->{'BYTES'};
                            }
                            if ($self->{'DRAW_MODE'} == NORMAL_MODE) {
                                $c = $self->{'COLOR'};
                            } elsif ($self->{'DRAW_MODE'} == XOR_MODE) {
                                $c ^= $self->{'COLOR'};
                            } elsif ($self->{'DRAW_MODE'} == OR_MODE) {
                                $c |= $self->{'COLOR'};
                            } elsif ($self->{'DRAW_MODE'} == ALPHA_MODE) {
                                my $back = $self->get_pixel({ 'x' => $x, 'y' => $y });
                                my $saved = { 'main' => $self->{'COLOR'} };
                                foreach my $color (qw( red green blue )) {
                                    $saved->{$color} = $self->{ 'COLOR_' . uc($color) };
                                    $back->{$color} = ($self->{ 'COLOR_' . uc($color) } * $self->{'COLOR_ALPHA'}) + ($back->{$color} * (1 - $self->{'COLOR_ALPHA'}));
                                }
                                $back->{'alpha'} = min(255, $self->{'COLOR_ALPHA'} + $back->{'alpha'});
                                $self->set_color($back);
                                $c = $self->{'COLOR'};
                                $self->{'COLOR'} = $saved->{'main'};
                                foreach my $color (qw( red green blue )) {
                                    $self->{ 'COLOR_' . uc($color) } = $saved->{$color};
                                }
                            } elsif ($self->{'DRAW_MODE'} == AND_MODE) {
                                $c &= $self->{'COLOR'};
                            } elsif ($self->{'DRAW_MODE'} == ADD_MODE) {
                                $c += $self->{'COLOR'};
                            } elsif ($self->{'DRAW_MODE'} == SUBTRACT_MODE) {
                                $c -= $self->{'COLOR'};
                            } elsif ($self->{'DRAW_MODE'} == MULTIPLY_MODE) {
                                $c *= $self->{'COLOR'};
                            } elsif ($self->{'DRAW_MODE'} == DIVIDE_MODE) {
                                $c /= $self->{'COLOR'};
                            } elsif ($self->{'DRAW_MODE'} == MASK_MODE) {
                                if ($self->{'BITS'} == 32) {
                                    $c = $self->{'COLOR'} if (substr($self->{'COLOR'}, 0, 3) ne substr($self->{'B_COLOR'}, 0, 3));
                                } else {
                                    $c = $self->{'COLOR'} if ($self->{'COLOR'} ne $self->{'B_COLOR'});
                                }
                            } elsif ($self->{'DRAW_MODE'} == UNMASK_MODE) {
                                my $pixel = $self->pixel({ 'x' => $x, 'y' => $y });
                                my $raw = $pixel->{'raw'};
                                if ($self->{'BITS'} == 32) {
                                    $c = $self->{'COLOR'} if (substr($raw, 0, 3) eq substr($self->{'B_COLOR'}, 0, 3));
                                } else {
                                    $c = $self->{'COLOR'} if ($raw eq $self->{'B_COLOR'});
                                }
                            } ## end elsif ($self->{'DRAW_MODE'...})
                            if ($self->{'FILE_MODE'}) {
                                seek($fb, $index, 0);
                                print $fb $c;
                            } else {
                                substr($self->{'SCREEN'}, $index, $self->{'BYTES'}) = $c;
                            }
                        };
                        my $error = $@;
                        warn __LINE__ . " $error\n" if ($error && $self->{'SHOW_ERRORS'});
                        $self->_fix_mapping() if ($error);
                    } ## end if ($index >= 0 && $index...)
                    $self->{'history'}->{$y}->{$x} = 1 if (exists($self->{'history'}));
                } ## end unless (exists($self->{'history'...}))
            } ## end unless (($x > $self->{'XX_CLIP'...}))
        } ## end else [ if ($self->{'ACCELERATED'...})]
    } ## end else [ if (abs($size) > 1) ]

    $self->{'X'} = $x;
    $self->{'Y'} = $y;

    select($self->{'FB'});
    $| = 1;
} ## end sub plot

sub line {
    my $self   = shift;
    my $params = shift;

    $self->plot($params);
    $params->{'x'} = $params->{'xx'};
    $params->{'y'} = $params->{'yy'};
    $self->drawto($params);
} ## end sub line

sub drawto {
    ##########################################################
    # Perfectly horizontal line drawing is optimized by      #
    # using the BLIT functions.  This assists greatly with   #
    # drawing filled objects.  In fact, it's hundreds of     #
    # times faster!                                          #
    ##########################################################
    my $self   = shift;
    my $params = shift;

    my $x_end = int($params->{'x'});
    my $y_end = int($params->{'y'});
    my $size  = int($params->{'pixel_size'} || 1);

    my ($width, $height);
    my $start_x     = $self->{'X'};
    my $start_y     = $self->{'Y'};
    my $antialiased = $params->{'antialiased'} || 0;
    my $XX          = $x_end;
    my $YY          = $y_end;

    if ($self->{'ACCELERATED'} && $size == 1 && !$self->{'FILE_MODE'} && !$antialiased) {
        c_line(
            $self->{'SCREEN'},
            $start_x, $start_y, $x_end, $y_end,
            $self->{'DRAW_MODE'},
            $self->{'INT_COLOR'},
            $self->{'INT_B_COLOR'},
            $self->{'BYTES'},
            $self->{'BYTES_PER_LINE'},
            $self->{'X_CLIP'},  $self->{'Y_CLIP'}, $self->{'XX_CLIP'}, $self->{'YY_CLIP'},
            $self->{'XOFFSET'}, $self->{'YOFFSET'},
            $self->{'COLOR_ALPHA'},

            #            $antialiased,
        );
    } else {

        # Determines if the coordinates sent were right-side-up or up-side-down.
        if ($start_x > $x_end) {
            $width = $start_x - $x_end;
        } else {
            $width = $x_end - $start_x;
        }
        if ($start_y > $y_end) {
            $height = $start_y - $y_end;
        } else {
            $height = $y_end - $start_y;
        }

        # We need only plot if start and end are the same
        if (($x_end == $start_x) && ($y_end == $start_y)) {
            $self->plot({ 'x' => $x_end, 'y' => $y_end, 'pixel_size' => $size });

            # Else, let's get to drawing
        } elsif ($x_end == $start_x) {    # Draw a perfectly verticle line
            if ($start_y > $y_end) {      # Draw direction is UP
                foreach my $y ($y_end .. $start_y) {
                    $self->plot({ 'x' => $start_x, 'y' => $y, 'pixel_size' => $size });
                }
            } else {                      # Draw direction is DOWN
                foreach my $y ($start_y .. $y_end) {
                    $self->plot({ 'x' => $start_x, 'y' => $y, 'pixel_size' => $size });
                }
            }
        } elsif ($y_end == $start_y) {    # Draw a perfectly horizontal line (fast)
            $x_end   = max($self->{'X_CLIP'}, min($x_end,   $self->{'XX_CLIP'}));
            $start_x = max($self->{'X_CLIP'}, min($start_x, $self->{'XX_CLIP'}));
            $width   = abs($x_end - $start_x);
            if ($size == 1) {
                if ($start_x > $x_end) {
                    $self->blit_write({ 'x' => $x_end, 'y' => $y_end, 'width' => $width, 'height' => 1, 'image' => $self->{'COLOR'} x $width });    # Blitting a horizontal line is much faster!
                } else {
                    $self->blit_write({ 'x' => $start_x, 'y' => $start_y, 'width' => $width, 'height' => 1, 'image' => $self->{'COLOR'} x $width });    # Blitting a horizontal line is much faster!
                }
            } else {
                if ($start_x > $x_end) {
                    $self->blit_write({ 'x' => $x_end, 'y' => ($y_end - ($size / 2)), 'width' => $width, 'height' => $size, 'image' => $self->{'COLOR'} x ($width * $size) });    # Blitting a horizontal line is much faster!
                } else {
                    $self->blit_write({ 'x' => $start_x, 'y' => ($y_end - ($size / 2)), 'width' => $width, 'height' => $size, 'image' => $self->{'COLOR'} x ($width * $size) });    # Blitting a horizontal line is much faster!
                }
            } ## end else [ if ($size == 1) ]
        } elsif ($antialiased) {
            $self->_draw_line_antialiased($start_x, $start_y, $x_end, $y_end);
        } elsif ($width > $height) {    # Wider than it is high
            my $factor = $height / $width;
            if (($start_x < $x_end) && ($start_y < $y_end)) {    # Draw UP and to the RIGHT
                while ($start_x < $x_end) {
                    $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
                    $start_y += $factor;
                    $start_x++;
                }
            } elsif (($start_x > $x_end) && ($start_y < $y_end)) {    # Draw UP and to the LEFT
                while ($start_x > $x_end) {
                    $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
                    $start_y += $factor;
                    $start_x--;
                }
            } elsif (($start_x < $x_end) && ($start_y > $y_end)) {    # Draw DOWN and to the RIGHT
                while ($start_x < $x_end) {
                    $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
                    $start_y -= $factor;
                    $start_x++;
                }
            } elsif (($start_x > $x_end) && ($start_y > $y_end)) {    # Draw DOWN and to the LEFT
                while ($start_x > $x_end) {
                    $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
                    $start_y -= $factor;
                    $start_x--;
                }
            } ## end elsif (($start_x > $x_end...))
        } elsif ($width < $height) {    # Higher than it is wide
            my $factor = $width / $height;
            if (($start_x < $x_end) && ($start_y < $y_end)) {    # Draw UP and to the RIGHT
                while ($start_y < $y_end) {
                    $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
                    $start_x += $factor;
                    $start_y++;
                }
            } elsif (($start_x > $x_end) && ($start_y < $y_end)) {    # Draw UP and to the LEFT
                while ($start_y < $y_end) {
                    $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
                    $start_x -= $factor;
                    $start_y++;
                }
            } elsif (($start_x < $x_end) && ($start_y > $y_end)) {    # Draw DOWN and to the RIGHT
                while ($start_y > $y_end) {
                    $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
                    $start_x += $factor;
                    $start_y--;
                }
            } elsif (($start_x > $x_end) && ($start_y > $y_end)) {    # Draw DOWN and to the LEFT
                while ($start_y > $y_end) {
                    $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
                    $start_x -= $factor;
                    $start_y--;
                }
            } ## end elsif (($start_x > $x_end...))
        } else {    # $width == $height
            if (($start_x < $x_end) && ($start_y < $y_end)) {    # Draw UP and to the RIGHT
                while ($start_y < $y_end) {
                    $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
                    $start_x++;
                    $start_y++;
                }
            } elsif (($start_x > $x_end) && ($start_y < $y_end)) {    # Draw UP and to the LEFT
                while ($start_y < $y_end) {
                    $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
                    $start_x--;
                    $start_y++;
                }
            } elsif (($start_x < $x_end) && ($start_y > $y_end)) {    # Draw DOWN and to the RIGHT
                while ($start_y > $y_end) {
                    $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
                    $start_x++;
                    $start_y--;
                }
            } elsif (($start_x > $x_end) && ($start_y > $y_end)) {    # Draw DOWN and to the LEFT
                while ($start_y > $y_end) {
                    $self->plot({ 'x' => $start_x, 'y' => $start_y, 'pixel_size' => $size });
                    $start_x--;
                    $start_y--;
                }
            } ## end elsif (($start_x > $x_end...))

        } ## end else [ if (($x_end == $start_x...))]
    } ## end else [ if ($self->{'ACCELERATED'...})]
    $self->{'X'} = $XX;
    $self->{'Y'} = $YY;

    select($self->{'FB'});
    $| = 1;
} ## end sub drawto

sub _adj_plot {
    my $self = shift;
    my $x    = shift;
    my $y    = shift;
    my $c    = shift;
    my $s    = shift;

    $self->set_color({ 'red' => $s->{'red'} * $c, 'green' => $s->{'green'} * $c, 'blue' => $s->{'blue'} * $c });
    $self->plot({ 'x' => $x, 'y' => $y });
} ## end sub _adj_plot

sub _draw_line_antialiased {
    my $self = shift;
    my $x0   = shift;
    my $y0   = shift;
    my $x1   = shift;
    my $y1   = shift;

    my $saved = { %{ $self->{'SET_COLOR'} } };

    my $plot = \&_adj_plot;

    if (abs($y1 - $y0) > abs($x1 - $x0)) {
        $plot = sub { _adj_plot(@_[0, 2, 1, 3, 4]) };
        ($x0, $y0, $x1, $y1) = ($y0, $x0, $y1, $x1);
    }

    if ($x0 > $x1) {
        ($x0, $x1, $y0, $y1) = ($x1, $x0, $y1, $y0);
    }

    my $dx       = $x1 - $x0;
    my $dy       = $y1 - $y0;
    my $gradient = $dy / $dx;

    my @xends;
    my $intery;

    # handle the endpoints
    foreach my $xy ([$x0, $y0], [$x1, $y1]) {
        my ($x, $y) = @{$xy};
        my $xend = int($x + 0.5);                   # POSIX::lround($x);
        my $yend = $y + $gradient * ($xend - $x);
        my $xgap = _rfpart($x + 0.5);

        my $x_pixel = $xend;
        my $y_pixel = int($yend);
        push(@xends, $x_pixel);

        $plot->($self, $x_pixel, $y_pixel,     _rfpart($yend) * $xgap, $saved);
        $plot->($self, $x_pixel, $y_pixel + 1, _fpart($yend) * $xgap,  $saved);
        next if (defined($intery));

        # first y-intersection for the main loop
        $intery = $yend + $gradient;
    } ## end foreach my $xy ([$x0, $y0],...)

    # main loop

    foreach my $x ($xends[0] + 1 .. $xends[1] - 1) {
        $plot->($self, $x, int($intery),     _rfpart($intery), $saved);
        $plot->($self, $x, int($intery) + 1, _fpart($intery),  $saved);
        $intery += $gradient;
    }
    $self->set_color($saved);
} ## end sub _draw_line_antialiased

# This also doubles as the rounded box routine.

sub circle {
    my $self   = shift;
    my $params = shift;

    my $x0            = int($params->{'x'});
    my $y0            = int($params->{'y'});
    my $x1            = int($params->{'xx'}) || $x0;
    my $y1            = int($params->{'yy'}) || $y0;
    my $bx            = int($params->{'bx'}) || 0;
    my $by            = int($params->{'by'}) || 0;
    my $bxx           = int($params->{'bxx'}) || 1;
    my $byy           = int($params->{'byy'}) || 1;
    my $r             = int($params->{'radius'});
    my $filled        = $params->{'filled'} || FALSE;
    my $gradient      = (defined($params->{'gradient'})) ? TRUE : FALSE;
    my $size          = $params->{'pixel_size'} || 1;
    my $start         = $y0 - $r;
    my $x             = $r;
    my $y             = 0;
    my $decisionOver2 = 1 - $x;
    my (@rc, @gc, @bc);

    ($x0, $x1) = ($x1, $x0) if ($x0 > $x1);
    ($y0, $y1) = ($y1, $y0) if ($y0 > $y1);
    my $_x     = $x0 - $r;
    my $_xx    = $x1 + $r;
    my $_y     = $y0 - $r;
    my $_yy    = $y1 + $r;
    my $xstart = $_x;

    my @coords;
    my $saved = $self->{'COLOR'};
    my $W     = $r * 2;
    my $count = $W + abs($y1 - $y0);
    my $pattern;
    my $wdth  = $_xx - $_x;
    my $hgth  = $_yy - $_y;
    my $bytes = $self->{'BYTES'};
    my $plen  = $wdth * $bytes;
    $self->{'history'} = {};

    if ($gradient) {
        if (defined($params->{'gradient'}->{'direction'}) && $params->{'gradient'}->{'direction'} !~ /vertical/i) {
            $W = $bxx - $bx unless ($x0 == $x1 && $y0 == $y1);
            if (exists($params->{'gradient'}->{'colors'})) {
                $pattern = $self->_generate_fill($wdth, $hgth, $params->{'gradient'}->{'colors'}, $params->{'gradient'}->{'direction'});
            } else {
                $pattern = $self->_generate_fill(
                    $wdth, $hgth,
                    {
                        'red'   => [$params->{'gradient'}->{'start'}->{'red'},   $params->{'gradient'}->{'end'}->{'red'}],
                        'green' => [$params->{'gradient'}->{'start'}->{'green'}, $params->{'gradient'}->{'end'}->{'green'}],
                        'blue'  => [$params->{'gradient'}->{'start'}->{'blue'},  $params->{'gradient'}->{'end'}->{'blue'}]
                    },
                    $params->{'gradient'}->{'direction'}
                );
            } ## end else [ if (exists($params->{'gradient'...}))]
            $plen     = $wdth * $bytes;
            $gradient = 2;
        } else {
            $W = $byy - $by unless ($x0 == $x1 && $y0 == $y1);
            if (exists($params->{'gradient'}->{'colors'})) {
                @rc = multi_gradient($W, @{ $params->{'gradient'}->{'colors'}->{'red'} });
                @gc = multi_gradient($W, @{ $params->{'gradient'}->{'colors'}->{'green'} });
                @bc = multi_gradient($W, @{ $params->{'gradient'}->{'colors'}->{'blue'} });
            } else {
                @rc = gradient($params->{'gradient'}->{'start'}->{'red'},   $params->{'gradient'}->{'end'}->{'red'},   $W);
                @gc = gradient($params->{'gradient'}->{'start'}->{'green'}, $params->{'gradient'}->{'end'}->{'green'}, $W);
                @bc = gradient($params->{'gradient'}->{'start'}->{'blue'},  $params->{'gradient'}->{'end'}->{'blue'},  $W);
            }
        } ## end else [ if (defined($params->{...}))]
    } elsif (exists($params->{'texture'})) {
        $pattern = $self->_generate_fill($wdth, $hgth, undef, $params->{'texture'});
        $gradient = 2;
    } elsif (exists($params->{'hatch'})) {
        $pattern = $self->_generate_fill($wdth, $hgth, undef, $params->{'hatch'});
        $gradient = 2;
    } elsif ($filled && $x0 == $x1 && $y0 == $y1 && $self->{'ACCELERATED'} && !$self->{'FILE_MODE'}) {
        c_filled_circle($self->{'SCREEN'}, $x0, $y0, $r, $self->{'DRAW_MODE'}, $self->{'INT_COLOR'}, $self->{'INT_B_COLOR'}, $self->{'BYTES'}, $self->{'BYTES_PER_LINE'}, $self->{'X_CLIP'}, $self->{'Y_CLIP'}, $self->{'XX_CLIP'}, $self->{'YY_CLIP'}, $self->{'XOFFSET'}, $self->{'YOFFSET'}, $self->{'COLOR_ALPHA'},);
        return;
    }    # end if ($gradient)
    my ($ymy, $lymy, $ymx, $lymx, $ypy, $lypy, $ypx, $lypx, $xmy, $xmx, $xpy, $xpx);
    while ($x >= ($y - 1)) {
        $ymy = $y0 - $y;    # Top
        $ymx = $y0 - $x;
        $ypy = $y1 + $y;    # Bottom
        $ypx = $y1 + $x;
        $xmy = $x0 - $y;    # Left
        $xmx = $x0 - $x;
        $xpy = $x1 + $y;    # Right
        $xpx = $x1 + $x;

        if ($filled) {
            my $ymy_i = $ymy - $start;
            my $ymx_i = $ymx - $start;
            my $ypy_i = $ypy - $start;
            my $ypx_i = $ypx - $start;

            if ($gradient == 2) {
                my $fxmy = $xmy;
                my $fxmx = $xmx;
                my $fxpy = $xpy;
                my $fxpx = $xpx;

                # Top
                my $fwd = $fxpx - $fxmx;
                my $wd  = $xpx - $xmx;
                if ($ymy != $lymy && $ymy != $lymx && $ymy != $lypx && $ymy != $lypy) {
                    ($params->{'x'}, $params->{'y'}, $params->{'width'}, $params->{'height'}, $params->{'image'}) = ($fxmx, $ymy, $fwd, 1, substr($pattern, (($plen - ($bytes * $wd)) / 2) + ($ymy_i * $plen), $fwd * $bytes));
                    $self->blit_write($params);
                }

                $fwd = $fxpy - $fxmy;
                $wd  = $xpy - $xmy;
                if ($ymx != $lymx && $ymx != $lymy && $ymx != $lypx && $ymx != $lypy) {
                    ($params->{'x'}, $params->{'y'}, $params->{'width'}, $params->{'height'}, $params->{'image'}) = ($fxmy, $ymx, $fwd, 1, substr($pattern, (($plen - ($bytes * $wd)) / 2) + ($ymx_i * $plen), $fwd * $bytes));
                    $self->blit_write($params);
                }

                # Bottom
                $fwd = $fxpx - $fxmx;
                $wd  = $xpx - $xmx;
                if ($ypy != $lypy && $ypy != $lypx && $ypy != $lymy && $ypy != $lymx) {
                    ($params->{'x'}, $params->{'y'}, $params->{'width'}, $params->{'height'}, $params->{'image'}) = ($fxmx, $ypy, $fwd, 1, substr($pattern, (($plen - ($bytes * $wd)) / 2) + ($ypy_i * $plen), $fwd * $bytes));
                    $self->blit_write($params);
                }

                $fwd = $fxpy - $fxmy;
                $wd  = $xpy - $xmy;
                if ($ypx != $lypx && $ypx != $lypy && $ypx != $lymx && $ypx != $lymy) {
                    ($params->{'x'}, $params->{'y'}, $params->{'width'}, $params->{'height'}, $params->{'image'}) = ($fxmy, $ypx, $fwd, 1, substr($pattern, (($plen - ($bytes * $wd)) / 2) + ($ypx_i * $plen), $fwd * $bytes));
                    $self->blit_write($params);
                }
            } elsif ($gradient) {

                # Top
                if ($ymy != $lymy && $ymy != $lymx && $ymy != $lypx && $ymy != $lypy) {
                    $self->set_color({ 'red' => $rc[$ymy_i], 'green' => $gc[$ymy_i], 'blue' => $bc[$ymy_i] });
                    ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmx, $ymy, $xpx, $ymy);
                    $self->line($params);
                }
                if ($ymx != $lymx && $ymx != $lymy && $ymx != $lypx && $ymx != $lypy) {
                    $self->set_color({ 'red' => $rc[$ymx_i], 'green' => $gc[$ymx_i], 'blue' => $bc[$ymx_i] });
                    ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmy, $ymx, $xpy, $ymx);
                    $self->line($params);
                }

                # Bottom
                if ($ypy != $lypy && $ypy != $lypx && $ypy != $lymy && $ypy != $lymx) {
                    $self->set_color({ 'red' => $rc[$ypy_i], 'green' => $gc[$ypy_i], 'blue' => $bc[$ypy_i] });
                    ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmx, $ypy, $xpx, $ypy);
                    $self->line($params);
                }
                if ($ypx != $lypx && $ypx != $lypy && $ypx != $lymx && $ypx != $lymy) {
                    $self->set_color({ 'red' => $rc[$ypx_i], 'green' => $gc[$ypx_i], 'blue' => $bc[$ypx_i] });
                    ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmy, $ypx, $xpy, $ypx);
                    $self->line($params);
                }
            } else {

                # Top
                if ($ymy != $lymy && $ymy != $lymx && $ymy != $lypx && $ymy != $lypy) {
                    ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmx, $ymy, $xpx, $ymy);
                    $self->line($params);
                }
                if ($ymx != $lymx && $ymx != $lymy && $ymx != $lypx && $ymx != $lypy) {
                    ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmy, $ymx, $xpy, $ymx);
                    $self->line($params);
                }

                # Bottom
                if ($ypy != $lypy && $ypy != $lypx && $ypy != $lymy && $ypy != $lymx) {
                    ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmx, $ypy, $xpx, $ypy);
                    $self->line($params);
                }
                if ($ypx != $lypx && $ypx != $lypy && $ypx != $lymx && $ypx != $lymy) {
                    ($params->{'x'}, $params->{'y'}, $params->{'xx'}, $params->{'yy'}) = ($xmy, $ypx, $xpy, $ypx);
                    $self->line($params);
                }
            } ## end else [ if ($gradient == 2) ]
            $lymy = $ymy;
            $lymx = $ymx;
            $lypy = $ypy;
            $lypx = $ypx;
        } else {

            # Top left
            ($params->{'x'}, $params->{'y'}) = ($xmx, $ymy);
            $self->plot($params);
            ($params->{'x'}, $params->{'y'}) = ($xmy, $ymx);
            $self->plot($params);

            # Top right
            ($params->{'x'}, $params->{'y'}) = ($xpx, $ymy);
            $self->plot($params);
            ($params->{'x'}, $params->{'y'}) = ($xpy, $ymx);
            $self->plot($params);

            # Bottom right
            ($params->{'x'}, $params->{'y'}) = ($xpx, $ypy);
            $self->plot($params);
            ($params->{'x'}, $params->{'y'}) = ($xpy, $ypx);
            $self->plot($params);

            # Bottom left
            ($params->{'x'}, $params->{'y'}) = ($xmx, $ypy);
            $self->plot($params);
            ($params->{'x'}, $params->{'y'}) = ($xmy, $ypx);
            $self->plot($params);

            $lymy = $ymy;
            $lymx = $ymx;
            $lypy = $ypy;
            $lypx = $ypx;
        } ## end else [ if ($filled) ]
        $y++;
        if ($decisionOver2 <= 0) {
            $decisionOver2 += 2 * $y + 1;
        } else {
            $x--;
            $decisionOver2 += 2 * ($y - $x) + 1;
        }
    } ## end while ($x >= ($y - 1))
    unless ($x0 == $x1 && $y0 == $y1) {
        if ($filled) {
            if ($gradient == 2) {
                my $x      = $_x;
                my $y      = $y0;
                my $width  = $wdth;
                my $height = $y1 - $y0;
                my $index  = ($y0 - $start) * $plen;
                my $sz     = $plen * $height;
                $self->blit_write({ 'x' => $x, 'y' => $y, 'width' => $width, 'height' => $height, 'image' => substr($pattern, $index, $sz) }) if ($height && $width);
            } elsif ($gradient) {
                foreach my $v ($y0 .. $y1) {
                    my $offset = $v - $start;
                    $self->set_color({ 'red' => $rc[$offset], 'green' => $gc[$offset], 'blue' => $bc[$offset] });
                    $self->line({ 'x' => $_x, 'y' => $v, 'xx' => $_xx, 'yy' => $v, 'pixel_size' => 1 });
                }
            } else {
                $self->{'COLOR'} = $saved;
                $self->box({ 'x' => $_x, 'y' => $y0, 'xx' => $_xx, 'yy' => $y1, 'filled' => 1 });
            }
        } else {

            # top
            $self->line({ 'x' => $x0, 'y' => $_y, 'xx' => $x1, 'yy' => $_y, 'pixel_size' => $size });

            # right
            $self->line({ 'x' => $_xx, 'y' => $y0, 'xx' => $_xx, 'yy' => $y1, 'pixel_size' => $size });

            # bottom
            $self->line({ 'x' => $x0, 'y' => $_yy, 'xx' => $x1, 'yy' => $_yy, 'pixel_size' => $size });

            # left
            $self->line({ 'x' => $_x, 'y' => $y0, 'xx' => $_x, 'yy' => $y1, 'pixel_size' => $size });
        } ## end else [ if ($filled) ]
    } ## end unless ($x0 == $x1 && $y0 ...)
    $self->{'COLOR'} = $saved;
    delete($self->{'history'});
} ## end sub circle

sub _fpart {
    return ((POSIX::modf(shift))[0]);
}

sub _rfpart {
    return (1 - _fpart(shift));
}

sub _transformed_bounds {
    my $bbox   = shift;
    my $matrix = shift;

    my $bounds;
    foreach my $point ([$bbox->start_offset, $bbox->ascent], [$bbox->start_offset, $bbox->descent], [$bbox->end_offset, $bbox->ascent], [$bbox->end_offset, $bbox->descent]) {
        $bounds = _add_bound($bounds, _transform_point(@{$point}, $matrix));
    }
    return (@{$bounds});
} ## end sub _transformed_bounds

sub _add_bound {
    my $bounds = shift;
    my $x      = shift;
    my $y      = shift;

    $bounds or return ([$x, $y, $x, $y]);

    $x < $bounds->[0] and $bounds->[0] = $x;
    $y < $bounds->[1] and $bounds->[1] = $y;
    $x > $bounds->[2] and $bounds->[2] = $x;
    $y > $bounds->[3] and $bounds->[3] = $y;

    return ($bounds);
} ## end sub _add_bound

sub _transform_point {
    my $x      = shift;
    my $y      = shift;
    my $matrix = shift;

    return ($x * $matrix->[0] + $y * $matrix->[1] + $matrix->[2], $x * $matrix->[3] + $y * $matrix->[4] + $matrix->[5]);
} ## end sub _transform_point

1;

=head1 NAME

Graphics::Framebuffer::Accel

=head1 DESCRIPTION

See the "Graphics::Frambuffer" documentation, as methods within here are pulled into the main module

=cut
