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,

    NORMAL_MODE => 0,    #   Constants for DRAW_MODE
    XOR_MODE    => 1,    #   Constants for DRAW_MODE
    OR_MODE     => 2,    #   Constants for DRAW_MODE
    AND_MODE    => 3,    #   Constants for DRAW_MODE
    MASK_MODE   => 4,    #   Constants for DRAW_MODE
    UNMASK_MODE => 5,    #   Constants for DRAW_MODE
    ALPHA_MODE  => 6,    #   Constants for DRAW_MODE

    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 List::Util qw(min max);

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

#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 RGB         0
#define RBG         1
#define BGR         2
#define BRG         3
#define GBR         4
#define GRB         5


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_plot(
    char *framebuffer,
    short x, short y,
    unsigned char draw_mode,
    unsigned int color,
    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 :
              break;
            case UNMASK_MODE :
              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;
              }
        }
    }
}

void c_line(
    char *framebuffer,
    short x1, short y1, short x2, short y2,
    unsigned char draw_mode,
    unsigned int color,
    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
) {
/*    printf("X=%d, Y=%d, XX=%d, YY=%d, DM=%d, C=%d, BPP=%d, BPL=%d, XC=%d, YC=%d, XXC=%d, YYC=%d, XO=%d, YO=%d, A=%d\\n", x1,y1,x2,y2,draw_mode,color,bytes_per_pixel,bytes_per_line,x_clip,y_clip,xx_clip,yy_clip,xoffset,yoffset,alpha);

    int cx, cy,
      ix, iy,
      dx, dy, 
      ddx = x2 - x1, ddy = y2 - y1;
    
    if (!ddx) { //vertical line special case
          if (ddy > 0) {
              cy = y1;  
              do
                c_plot(framebuffer, x1, cy++, draw_mode, color, bytes_per_pixel, bytes_per_line, x_clip, y_clip, xx_clip, yy_clip, xoffset, yoffset, alpha);
              while (cy <= y2);
              return;
          } else {
              cy = y2;
              do
                c_plot(framebuffer, x1, cy++, draw_mode, color, bytes_per_pixel, bytes_per_line, x_clip, y_clip, xx_clip, yy_clip, xoffset, yoffset, alpha);
              while (cy <= y1);
              return;
          }
    }
    if (!ddy) { //horizontal line special case
          if (ddx > 0) {
              cx = x1;
              do
                c_plot(framebuffer, cx, y1, draw_mode, color, bytes_per_pixel, bytes_per_line, x_clip, y_clip, xx_clip, yy_clip, xoffset, yoffset, alpha);
              while (++cx <= x2);
              return;
          } else {
              cx = x2; 
              do
                c_plot(framebuffer, cx, y1, draw_mode, color, bytes_per_pixel, bytes_per_line, x_clip, y_clip, xx_clip, yy_clip, xoffset, yoffset, alpha);
              while (++cx <= x1);
              return;
          }
    }
    if (ddy < 0) { iy= -1; ddy= -ddy; }//pointing up
      else iy= 1;
    if (ddx < 0) { ix= -1; ddx= -ddx; }//pointing left
      else ix= 1;
    dx = dy = ddx * ddy;
    cy = y1, cx = x1; 
    if (ddx < ddy) { // < 45 degrees, a tall line    
          do {
              dx -= ddy;
              do {
                c_plot(framebuffer, cx, cy, draw_mode, color, bytes_per_pixel, bytes_per_line, x_clip, y_clip, xx_clip, yy_clip, xoffset, yoffset, alpha);
                  cy += iy, dy -= ddx;
              } while (dy >=dx);
              cx += ix;
          } while (dx > 0);
    } else { // >= 45 degrees, a wide line
          do {
              dy -= ddx;
              do {
                c_plot(framebuffer, cx, cy, draw_mode, color, bytes_per_pixel, bytes_per_line, x_clip, y_clip, xx_clip, yy_clip, xoffset, yoffset, alpha);
                  cx += ix, dx -= ddy;
              } while (dx >=dy);
              cy += iy;
          } while (dy > 0);
    }
*/    
    
    
    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, 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, 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, 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, bytes_per_pixel, bytes_per_line, x_clip, y_clip, xx_clip, yy_clip, xoffset, yoffset, alpha);
        count -= decInc;
    }

}

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 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;
                        switch(draw_mode) {
                            case NORMAL_MODE :
                              if (bytes_per_pixel == 4) {
                                  *((unsigned int*)(framebuffer + xhbp + yvbl)) = *((unsigned int*)(blit_data + (vertical * bline) + hzpixel));
                              } else if (bytes_per_pixel == 3) {
                                  *(framebuffer + xhbp + yvbl )     = *(blit_data + (vertical * bline) + hzpixel );
                                  *(framebuffer + xhbp + yvbl  + 1) = *(blit_data + (vertical * bline) + hzpixel  + 1);
                                  *(framebuffer + xhbp + yvbl  + 2) = *(blit_data + (vertical * bline) + hzpixel  + 2);
                              } else {
                                  *((unsigned short*)(framebuffer + xhbp + yvbl )) = *((unsigned short*)(blit_data + (vertical * bline) + hzpixel ));
                              }
                            break;
                            case XOR_MODE :
                              if (bytes_per_pixel == 4) {
                                  *((unsigned int*)(framebuffer + xhbp + yvbl )) ^= *((unsigned int*)(blit_data + (vertical * bline) + hzpixel ));
                              } else if (bytes_per_pixel == 3) {
                                  *(framebuffer + xhbp + yvbl )     ^= *(blit_data + (vertical * bline) + hzpixel );
                                  *(framebuffer + xhbp + yvbl  + 1) ^= *(blit_data + (vertical * bline) + hzpixel  + 1);
                                  *(framebuffer + xhbp + yvbl  + 2) ^= *(blit_data + (vertical * bline) + hzpixel  + 2);
                              } else {
                                  *((unsigned short*)(framebuffer + xhbp + yvbl )) ^= *((unsigned short*)(blit_data + (vertical * bline) + hzpixel ));
                              }
                            break;
                            case OR_MODE :
                              if (bytes_per_pixel == 4) {
                                  *((unsigned int*)(framebuffer + xhbp + yvbl )) |= *((unsigned int*)(blit_data + (vertical * bline) + hzpixel ));
                              } else if (bytes_per_pixel == 3) {
                                  *(framebuffer + xhbp + yvbl )     |= *(blit_data + (vertical * bline) + hzpixel );
                                  *(framebuffer + xhbp + yvbl  + 1) |= *(blit_data + (vertical * bline) + hzpixel  + 1);
                                  *(framebuffer + xhbp + yvbl  + 2) |= *(blit_data + (vertical * bline) + hzpixel  + 2);
                              } else {
                                  *((unsigned short*)(framebuffer + xhbp + yvbl )) |= *((unsigned short*)(blit_data + (vertical * bline) + hzpixel ));
                              }
                            break;
                            case AND_MODE :
                              if (bytes_per_pixel == 4) {
                                  *((unsigned int*)(framebuffer + xhbp + yvbl )) &= *((unsigned int*)(blit_data + (vertical * bline) + hzpixel ));
                              } else if (bytes_per_pixel == 3) {
                                  *(framebuffer + xhbp + yvbl )     &= *(blit_data + (vertical * bline) + hzpixel );
                                  *(framebuffer + xhbp + yvbl  + 1) &= *(blit_data + (vertical * bline) + hzpixel  + 1);
                                  *(framebuffer + xhbp + yvbl  + 2) &= *(blit_data + (vertical * bline) + hzpixel  + 2);
                              } else {
                                  *((unsigned short*)(framebuffer + xhbp + yvbl )) &= *((unsigned short*)(blit_data + (vertical * bline) + hzpixel ));
                              }
                            break;
                            case MASK_MODE :
                              if (bytes_per_pixel == 4) {
                                  unsigned int rgb = *((unsigned int*)(blit_data + (vertical * bline) + hzpixel ));
                                  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 + (vertical * bline) + hzpixel )) & 0xFFFFFF00;
                                  if (rgb != (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
                                        *(framebuffer + xhbp + yvbl )     = *(blit_data + (vertical * bline) + hzpixel );
                                      *(framebuffer + xhbp + yvbl  + 1) = *(blit_data + (vertical * bline) + hzpixel  + 1);
                                      *(framebuffer + xhbp + yvbl  + 2) = *(blit_data + (vertical * bline) + hzpixel  + 2);
                                  }
                              } else {
                                  unsigned short rgb = *((unsigned short*)(blit_data + (vertical * bline) + hzpixel ));
                                  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 + (vertical * bline) + hzpixel ));
                                  }
                              } else if (bytes_per_pixel == 3) {
                                  unsigned int rgb = *((unsigned int*)(framebuffer + xhbp + yvbl + hzpixel ));
                                  if (rgb == (bcolor & 0xFFFFFF00)) { // Ignore alpha channel
                                        *(framebuffer + xhbp + yvbl )     = *(blit_data + (vertical * bline) + hzpixel );
                                      *(framebuffer + xhbp + yvbl  + 1) = *(blit_data + (vertical * bline) + hzpixel  + 1);
                                      *(framebuffer + xhbp + yvbl  + 2) = *(blit_data + (vertical * bline) + hzpixel  + 2);
                                  }
                              } else {
                                  unsigned short rgb = *((unsigned short*)(framebuffer + xhbp + yvbl + hzpixel ));
                                  if (rgb == (bcolor & 0xFFFF)) {
                                      *((unsigned short*)(framebuffer + xhbp + yvbl )) = *((unsigned short*)(blit_data + (vertical * bline) + hzpixel ));
                                  }
                              }
                            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 + (vertical * bline) + hzpixel );
                                  unsigned char G    = *(blit_data + (vertical * bline) + hzpixel  + 1);
                                  unsigned char B    = *(blit_data + (vertical * bline) + hzpixel  + 2);
                                  unsigned char A    = *(blit_data + (vertical * bline) + hzpixel  + 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 + (vertical * bline) + hzpixel );
                                  unsigned char G    = *(blit_data + (vertical * bline) + hzpixel + 1);
                                  unsigned char B    = *(blit_data + (vertical * bline) + hzpixel + 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 + (vertical * bline) + hzpixel ));
                                  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;
                        }
                    }
                }
            }
        }
    }
}

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;

    while(loc16 < size16) {
        unsigned short rgb565 = *((unsigned short*)(buf16 + loc16));
        loc16 += 2;
        unsigned short r = rgb565;
        unsigned short g = rgb565;
        unsigned short b = rgb565;
        if (color_order == 0) {
            b >>= 11;
        } else {
            r >>= 11;
        }
        g >>= 5;
        r &= 31;
        g &= 63;
        b &= 31;
        r = (r * 527 + 23) >> 6;
        g = (g * 259 + 33) >> 6;
        b = (b * 527 * 23) >> 6;
        *(buf24 + loc24++) = r;
        *(buf24 + loc24++) = g;
        *(buf24 + loc24++) = b;
    }
}

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 r = *(buf24 + loc24++);
        unsigned char g = *(buf24 + loc24++);
        unsigned char b = *(buf24 + loc24++);
        r >>= 3;
        g >>= 2;
        b >>= 3;
        if (color_order == 0) {
            rgb565 = (b << 11) | (g << 5) | r;
            *((unsigned short*)(buf16 + loc16)) = rgb565;
        } else {
            rgb565 = (r << 11) | (g << 5) | b;
            *((unsigned short*)(buf16 + loc16)) = rgb565;
        }
        loc16 += 2;
    }
}

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;
        }
    }
}


END_C
      $INLINE = TRUE;
};

BEGIN {
    our $VERSION = 1.00;
    our @EXPORT_OK = qw(c_monochrome c_convert_16_24 c_convert_24_16 c_get_screen_info c_plot c_line c_blit_write c_flip_vertical c_flip_horizontal c_rotate );
    our @EXPORT    = qw( $INLINE );
}

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 for (my $idx = 0; $idx ...)
    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;
    }

    return ($new_img);
} ## end sub _convert_24_to_16

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 });
} ## end sub RGBA8888_to_RGB565

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 });
} ## end sub RGB888_to_RGBA8888

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));
    $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);

    $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 == ALPHA_MODE) {
                    foreach $px (0 .. ($w - 1)) {
                        $px4 = $px * $bytes;
                        if ($self->{'FILE_MODE'}) {
                            seek($fb, $index, 0);
                            read($fb, $data, $bytes);
                        } else {
                            $data = substr($self->{'SCREEN'}, ($index + $px4), $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, $index + $px4, 0);
                                    print $fb $c;
                                } else {
                                    substr($self->{'SCREEN'}, ($index + $px4), $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, $index + $px4, 0);
                                    print $fb $c;
                                } else {
                                    substr($self->{'SCREEN'}, ($index + $px4), $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, $index + $px4, 0);
                                    print $fb $c;
                                } else {
                                    substr($self->{'SCREEN'}, ($index + $px4), $bytes) = $c;
                                }
                            } ## end if (substr($scrn, ($idx...)))
                        } ## end elsif ($self->{'BITS'} ==...)
                    } ## end for ($px = 0; $px < $w;...)
                } 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;
                        if ($self->{'FILE_MODE'}) {
                            seek($fb, $index, 0);
                            read($fb, $data, $bytes);
                        } else {
                            $data = substr($self->{'SCREEN'}, ($index + $px4), $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, $index + $px4, 0);
                                    print $fb substr($scrn, ($idx + $px4), $bytes);
                                } else {
                                    substr($self->{'SCREEN'}, ($index + $px4), $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, $index + $px4, 0);
                                    print $fb substr($scrn, ($idx + $px4), $bytes);
                                } else {
                                    substr($self->{'SCREEN'}, ($index + $px4), $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, $index + $px4, 0);
                                    print $fb substr($scrn, ($idx + $px4), $bytes);
                                } else {
                                    substr($self->{'SCREEN'}, ($index + $px4), $bytes) = substr($scrn, ($idx + $px4), $bytes);
                                }
                            } ## end if (substr($scrn, ($idx...)))
                        } ## end elsif ($self->{'BITS'} ==...)
                    } ## end for ($px = 0; $px < $w;...)
                } elsif ($draw_mode == UNMASK_MODE) {
                    foreach $px (0 .. ($w - 1)) {
                        $px4 = $px * $bytes;
                        if ($self->{'FILE_MODE'}) {
                            seek($fb, $index + $px4, 0);
                            read($fb, $data, $bytes);
                        } else {
                            $data = substr($self->{'SCREEN'}, ($index + $px4), $bytes);
                        }
                        if ($self->{'BITS'} == 32) {
                            if (substr($self->{'SCREEN'}, ($index + $px4), 3) eq substr($self->{'B_COLOR'}, 0, 3)) {
                                if ($self->{'FILE_MODE'}) {
                                    seek($fb, $index + $px4, 0);
                                    print $fb substr($scrn, $idx + $px4, $bytes);
                                } else {
                                    substr($self->{'SCREEN'}, ($index + $px4), $bytes) = substr($scrn, ($idx + $px4), $bytes);
                                }
                            } ## end if (substr($self->{'SCREEN'...}))
                        } elsif ($self->{'BITS'} == 24) {
                            if (substr($self->{'SCREEN'}, ($index + $px4), 3) eq $self->{'B_COLOR'}) {
                                if ($self->{'FILE_MODE'}) {
                                    seek($fb, $index + $px4, 0);
                                    print $fb substr($scrn, $idx + $px4, $bytes);
                                } else {
                                    substr($self->{'SCREEN'}, ($index + $px4), $bytes) = substr($scrn, ($idx + $px4), $bytes);
                                }
                            } ## end if (substr($self->{'SCREEN'...}))
                        } elsif ($self->{'BITS'} == 16) {
                            if (substr($self->{'SCREEN'}, ($index + $px4), 2) eq $self->{'B_COLOR'}) {
                                if ($self->{'FILE_MODE'}) {
                                    seek($fb, $index + $px4, 0);
                                    print $fb substr($scrn, $idx + $px4, $bytes);
                                } else {
                                    substr($self->{'SCREEN'}, ($index + $px4), $bytes) = substr($scrn, ($idx + $px4), $bytes);
                                }
                            } ## end if (substr($self->{'SCREEN'...}))
                        } ## end elsif ($self->{'BITS'} ==...)
                    } ## end for ($px = 0; $px < $w;...)
                } ## end elsif ($draw_mode == UNMASK_MODE)
                $idx += $WW;
            } ## end if ($index >= 0 && $index...)
        } ## end for ($line = $y; $line ...)
        select($self->{'FB'});
        $|++;
    };
    my $error = $@;
    print STDERR "$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'} < $self...)
    $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 > $self->{'XX_CLIP'...})
#    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...)
    }
    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
            );
        };
        print STDERR "$@\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   = "$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);
        }
        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;
            }
        }
        return ($params->{'blit_data'}) if (abs($degrees) == 360 || $degrees == 0);    # 0 and 360 are not a rotation
        unless ($params->{'rotate'}->{'quality'} eq 'high') {
             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
                    }
                );
            }
        } 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);
            };
            print STDERR "$@\n" if ($@ && $self->{'SHOW_ERRORS'});
        }
        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,RGB) 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
            );
        };
        print STDERR "$@\n" if ($@ && $self->{'SHOW_ERRORS'});
        $data = $self->_convert_24_to_16($data,RGB) 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 sub blit_transform
sub blit_read {
    my $self   = shift;
    my $params = 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'});
    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 acceleration isn't working, then just set 'ACCELERATED' to zero.
#    if ($self->{'ACCELERATED'} && $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 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->{'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'} == 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 = $@;
                        print STDERR "$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 (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'} && ! $self->{'FILE_MODE'} && ! $antialiased) {
        c_line(
            $self->{'SCREEN'},
            $start_x, $start_y, $x_end, $y_end,
            $self->{'DRAW_MODE'},
            $self->{'INT_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 {
    
        # 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...))]
    }
    $self->{'X'} = $XX;
    $self->{'Y'} = $YY;
    
    select($self->{'FB'});
    $| = 1;
} ## end sub drawto

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

sub _draw_line_antialiased {
    my ($self, $x0, $y0, $x1, $y1) = @_;

    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
    for my $xy ([$x0, $y0], [$x1, $y1]) {
        my ($x, $y) = @{$xy};
        my $xend = _round($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 for my $xy ([$x0, $y0],...)

    # main loop

    for 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
sub _round {
    return (int(0.5 + shift));
}

sub _fpart {
    my $x = shift;
    return ($x - int $x);
}

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
